ui: use line-based rather than char-based updates in screen.c

Add ext_newgrid and ext_hlstate extensions. These use predefined
highlights and line-segment based updates, for efficiency and
simplicity.. The ext_hlstate extension in addition allows semantic
identification of builtin and syntax highlights.

Reimplement the old char-based updates in the remote UI layer, for
compatibility. For the moment, this is still the default. The bulitin
TUI uses the new line-based protocol.

cmdline uses curwin cursor position when ext_cmdline is active.
This commit is contained in:
Björn Linse 2018-07-06 14:39:50 +02:00
parent 2134396074
commit 1adb01c120
28 changed files with 979 additions and 576 deletions

View File

@ -26,6 +26,12 @@
typedef struct {
uint64_t channel_id;
Array buffer;
int hl_id; // current higlight for legacy put event
Integer cursor_row, cursor_col; // Intended visibule cursor position
// Position of legacy cursor, used both for drawing and visible user cursor.
Integer client_row, client_col;
} UIData;
static PMap(uint64_t) *connected_uis = NULL;
@ -71,10 +77,9 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->width = (int)width;
ui->height = (int)height;
ui->rgb = true;
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->grid_resize = remote_ui_grid_resize;
ui->grid_clear = remote_ui_grid_clear;
ui->grid_cursor_goto = remote_ui_grid_cursor_goto;
ui->mode_info_set = remote_ui_mode_info_set;
ui->update_menu = remote_ui_update_menu;
ui->busy_start = remote_ui_busy_start;
@ -82,16 +87,12 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->mouse_on = remote_ui_mouse_on;
ui->mouse_off = remote_ui_mouse_off;
ui->mode_change = remote_ui_mode_change;
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->grid_scroll = remote_ui_grid_scroll;
ui->hl_attr_define = remote_ui_hl_attr_define;
ui->raw_line = remote_ui_raw_line;
ui->bell = remote_ui_bell;
ui->visual_bell = remote_ui_visual_bell;
ui->default_colors_set = remote_ui_default_colors_set;
ui->update_fg = remote_ui_update_fg;
ui->update_bg = remote_ui_update_bg;
ui->update_sp = remote_ui_update_sp;
ui->flush = remote_ui_flush;
ui->suspend = remote_ui_suspend;
ui->set_title = remote_ui_set_title;
@ -103,16 +104,22 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
for (size_t i = 0; i < options.size; i++) {
ui_set_option(ui, options.items[i].key, options.items[i].value, err);
ui_set_option(ui, true, options.items[i].key, options.items[i].value, err);
if (ERROR_SET(err)) {
xfree(ui);
return;
}
}
if (ui->ui_ext[kUIHlState]) {
ui->ui_ext[kUINewgrid] = true;
}
UIData *data = xmalloc(sizeof(UIData));
data->channel_id = channel_id;
data->buffer = (Array)ARRAY_DICT_INIT;
data->hl_id = 0;
data->client_col = -1;
ui->data = data;
pmap_put(uint64_t)(connected_uis, channel_id, ui);
@ -174,13 +181,11 @@ void nvim_ui_set_option(uint64_t channel_id, String name,
}
UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
ui_set_option(ui, name, value, error);
if (!ERROR_SET(error)) {
ui_refresh();
}
ui_set_option(ui, false, name, value, error);
}
static void ui_set_option(UI *ui, String name, Object value, Error *error)
static void ui_set_option(UI *ui, bool init, String name, Object value,
Error *error)
{
if (strequal(name.data, "rgb")) {
if (value.type != kObjectTypeBoolean) {
@ -188,40 +193,46 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error)
return;
}
ui->rgb = value.data.boolean;
// A little drastic, but only legacy uis need to use this option
if (!init) {
ui_refresh();
}
return;
}
// LEGACY: Deprecated option, use `ext_cmdline` instead.
bool is_popupmenu = strequal(name.data, "popupmenu_external");
for (UIExtension i = 0; i < kUIExtCount; i++) {
if (strequal(name.data, ui_ext_names[i])) {
if (strequal(name.data, ui_ext_names[i])
|| (i == kUIPopupmenu && is_popupmenu)) {
if (value.type != kObjectTypeBoolean) {
snprintf((char *)IObuff, IOSIZE, "%s must be a Boolean",
ui_ext_names[i]);
name.data);
api_set_error(error, kErrorTypeValidation, (char *)IObuff);
return;
}
ui->ui_ext[i] = value.data.boolean;
bool boolval = value.data.boolean;
if (!init && i == kUINewgrid && boolval != ui->ui_ext[i]) {
// There shouldn't be a reason for an UI to do this ever
// so explicitly don't support this.
api_set_error(error, kErrorTypeValidation,
"ext_newgrid option cannot be changed");
}
ui->ui_ext[i] = boolval;
if (!init) {
ui_set_ext_option(ui, i, boolval);
}
return;
}
}
if (strequal(name.data, "popupmenu_external")) {
// LEGACY: Deprecated option, use `ext_cmdline` instead.
if (value.type != kObjectTypeBoolean) {
api_set_error(error, kErrorTypeValidation,
"popupmenu_external must be a Boolean");
return;
}
ui->ui_ext[kUIPopupmenu] = value.data.boolean;
return;
}
api_set_error(error, kErrorTypeValidation, "No such UI option: %s",
name.data);
#undef UI_EXT_OPTION
}
/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().
static void push_call(UI *ui, char *name, Array args)
static void push_call(UI *ui, const char *name, Array args)
{
Array call = ARRAY_DICT_INIT;
UIData *data = ui->data;
@ -243,27 +254,293 @@ static void push_call(UI *ui, char *name, Array args)
kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call;
}
static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
static void remote_ui_grid_clear(UI *ui, Integer grid)
{
Array args = ARRAY_DICT_INIT;
if (ui->ui_ext[kUINewgrid]) {
ADD(args, INTEGER_OBJ(grid));
}
const char *name = ui->ui_ext[kUINewgrid] ? "grid_clear" : "clear";
push_call(ui, name, args);
}
static void remote_ui_grid_resize(UI *ui, Integer grid,
Integer width, Integer height)
{
Array args = ARRAY_DICT_INIT;
if (ui->ui_ext[kUINewgrid]) {
ADD(args, INTEGER_OBJ(grid));
}
ADD(args, INTEGER_OBJ(width));
ADD(args, INTEGER_OBJ(height));
const char *name = ui->ui_ext[kUINewgrid] ? "grid_resize" : "resize";
push_call(ui, name, args);
}
static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top,
Integer bot, Integer left, Integer right,
Integer rows, Integer cols)
{
if (ui->ui_ext[kUINewgrid]) {
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(grid));
ADD(args, INTEGER_OBJ(top));
ADD(args, INTEGER_OBJ(bot));
ADD(args, INTEGER_OBJ(left));
ADD(args, INTEGER_OBJ(right));
ADD(args, INTEGER_OBJ(rows));
ADD(args, INTEGER_OBJ(cols));
push_call(ui, "grid_scroll", args);
} else {
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(top));
ADD(args, INTEGER_OBJ(bot-1));
ADD(args, INTEGER_OBJ(left));
ADD(args, INTEGER_OBJ(right-1));
push_call(ui, "set_scroll_region", args);
args = (Array)ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(rows));
push_call(ui, "scroll", args);
}
}
static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg,
Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
{
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(rgb_fg));
ADD(args, INTEGER_OBJ(rgb_bg));
ADD(args, INTEGER_OBJ(rgb_sp));
ADD(args, INTEGER_OBJ(cterm_fg));
ADD(args, INTEGER_OBJ(cterm_bg));
push_call(ui, "default_colors_set", args);
// Deprecated
if (!ui->ui_ext[kUINewgrid]) {
args = (Array)ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1));
push_call(ui, "update_fg", args);
args = (Array)ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1));
push_call(ui, "update_bg", args);
args = (Array)ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1));
push_call(ui, "update_sp", args);
}
}
static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs,
HlAttrs cterm_attrs, Array info)
{
if (!ui->ui_ext[kUINewgrid]) {
return;
}
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(id));
Dictionary rgb_hl = hlattrs2dict(&rgb_attrs, true);
ADD(args, DICTIONARY_OBJ(rgb_hl));
Dictionary cterm_hl = hlattrs2dict(&cterm_attrs, false);
ADD(args, DICTIONARY_OBJ(cterm_hl));
if (ui->ui_ext[kUIHlState]) {
ADD(args, ARRAY_OBJ(copy_array(info)));
} else {
ADD(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT));
}
push_call(ui, "hl_attr_define", args);
}
static void remote_ui_highlight_set(UI *ui, int id)
{
Array args = ARRAY_DICT_INIT;
UIData *data = ui->data;
HlAttrs attrs = HLATTRS_INIT;
if (data->hl_id == id) {
return;
}
data->hl_id = id;
if (id != 0) {
HlAttrs *aep = syn_attr2entry(id);
if (aep) {
attrs = *aep;
}
}
Dictionary hl = hlattrs2dict(&attrs, ui->rgb);
ADD(args, DICTIONARY_OBJ(hl));
push_call(ui, "highlight_set", args);
}
/// "true" cursor used only for input focus
static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row,
Integer col)
{
if (ui->ui_ext[kUINewgrid]) {
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(grid));
ADD(args, INTEGER_OBJ(row));
ADD(args, INTEGER_OBJ(col));
push_call(ui, "grid_cursor_goto", args);
} else {
UIData *data = ui->data;
data->cursor_row = row;
data->cursor_col = col;
remote_ui_cursor_goto(ui, row, col);
}
}
/// emulated cursor used both for drawing and for input focus
static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col)
{
UIData *data = ui->data;
if (data->client_row == row && data->client_col == col) {
return;
}
data->client_row = row;
data->client_col = 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_put(UI *ui, const char *cell)
{
UIData *data = ui->data;
data->client_col++;
Array args = ARRAY_DICT_INIT;
ADD(args, STRING_OBJ(cstr_to_string(cell)));
push_call(ui, "put", args);
}
static void remote_ui_raw_line(UI *ui, Integer grid, Integer row,
Integer startcol, Integer endcol,
Integer clearcol, Integer clearattr,
const schar_T *chunk, const sattr_T *attrs)
{
UIData *data = ui->data;
if (ui->ui_ext[kUINewgrid]) {
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(grid));
ADD(args, INTEGER_OBJ(row));
ADD(args, INTEGER_OBJ(startcol));
Array cells = ARRAY_DICT_INIT;
int repeat = 0;
size_t ncells = (size_t)(endcol-startcol);
int last_hl = -1;
for (size_t i = 0; i < ncells; i++) {
repeat++;
if (i == ncells-1 || attrs[i] != attrs[i+1]
|| STRCMP(chunk[i], chunk[i+1])) {
Array cell = ARRAY_DICT_INIT;
ADD(cell, STRING_OBJ(cstr_to_string((const char *)chunk[i])));
if (attrs[i] != last_hl || repeat > 1) {
ADD(cell, INTEGER_OBJ(attrs[i]));
last_hl = attrs[i];
}
if (repeat > 1) {
ADD(cell, INTEGER_OBJ(repeat));
}
ADD(cells, ARRAY_OBJ(cell));
repeat = 0;
}
}
if (endcol < clearcol) {
Array cell = ARRAY_DICT_INIT;
ADD(cell, STRING_OBJ(cstr_to_string(" ")));
ADD(cell, INTEGER_OBJ(clearattr));
ADD(cell, INTEGER_OBJ(clearcol-endcol));
ADD(cells, ARRAY_OBJ(cell));
}
ADD(args, ARRAY_OBJ(cells));
push_call(ui, "grid_line", args);
} else {
for (int i = 0; i < endcol-startcol; i++) {
remote_ui_cursor_goto(ui, row, startcol+i);
remote_ui_highlight_set(ui, attrs[i]);
remote_ui_put(ui, (const char *)chunk[i]);
if (utf_ambiguous_width(utf_ptr2char(chunk[i]))) {
data->client_col = -1; // force cursor update
}
}
if (endcol < clearcol) {
remote_ui_cursor_goto(ui, row, endcol);
remote_ui_highlight_set(ui, (int)clearattr);
// legacy eol_clear was only ever used with cleared attributes
// so be on the safe side
if (clearattr == 0 && clearcol == Columns) {
Array args = ARRAY_DICT_INIT;
push_call(ui, "eol_clear", args);
} else {
for (Integer c = endcol; c < clearcol; c++) {
remote_ui_put(ui, " ");
}
}
}
}
}
static void remote_ui_flush(UI *ui)
{
UIData *data = ui->data;
if (data->buffer.size > 0) {
if (!ui->ui_ext[kUINewgrid]) {
remote_ui_cursor_goto(ui, data->cursor_row, data->cursor_col);
}
rpc_send_event(data->channel_id, "redraw", data->buffer);
data->buffer = (Array)ARRAY_DICT_INIT;
}
}
static void remote_ui_cmdline_show(UI *ui, Array args)
{
Array new_args = ARRAY_DICT_INIT;
Array contents = args.items[0].data.array;
Array new_contents = ARRAY_DICT_INIT;
for (size_t i = 0; i < contents.size; i++) {
Array item = contents.items[i].data.array;
Array new_item = ARRAY_DICT_INIT;
int attr = (int)item.items[0].data.integer;
if (attr) {
HlAttrs *aep = syn_attr2entry(attr);
Dictionary rgb_attrs = hlattrs2dict(aep, ui->rgb ? kTrue : kFalse);
ADD(new_item, DICTIONARY_OBJ(rgb_attrs));
} else {
ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
}
ADD(new_item, copy_object(item.items[1]));
ADD(new_contents, ARRAY_OBJ(new_item));
}
ADD(new_args, ARRAY_OBJ(new_contents));
for (size_t i = 1; i < args.size; i++) {
ADD(new_args, copy_object(args.items[i]));
}
push_call(ui, "cmdline_show", new_args);
}
static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
{
if (!ui->ui_ext[kUINewgrid]) {
// the representation of cmdline_show changed, translate back
if (strequal(name, "cmdline_show")) {
remote_ui_cmdline_show(ui, args);
// never consumes args
return;
}
}
Array my_args = ARRAY_DICT_INIT;
// Objects are currently single-reference
// make a copy, but only if necessary

View File

@ -10,14 +10,6 @@
#include "nvim/func_attr.h"
#include "nvim/ui.h"
void resize(Integer width, Integer height)
FUNC_API_SINCE(3);
void clear(void)
FUNC_API_SINCE(3);
void eol_clear(void)
FUNC_API_SINCE(3);
void cursor_goto(Integer row, Integer col)
FUNC_API_SINCE(3);
void mode_info_set(Boolean enabled, Array cursor_styles)
FUNC_API_SINCE(3);
void update_menu(void)
@ -32,29 +24,12 @@ void mouse_off(void)
FUNC_API_SINCE(3);
void mode_change(String mode, Integer mode_idx)
FUNC_API_SINCE(3);
void set_scroll_region(Integer top, Integer bot, Integer left, Integer right)
FUNC_API_SINCE(3);
void scroll(Integer count)
FUNC_API_SINCE(3);
void highlight_set(HlAttrs attrs)
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL;
void put(String str)
FUNC_API_SINCE(3);
void bell(void)
FUNC_API_SINCE(3);
void visual_bell(void)
FUNC_API_SINCE(3);
void flush(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL;
void update_fg(Integer fg)
FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
void update_bg(Integer bg)
FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
void update_sp(Integer sp)
FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
FUNC_API_SINCE(4);
void suspend(void)
FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL;
void set_title(String title)
@ -64,6 +39,49 @@ void set_icon(String icon)
void option_set(String name, Object value)
FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL;
// First revison of the grid protocol, used by default
void update_fg(Integer fg)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void update_bg(Integer bg)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void update_sp(Integer sp)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void resize(Integer width, Integer height)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void clear(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void eol_clear(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void cursor_goto(Integer row, Integer col)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void highlight_set(HlAttrs attrs)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL;
void put(String str)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void set_scroll_region(Integer top, Integer bot, Integer left, Integer right)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void scroll(Integer count)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
// Second revison of the grid protocol, used with ext_newgrid ui option
void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs,
Array info)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL;
void grid_resize(Integer grid, Integer width, Integer height)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void grid_clear(Integer grid)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void grid_cursor_goto(Integer grid, Integer row, Integer col)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void grid_line(Integer grid, Integer row, Integer col_start, Array data)
FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY;
void grid_scroll(Integer grid, Integer top, Integer bot,
Integer left, Integer right, Integer rows, Integer cols)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void popupmenu_show(Array items, Integer selected, Integer row, Integer col)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void popupmenu_hide(void)

View File

@ -4,7 +4,7 @@
#include <assert.h>
#include <stdarg.h>
#define EVENT_HANDLER_MAX_ARGC 6
#define EVENT_HANDLER_MAX_ARGC 9
typedef void (*argv_callback)(void **argv);
typedef struct message {

View File

@ -6320,8 +6320,10 @@ static void ex_stop(exarg_T *eap)
autowrite_all();
}
apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL);
// TODO(bfredl): the TUI should do this on suspend
ui_cursor_goto((int)Rows - 1, 0);
ui_linefeed();
ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0);
ui_flush();
ui_call_suspend(); // call machine specific function

View File

@ -215,6 +215,8 @@ static int hislen = 0; /* actual length of history tables */
/// user interrupting highlight function to not interrupt command-line.
static bool getln_interrupted_highlight = false;
static bool need_cursor_update = false;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_getln.c.generated.h"
@ -2944,30 +2946,22 @@ static void ui_ext_cmdline_show(CmdlineInfo *line)
char *buf = xmallocz(len);
memset(buf, '*', len);
Array item = ARRAY_DICT_INIT;
ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
ADD(item, INTEGER_OBJ(0));
ADD(item, STRING_OBJ(((String) { .data = buf, .size = len })));
ADD(content, ARRAY_OBJ(item));
} else if (kv_size(line->last_colors.colors)) {
for (size_t i = 0; i < kv_size(line->last_colors.colors); i++) {
CmdlineColorChunk chunk = kv_A(line->last_colors.colors, i);
Array item = ARRAY_DICT_INIT;
ADD(item, INTEGER_OBJ(chunk.attr));
if (chunk.attr) {
HlAttrs *aep = syn_attr2entry(chunk.attr);
// TODO(bfredl): this desicion could be delayed by making attr_code a
// recognized type
Dictionary rgb_attrs = hlattrs2dict(aep, true);
ADD(item, DICTIONARY_OBJ(rgb_attrs));
} else {
ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
}
ADD(item, STRING_OBJ(cbuf_to_string((char *)line->cmdbuff + chunk.start,
chunk.end-chunk.start)));
ADD(content, ARRAY_OBJ(item));
}
} else {
Array item = ARRAY_DICT_INIT;
ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
ADD(item, INTEGER_OBJ(0));
ADD(item, STRING_OBJ(cstr_to_string((char *)(line->cmdbuff))));
ADD(content, ARRAY_OBJ(item));
}
@ -3033,6 +3027,8 @@ void cmdline_screen_cleared(void)
}
prev_ccline = prev_ccline->prev_ccline;
}
need_cursor_update = true;
}
/// called by ui_flush, do what redraws neccessary to keep cmdline updated.
@ -3501,6 +3497,10 @@ static void cursorcmd(void)
if (ccline.redraw_state < kCmdRedrawPos) {
ccline.redraw_state = kCmdRedrawPos;
}
if (need_cursor_update) {
need_cursor_update = false;
setcursor();
}
return;
}

View File

@ -132,19 +132,21 @@ for i = 1, #events do
end
end
call_output:write('void ui_call_'..ev.name)
write_signature(call_output, ev, '')
call_output:write('\n{\n')
if ev.remote_only then
write_arglist(call_output, ev, false)
call_output:write(' UI_LOG('..ev.name..', 0);\n')
call_output:write(' ui_event("'..ev.name..'", args);\n')
else
call_output:write(' UI_CALL')
write_signature(call_output, ev, ev.name, true)
call_output:write(";\n")
if not (ev.remote_only and ev.remote_impl) then
call_output:write('void ui_call_'..ev.name)
write_signature(call_output, ev, '')
call_output:write('\n{\n')
if ev.remote_only then
write_arglist(call_output, ev, false)
call_output:write(' UI_LOG('..ev.name..', 0);\n')
call_output:write(' ui_event("'..ev.name..'", args);\n')
else
call_output:write(' UI_CALL')
write_signature(call_output, ev, ev.name, true)
call_output:write(";\n")
end
call_output:write("}\n\n")
end
call_output:write("}\n\n")
end

View File

@ -38,7 +38,7 @@ void highlight_init(void)
bool highlight_use_hlstate(void)
{
if (hlstate_active) {
return false;
return false;
}
hlstate_active = true;
// hl tables must now be rebuilt.
@ -89,9 +89,26 @@ static int get_attr_entry(HlEntry entry)
map_put(HlEntry, int)(attr_entry_ids, entry, id);
Array inspect = hl_inspect(id);
// Note: internally we don't distinguish between cterm and rgb attributes,
// remote_ui_hl_attr_define will however.
ui_call_hl_attr_define(id, entry.attr, entry.attr, inspect);
api_free_array(inspect);
return id;
}
/// When a UI connects, we need to send it the table of higlights used so far.
void ui_send_all_hls(UI *ui)
{
for (size_t i = 1; i < kv_size(attr_entries); i++) {
Array inspect = hl_inspect((int)i);
ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr,
kv_A(attr_entries, i).attr, inspect);
api_free_array(inspect);
}
}
/// Get attribute code for a syntax group.
int hl_get_syn_attr(int idx, HlAttrs at_en)
{

View File

@ -8,6 +8,8 @@
typedef int32_t RgbValue;
/// Highlighting attribute bits.
///
/// sign bit should not be used here, as it identifies invalid highlight
typedef enum {
HL_INVERSE = 0x01,
HL_BOLD = 0x02,
@ -35,6 +37,17 @@ typedef struct attr_entry {
.cterm_bg_color = 0, \
}
// sentinel value that compares unequal to any valid highlight
#define HLATTRS_INVALID (HlAttrs) { \
.rgb_ae_attr = -1, \
.cterm_ae_attr = -1, \
.rgb_fg_color = -1, \
.rgb_bg_color = -1, \
.rgb_sp_color = -1, \
.cterm_fg_color = 0, \
.cterm_bg_color = 0, \
}
/// Values for index in highlight_attr[].
/// When making changes, also update hlf_names below!
typedef enum {

View File

@ -98,14 +98,14 @@
(*kv_pushp(v) = (x))
#define kv_a(v, i) \
(((v).capacity <= (size_t) (i) \
(*(((v).capacity <= (size_t) (i) \
? ((v).capacity = (v).size = (i) + 1, \
kv_roundup32((v).capacity), \
kv_resize((v), (v).capacity), 0) \
kv_resize((v), (v).capacity), 0UL) \
: ((v).size <= (size_t) (i) \
? (v).size = (i) + 1 \
: 0)), \
(v).items[(i)])
: 0UL)), \
&(v).items[(i)]))
/// Type of a vector with a few first members allocated on stack
///

View File

@ -454,7 +454,6 @@ int main(int argc, char **argv)
}
setmouse(); // may start using the mouse
ui_reset_scroll_region(); // In case Rows changed
if (exmode_active) {
must_redraw = CLEAR; // Don't clear the screen when starting in Ex mode.
@ -1374,7 +1373,7 @@ static void handle_quickfix(mparm_T *paramp)
paramp->use_ef, OPT_FREE, SID_CARG);
vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef);
if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) {
ui_linefeed();
msg_putchar('\n');
mch_exit(3);
}
TIME_MSG("reading errorfile");

View File

@ -1888,11 +1888,9 @@ static void msg_scroll_up(void)
fill_msgsep, fill_msgsep, HL_ATTR(HLF_MSGSEP));
}
int nscroll = MIN(msg_scrollsize()+1, Rows);
ui_call_set_scroll_region(Rows-nscroll, Rows-1, 0, Columns-1);
screen_del_lines(Rows-nscroll, 0, 1, nscroll, NULL);
ui_reset_scroll_region();
screen_del_lines(Rows-nscroll, 1, Rows, 0, Columns);
} else {
screen_del_lines(0, 0, 1, (int)Rows, NULL);
screen_del_lines(0, 1, (int)Rows, 0, Columns);
}
}
@ -2307,9 +2305,9 @@ static int do_more_prompt(int typed_char)
mp_last = msg_sb_start(mp_last->sb_prev);
}
if (toscroll == -1 && screen_ins_lines(0, 0, 1,
(int)Rows, NULL) == OK) {
/* display line at top */
if (toscroll == -1
&& screen_ins_lines(0, 1, (int)Rows, 0, (int)Columns) == OK) {
// display line at top
(void)disp_sb_line(0, mp);
} else {
/* redisplay all lines */

View File

@ -2714,7 +2714,7 @@ int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg)
if (p_verbose > 3) {
verbose_enter();
smsg(_("Calling shell to execute: \"%s\""), cmd == NULL ? p_sh : cmd);
ui_linefeed();
msg_putchar('\n');
verbose_leave();
}

View File

@ -341,6 +341,8 @@ void pum_redraw(void)
idx = i + pum_first;
attr = (idx == pum_selected) ? attr_select : attr_norm;
screen_puts_line_start(row);
// prepend a space if there is room
if (curwin->w_p_rl) {
if (pum_col < curwin->w_wincol + curwin->w_width - 1) {
@ -488,6 +490,7 @@ void pum_redraw(void)
? attr_thumb : attr_scroll);
}
}
screen_puts_line_flush(false);
row++;
}
}

View File

@ -300,7 +300,8 @@ void update_screen(int type)
type = CLEAR;
} else if (type != CLEAR) {
check_for_delay(false);
if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) {
if (screen_ins_lines(0, msg_scrolled, (int)Rows, 0, (int)Columns)
== FAIL) {
type = CLEAR;
}
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@ -4314,13 +4315,14 @@ static void screen_line(int row, int coloff, int endcol,
/* 2: occupies two display cells */
# define CHAR_CELLS char_cells
int start_dirty = -1, end_dirty = 0;
/* Check for illegal row and col, just in case. */
if (row >= Rows)
row = Rows - 1;
if (endcol > Columns)
endcol = Columns;
off_from = (unsigned)(current_ScreenLine - ScreenLines);
off_to = LineOffset[row] + coloff;
max_off_from = off_from + screen_Columns;
@ -4368,6 +4370,10 @@ static void screen_line(int row, int coloff, int endcol,
if (redraw_this) {
if (start_dirty == -1) {
start_dirty = col;
}
end_dirty = col + char_cells;
// When writing a single-width character over a double-width
// character and at the end of the redrawn text, need to clear out
// the right halve of the old character.
@ -4388,12 +4394,11 @@ static void screen_line(int row, int coloff, int endcol,
}
ScreenAttrs[off_to] = ScreenAttrs[off_from];
/* For simplicity set the attributes of second half of a
* double-wide character equal to the first half. */
if (char_cells == 2)
// For simplicity set the attributes of second half of a
// double-wide character equal to the first half.
if (char_cells == 2) {
ScreenAttrs[off_to + 1] = ScreenAttrs[off_from];
screen_char(off_to, row, col + coloff);
}
}
off_to += CHAR_CELLS;
@ -4405,23 +4410,29 @@ static void screen_line(int row, int coloff, int endcol,
/* Clear the second half of a double-wide character of which the left
* half was overwritten with a single-wide character. */
schar_from_ascii(ScreenLines[off_to], ' ');
screen_char(off_to, row, col + coloff);
end_dirty++;
}
int clear_end = -1;
if (clear_width > 0 && !rlflag) {
// blank out the rest of the line
while (col < clear_width && ScreenLines[off_to][0] == ' '
&& ScreenLines[off_to][1] == NUL
&& ScreenAttrs[off_to] == bg_attr
) {
++off_to;
++col;
}
if (col < clear_width) {
screen_fill(row, row + 1, col + coloff, clear_width + coloff, ' ', ' ',
bg_attr);
off_to += clear_width - col;
col = clear_width;
// TODO(bfredl): we could cache winline widths
while (col < clear_width) {
if (ScreenLines[off_to][0] != ' ' || ScreenLines[off_to][1] != NUL
|| ScreenAttrs[off_to] != bg_attr) {
ScreenLines[off_to][0] = ' ';
ScreenLines[off_to][1] = NUL;
ScreenAttrs[off_to] = bg_attr;
if (start_dirty == -1) {
start_dirty = col;
end_dirty = col;
} else if (clear_end == -1) {
end_dirty = endcol;
}
clear_end = col+1;
}
col++;
off_to++;
}
}
@ -4436,11 +4447,25 @@ static void screen_line(int row, int coloff, int endcol,
|| ScreenAttrs[off_to] != hl) {
schar_copy(ScreenLines[off_to], sc);
ScreenAttrs[off_to] = hl;
screen_char(off_to, row, col + coloff);
if (start_dirty == -1) {
start_dirty = col;
}
end_dirty = col+1;
}
} else
LineWraps[row] = FALSE;
}
if (clear_end < end_dirty) {
clear_end = end_dirty;
}
if (start_dirty == -1) {
start_dirty = end_dirty;
}
if (clear_end > start_dirty) {
ui_line(row, coloff+start_dirty, coloff+end_dirty, coloff+clear_end,
bg_attr);
}
}
/*
@ -4722,11 +4747,11 @@ win_redr_status_matches (
/* Put the wildmenu just above the command line. If there is
* no room, scroll the screen one line up. */
if (cmdline_row == Rows - 1) {
screen_del_lines(0, 0, 1, (int)Rows, NULL);
++msg_scrolled;
screen_del_lines(0, 1, (int)Rows, 0, (int)Columns);
msg_scrolled++;
} else {
++cmdline_row;
++row;
cmdline_row++;
row++;
}
wild_menu_showing = WM_SCROLLED;
} else {
@ -5090,6 +5115,8 @@ win_redr_custom (
/*
* Draw each snippet with the specified highlighting.
*/
screen_puts_line_start(row);
curattr = attr;
p = buf;
for (n = 0; hltab[n].start != NULL; n++) {
@ -5110,6 +5137,8 @@ win_redr_custom (
// Make sure to use an empty string instead of p, if p is beyond buf + len.
screen_puts(p >= buf + len ? (char_u *)"" : p, row, col, curattr);
screen_puts_line_flush(false);
if (wp == NULL) {
// Fill the tab_page_click_defs array for clicking in the tab pages line.
col = 0;
@ -5207,7 +5236,6 @@ void screen_getbytes(int row, int col, char_u *bytes, int *attrp)
}
}
/*
* Put string '*text' on the screen at position 'row' and 'col', with
* attributes 'attr', and update ScreenLines[] and ScreenAttrs[].
@ -5219,6 +5247,20 @@ void screen_puts(char_u *text, int row, int col, int attr)
screen_puts_len(text, -1, row, col, attr);
}
static int put_dirty_row = -1;
static int put_dirty_first = -1;
static int put_dirty_last = 0;
/// Start a group of screen_puts_len calls that builds a single screen line.
///
/// Must be matched with a screen_puts_line_flush call before moving to
/// another line.
void screen_puts_line_start(int row)
{
assert(put_dirty_row == -1);
put_dirty_row = row;
}
/*
* Like screen_puts(), but output "text[len]". When "len" is -1 output up to
* a NUL.
@ -5242,6 +5284,16 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr)
int force_redraw_next = FALSE;
int need_redraw;
bool do_flush = false;
if (put_dirty_row == -1) {
screen_puts_line_start(row);
do_flush = true;
} else {
if (row != put_dirty_row) {
abort();
}
}
if (ScreenLines == NULL || row >= screen_Rows) /* safety check */
return;
off = LineOffset[row] + col;
@ -5252,9 +5304,12 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr)
schar_from_ascii(ScreenLines[off - 1], ' ');
ScreenAttrs[off - 1] = 0;
// redraw the previous cell, make it empty
screen_char(off - 1, row, col - 1);
/* force the cell at "col" to be redrawn */
force_redraw_next = TRUE;
if (put_dirty_first == -1) {
put_dirty_first = col-1;
}
put_dirty_last = col+1;
// force the cell at "col" to be redrawn
force_redraw_next = true;
}
max_off = LineOffset[row] + screen_Columns;
@ -5333,8 +5388,12 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr)
ScreenLines[off + 1][0] = 0;
ScreenAttrs[off + 1] = attr;
}
screen_char(off, row, col);
if (put_dirty_first == -1) {
put_dirty_first = col;
}
put_dirty_last = col+mbyte_cells;
}
off += mbyte_cells;
col += mbyte_cells;
ptr += mbyte_blen;
@ -5345,13 +5404,31 @@ void screen_puts_len(char_u *text, int textlen, int row, int col, int attr)
}
}
/* If we detected the next character needs to be redrawn, but the text
* doesn't extend up to there, update the character here. */
if (force_redraw_next && col < screen_Columns) {
screen_char(off, row, col);
if (do_flush) {
screen_puts_line_flush(true);
}
}
/// End a group of screen_puts_len calls and send the screen buffer to the UI
/// layer.
///
/// @param set_cursor Move the visible cursor to the end of the changed region.
/// This is a workaround for not yet refactored code paths
/// and shouldn't be used in new code.
void screen_puts_line_flush(bool set_cursor)
{
assert(put_dirty_row != -1);
if (put_dirty_first != -1) {
if (set_cursor) {
ui_cursor_goto(put_dirty_row, put_dirty_last);
}
ui_line(put_dirty_row, put_dirty_first, put_dirty_last, put_dirty_last, 0);
put_dirty_first = -1;
put_dirty_last = 0;
}
put_dirty_row = -1;
}
/*
* Prepare for 'hlsearch' highlighting.
*/
@ -5641,32 +5718,6 @@ next_search_hl_pos(
return 0;
}
/*
* Put character ScreenLines["off"] on the screen at position "row" and "col",
* using the attributes from ScreenAttrs["off"].
*/
static void screen_char(unsigned off, int row, int col)
{
// Check for illegal values, just in case (could happen just after resizing).
if (row >= screen_Rows || col >= screen_Columns) {
return;
}
// Outputting the last character on the screen may scrollup the screen.
// Don't to it! Mark the character invalid (update it when scrolled up)
// FIXME: The premise here is not actually true (cf. deferred wrap).
if (row == screen_Rows - 1 && col == screen_Columns - 1
// account for first command-line character in rightleft mode
&& !cmdmsg_rl) {
ScreenAttrs[off] = (sattr_T)-1;
return;
}
ui_cursor_goto(row, col);
ui_set_highlight(ScreenAttrs[off]);
ui_puts(ScreenLines[off]);
}
/*
* Fill the screen from 'start_row' to 'end_row', from 'start_col' to 'end_col'
@ -5675,12 +5726,6 @@ static void screen_char(unsigned off, int row, int col)
*/
void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, int c2, int attr)
{
int row;
int col;
int off;
int end_off;
int did_delete;
int c;
schar_T sc;
if (end_row > screen_Rows) /* safety check */
@ -5692,8 +5737,7 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1,
|| start_col >= end_col) /* nothing to do */
return;
/* it's a "normal" terminal when not in a GUI or cterm */
for (row = start_row; row < end_row; ++row) {
for (int row = start_row; row < end_row; row++) {
if (has_mbyte) {
// When drawing over the right halve of a double-wide char clear
// out the left halve. When drawing over the left halve of a
@ -5706,71 +5750,52 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1,
screen_puts_len((char_u *)" ", 1, row, end_col, 0);
}
}
/*
* Try to use delete-line termcap code, when no attributes or in a
* "normal" terminal, where a bold/italic space is just a
* space.
*/
did_delete = FALSE;
if (c2 == ' '
&& end_col == Columns
&& attr == 0) {
/*
* check if we really need to clear something
*/
col = start_col;
if (c1 != ' ') /* don't clear first char */
++col;
off = LineOffset[row] + col;
end_off = LineOffset[row] + end_col;
// skip blanks (used often, keep it fast!)
while (off < end_off && ScreenLines[off][0] == ' '
&& ScreenLines[off][1] == 0 && ScreenAttrs[off] == 0) {
off++;
}
if (off < end_off) { // something to be cleared
col = off - LineOffset[row];
ui_clear_highlight();
ui_cursor_goto(row, col); // clear rest of this screen line
ui_call_eol_clear();
col = end_col - col;
while (col--) { // clear chars in ScreenLines
schar_from_ascii(ScreenLines[off], ' ');
ScreenAttrs[off] = 0;
++off;
}
}
did_delete = TRUE; /* the chars are cleared now */
}
off = LineOffset[row] + start_col;
c = c1;
schar_from_char(sc, c);
int dirty_first = INT_MAX;
int dirty_last = 0;
int col = start_col;
schar_from_char(sc, c1);
int lineoff = LineOffset[row];
for (col = start_col; col < end_col; col++) {
int off = lineoff + col;
if (schar_cmp(ScreenLines[off], sc) || ScreenAttrs[off] != attr) {
schar_copy(ScreenLines[off], sc);
ScreenAttrs[off] = attr;
if (!did_delete || c != ' ')
screen_char(off, row, col);
if (dirty_first == INT_MAX) {
dirty_first = col;
}
dirty_last = col+1;
}
++off;
if (col == start_col) {
if (did_delete)
break;
c = c2;
schar_from_char(sc, c);
schar_from_char(sc, c2);
}
}
if (end_col == Columns)
LineWraps[row] = FALSE;
if (row == Rows - 1) { /* overwritten the command line */
redraw_cmdline = TRUE;
if (c1 == ' ' && c2 == ' ')
clear_cmdline = FALSE; /* command line has been cleared */
if (start_col == 0)
mode_displayed = FALSE; /* mode cleared or overwritten */
if (dirty_last > dirty_first) {
// TODO(bfredl): support a cleared suffix even with a batched line?
if (put_dirty_row == row) {
if (put_dirty_first == -1) {
put_dirty_first = dirty_first;
}
put_dirty_last = MAX(put_dirty_last, dirty_last);
} else {
int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' ');
ui_line(row, dirty_first, last, dirty_last, attr);
}
}
if (end_col == Columns) {
LineWraps[row] = false;
}
// TODO(bfredl): The relevant caller should do this
if (row == Rows - 1) { // overwritten the command line
redraw_cmdline = true;
if (c1 == ' ' && c2 == ' ') {
clear_cmdline = false; // command line has been cleared
}
if (start_col == 0) {
mode_displayed = false; // mode cleared or overwritten
}
}
}
}
@ -6027,15 +6052,13 @@ static void screenclear2(void)
return;
}
ui_clear_highlight(); // don't want highlighting here
/* blank out ScreenLines */
for (i = 0; i < Rows; ++i) {
lineclear(LineOffset[i], (int)Columns);
LineWraps[i] = FALSE;
}
ui_call_clear(); // clear the display
ui_call_grid_clear(1); // clear the display
clear_cmdline = false;
mode_displayed = false;
screen_cleared = true; // can use contents of ScreenLines now
@ -6064,18 +6087,16 @@ static void lineclear(unsigned off, int width)
(void)memset(ScreenAttrs + off, 0, (size_t)width * sizeof(sattr_T));
}
/*
* Copy part of a Screenline for vertically split window "wp".
*/
static void linecopy(int to, int from, win_T *wp)
/// Copy part of a Screenline for vertically split window.
static void linecopy(int to, int from, int col, int width)
{
const unsigned off_to = LineOffset[to] + wp->w_wincol;
const unsigned off_from = LineOffset[from] + wp->w_wincol;
unsigned off_to = LineOffset[to] + col;
unsigned off_from = LineOffset[from] + col;
memmove(ScreenLines + off_to, ScreenLines + off_from,
wp->w_width * sizeof(schar_T));
width * sizeof(schar_T));
memmove(ScreenAttrs + off_to, ScreenAttrs + off_from,
wp->w_width * sizeof(ScreenAttrs[0]));
width * sizeof(sattr_T));
}
/*
@ -6153,15 +6174,16 @@ static int win_do_lines(win_T *wp, int row, int line_count,
// otherwise it will stay there forever.
clear_cmdline = TRUE;
int retval;
ui_set_scroll_region(wp, row);
if (del) {
retval = screen_del_lines(wp->w_winrow + row, 0, line_count,
wp->w_height - row, wp);
retval = screen_del_lines(wp->w_winrow + row, line_count,
wp->w_winrow + wp->w_height,
wp->w_wincol, wp->w_width);
} else {
retval = screen_ins_lines(wp->w_winrow + row, 0, line_count,
wp->w_height - row, wp);
retval = screen_ins_lines(wp->w_winrow + row, line_count,
wp->w_winrow + wp->w_height,
wp->w_wincol, wp->w_width);
}
ui_reset_scroll_region();
return retval;
}
@ -6189,19 +6211,13 @@ static void win_rest_invalid(win_T *wp)
*/
// insert lines on the screen and update ScreenLines[]
// 'end' is the line after the scrolled part. Normally it is Rows.
// When scrolling region used 'off' is the offset from the top for the region.
// 'row' and 'end' are relative to the start of the region.
//
// return FAIL for failure, OK for success.
int screen_ins_lines (
int off,
int row,
int line_count,
int end,
win_T *wp /* NULL or window to use width from */
)
/// insert lines on the screen and update ScreenLines[]
/// 'end' is the line after the scrolled part. Normally it is Rows.
/// When scrolling region used 'off' is the offset from the top for the region.
/// 'row' and 'end' are relative to the start of the region.
///
/// @return FAIL for failure, OK for success.
int screen_ins_lines(int row, int line_count, int end, int col, int width)
{
int i;
int j;
@ -6213,18 +6229,16 @@ int screen_ins_lines (
// Shift LineOffset[] line_count down to reflect the inserted lines.
// Clear the inserted lines in ScreenLines[].
row += off;
end += off;
for (i = 0; i < line_count; ++i) {
if (wp != NULL && wp->w_width != Columns) {
for (i = 0; i < line_count; i++) {
if (width != Columns) {
// need to copy part of a line
j = end - 1 - i;
while ((j -= line_count) >= row) {
linecopy(j + line_count, j, wp);
linecopy(j + line_count, j, col, width);
}
j += line_count;
lineclear(LineOffset[j] + wp->w_wincol, wp->w_width);
LineWraps[j] = FALSE;
lineclear(LineOffset[j] + col, width);
LineWraps[j] = false;
} else {
j = end - 1 - i;
temp = LineOffset[j];
@ -6233,29 +6247,23 @@ int screen_ins_lines (
LineWraps[j + line_count] = LineWraps[j];
}
LineOffset[j + line_count] = temp;
LineWraps[j + line_count] = FALSE;
LineWraps[j + line_count] = false;
lineclear(temp, (int)Columns);
}
}
ui_call_scroll(-line_count);
ui_call_grid_scroll(1, row, end, col, col+width, -line_count, 0);
return OK;
}
// delete lines on the screen and update ScreenLines[]
// 'end' is the line after the scrolled part. Normally it is Rows.
// When scrolling region used 'off' is the offset from the top for the region.
// 'row' and 'end' are relative to the start of the region.
//
// Return OK for success, FAIL if the lines are not deleted.
int screen_del_lines (
int off,
int row,
int line_count,
int end,
win_T *wp /* NULL or window to use width from */
)
/// delete lines on the screen and update ScreenLines[]
/// 'end' is the line after the scrolled part. Normally it is Rows.
/// When scrolling region used 'off' is the offset from the top for the region.
/// 'row' and 'end' are relative to the start of the region.
///
/// Return OK for success, FAIL if the lines are not deleted.
int screen_del_lines(int row, int line_count, int end, int col, int width)
{
int j;
int i;
@ -6267,18 +6275,16 @@ int screen_del_lines (
// Now shift LineOffset[] line_count up to reflect the deleted lines.
// Clear the inserted lines in ScreenLines[].
row += off;
end += off;
for (i = 0; i < line_count; ++i) {
if (wp != NULL && wp->w_width != Columns) {
for (i = 0; i < line_count; i++) {
if (width != Columns) {
// need to copy part of a line
j = row + i;
while ((j += line_count) <= end - 1) {
linecopy(j - line_count, j, wp);
linecopy(j - line_count, j, col, width);
}
j -= line_count;
lineclear(LineOffset[j] + wp->w_wincol, wp->w_width);
LineWraps[j] = FALSE;
lineclear(LineOffset[j] + col, width);
LineWraps[j] = false;
} else {
// whole width, moving the line pointers is faster
j = row + i;
@ -6288,16 +6294,17 @@ int screen_del_lines (
LineWraps[j - line_count] = LineWraps[j];
}
LineOffset[j - line_count] = temp;
LineWraps[j - line_count] = FALSE;
LineWraps[j - line_count] = false;
lineclear(temp, (int)Columns);
}
}
ui_call_scroll(line_count);
ui_call_grid_scroll(1, row, end, col, col+width, line_count, 0);
return OK;
}
/*
* show the current mode and ruler
*

View File

@ -6798,14 +6798,12 @@ void do_highlight(const char *line, const bool forceit, const bool init)
HL_TABLE()[idx].sg_cterm_fg = color + 1;
if (is_normal_group) {
cterm_normal_fg_color = color + 1;
must_redraw = CLEAR;
}
} else {
HL_TABLE()[idx].sg_cterm_bg = color + 1;
if (is_normal_group) {
cterm_normal_bg_color = color + 1;
if (!ui_rgb_attached()) {
must_redraw = CLEAR;
if (color >= 0) {
int dark = -1;
@ -6909,8 +6907,16 @@ void do_highlight(const char *line, const bool forceit, const bool init)
// Need to update all groups, because they might be using "bg" and/or
// "fg", which have been changed now.
highlight_attr_set_all();
// If the normal group has changed, it is simpler to refresh every UI
ui_refresh();
if (!ui_is_external(kUINewgrid)) {
// Older UIs assume that we clear the screen after normal group is
// changed
ui_refresh();
} else {
// TUI and newer UIs will repaint the screen themselves. NOT_VALID
// redraw below will still handle usages of guibg=fg etc.
ui_default_colors_set();
}
} else {
set_hl_attr(idx);
}

View File

@ -88,6 +88,7 @@ typedef struct {
bool cont_received;
UGrid grid;
kvec_t(Rect) invalid_regions;
int row, col;
int out_fd;
bool scroll_region_is_full_screen;
bool can_change_scroll_region;
@ -98,6 +99,8 @@ typedef struct {
bool busy, is_invisible;
bool cork, overflow;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
HlAttrs clear_attrs;
kvec_t(HlAttrs) attrs;
HlAttrs print_attrs;
bool default_attr;
ModeShape showing_mode;
@ -126,10 +129,9 @@ UI *tui_start(void)
{
UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop().
ui->stop = tui_stop;
ui->resize = tui_resize;
ui->clear = tui_clear;
ui->eol_clear = tui_eol_clear;
ui->cursor_goto = tui_cursor_goto;
ui->grid_resize = tui_grid_resize;
ui->grid_clear = tui_grid_clear;
ui->grid_cursor_goto = tui_grid_cursor_goto;
ui->mode_info_set = tui_mode_info_set;
ui->update_menu = tui_update_menu;
ui->busy_start = tui_busy_start;
@ -137,10 +139,8 @@ UI *tui_start(void)
ui->mouse_on = tui_mouse_on;
ui->mouse_off = tui_mouse_off;
ui->mode_change = tui_mode_change;
ui->set_scroll_region = tui_set_scroll_region;
ui->scroll = tui_scroll;
ui->highlight_set = tui_highlight_set;
ui->put = tui_put;
ui->grid_scroll = tui_grid_scroll;
ui->hl_attr_define = tui_hl_attr_define;
ui->bell = tui_bell;
ui->visual_bell = tui_visual_bell;
ui->default_colors_set = tui_default_colors_set;
@ -149,8 +149,10 @@ UI *tui_start(void)
ui->set_title = tui_set_title;
ui->set_icon = tui_set_icon;
ui->option_set= tui_option_set;
ui->raw_line = tui_raw_line;
memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
ui->ui_ext[kUINewgrid] = true;
return ui_bridge_attach(ui, tui_main, tui_scheduler);
}
@ -290,7 +292,7 @@ static void terminfo_stop(UI *ui)
static void tui_terminal_start(UI *ui)
{
TUIData *data = ui->data;
data->print_attrs = HLATTRS_INIT;
data->print_attrs = HLATTRS_INVALID;
ugrid_init(&data->grid);
terminfo_start(ui);
update_size(ui);
@ -346,6 +348,9 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT);
#endif
// TODO(bfredl): zero hl is empty, send this explicitly?
kv_push(data->attrs, HLATTRS_INIT);
#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
data->input.tk_ti_hook_fn = tui_tk_ti_getstr;
#endif
@ -380,6 +385,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
signal_watcher_close(&data->winch_handle, NULL);
loop_close(&tui_loop, false);
kv_destroy(data->invalid_regions);
kv_destroy(data->attrs);
xfree(data);
}
@ -438,18 +444,17 @@ static void update_attrs(UI *ui, HlAttrs attrs)
}
data->print_attrs = attrs;
UGrid *grid = &data->grid;
int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1);
if (fg == -1) {
fg = ui->rgb ? grid->clear_attrs.rgb_fg_color
: (grid->clear_attrs.cterm_fg_color - 1);
fg = ui->rgb ? data->clear_attrs.rgb_fg_color
: (data->clear_attrs.cterm_fg_color - 1);
}
int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1);
if (bg == -1) {
bg = ui->rgb ? grid->clear_attrs.rgb_bg_color
: (grid->clear_attrs.cterm_bg_color - 1);
bg = ui->rgb ? data->clear_attrs.rgb_bg_color
: (data->clear_attrs.cterm_bg_color - 1);
}
int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr;
@ -592,6 +597,8 @@ static void cursor_goto(UI *ui, int row, int col)
if (row == grid->row && col == grid->col) {
return;
}
grid->row = row;
grid->col = col;
if (0 == row && 0 == col) {
unibi_out(ui, unibi_cursor_home);
ugrid_goto(grid, row, col);
@ -679,20 +686,20 @@ static void cursor_goto(UI *ui, int row, int col)
ugrid_goto(grid, row, col);
}
static void clear_region(UI *ui, int top, int bot, int left, int right)
static void clear_region(UI *ui, int top, int bot, int left, int right,
HlAttrs attrs)
{
TUIData *data = ui->data;
UGrid *grid = &data->grid;
int saved_row = grid->row;
int saved_col = grid->col;
bool cleared = false;
bool nobg = ui->rgb ? grid->clear_attrs.rgb_bg_color == -1
: grid->clear_attrs.cterm_bg_color == 0;
// TODO(bfredl): support BCE for non-default background
bool nobg = ui->rgb ? attrs.rgb_bg_color == -1
: attrs.cterm_bg_color == 0;
if (nobg && 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.
update_attrs(ui, grid->clear_attrs);
update_attrs(ui, attrs);
if (left == 0) {
if (bot == ui->height - 1) {
if (top == 0) {
@ -725,7 +732,7 @@ static void clear_region(UI *ui, int top, int bot, int left, int right)
}
// restore cursor
cursor_goto(ui, saved_row, saved_col);
cursor_goto(ui, data->row, data->col);
}
static bool can_use_scroll(UI * ui)
@ -792,7 +799,7 @@ static void reset_scroll_region(UI *ui)
unibi_goto(ui, grid->row, grid->col);
}
static void tui_resize(UI *ui, Integer width, Integer height)
static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height)
{
TUIData *data = ui->data;
ugrid_resize(&data->grid, (int)width, (int)height);
@ -810,25 +817,21 @@ static void tui_resize(UI *ui, Integer width, Integer height)
}
}
static void tui_clear(UI *ui)
static void tui_grid_clear(UI *ui, Integer g)
{
TUIData *data = ui->data;
UGrid *grid = &data->grid;
ugrid_clear(grid);
kv_size(data->invalid_regions) = 0;
clear_region(ui, grid->top, grid->bot, grid->left, grid->right);
clear_region(ui, grid->top, grid->bot, grid->left, grid->right,
data->clear_attrs);
}
static void tui_eol_clear(UI *ui)
static void tui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col)
{
TUIData *data = ui->data;
UGrid *grid = &data->grid;
ugrid_eol_clear(grid);
clear_region(ui, grid->row, grid->row, grid->col, grid->right);
}
static void tui_cursor_goto(UI *ui, Integer row, Integer col)
{
data->row = (int)row;
data->col = (int)col;
cursor_goto(ui, (int)row, (int)col);
}
@ -958,27 +961,23 @@ static void tui_mode_change(UI *ui, String mode, Integer mode_idx)
data->showing_mode = (ModeShape)mode_idx;
}
static void tui_set_scroll_region(UI *ui, Integer top, Integer bot,
Integer left, Integer right)
{
TUIData *data = ui->data;
ugrid_set_scroll_region(&data->grid, (int)top, (int)bot,
(int)left, (int)right);
data->scroll_region_is_full_screen =
left == 0 && right == ui->width - 1
&& top == 0 && bot == ui->height - 1;
}
static void tui_scroll(UI *ui, Integer count)
static void tui_grid_scroll(UI *ui, Integer g, Integer top, Integer bot,
Integer left, Integer right,
Integer rows, Integer cols)
{
TUIData *data = ui->data;
UGrid *grid = &data->grid;
ugrid_set_scroll_region(&data->grid, (int)top, (int)bot-1,
(int)left, (int)right-1);
data->scroll_region_is_full_screen =
left == 0 && right == ui->width
&& top == 0 && bot == ui->height;
int clear_top, clear_bot;
ugrid_scroll(grid, (int)count, &clear_top, &clear_bot);
ugrid_scroll(grid, (int)rows, &clear_top, &clear_bot);
if (can_use_scroll(ui)) {
int saved_row = grid->row;
int saved_col = grid->col;
bool scroll_clears_to_current_colour =
unibi_get_bool(data->ut, unibi_back_color_erase);
@ -989,21 +988,21 @@ static void tui_scroll(UI *ui, Integer count)
cursor_goto(ui, grid->top, grid->left);
// also set default color attributes or some terminals can become funny
if (scroll_clears_to_current_colour) {
update_attrs(ui, grid->clear_attrs);
update_attrs(ui, data->clear_attrs);
}
if (count > 0) {
if (count == 1) {
if (rows > 0) {
if (rows == 1) {
unibi_out(ui, unibi_delete_line);
} else {
UNIBI_SET_NUM_VAR(data->params[0], (int)count);
UNIBI_SET_NUM_VAR(data->params[0], (int)rows);
unibi_out(ui, unibi_parm_delete_line);
}
} else {
if (count == -1) {
if (rows == -1) {
unibi_out(ui, unibi_insert_line);
} else {
UNIBI_SET_NUM_VAR(data->params[0], -(int)count);
UNIBI_SET_NUM_VAR(data->params[0], -(int)rows);
unibi_out(ui, unibi_parm_insert_line);
}
}
@ -1012,12 +1011,13 @@ static void tui_scroll(UI *ui, Integer count)
if (!data->scroll_region_is_full_screen) {
reset_scroll_region(ui);
}
cursor_goto(ui, saved_row, saved_col);
cursor_goto(ui, data->row, data->col);
if (!scroll_clears_to_current_colour) {
// Scrolling will leave wrong background in the cleared area on non-BCE
// terminals. Update the cleared area.
clear_region(ui, clear_top, clear_bot, grid->left, grid->right);
clear_region(ui, clear_top, clear_bot, grid->left, grid->right,
data->clear_attrs);
}
} else {
// Mark the entire scroll region as invalid for redrawing later
@ -1025,23 +1025,11 @@ static void tui_scroll(UI *ui, Integer count)
}
}
static void tui_highlight_set(UI *ui, HlAttrs attrs)
{
((TUIData *)ui->data)->grid.attrs = attrs;
}
static void tui_put(UI *ui, String text)
static void tui_hl_attr_define(UI *ui, Integer id, HlAttrs attrs,
HlAttrs cterm_attrs, Array info)
{
TUIData *data = ui->data;
UGrid *grid = &data->grid;
UCell *cell;
cell = ugrid_put(&data->grid, (uint8_t *)text.data, text.size);
// ugrid_put does not advance the cursor correctly, as the actual terminal
// will when we print. Its cursor motion model is simplistic and wrong. So
// we have to undo what it has just done before doing it right.
grid->col--;
print_cell(ui, cell);
kv_a(data->attrs, (size_t)id) = attrs;
}
static void tui_bell(UI *ui)
@ -1058,12 +1046,16 @@ static void tui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg,
Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
{
UGrid *grid = &((TUIData *)ui->data)->grid;
grid->clear_attrs.rgb_fg_color = (int)rgb_fg;
grid->clear_attrs.rgb_bg_color = (int)rgb_bg;
grid->clear_attrs.rgb_sp_color = (int)rgb_sp;
grid->clear_attrs.cterm_fg_color = (int)cterm_fg;
grid->clear_attrs.cterm_bg_color = (int)cterm_bg;
TUIData *data = ui->data;
data->clear_attrs.rgb_fg_color = (int)rgb_fg;
data->clear_attrs.rgb_bg_color = (int)rgb_bg;
data->clear_attrs.rgb_sp_color = (int)rgb_sp;
data->clear_attrs.cterm_fg_color = (int)cterm_fg;
data->clear_attrs.cterm_bg_color = (int)cterm_bg;
data->print_attrs = HLATTRS_INVALID;
invalidate(ui, 0, data->grid.height-1, 0, data->grid.width-1);
}
static void tui_flush(UI *ui)
@ -1083,9 +1075,6 @@ static void tui_flush(UI *ui)
tui_busy_stop(ui); // avoid hidden cursor
}
int saved_row = grid->row;
int saved_col = grid->col;
while (kv_size(data->invalid_regions)) {
Rect r = kv_pop(data->invalid_regions);
assert(r.bot < grid->height && r.right < grid->width);
@ -1095,7 +1084,7 @@ static void tui_flush(UI *ui)
});
}
cursor_goto(ui, saved_row, saved_col);
cursor_goto(ui, data->row, data->col);
flush_buf(ui);
}
@ -1176,10 +1165,37 @@ static void tui_option_set(UI *ui, String name, Object value)
TUIData *data = ui->data;
if (strequal(name.data, "termguicolors")) {
ui->rgb = value.data.boolean;
data->print_attrs = HLATTRS_INVALID;
invalidate(ui, 0, data->grid.height-1, 0, data->grid.width-1);
}
}
static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol,
Integer endcol, Integer clearcol, Integer clearattr,
const schar_T *chunk, const sattr_T *attrs)
{
TUIData *data = ui->data;
UGrid *grid = &data->grid;
for (Integer c = startcol; c < endcol; c++) {
memcpy(grid->cells[linerow][c].data, chunk[c-startcol], sizeof(schar_T));
grid->cells[linerow][c].attrs = kv_A(data->attrs, attrs[c-startcol]);
}
UGRID_FOREACH_CELL(grid, (int)linerow, (int)linerow, (int)startcol,
(int)endcol-1, {
cursor_goto(ui, row, col);
print_cell(ui, cell);
});
if (clearcol > endcol) {
HlAttrs cl_attrs = kv_A(data->attrs, (size_t)clearattr);
ugrid_clear_chunk(grid, (int)linerow, (int)endcol, (int)clearcol,
cl_attrs);
clear_region(ui, (int)linerow, (int)linerow, (int)endcol, (int)clearcol-1,
cl_attrs);
}
}
static void invalidate(UI *ui, int top, int bot, int left, int right)
{
TUIData *data = ui->data;

View File

@ -17,7 +17,6 @@
void ugrid_init(UGrid *grid)
{
grid->attrs = HLATTRS_INIT;
grid->clear_attrs = HLATTRS_INIT;
grid->cells = NULL;
}
@ -45,12 +44,13 @@ void ugrid_resize(UGrid *grid, int width, int height)
void ugrid_clear(UGrid *grid)
{
clear_region(grid, grid->top, grid->bot, grid->left, grid->right);
clear_region(grid, grid->top, grid->bot, grid->left, grid->right,
HLATTRS_INIT);
}
void ugrid_eol_clear(UGrid *grid)
void ugrid_clear_chunk(UGrid *grid, int row, int col, int endcol, HlAttrs attrs)
{
clear_region(grid, grid->row, grid->row, grid->col, grid->right);
clear_region(grid, row, row, col, endcol-1, attrs);
}
void ugrid_goto(UGrid *grid, int row, int col)
@ -99,7 +99,8 @@ void ugrid_scroll(UGrid *grid, int count, int *clear_top, int *clear_bot)
*clear_bot = stop;
*clear_top = stop + count + 1;
}
clear_region(grid, *clear_top, *clear_bot, grid->left, grid->right);
clear_region(grid, *clear_top, *clear_bot, grid->left, grid->right,
HLATTRS_INIT);
}
UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size)
@ -117,13 +118,13 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size)
return cell;
}
static void clear_region(UGrid *grid, int top, int bot, int left, int right)
static void clear_region(UGrid *grid, int top, int bot, int left, int right,
HlAttrs attrs)
{
HlAttrs clear_attrs = grid->clear_attrs;
UGRID_FOREACH_CELL(grid, top, bot, left, right, {
cell->data[0] = ' ';
cell->data[1] = 0;
cell->attrs = clear_attrs;
cell->attrs = attrs;
});
}

View File

@ -7,7 +7,7 @@
typedef struct ucell UCell;
typedef struct ugrid UGrid;
#define CELLBYTES (4 * (MAX_MCO+1))
#define CELLBYTES (sizeof(schar_T))
struct ucell {
char data[CELLBYTES + 1];
@ -17,7 +17,6 @@ struct ucell {
struct ugrid {
int top, bot, left, right;
int row, col;
HlAttrs clear_attrs;
int width, height;
HlAttrs attrs;
UCell **cells;

View File

@ -52,14 +52,10 @@ static UI *uis[MAX_UI_COUNT];
static bool ui_ext[kUIExtCount] = { 0 };
static size_t ui_count = 0;
static int row = 0, col = 0;
static struct {
int top, bot, left, right;
} sr;
static int current_attr_code = -1;
static bool pending_cursor_update = false;
static int busy = 0;
static int height, width;
static int old_mode_idx = -1;
static int mode_idx = SHAPE_IDX_N;
static bool pending_mode_update = false;
#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL
# define UI_LOG(funname, ...)
@ -89,7 +85,6 @@ static char uilog_last_event[1024] = { 0 };
#ifdef _MSC_VER
# define UI_CALL(funname, ...) \
do { \
flush_cursor_update(); \
UI_LOG(funname, 0); \
for (size_t i = 0; i < ui_count; i++) { \
UI *ui = uis[i]; \
@ -99,7 +94,6 @@ static char uilog_last_event[1024] = { 0 };
#else
# define UI_CALL(...) \
do { \
flush_cursor_update(); \
UI_LOG(__VA_ARGS__, 0); \
for (size_t i = 0; i < ui_count; i++) { \
UI *ui = uis[i]; \
@ -108,8 +102,8 @@ static char uilog_last_event[1024] = { 0 };
} while (0)
#endif
#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, \
MORE, MORE, ZERO, ignore)
#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, ...) a7
MORE, MORE, MORE, MORE, MORE, ZERO, ignore)
#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10
#define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__)
// Resolves to UI_CALL_MORE or UI_CALL_ZERO.
#define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__)
@ -199,6 +193,9 @@ void ui_refresh(void)
}
row = col = 0;
pending_cursor_update = true;
ui_default_colors_set();
int save_p_lz = p_lz;
p_lz = false; // convince redrawing() to return true ...
@ -207,13 +204,14 @@ void ui_refresh(void)
for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
ui_ext[i] = ext_widgets[i];
ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]),
BOOLEAN_OBJ(ext_widgets[i]));
if (i < kUIGlobalCount) {
ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]),
BOOLEAN_OBJ(ext_widgets[i]));
}
}
ui_mode_info_set();
old_mode_idx = -1;
pending_mode_update = true;
ui_cursor_shape();
current_attr_code = -1;
}
static void ui_refresh_event(void **argv)
@ -226,25 +224,15 @@ void ui_schedule_refresh(void)
loop_schedule(&main_loop, event_create(ui_refresh_event, 0));
}
void ui_resize(int new_width, int new_height)
void ui_resize(int width, int height)
{
width = new_width;
height = new_height;
ui_call_grid_resize(1, width, height);
}
// TODO(bfredl): update default colors when they changed, NOT on resize.
void ui_default_colors_set(void)
{
ui_call_default_colors_set(normal_fg, normal_bg, normal_sp,
cterm_normal_fg_color, cterm_normal_bg_color);
// Deprecated:
UI_CALL(update_fg, (ui->rgb ? normal_fg : cterm_normal_fg_color - 1));
UI_CALL(update_bg, (ui->rgb ? normal_bg : cterm_normal_bg_color - 1));
UI_CALL(update_sp, (ui->rgb ? normal_sp : -1));
sr.top = 0;
sr.bot = height - 1;
sr.left = 0;
sr.right = width - 1;
ui_call_resize(width, height);
}
void ui_busy_start(void)
@ -269,6 +257,18 @@ void ui_attach_impl(UI *ui)
uis[ui_count++] = ui;
ui_refresh_options();
for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) {
ui_set_ext_option(ui, i, ui->ui_ext[i]);
}
bool sent = false;
if (ui->ui_ext[kUIHlState]) {
sent = highlight_use_hlstate();
}
if (!sent) {
ui_send_all_hls(ui);
}
ui_refresh();
}
@ -302,95 +302,32 @@ void ui_detach_impl(UI *ui)
}
}
// Set scrolling region for window 'wp'.
// The region starts 'off' lines from the start of the window.
// Also set the vertical scroll region for a vertically split window. Always
// the full width of the window, excluding the vertical separator.
void ui_set_scroll_region(win_T *wp, int off)
void ui_set_ext_option(UI *ui, UIExtension ext, bool active)
{
sr.top = wp->w_winrow + off;
sr.bot = wp->w_winrow + wp->w_height - 1;
if (wp->w_width != Columns) {
sr.left = wp->w_wincol;
sr.right = wp->w_wincol + wp->w_width - 1;
}
ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right);
}
// Reset scrolling region to the whole screen.
void ui_reset_scroll_region(void)
{
sr.top = 0;
sr.bot = (int)Rows - 1;
sr.left = 0;
sr.right = (int)Columns - 1;
ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right);
}
void ui_set_highlight(int attr_code)
{
if (current_attr_code == attr_code) {
if (ext < kUIGlobalCount) {
ui_refresh();
return;
}
current_attr_code = attr_code;
HlAttrs attrs = HLATTRS_INIT;
if (attr_code != 0) {
HlAttrs *aep = syn_attr2entry(attr_code);
if (aep) {
attrs = *aep;
}
}
UI_CALL(highlight_set, attrs);
}
void ui_clear_highlight(void)
{
ui_set_highlight(0);
}
void ui_puts(uint8_t *str)
{
uint8_t *p = str;
uint8_t c;
while ((c = *p)) {
if (c < 0x20) {
abort();
}
size_t clen = (size_t)mb_ptr2len(p);
ui_call_put((String){ .data = (char *)p, .size = clen });
col++;
if (mb_ptr2cells(p) > 1) {
// double cell character, blank the next cell
ui_call_put((String)STRING_INIT);
col++;
}
if (utf_ambiguous_width(utf_ptr2char(p))) {
pending_cursor_update = true;
}
if (col >= width) {
ui_linefeed();
}
p += clen;
if (p_wd) { // 'writedelay': flush & delay each time.
ui_flush();
uint64_t wd = (uint64_t)labs(p_wd);
os_microdelay(wd * 1000u, true);
}
if (ui->option_set) {
ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]),
BOOLEAN_OBJ(active));
}
}
void ui_putc(uint8_t c)
void ui_line(int row, int startcol, int endcol, int clearcol, int clearattr)
{
uint8_t buf[2] = {c, 0};
ui_puts(buf);
size_t off = LineOffset[row]+(size_t)startcol;
UI_CALL(raw_line, 1, row, startcol, endcol, clearcol, clearattr,
(const schar_T *)ScreenLines+off, (const sattr_T *)ScreenAttrs+off);
if (p_wd) { // 'writedelay': flush & delay each time.
int old_row = row, old_col = col;
// If'writedelay is active, we set the cursor to highlight what was drawn
ui_cursor_goto(row, MIN(clearcol, (int)Columns-1));
ui_flush();
uint64_t wd = (uint64_t)labs(p_wd);
os_microdelay(wd * 1000u, true);
ui_cursor_goto(old_row, old_col);
}
}
void ui_cursor_goto(int new_row, int new_col)
@ -450,30 +387,19 @@ int ui_current_col(void)
void ui_flush(void)
{
cmdline_ui_flush();
if (pending_cursor_update) {
ui_call_grid_cursor_goto(1, row, col);
pending_cursor_update = false;
}
if (pending_mode_update) {
char *full_name = shape_table[mode_idx].full_name;
ui_call_mode_change(cstr_as_string(full_name), mode_idx);
pending_mode_update = false;
}
ui_call_flush();
}
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 flush_cursor_update(void)
{
if (pending_cursor_update) {
pending_cursor_update = false;
ui_call_cursor_goto(row, col);
}
}
/// Check if current mode has changed.
/// May update the shape of the cursor.
void ui_cursor_shape(void)
@ -481,12 +407,11 @@ void ui_cursor_shape(void)
if (!full_screen) {
return;
}
int mode_idx = cursor_get_mode_idx();
int new_mode_idx = cursor_get_mode_idx();
if (old_mode_idx != mode_idx) {
old_mode_idx = mode_idx;
char *full_name = shape_table[mode_idx].full_name;
ui_call_mode_change(cstr_as_string(full_name), mode_idx);
if (new_mode_idx != mode_idx) {
mode_idx = new_mode_idx;
pending_mode_update = true;
}
conceal_check_cursur_line();
}

View File

@ -5,13 +5,18 @@
#include <stdbool.h>
#include <stdint.h>
#include "api/private/defs.h"
#include "nvim/globals.h"
#include "nvim/api/private/defs.h"
#include "nvim/highlight_defs.h"
typedef enum {
kUICmdline = 0,
kUIPopupmenu,
kUITabline,
kUIWildmenu,
#define kUIGlobalCount (kUIWildmenu+1)
kUINewgrid,
kUIHlState,
kUIExtCount,
} UIExtension;
@ -19,7 +24,9 @@ EXTERN const char *ui_ext_names[] INIT(= {
"ext_cmdline",
"ext_popupmenu",
"ext_tabline",
"ext_wildmenu"
"ext_wildmenu",
"ext_newgrid",
"ext_hlstate",
});
@ -30,9 +37,17 @@ struct ui_t {
bool ui_ext[kUIExtCount]; ///< Externalized widgets
int width, height;
void *data;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_events.generated.h"
#endif
// For perfomance and simplicity, we use the dense screen representation
// in the bridge and the TUI. The remote_ui module will translate this
// in to the public grid_line format.
void (*raw_line)(UI *ui, Integer grid, Integer row, Integer startcol,
Integer endcol, Integer clearcol, Integer clearattr,
const schar_T *chunk, const sattr_T *attrs);
void (*event)(UI *ui, char *name, Array args, bool *args_consumed);
void (*stop)(UI *ui);
void (*inspect)(UI *ui, Dictionary *info);

View File

@ -42,10 +42,9 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
rv->ui = ui;
rv->bridge.rgb = ui->rgb;
rv->bridge.stop = ui_bridge_stop;
rv->bridge.resize = ui_bridge_resize;
rv->bridge.clear = ui_bridge_clear;
rv->bridge.eol_clear = ui_bridge_eol_clear;
rv->bridge.cursor_goto = ui_bridge_cursor_goto;
rv->bridge.grid_resize = ui_bridge_grid_resize;
rv->bridge.grid_clear = ui_bridge_grid_clear;
rv->bridge.grid_cursor_goto = ui_bridge_grid_cursor_goto;
rv->bridge.mode_info_set = ui_bridge_mode_info_set;
rv->bridge.update_menu = ui_bridge_update_menu;
rv->bridge.busy_start = ui_bridge_busy_start;
@ -53,10 +52,8 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
rv->bridge.mouse_on = ui_bridge_mouse_on;
rv->bridge.mouse_off = ui_bridge_mouse_off;
rv->bridge.mode_change = ui_bridge_mode_change;
rv->bridge.set_scroll_region = ui_bridge_set_scroll_region;
rv->bridge.scroll = ui_bridge_scroll;
rv->bridge.highlight_set = ui_bridge_highlight_set;
rv->bridge.put = ui_bridge_put;
rv->bridge.grid_scroll = ui_bridge_grid_scroll;
rv->bridge.hl_attr_define = ui_bridge_hl_attr_define;
rv->bridge.bell = ui_bridge_bell;
rv->bridge.visual_bell = ui_bridge_visual_bell;
rv->bridge.default_colors_set = ui_bridge_default_colors_set;
@ -65,6 +62,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
rv->bridge.set_title = ui_bridge_set_title;
rv->bridge.set_icon = ui_bridge_set_icon;
rv->bridge.option_set = ui_bridge_option_set;
rv->bridge.raw_line = ui_bridge_raw_line;
rv->scheduler = scheduler;
for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
@ -133,19 +131,45 @@ static void ui_bridge_stop_event(void **argv)
ui->stop(ui);
}
static void ui_bridge_highlight_set(UI *b, HlAttrs attrs)
static void ui_bridge_hl_attr_define(UI *ui, Integer id, HlAttrs attrs,
HlAttrs cterm_attrs, Array info)
{
HlAttrs *a = xmalloc(sizeof(HlAttrs));
*a = attrs;
UI_BRIDGE_CALL(b, highlight_set, 2, b, a);
UI_BRIDGE_CALL(ui, hl_attr_define, 3, ui, INT2PTR(id), a);
}
static void ui_bridge_highlight_set_event(void **argv)
static void ui_bridge_hl_attr_define_event(void **argv)
{
UI *ui = UI(argv[0]);
ui->highlight_set(ui, *((HlAttrs *)argv[1]));
xfree(argv[1]);
Array info = ARRAY_DICT_INIT;
ui->hl_attr_define(ui, PTR2INT(argv[1]), *((HlAttrs *)argv[2]),
*((HlAttrs *)argv[2]), info);
xfree(argv[2]);
}
static void ui_bridge_raw_line_event(void **argv)
{
UI *ui = UI(argv[0]);
ui->raw_line(ui, PTR2INT(argv[1]), PTR2INT(argv[2]), PTR2INT(argv[3]),
PTR2INT(argv[4]), PTR2INT(argv[5]), PTR2INT(argv[6]),
argv[7], argv[8]);
xfree(argv[7]);
xfree(argv[8]);
}
static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row,
Integer startcol, Integer endcol,
Integer clearcol, Integer clearattr,
const schar_T *chunk, const sattr_T *attrs)
{
size_t ncol = (size_t)(endcol-startcol);
schar_T *c = xmemdup(chunk, ncol * sizeof(schar_T));
sattr_T *hl = xmemdup(attrs, ncol * sizeof(sattr_T));
UI_BRIDGE_CALL(ui, raw_line, 9, ui, INT2PTR(grid), INT2PTR(row),
INT2PTR(startcol), INT2PTR(endcol), INT2PTR(clearcol),
INT2PTR(clearattr), c, hl);
}
static void ui_bridge_suspend(UI *b)
{
UIBridgeData *data = (UIBridgeData *)b;

View File

@ -156,6 +156,6 @@ describe("ui_options in metadata", function()
local api = helpers.call('api_info')
local options = api.ui_options
eq({'rgb', 'ext_cmdline', 'ext_popupmenu',
'ext_tabline', 'ext_wildmenu'}, options)
'ext_tabline', 'ext_wildmenu', 'ext_newgrid', 'ext_hlstate'}, options)
end)
end)

View File

@ -1242,6 +1242,8 @@ describe('API', function()
ext_popupmenu = false,
ext_tabline = false,
ext_wildmenu = false,
ext_newgrid = screen._options.ext_newgrid or false,
ext_hlstate=false,
height = 4,
rgb = true,
width = 20,
@ -1252,18 +1254,9 @@ describe('API', function()
screen:detach()
screen = Screen.new(44, 99)
screen:attach({ rgb = false })
expected = {
{
chan = 1,
ext_cmdline = false,
ext_popupmenu = false,
ext_tabline = false,
ext_wildmenu = false,
height = 99,
rgb = false,
width = 44,
}
}
expected[1].rgb = false
expected[1].width = 44
expected[1].height = 99
eq(expected, nvim("list_uis"))
end)
end)

View File

@ -207,7 +207,7 @@ describe('tui', function()
screen:set_default_attr_ids({
[1] = {reverse = true},
[2] = {foreground = 13, special = Screen.colors.Grey0},
[3] = {special = Screen.colors.Grey0, bold = true, reverse = true},
[3] = {bold = true, reverse = true, special = Screen.colors.Grey0},
[4] = {bold = true},
[5] = {special = Screen.colors.Grey0, reverse = true, foreground = 4},
[6] = {foreground = 4, special = Screen.colors.Grey0},
@ -257,11 +257,11 @@ describe('tui', function()
it('shows up in nvim_list_uis', function()
feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(v))})\013')
screen:expect([=[
{5: }|
[[['ext_cmdline', v:false], ['ext_popupmenu', v:fa|
lse], ['ext_tabline', v:false], ['ext_wildmenu', v|
:false], ['height', 6], ['rgb', v:false], ['width'|
, 50]]] |
[[['ext_cmdline', v:false], ['ext_hlstate', v:fals|
e], ['ext_newgrid', v:true], ['ext_popupmenu', v:f|
alse], ['ext_tabline', v:false], ['ext_wildmenu', |
v:false], ['height', 6], ['rgb', v:false], ['width|
', 50]]] |
{10:Press ENTER or type command to continue}{1: } |
{3:-- TERMINAL --} |
]=])

View File

@ -29,6 +29,9 @@ describe('external cmdline', function()
if name == "cmdline_show" then
local content, pos, firstc, prompt, indent, level = unpack(data)
ok(level > 0)
for _,item in ipairs(content) do
item[1] = screen:get_hl(item[1])
end
cmdline[level] = {content=content, pos=pos, firstc=firstc,
prompt=prompt, indent=indent}
last_level = level
@ -87,6 +90,7 @@ describe('external cmdline', function()
|
]], nil, nil, function()
eq(1, last_level)
--print(require('inspect')(cmdline))
eq({{
content = { { {}, "" } },
firstc = ":",
@ -168,10 +172,10 @@ describe('external cmdline', function()
it('from normal mode', function()
feed(':')
screen:expect([[
|
^ |
{1:~ }|
{1:~ }|
{3:c^ }|
{3:c }|
|
]], nil, nil, function()
eq({{
@ -351,11 +355,11 @@ describe('external cmdline', function()
-- redraw! forgets cursor position. Be OK with that, as UI should indicate
-- focus is at external cmdline anyway.
screen:expect([[
|
{1:~ }|
{1:~ }|
{1:~ }|
^ |
{1:~ }|
{1:~ }|
{1:~ }|
|
]], nil, nil, function()
eq(expectation, cmdline)
end)
@ -363,11 +367,11 @@ describe('external cmdline', function()
feed('<cr>')
screen:expect([[
|
{1:~ }|
{1:~ }|
{1:~ }|
^ |
{1:~ }|
{1:~ }|
{1:~ }|
|
]], nil, nil, function()
eq({{
content = { { {}, "xx3" } },
@ -424,11 +428,11 @@ describe('external cmdline', function()
block = {}
command("redraw!")
screen:expect([[
|
{1:~ }|
{1:~ }|
{1:~ }|
^ |
{1:~ }|
{1:~ }|
{1:~ }|
|
]], nil, nil, function()
eq({ { { {}, 'function Foo()'} },
{ { {}, ' line1'} } }, block)
@ -528,9 +532,9 @@ describe('external cmdline', function()
screen:expect([[
|
{2:[No Name] }|
{1::}make |
{1::}make^ |
{3:[Command Line] }|
^ |
|
]], nil, nil, function()
eq({nil, {
content = { { {}, "yank" } },
@ -572,11 +576,11 @@ describe('external cmdline', function()
cmdline = {}
command("redraw!")
screen:expect([[
|
{1:~ }|
{1:~ }|
{1:~ }|
^ |
{1:~ }|
{1:~ }|
{1:~ }|
|
]], nil, nil, function()
eq({{
content = { { {}, "make" } },

View File

@ -30,10 +30,15 @@ describe('ui receives option updates', function()
ext_popupmenu=false,
ext_tabline=false,
ext_wildmenu=false,
ext_newgrid=false,
ext_hlstate=false,
}
it("for defaults", function()
screen:attach()
-- NB: UI test suite can be run in both "newgrid" and legacy grid mode.
-- In both cases check that the received value is the one requested.
defaults.ext_newgrid = screen._options.ext_newgrid or false
screen:expect(function()
eq(defaults, screen.options)
end)
@ -41,6 +46,7 @@ describe('ui receives option updates', function()
it("when setting options", function()
screen:attach()
defaults.ext_newgrid = screen._options.ext_newgrid or false
local changed = {}
for k,v in pairs(defaults) do
changed[k] = v
@ -89,6 +95,7 @@ describe('ui receives option updates', function()
end
screen:attach({ext_cmdline=true, ext_wildmenu=true})
defaults.ext_newgrid = screen._options.ext_newgrid or false
changed.ext_cmdline = true
changed.ext_wildmenu = true
screen:expect(function()

View File

@ -142,6 +142,8 @@ function Screen.new(width, height)
_default_attr_ignore = nil,
_mouse_enabled = true,
_attrs = {},
_attr_table = {[0]={{},{}}},
_clear_attrs = {},
_cursor = {
row = 1, col = 1
},
@ -163,6 +165,11 @@ function Screen:attach(options)
if options == nil then
options = {rgb=true}
end
if options.ext_newgrid == nil then
options.ext_newgrid = true
end
self._options = options
self._clear_attrs = (options.ext_newgrid and {{},{}}) or {}
uimeths.attach(self._width, self._height, options)
end
@ -176,6 +183,7 @@ end
function Screen:set_option(option, value)
uimeths.set_option(option, value)
self._options[option] = value
end
-- Asserts that `expected` eventually matches the screen state.
@ -339,7 +347,7 @@ function Screen:_handle_resize(width, height)
for _ = 1, height do
local cols = {}
for _ = 1, width do
table.insert(cols, {text = ' ', attrs = {}})
table.insert(cols, {text = ' ', attrs = self._clear_attrs})
end
table.insert(rows, cols)
end
@ -353,14 +361,24 @@ function Screen:_handle_resize(width, height)
}
end
function Screen:_handle_grid_resize(grid, width, height)
assert(grid == 1)
self:_handle_resize(width, height)
end
function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info)
self._cursor_style_enabled = cursor_style_enabled
self._mode_info = mode_info
end
function Screen:_handle_clear()
self:_clear_block(self._scroll_region.top, self._scroll_region.bot,
self._scroll_region.left, self._scroll_region.right)
self:_clear_block(1, self._height, 1, self._width)
end
function Screen:_handle_grid_clear(grid)
assert(grid == 1)
self:_handle_clear()
end
function Screen:_handle_eol_clear()
@ -373,6 +391,12 @@ function Screen:_handle_cursor_goto(row, col)
self._cursor.col = col + 1
end
function Screen:_handle_grid_cursor_goto(grid, row, col)
assert(grid == 1)
self._cursor.row = row + 1
self._cursor.col = col + 1
end
function Screen:_handle_busy_start()
self._busy = true
end
@ -434,6 +458,27 @@ function Screen:_handle_scroll(count)
end
end
function Screen:_handle_grid_scroll(grid, top, bot, left, right, rows, cols)
assert(grid == 1)
assert(cols == 0)
-- TODO: if we truly believe we should translate the other way
self:_handle_set_scroll_region(top,bot-1,left,right-1)
self:_handle_scroll(rows)
end
function Screen:_handle_hl_attr_define(id, rgb_attrs, cterm_attrs, info)
self._attr_table[id] = {rgb_attrs, cterm_attrs}
self._new_attrs = true
end
function Screen:get_hl(val)
if self._options.ext_newgrid then
return self._attr_table[val][1]
else
return val
end
end
function Screen:_handle_highlight_set(attrs)
self._attrs = attrs
end
@ -445,6 +490,25 @@ function Screen:_handle_put(str)
self._cursor.col = self._cursor.col + 1
end
function Screen:_handle_grid_line(grid, row, col, items)
assert(grid == 1)
local line = self._rows[row+1]
local colpos = col+1
local hl = self._clear_attrs
for _,item in ipairs(items) do
local text, hlid, count = unpack(item)
if hlid ~= nil then
hl = self._attr_table[hlid]
end
for _ = 1, (count or 1) do
local cell = line[colpos]
cell.text = text
cell.attrs = hl
colpos = colpos+1
end
end
end
function Screen:_handle_bell()
self.bell = true
end
@ -498,7 +562,7 @@ function Screen:_clear_row_section(rownum, startcol, stopcol)
local row = self._rows[rownum]
for i = startcol, stopcol do
row[i].text = ' '
row[i].attrs = {}
row[i].attrs = self._clear_attrs
end
end
@ -506,7 +570,11 @@ function Screen:_row_repr(row, attr_ids, attr_ignore)
local rv = {}
local current_attr_id
for i = 1, self._width do
local attr_id = self:_get_attr_id(attr_ids, attr_ignore, row[i].attrs)
local attrs = row[i].attrs
if self._options.ext_newgrid then
attrs = attrs[(self._options.rgb and 1) or 2]
end
local attr_id = self:_get_attr_id(attr_ids, attr_ignore, attrs, row[i].hl_id)
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
@ -647,6 +715,7 @@ function Screen:_get_attr_id(attr_ids, ignore, attrs)
if not attr_ids then
return
end
for id, a in pairs(attr_ids) do
if self:_equal_attrs(a, attrs) then
return id

View File

@ -48,13 +48,13 @@ describe('screen', function()
end)
end)
describe('Screen', function()
local function screen_tests(newgrid)
local screen
before_each(function()
clear()
screen = Screen.new()
screen:attach()
screen:attach({rgb=true,ext_newgrid=newgrid})
screen:set_default_attr_ids( {
[0] = {bold=true, foreground=255},
[1] = {bold=true, reverse=true},
@ -741,4 +741,12 @@ describe('Screen', function()
|
]])
end)
end
describe("Screen (char-based)", function()
screen_tests(false)
end)
describe("Screen (line-based)", function()
screen_tests(true)
end)