Merge pull request #8221 from bfredl/hlstate

UI grid protocol revision: line based updates and semantic highlights
This commit is contained in:
Björn Linse 2018-07-21 14:41:49 +02:00 committed by GitHub
commit 94841e5eae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2193 additions and 943 deletions

View File

@ -21,7 +21,7 @@ After connecting to Nvim (usually a spawned, embedded instance) use the
|nvim_ui_attach| API method to tell Nvim that your program wants to draw the
Nvim screen grid with a size of width × height cells. `options` must be
a dictionary with these (optional) keys:
`rgb` Decides the color format.
`rgb` Decides the color format. |ui-rgb|
Set true (default) for 24-bit RGB colors.
Set false for terminal colors (max of 256).
*ui-ext-options*
@ -29,6 +29,8 @@ a dictionary with these (optional) keys:
`ext_tabline` Externalize the tabline. |ui-tabline|
`ext_cmdline` Externalize the cmdline. |ui-cmdline|
`ext_wildmenu` Externalize the wildmenu. |ui-ext-wildmenu|
`ext_newgrid` Use new revision of the grid events. |ui-newgrid|
`ext_hlstate` Use detailed highlight state. |ui-hlstate|
Specifying a non-existent option is an error. UIs can check the |api-metadata|
`ui_options` key for supported options. Additionally Nvim (currently) requires
@ -49,12 +51,18 @@ Events must be handled in-order. The user should only see the updated screen
state after all events in the same "redraw" batch are processed (not any
intermediate state after processing only part of the array).
Nvim sends |ui-global| and |ui-grid| events unconditionally; these suffice to
implement a terminal-like layout.
By default, Nvim sends |ui-global| and |ui-grid-old| events; these suffice to
implement a terminal-like interface. However there are two revisions of the
grid part of the protocol. The newer revision |ui-newgrid|, enabled by
`ext_newgrid` option, has some improvements, such as a more efficient
representation of highlighted text, simplified events and room for futher
enhancements that will use multiple grids. The older revision is available and
used by default only for backwards compatibility reasons. New UIs are strongly
recommended to use |ui-newgrid|, as further protocol extensions will require it.
Nvim optionally sends screen elements "semantically" as structured events
instead of raw grid-lines, controlled by |ui-ext-options|. The UI must present
those elements itself; Nvim will not draw those elements on the |ui-grid|.
those elements itself; Nvim will not draw those elements on the grid.
Future versions of Nvim may add new update kinds and may append new parameters
to existing update kinds. Clients must be prepared to ignore such extensions,
@ -149,7 +157,147 @@ Global Events *ui-global*
Notify the user with an audible or visual bell, respectively.
==============================================================================
Grid Events *ui-grid*
Grid Events (new revision) *ui-newgrid*
These events are used if `ext_newgrid` option is set (recommended for all new
UIs).
Most of these events take a `grid` index as first parameter. Grid 1 is the
global grid used by default for the entire editor screen state. Grids other
than that will be defined by future extensions. Just activating the `ext_newgrid`
option by itself will never cause any additional grids to be created.
Highlight attribute groups are predefined. UIs should maintain a table to map
numerical highlight `id`:s to the actual attributes.
["grid_resize", grid, width, height]
Resize a `grid`. If `grid` wasn't seen by the client before, a new grid is
being created with this size.
["default_colors_set", rgb_fg, rgb_bg, rgb_sp, cterm_fg, cterm_bg]
The first three arguments set the default foreground, background and
special colors respectively. `cterm_fg` and `cterm_bg` specifies the
default color codes to use in a 256-color terminal.
Note: unlike the corresponding events in the first revision, the
screen is not always cleared after sending this event. The GUI has to
repaint the screen with changed background color itself.
*ui-event-hl_attr_define*
["hl_attr_define", id, rgb_attr, cterm_attr, info]
Add a highlight with `id` to the highlight table, with the
attributes specified by the `rgb_attr` and `cterm_attr` dicts, with the
following (all optional) keys.
`foreground`: foreground color.
`background`: background color.
`special`: color to use for underline and undercurl, when present.
`reverse`: reverse video. Foreground and background colors are
switched.
`italic`: italic text.
`bold`: bold text.
`underline`: underlined text. The line has `special` color.
`undercurl`: undercurled text. The curl has `special` color.
For absent color keys the default color should be used. Don't store
the default value in the table, rather a sentinel value, so that
a changed default color will take effect.
All boolean keys default to false, and will only be sent when they
are true.
Highlights are always transmitted both for both the rgb format and as
terminal 256-color codes, as the `rgb_attr` and `cterm_attr` parameters
respectively. The |ui-rgb| option has no effect effect anymore.
Most external UIs will only need to store and use the `rgb_attr`
attributes.
`id` 0 will always be used for the default highlight with colors defined
by `default_colors_set` and no styles applied.
Note: `id`:s can be reused if Nvim's internal highlight table is full.
In this case, Nvim will always issue redraws of screen cells that are
affected by redefined `id`:s, so UIs do not need to keep track of this
themselves.
`info` is an empty array per default, and will be used by the
|ui-hlstate| extension explaned below.
*ui-event-grid_line*
["grid_line", grid, row, col_start, cells]
Redraw a continous part of a `row` on a `grid`, starting at the column
`col_start`. `cells` is an array of arrays each with 1 to 3 items:
`[text(, hl_id, repeat)]` . `text` is the UTF-8 text that should be put in
a cell, with the highlight `hl_id` defined by a previous `hl_attr_define`
call. If `hl_id` is not present the most recently seen `hl_id` in
the same call should be used (it is always sent for the first
cell in the event). If `repeat` is present, the cell should be
repeated `repeat` times (including the first time), otherwise just
once.
The right cell of a double-width char will be represented as the empty
string. Double-width chars never use `repeat`.
If the array of cell changes doesn't reach to the end of the line, the
rest should remain unchanged. A whitespace char, repeated
enough to cover the remaining line, will be sent when the rest of the
line should be cleared.
["grid_clear", grid]
Clear a `grid`.
["grid_destroy", grid]
`grid` will not be used anymore and the UI can free any data associated
with it.
["grid_cursor_goto", grid, row, column]
Makes `grid` the current grid and `row, column` the cursor position on this
grid. This event will be sent at most once in a `redraw` batch and
indicates the visible cursor position.
["grid_scroll", grid, top, bot, left, right, rows, cols]
Scroll the text in the a region of `grid`. The diagrams below illustrate
what will happen, depending on the scroll direction. "=" is used to
represent the SR(scroll region) boundaries and "-" the moved rectangles.
Note that dst and src share a common region.
If `rows` is bigger than 0, move a rectangle in the SR up, this can
happen while scrolling down.
>
+-------------------------+
| (clipped above SR) | ^
|=========================| dst_top |
| dst (still in SR) | |
+-------------------------+ src_top |
| src (moved up) and dst | |
|-------------------------| dst_bot |
| src (cleared) | |
+=========================+ src_bot
<
If `rows` is less than zero, move a rectangle in the SR down, this can
happen while scrolling up.
>
+=========================+ src_top
| src (cleared) | |
|------------------------ | dst_top |
| src (moved down) and dst| |
+-------------------------+ src_bot |
| dst (still in SR) | |
|=========================| dst_bot |
| (clipped below SR) | v
+-------------------------+
<
`cols` is always zero in this version of Nvim, and reserved for future
use.
Note when updating code from |ui-grid-old| events: ranges are
end-exclusive, which is consistent with API conventions, but different
from `set_scroll_region` which was end-inclusive.
==============================================================================
Grid Events (first revision) *ui-grid-old*
This is an older representation of the screen grid, used if `ext_newgrid`
option is not set.
["resize", width, height]
The grid is resized to `width` and `height` cells.
@ -173,7 +321,7 @@ Grid Events *ui-grid*
Set the default foreground, background and special colors
respectively.
*ui-event-highlight_set*
*ui-event-highlight_set*
["highlight_set", attrs]
Set the attributes that the next text put on the grid will have.
`attrs` is a dict with the keys below. Any absent key is reset
@ -197,6 +345,9 @@ Grid Events *ui-grid*
["set_scroll_region", top, bot, left, right]
Define the scroll region used by `scroll` below.
Note: ranges are end-inclusive, which is inconsistent with API
conventions.
["scroll", count]
Scroll the text in the scroll region. The diagrams below illustrate
@ -230,6 +381,42 @@ Grid Events *ui-grid*
| (clipped below SR) | v
+-------------------------+
<
==============================================================================
Detailed highlight state Extension *ui-hlstate*
Only sent if `ext_hlstate` option is set in |ui-options|. `ext_hlstate` implies
`ext_newgrid`.
By default, nvim will only describe grid cells using the final calculated
higlight attributes, as described by the dict keys in |ui-event-highlight_set|.
The `ext_hlstate` extension allows to the UI to also receive a semantic
describtion of the higlights active in a cell. In this mode highlights will be
predefined in a table, see |ui-event-hl_attr_define| and |ui-event-grid_line|.
The `info` parameter in `hl_attr_define` will contain a semantic description
of the highlights. As highlight groups can be combined, this will be an array
of items, with the item with highest priority last. Each item is a dictionary
with the following possible keys:
`kind`: always present. One of the following values:
"ui": A builtin ui highlight.
"syntax": highlight applied to a buffer by a syntax declaration or
other runtime/plugin functionallity such as
|nvim_buf_add_highlight|
"terminal": highlight from a process running in a |terminal-emulator|.
Contains no futher semantic information.
`ui_name`: Name of the builtin highlight. See |highlight-groups| for
possible values. Only present for "ui".
`hi_name`: Name of the final |:highlight| group where the used
attributes are defined.
`id`: Unique numeric id representing this item.
Note: "ui" items will have both `ui_name` and `hi_name` present. These can
differ, because the builtin group was linked to another group |hi-link| , or
because 'winhighlight' was used. UI items will be transmitted, even if the
highlight group is cleared, so `ui_name` can always be used to reliably identify
screen elements, even if no attributes have been applied.
==============================================================================
Popupmenu Events *ui-popupmenu*

View File

@ -16,6 +16,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/popupmnu.h"
#include "nvim/cursor_shape.h"
#include "nvim/highlight.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.c.generated.h"
@ -25,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;
@ -70,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;
@ -81,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;
@ -102,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);
@ -173,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) {
@ -187,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;
@ -242,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

@ -21,6 +21,7 @@
#include "nvim/vim.h"
#include "nvim/buffer.h"
#include "nvim/file_search.h"
#include "nvim/highlight.h"
#include "nvim/window.h"
#include "nvim/types.h"
#include "nvim/ex_docmd.h"
@ -1850,3 +1851,22 @@ Object nvim_get_proc(Integer pid, Error *err)
#endif
return rvobj;
}
/// NB: if your UI doesn't use hlstate, this will not return hlstate first time
Array nvim__inspect_cell(Integer row, Integer col, Error *err)
{
Array ret = ARRAY_DICT_INIT;
if (row < 0 || row >= screen_Rows
|| col < 0 || col >= screen_Columns) {
return ret;
}
size_t off = LineOffset[(size_t)row] + (size_t)col;
ADD(ret, STRING_OBJ(cstr_to_string((char *)ScreenLines[off])));
int attr = ScreenAttrs[off];
ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err)));
// will not work first time
if (!highlight_use_hlstate()) {
ADD(ret, ARRAY_OBJ(hl_inspect(attr)));
}
return ret;
}

View File

@ -45,6 +45,7 @@
#include "nvim/fold.h"
#include "nvim/getchar.h"
#include "nvim/hashtab.h"
#include "nvim/highlight.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/main.h"

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

@ -34,6 +34,7 @@
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
#include "nvim/highlight.h"
#include "nvim/indent.h"
#include "nvim/buffer_updates.h"
#include "nvim/main.h"

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

@ -31,6 +31,7 @@
#include "nvim/fileio.h"
#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/highlight.h"
#include "nvim/if_cscope.h"
#include "nvim/indent.h"
#include "nvim/main.h"
@ -214,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"
@ -2943,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_cterm_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));
}
@ -3032,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.
@ -3500,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

418
src/nvim/highlight.c Normal file
View File

@ -0,0 +1,418 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// highlight.c: low level code for UI and syntax highlighting
#include "nvim/vim.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/map.h"
#include "nvim/screen.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "highlight.c.generated.h"
#endif
static bool hlstate_active = false;
static kvec_t(HlEntry) attr_entries = KV_INITIAL_VALUE;
static Map(HlEntry, int) *attr_entry_ids;
static Map(int, int) *combine_attr_entries;
void highlight_init(void)
{
attr_entry_ids = map_new(HlEntry, int)();
combine_attr_entries = map_new(int, int)();
// index 0 is no attribute, add dummy entry:
kv_push(attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlUnknown,
.id1 = 0, .id2 = 0 }));
}
/// @return TRUE if hl table was reset
bool highlight_use_hlstate(void)
{
if (hlstate_active) {
return false;
}
hlstate_active = true;
// hl tables must now be rebuilt.
clear_hl_tables(true);
return true;
}
/// Return the attr number for a set of colors and font, and optionally
/// a semantic description (see ext_hlstate documentation).
/// Add a new entry to the attr_entries array if the combination is new.
/// @return 0 for error.
static int get_attr_entry(HlEntry entry)
{
if (!hlstate_active) {
// This information will not be used, erase it and reduce the table size.
entry.kind = kHlUnknown;
entry.id1 = 0;
entry.id2 = 0;
}
int id = map_get(HlEntry, int)(attr_entry_ids, entry);
if (id > 0) {
return id;
}
static bool recursive = false;
if (kv_size(attr_entries) > MAX_TYPENR) {
// Running out of attribute entries! remove all attributes, and
// compute new ones for all groups.
// When called recursively, we are really out of numbers.
if (recursive) {
EMSG(_("E424: Too many different highlighting attributes in use"));
return 0;
}
recursive = true;
clear_hl_tables(true);
recursive = false;
if (entry.kind == kHlCombine) {
// This entry is now invalid, don't put it
return 0;
}
}
id = (int)kv_size(attr_entries);
kv_push(attr_entries, 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)
{
// TODO(bfredl): should we do this unconditionally
if (at_en.cterm_fg_color != 0 || at_en.cterm_bg_color != 0
|| at_en.rgb_fg_color != -1 || at_en.rgb_bg_color != -1
|| at_en.rgb_sp_color != -1 || at_en.cterm_ae_attr != 0
|| at_en.rgb_ae_attr != 0) {
return get_attr_entry((HlEntry){ .attr = at_en, .kind = kHlSyntax,
.id1 = idx, .id2 = 0 });
} else {
// If all the fields are cleared, clear the attr field back to default value
return 0;
}
}
/// Get attribute code for a builtin highlight group.
///
/// The final syntax group could be modified by hi-link or 'winhighlight'.
int hl_get_ui_attr(int idx, int final_id, bool optional)
{
HlAttrs attrs = HLATTRS_INIT;
bool available = false;
int syn_attr = syn_id2attr(final_id);
if (syn_attr != 0) {
HlAttrs *aep = syn_attr2entry(syn_attr);
if (aep) {
attrs = *aep;
available = true;
}
}
if (optional && !available) {
return 0;
}
return get_attr_entry((HlEntry){ .attr = attrs, .kind = kHlUI,
.id1 = idx, .id2 = final_id });
}
void update_window_hl(win_T *wp, bool invalid)
{
if (!wp->w_hl_needs_update && !invalid) {
return;
}
wp->w_hl_needs_update = false;
// determine window specific background set in 'winhighlight'
if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] > 0) {
wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE,
wp->w_hl_ids[HLF_INACTIVE], true);
} else if (wp->w_hl_id_normal > 0) {
wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, true);
} else {
wp->w_hl_attr_normal = 0;
}
if (wp != curwin) {
wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE),
wp->w_hl_attr_normal);
}
for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) {
int attr;
if (wp->w_hl_ids[hlf] > 0) {
attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false);
} else {
attr = HL_ATTR(hlf);
}
wp->w_hl_attrs[hlf] = attr;
}
}
/// Get attribute code for forwarded :terminal highlights.
int get_term_attr_entry(HlAttrs *aep)
{
return get_attr_entry((HlEntry){ .attr= *aep, .kind = kHlTerminal,
.id1 = 0, .id2 = 0 });
}
/// Clear all highlight tables.
void clear_hl_tables(bool reinit)
{
if (reinit) {
kv_size(attr_entries) = 1;
map_clear(HlEntry, int)(attr_entry_ids);
map_clear(int, int)(combine_attr_entries);
highlight_attr_set_all();
highlight_changed();
redraw_all_later(NOT_VALID);
if (ScreenAttrs) {
// the meaning of 0 doesn't change anyway
// but the rest must be retransmitted
memset(ScreenAttrs, 0,
sizeof(*ScreenAttrs) * (size_t)(screen_Rows * screen_Columns));
}
} else {
kv_destroy(attr_entries);
map_free(HlEntry, int)(attr_entry_ids);
map_free(int, int)(combine_attr_entries);
}
}
// Combine special attributes (e.g., for spelling) with other attributes
// (e.g., for syntax highlighting).
// "prim_attr" overrules "char_attr".
// This creates a new group when required.
// Since we expect there to be few spelling mistakes we don't cache the
// result.
// Return the resulting attributes.
int hl_combine_attr(int char_attr, int prim_attr)
{
if (char_attr == 0) {
return prim_attr;
} else if (prim_attr == 0) {
return char_attr;
}
// TODO(bfredl): could use a struct for clearer intent.
int combine_tag = (char_attr << 16) + prim_attr;
int id = map_get(int, int)(combine_attr_entries, combine_tag);
if (id > 0) {
return id;
}
HlAttrs *char_aep, *spell_aep;
HlAttrs new_en = HLATTRS_INIT;
// Find the entry for char_attr
char_aep = syn_attr2entry(char_attr);
if (char_aep != NULL) {
// Copy all attributes from char_aep to the new entry
new_en = *char_aep;
}
spell_aep = syn_attr2entry(prim_attr);
if (spell_aep != NULL) {
new_en.cterm_ae_attr |= spell_aep->cterm_ae_attr;
new_en.rgb_ae_attr |= spell_aep->rgb_ae_attr;
if (spell_aep->cterm_fg_color > 0) {
new_en.cterm_fg_color = spell_aep->cterm_fg_color;
}
if (spell_aep->cterm_bg_color > 0) {
new_en.cterm_bg_color = spell_aep->cterm_bg_color;
}
if (spell_aep->rgb_fg_color >= 0) {
new_en.rgb_fg_color = spell_aep->rgb_fg_color;
}
if (spell_aep->rgb_bg_color >= 0) {
new_en.rgb_bg_color = spell_aep->rgb_bg_color;
}
if (spell_aep->rgb_sp_color >= 0) {
new_en.rgb_sp_color = spell_aep->rgb_sp_color;
}
}
id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine,
.id1 = char_attr, .id2 = prim_attr });
if (id > 0) {
map_put(int, int)(combine_attr_entries, combine_tag, id);
}
return id;
}
/// Get highlight attributes for a attribute code
HlAttrs *syn_attr2entry(int attr)
{
if (attr <= 0 || attr >= (int)kv_size(attr_entries)) {
// invalid attribute code, or the tables were cleared
return NULL;
}
return &(kv_A(attr_entries, attr).attr);
}
/// Gets highlight description for id `attr_id` as a map.
Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err)
{
HlAttrs *aep = NULL;
Dictionary dic = ARRAY_DICT_INIT;
if (attr_id == 0) {
return dic;
}
aep = syn_attr2entry((int)attr_id);
if (!aep) {
api_set_error(err, kErrorTypeException,
"Invalid attribute id: %" PRId64, attr_id);
return dic;
}
return hlattrs2dict(aep, rgb);
}
/// Converts an HlAttrs into Dictionary
///
/// @param[in] aep data to convert
/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*'
Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb)
{
assert(aep);
Dictionary hl = ARRAY_DICT_INIT;
int mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr;
if (mask & HL_BOLD) {
PUT(hl, "bold", BOOLEAN_OBJ(true));
}
if (mask & HL_STANDOUT) {
PUT(hl, "standout", BOOLEAN_OBJ(true));
}
if (mask & HL_UNDERLINE) {
PUT(hl, "underline", BOOLEAN_OBJ(true));
}
if (mask & HL_UNDERCURL) {
PUT(hl, "undercurl", BOOLEAN_OBJ(true));
}
if (mask & HL_ITALIC) {
PUT(hl, "italic", BOOLEAN_OBJ(true));
}
if (mask & HL_INVERSE) {
PUT(hl, "reverse", BOOLEAN_OBJ(true));
}
if (use_rgb) {
if (aep->rgb_fg_color != -1) {
PUT(hl, "foreground", INTEGER_OBJ(aep->rgb_fg_color));
}
if (aep->rgb_bg_color != -1) {
PUT(hl, "background", INTEGER_OBJ(aep->rgb_bg_color));
}
if (aep->rgb_sp_color != -1) {
PUT(hl, "special", INTEGER_OBJ(aep->rgb_sp_color));
}
} else {
if (cterm_normal_fg_color != aep->cterm_fg_color) {
PUT(hl, "foreground", INTEGER_OBJ(aep->cterm_fg_color - 1));
}
if (cterm_normal_bg_color != aep->cterm_bg_color) {
PUT(hl, "background", INTEGER_OBJ(aep->cterm_bg_color - 1));
}
}
return hl;
}
Array hl_inspect(int attr)
{
Array ret = ARRAY_DICT_INIT;
if (hlstate_active) {
hl_inspect_impl(&ret, attr);
}
return ret;
}
static void hl_inspect_impl(Array *arr, int attr)
{
Dictionary item = ARRAY_DICT_INIT;
if (attr <= 0 || attr >= (int)kv_size(attr_entries)) {
return;
}
HlEntry e = kv_A(attr_entries, attr);
switch (e.kind) {
case kHlSyntax:
PUT(item, "kind", STRING_OBJ(cstr_to_string("syntax")));
PUT(item, "hi_name",
STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id1))));
break;
case kHlUI:
PUT(item, "kind", STRING_OBJ(cstr_to_string("ui")));
const char *ui_name = (e.id1 == -1) ? "Normal" : hlf_names[e.id1];
PUT(item, "ui_name", STRING_OBJ(cstr_to_string(ui_name)));
PUT(item, "hi_name",
STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id2))));
break;
case kHlTerminal:
PUT(item, "kind", STRING_OBJ(cstr_to_string("term")));
break;
case kHlCombine:
// attribute combination is associative, so flatten to an array
hl_inspect_impl(arr, e.id1);
hl_inspect_impl(arr, e.id2);
return;
case kHlUnknown:
return;
}
PUT(item, "id", INTEGER_OBJ(attr));
ADD(*arr, DICTIONARY_OBJ(item));
}

13
src/nvim/highlight.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef NVIM_HIGHLIGHT_H
#define NVIM_HIGHLIGHT_H
#include <stdbool.h>
#include "nvim/highlight_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/ui.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "highlight.h.generated.h"
#endif
#endif // NVIM_HIGHLIGHT_H

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 {
@ -152,4 +165,19 @@ EXTERN RgbValue normal_fg INIT(= -1);
EXTERN RgbValue normal_bg INIT(= -1);
EXTERN RgbValue normal_sp INIT(= -1);
typedef enum {
kHlUnknown,
kHlUI,
kHlSyntax,
kHlTerminal,
kHlCombine,
} HlKind;
typedef struct {
HlAttrs attr;
HlKind kind;
int id1;
int id2;
} HlEntry;
#endif // NVIM_HIGHLIGHT_DEFS_H

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

@ -23,6 +23,7 @@
#include "nvim/fold.h"
#include "nvim/getchar.h"
#include "nvim/hashtab.h"
#include "nvim/highlight.h"
#include "nvim/iconv.h"
#include "nvim/if_cscope.h"
#ifdef HAVE_LOCALE_H
@ -182,6 +183,7 @@ void early_init(void)
eval_init(); // init global variables
init_path(argv0 ? argv0 : "nvim");
init_normal_cmds(); // Init the table of Normal mode commands.
highlight_init();
#if defined(HAVE_LOCALE_H)
// Setup to use the current locale (for ctype() and many other things).
@ -452,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.
@ -1372,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

@ -140,6 +140,22 @@ static inline bool String_eq(String a, String b)
return memcmp(a.data, b.data, a.size) == 0;
}
static inline khint_t HlEntry_hash(HlEntry ae)
{
const uint8_t *data = (const uint8_t *)&ae;
khint_t h = 0;
for (size_t i = 0; i < sizeof(ae); i++) {
h = (h << 5) - h + data[i];
}
return h;
}
static inline bool HlEntry_eq(HlEntry ae1, HlEntry ae2)
{
return memcmp(&ae1, &ae2, sizeof(ae1)) == 0;
}
MAP_IMPL(int, int, DEFAULT_INITIALIZER)
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
@ -149,3 +165,4 @@ MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .async = false }
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)

View File

@ -7,6 +7,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/bufhl_defs.h"
#include "nvim/highlight_defs.h"
#if defined(__NetBSD__)
# undef uint64_t
@ -35,6 +36,7 @@ MAP_DECLS(ptr_t, ptr_t)
MAP_DECLS(uint64_t, ptr_t)
MAP_DECLS(handle_T, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int)
#define map_new(T, U) map_##T##_##U##_new
#define map_free(T, U) map_##T##_##U##_free

View File

@ -10,6 +10,7 @@
#include "nvim/vim.h"
#include "nvim/eval.h"
#include "nvim/highlight.h"
#include "nvim/memfile.h"
#include "nvim/memory.h"
#include "nvim/message.h"
@ -696,7 +697,7 @@ void free_all_mem(void)
/* screenlines (can't display anything now!) */
free_screenlines();
clear_hl_tables();
clear_hl_tables(false);
list_free_log();
}

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

@ -85,6 +85,7 @@
#include "nvim/fold.h"
#include "nvim/indent.h"
#include "nvim/getchar.h"
#include "nvim/highlight.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@ -299,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) {
@ -1479,6 +1481,8 @@ static void win_update(win_T *wp)
wp->w_empty_rows = 0;
wp->w_filler_rows = 0;
if (!eof && !didline) {
int at_attr = hl_combine_attr(wp->w_hl_attr_normal,
win_hl_attr(wp, HLF_AT));
if (lnum == wp->w_topline) {
/*
* Single line that does not fit!
@ -1493,12 +1497,11 @@ static void win_update(win_T *wp)
int scr_row = wp->w_winrow + wp->w_height - 1;
// Last line isn't finished: Display "@@@" in the last screen line.
screen_puts_len((char_u *)"@@", 2, scr_row, wp->w_wincol,
win_hl_attr(wp, HLF_AT));
screen_puts_len((char_u *)"@@", 2, scr_row, wp->w_wincol, at_attr);
screen_fill(scr_row, scr_row + 1,
(int)wp->w_wincol + 2, (int)W_ENDCOL(wp),
'@', ' ', win_hl_attr(wp, HLF_AT));
'@', ' ', at_attr);
set_empty_rows(wp, srow);
wp->w_botline = lnum;
} else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline"
@ -1506,7 +1509,7 @@ static void win_update(win_T *wp)
screen_fill(wp->w_winrow + wp->w_height - 1,
wp->w_winrow + wp->w_height,
W_ENDCOL(wp) - 3, W_ENDCOL(wp),
'@', '@', win_hl_attr(wp, HLF_AT));
'@', '@', at_attr);
set_empty_rows(wp, srow);
wp->w_botline = lnum;
} else {
@ -1604,7 +1607,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T h
# define FDC_OFF n
int fdc = compute_foldcolumn(wp, 0);
int attr = win_hl_attr(wp, hl);
int attr = hl_combine_attr(wp->w_hl_attr_normal, win_hl_attr(wp, hl));
if (wp->w_p_rl) {
// No check for cmdline window: should never be right-left.
@ -1991,7 +1994,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T
}
screen_line(row + wp->w_winrow, wp->w_wincol, wp->w_width,
wp->w_width, false, wp, 0);
wp->w_width, false, wp, wp->w_hl_attr_normal);
/*
* Update w_cline_height and w_cline_folded if the cursor line was
@ -2407,7 +2410,7 @@ win_line (
if (wp->w_p_cul && lnum == wp->w_cursor.lnum
&& !(wp == curwin && VIsual_active)) {
int cul_attr = win_hl_attr(wp, HLF_CUL);
HlAttrs *aep = syn_cterm_attr2entry(cul_attr);
HlAttrs *aep = syn_attr2entry(cul_attr);
// We make a compromise here (#7383):
// * low-priority CursorLine if fg is not set
@ -4224,25 +4227,7 @@ win_line (
LineOffset[screen_row] + screen_Columns)
== 2))
) {
/* First make sure we are at the end of the screen line,
* then output the same character again to let the
* terminal know about the wrap. If the terminal doesn't
* auto-wrap, we overwrite the character. */
if (ui_current_col() != wp->w_width)
screen_char(LineOffset[screen_row - 1]
+ (unsigned)Columns - 1,
screen_row - 1, (int)(Columns - 1));
/* When there is a multi-byte character, just output a
* space to keep it simple. */
if (ScreenLines[LineOffset[screen_row - 1]
+ (Columns - 1)][1] != 0) {
ui_putc(' ');
} else {
ui_puts(ScreenLines[LineOffset[screen_row - 1] + (Columns - 1)]);
}
/* force a redraw of the first char on the next line */
ScreenAttrs[LineOffset[screen_row]] = (sattr_T)-1;
ui_add_linewrap(screen_row-1);
}
}
@ -4330,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;
@ -4384,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.
@ -4404,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;
@ -4421,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++;
}
}
@ -4452,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);
}
}
/*
@ -4738,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 {
@ -5106,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++) {
@ -5126,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;
@ -5223,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[].
@ -5235,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.
@ -5258,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;
@ -5268,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;
@ -5349,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;
@ -5361,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.
*/
@ -5391,41 +5452,6 @@ static void end_search_hl(void)
}
}
static void update_window_hl(win_T *wp, bool invalid)
{
if (!wp->w_hl_needs_update && !invalid) {
return;
}
wp->w_hl_needs_update = false;
// determine window specific background set in 'winhighlight'
if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] > 0) {
wp->w_hl_attr_normal = syn_id2attr(wp->w_hl_ids[HLF_INACTIVE]);
} else if (wp->w_hl_id_normal > 0) {
wp->w_hl_attr_normal = syn_id2attr(wp->w_hl_id_normal);
} else {
wp->w_hl_attr_normal = 0;
}
if (wp != curwin) {
wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE),
wp->w_hl_attr_normal);
}
for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) {
int attr;
if (wp->w_hl_ids[hlf] > 0) {
attr = syn_id2attr(wp->w_hl_ids[hlf]);
} else {
attr = HL_ATTR(hlf);
}
if (wp->w_hl_attr_normal != 0) {
attr = hl_combine_attr(wp->w_hl_attr_normal, attr);
}
wp->w_hl_attrs[hlf] = attr;
}
}
/*
* Init for calling prepare_search_hl().
@ -5692,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'
@ -5726,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 */
@ -5743,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
@ -5757,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
}
}
}
}
@ -6078,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
@ -6115,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));
}
/*
@ -6204,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;
}
@ -6240,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;
@ -6264,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];
@ -6284,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;
@ -6318,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;
@ -6339,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

@ -22,6 +22,7 @@
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/hashtab.h"
#include "nvim/highlight.h"
#include "nvim/indent_c.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@ -42,7 +43,6 @@
#include "nvim/ui.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/api/private/helpers.h"
#include "nvim/buffer.h"
static bool did_syntax_onoff = false;
@ -216,12 +216,6 @@ struct name_list {
# include "syntax.c.generated.h"
#endif
/*
* An attribute number is the index in attr_table plus ATTR_OFF.
*/
#define ATTR_OFF 1
static char *(spo_name_tab[SPO_COUNT]) =
{"ms=", "me=", "hs=", "he=", "rs=", "re=", "lc="};
@ -6804,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;
@ -6915,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);
}
@ -7001,161 +7001,6 @@ static void highlight_clear(int idx)
}
/// Table with the specifications for an attribute number.
/// Note that this table is used by ALL buffers. This is required because the
/// GUI can redraw at any time for any buffer.
static garray_T attr_table = GA_EMPTY_INIT_VALUE;
static inline HlAttrs * ATTR_ENTRY(int idx)
{
return &((HlAttrs *)attr_table.ga_data)[idx];
}
/// Return the attr number for a set of colors and font.
/// Add a new entry to the term_attr_table, attr_table or gui_attr_table
/// if the combination is new.
/// @return 0 for error.
int get_attr_entry(HlAttrs *aep)
{
garray_T *table = &attr_table;
HlAttrs *taep;
static int recursive = false;
/*
* Init the table, in case it wasn't done yet.
*/
table->ga_itemsize = sizeof(HlAttrs);
ga_set_growsize(table, 7);
// Try to find an entry with the same specifications.
for (int i = 0; i < table->ga_len; i++) {
taep = &(((HlAttrs *)table->ga_data)[i]);
if (aep->cterm_ae_attr == taep->cterm_ae_attr
&& aep->cterm_fg_color == taep->cterm_fg_color
&& aep->cterm_bg_color == taep->cterm_bg_color
&& aep->rgb_ae_attr == taep->rgb_ae_attr
&& aep->rgb_fg_color == taep->rgb_fg_color
&& aep->rgb_bg_color == taep->rgb_bg_color
&& aep->rgb_sp_color == taep->rgb_sp_color) {
return i + ATTR_OFF;
}
}
if (table->ga_len + ATTR_OFF > MAX_TYPENR) {
/*
* Running out of attribute entries! remove all attributes, and
* compute new ones for all groups.
* When called recursively, we are really out of numbers.
*/
if (recursive) {
EMSG(_("E424: Too many different highlighting attributes in use"));
return 0;
}
recursive = TRUE;
clear_hl_tables();
must_redraw = CLEAR;
for (int i = 0; i < highlight_ga.ga_len; ++i) {
set_hl_attr(i);
}
recursive = FALSE;
}
// This is a new combination of colors and font, add an entry.
taep = GA_APPEND_VIA_PTR(HlAttrs, table);
memset(taep, 0, sizeof(*taep));
taep->cterm_ae_attr = aep->cterm_ae_attr;
taep->cterm_fg_color = aep->cterm_fg_color;
taep->cterm_bg_color = aep->cterm_bg_color;
taep->rgb_ae_attr = aep->rgb_ae_attr;
taep->rgb_fg_color = aep->rgb_fg_color;
taep->rgb_bg_color = aep->rgb_bg_color;
taep->rgb_sp_color = aep->rgb_sp_color;
return table->ga_len - 1 + ATTR_OFF;
}
// Clear all highlight tables.
void clear_hl_tables(void)
{
ga_clear(&attr_table);
}
// Combine special attributes (e.g., for spelling) with other attributes
// (e.g., for syntax highlighting).
// "prim_attr" overrules "char_attr".
// This creates a new group when required.
// Since we expect there to be few spelling mistakes we don't cache the
// result.
// Return the resulting attributes.
int hl_combine_attr(int char_attr, int prim_attr)
{
HlAttrs *char_aep = NULL;
HlAttrs *spell_aep;
HlAttrs new_en = HLATTRS_INIT;
if (char_attr == 0) {
return prim_attr;
}
if (prim_attr == 0) {
return char_attr;
}
// Find the entry for char_attr
char_aep = syn_cterm_attr2entry(char_attr);
if (char_aep != NULL) {
// Copy all attributes from char_aep to the new entry
new_en = *char_aep;
}
spell_aep = syn_cterm_attr2entry(prim_attr);
if (spell_aep != NULL) {
new_en.cterm_ae_attr |= spell_aep->cterm_ae_attr;
new_en.rgb_ae_attr |= spell_aep->rgb_ae_attr;
if (spell_aep->cterm_fg_color > 0) {
new_en.cterm_fg_color = spell_aep->cterm_fg_color;
}
if (spell_aep->cterm_bg_color > 0) {
new_en.cterm_bg_color = spell_aep->cterm_bg_color;
}
if (spell_aep->rgb_fg_color >= 0) {
new_en.rgb_fg_color = spell_aep->rgb_fg_color;
}
if (spell_aep->rgb_bg_color >= 0) {
new_en.rgb_bg_color = spell_aep->rgb_bg_color;
}
if (spell_aep->rgb_sp_color >= 0) {
new_en.rgb_sp_color = spell_aep->rgb_sp_color;
}
}
return get_attr_entry(&new_en);
}
/// \note this function does not apply exclusively to cterm attr contrary
/// to what its name implies
/// \warn don't call it with attr 0 (i.e., the null attribute)
HlAttrs *syn_cterm_attr2entry(int attr)
{
attr -= ATTR_OFF;
if (attr >= attr_table.ga_len) {
// did ":syntax clear"
return NULL;
}
return ATTR_ENTRY(attr);
}
/// \addtogroup LIST_XXX
/// @{
#define LIST_ATTR 1
@ -7410,15 +7255,7 @@ static void set_hl_attr(int idx)
at_en.rgb_bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1;
at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1;
if (at_en.cterm_fg_color != 0 || at_en.cterm_bg_color != 0
|| at_en.rgb_fg_color != -1 || at_en.rgb_bg_color != -1
|| at_en.rgb_sp_color != -1 || at_en.cterm_ae_attr != 0
|| at_en.rgb_ae_attr != 0) {
sgp->sg_attr = get_attr_entry(&at_en);
} else {
// If all the fields are cleared, clear the attr field back to default value
sgp->sg_attr = 0;
}
sgp->sg_attr = hl_get_syn_attr(idx+1, at_en);
}
/// Lookup a highlight group name and return its ID.
@ -7553,7 +7390,7 @@ static void syn_unadd_group(void)
/// Translate a group ID to highlight attributes.
/// @see syn_cterm_attr2entry
/// @see syn_attr2entry
int syn_id2attr(int hl_id)
{
struct hl_group *sgp;
@ -7590,7 +7427,7 @@ int syn_get_final_id(int hl_id)
}
/// Refresh the color attributes of all highlight groups.
static void highlight_attr_set_all(void)
void highlight_attr_set_all(void)
{
for (int idx = 0; idx < highlight_ga.ga_len; idx++) {
struct hl_group *sgp = &HL_TABLE()[idx];
@ -7613,7 +7450,6 @@ static void highlight_attr_set_all(void)
/// screen redraw after any :highlight command.
void highlight_changed(void)
{
int attr;
int id;
char_u userhl[10];
int id_SNC = -1;
@ -7628,13 +7464,15 @@ void highlight_changed(void)
if (id == 0) {
abort();
}
attr = syn_id2attr(id);
int final_id = syn_get_final_id(id);
if (hlf == (int)HLF_SNC) {
id_SNC = syn_get_final_id(id);
id_SNC = final_id;
} else if (hlf == (int)HLF_S) {
id_S = syn_get_final_id(id);
id_S = final_id;
}
highlight_attr[hlf] = attr;
highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id,
hlf == (int)HLF_INACTIVE);
}
/* Setup the user highlights
@ -8522,26 +8360,6 @@ RgbValue name_to_color(const char_u *name)
return -1;
}
/// Gets highlight description for id `attr_id` as a map.
Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err)
{
HlAttrs *aep = NULL;
Dictionary dic = ARRAY_DICT_INIT;
if (attr_id == 0) {
return dic;
}
aep = syn_cterm_attr2entry((int)attr_id);
if (!aep) {
api_set_error(err, kErrorTypeException,
"Invalid attribute id: %" PRId64, attr_id);
return dic;
}
return hlattrs2dict(aep, rgb);
}
/**************************************
* End of Highlighting stuff *

View File

@ -49,6 +49,7 @@
#include "nvim/message.h"
#include "nvim/memory.h"
#include "nvim/option.h"
#include "nvim/highlight.h"
#include "nvim/macros.h"
#include "nvim/mbyte.h"
#include "nvim/buffer.h"
@ -602,7 +603,7 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr,
int attr_id = 0;
if (hl_attrs || vt_fg != -1 || vt_bg != -1) {
attr_id = get_attr_entry(&(HlAttrs) {
attr_id = get_term_attr_entry(&(HlAttrs) {
.cterm_ae_attr = (int16_t)hl_attrs,
.cterm_fg_color = vt_fg_idx,
.cterm_bg_color = vt_bg_idx,

View File

@ -20,6 +20,7 @@
#include "nvim/vim.h"
#include "nvim/log.h"
#include "nvim/ui.h"
#include "nvim/highlight.h"
#include "nvim/map.h"
#include "nvim/main.h"
#include "nvim/memory.h"
@ -31,13 +32,13 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui_bridge.h"
#include "nvim/ugrid.h"
#include "nvim/tui/input.h"
#include "nvim/tui/tui.h"
#include "nvim/tui/terminfo.h"
#include "nvim/cursor_shape.h"
#include "nvim/syntax.h"
#include "nvim/macros.h"
// Space reserved in two output buffers to make the cursor normal or invisible
@ -87,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;
@ -97,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;
@ -125,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;
@ -136,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;
@ -148,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);
}
@ -289,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);
@ -345,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
@ -379,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);
}
@ -437,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;
@ -591,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);
@ -678,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) {
@ -724,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)
@ -791,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);
@ -809,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);
}
@ -932,7 +936,7 @@ static void tui_set_mode(UI *ui, ModeShape mode)
if (c.id != 0 && ui->rgb) {
int attr = syn_id2attr(c.id);
if (attr > 0) {
HlAttrs *aep = syn_cterm_attr2entry(attr);
HlAttrs *aep = syn_attr2entry(attr);
UNIBI_SET_NUM_VAR(data->params[0], aep->rgb_bg_color);
unibi_out_ext(ui, data->unibi_ext.set_cursor_color);
}
@ -957,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);
@ -988,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);
}
}
@ -1011,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
@ -1024,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)
@ -1057,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)
@ -1082,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);
@ -1094,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);
}
@ -1175,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

@ -32,7 +32,7 @@
#include "nvim/os/signal.h"
#include "nvim/popupmnu.h"
#include "nvim/screen.h"
#include "nvim/syntax.h"
#include "nvim/highlight.h"
#include "nvim/window.h"
#include "nvim/cursor_shape.h"
#ifdef FEAT_TUI
@ -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__)
@ -172,66 +166,6 @@ void ui_event(char *name, Array args)
}
/// Converts an HlAttrs into Dictionary
///
/// @param[in] aep data to convert
/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*'
Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb)
{
assert(aep);
Dictionary hl = ARRAY_DICT_INIT;
int mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr;
if (mask & HL_BOLD) {
PUT(hl, "bold", BOOLEAN_OBJ(true));
}
if (mask & HL_STANDOUT) {
PUT(hl, "standout", BOOLEAN_OBJ(true));
}
if (mask & HL_UNDERLINE) {
PUT(hl, "underline", BOOLEAN_OBJ(true));
}
if (mask & HL_UNDERCURL) {
PUT(hl, "undercurl", BOOLEAN_OBJ(true));
}
if (mask & HL_ITALIC) {
PUT(hl, "italic", BOOLEAN_OBJ(true));
}
if (mask & HL_INVERSE) {
PUT(hl, "reverse", BOOLEAN_OBJ(true));
}
if (use_rgb) {
if (aep->rgb_fg_color != -1) {
PUT(hl, "foreground", INTEGER_OBJ(aep->rgb_fg_color));
}
if (aep->rgb_bg_color != -1) {
PUT(hl, "background", INTEGER_OBJ(aep->rgb_bg_color));
}
if (aep->rgb_sp_color != -1) {
PUT(hl, "special", INTEGER_OBJ(aep->rgb_sp_color));
}
} else {
if (cterm_normal_fg_color != aep->cterm_fg_color) {
PUT(hl, "foreground", INTEGER_OBJ(aep->cterm_fg_color - 1));
}
if (cterm_normal_bg_color != aep->cterm_bg_color) {
PUT(hl, "background", INTEGER_OBJ(aep->cterm_bg_color - 1));
}
}
return hl;
}
void ui_refresh(void)
{
if (!ui_active()) {
@ -259,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 ...
@ -267,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)
@ -286,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)
@ -329,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();
}
@ -362,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_cterm_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)
@ -463,6 +340,32 @@ void ui_cursor_goto(int new_row, int new_col)
pending_cursor_update = true;
}
void ui_add_linewrap(int row)
{
// TODO(bfredl): check that this actually still works
// and move to TUI module in that case.
#if 0
// First make sure we are at the end of the screen line,
// then output the same character again to let the
// terminal know about the wrap. If the terminal doesn't
// auto-wrap, we overwrite the character.
if (ui_current_col() != Columns) {
screen_char(LineOffset[row] + (unsigned)Columns - 1, row,
(int)(Columns - 1));
}
// When there is a multi-byte character, just output a
// space to keep it simple. */
if (ScreenLines[LineOffset[row] + (Columns - 1)][1] != 0) {
ui_putc(' ');
} else {
ui_puts(ScreenLines[LineOffset[row] + (Columns - 1)]);
}
// force a redraw of the first char on the next line
ScreenAttrs[LineOffset[row+1]] = (sattr_T)-1;
#endif
}
void ui_mode_info_set(void)
{
Array style = mode_style_array();
@ -484,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)
@ -515,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,14 +5,18 @@
#include <stdbool.h>
#include <stdint.h>
#include "api/private/defs.h"
#include "nvim/buffer_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;
@ -20,7 +24,9 @@ EXTERN const char *ui_ext_names[] INIT(= {
"ext_cmdline",
"ext_popupmenu",
"ext_tabline",
"ext_wildmenu"
"ext_wildmenu",
"ext_newgrid",
"ext_hlstate",
});
@ -31,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

@ -834,7 +834,7 @@ describe("'winhighlight' highlight", function()
{1:a^a }|
{2:~ }|
{2:~ }|
{11:[No Name] [+] }|
{3:[No Name] [+] }|
aa |
{0:~ }|
{4:[No Name] [+] }|
@ -846,7 +846,7 @@ describe("'winhighlight' highlight", function()
{1:^ }|
{2:~ }|
{2:~ }|
{11:[No Name] }|
{3:[No Name] }|
aa |
{0:~ }|
{4:[No Name] [+] }|
@ -891,7 +891,7 @@ describe("'winhighlight' highlight", function()
{1:a^a }|
{2:~ }|
{2:~ }|
{11:[No Name] [+] }|
{3:[No Name] [+] }|
aa |
{0:~ }|
{4:[No Name] [+] }|
@ -915,7 +915,7 @@ describe("'winhighlight' highlight", function()
{1:^aa }|
{2:~ }|
{2:~ }|
{11:[No Name] [+] }|
{3:[No Name] [+] }|
aa |
{0:~ }|
{4:[No Name] [+] }|
@ -931,10 +931,10 @@ describe("'winhighlight' highlight", function()
{1:^ }|
{2:~ }|
{2:~ }|
{11:[No Name] }|
{3:[No Name] }|
{5: }|
{6:~ }|
{12:[No Name] }|
{4:[No Name] }|
|
]])
@ -943,10 +943,10 @@ describe("'winhighlight' highlight", function()
{5: }|
{6:~ }|
{6:~ }|
{12:[No Name] }|
{4:[No Name] }|
{1:^ }|
{2:~ }|
{11:[No Name] }|
{3:[No Name] }|
|
]])
@ -955,10 +955,10 @@ describe("'winhighlight' highlight", function()
{1:^ }|
{2:~ }|
{2:~ }|
{11:[No Name] }|
{3:[No Name] }|
{5: }|
{6:~ }|
{12:[No Name] }|
{4:[No Name] }|
|
]])
end)
@ -974,7 +974,7 @@ describe("'winhighlight' highlight", function()
{3:[No Name] }|
{7: }|
{8:~ }|
{13:[No Name] }|
{4:[No Name] }|
|
]])
@ -983,7 +983,7 @@ describe("'winhighlight' highlight", function()
{7: }|
{8:~ }|
{8:~ }|
{13:[No Name] }|
{4:[No Name] }|
^ |
{0:~ }|
{3:[No Name] }|
@ -997,10 +997,10 @@ describe("'winhighlight' highlight", function()
{7: }|
{8:~ }|
{8:~ }|
{13:[No Name] }|
{4:[No Name] }|
{1:^ }|
{2:~ }|
{11:[No Name] }|
{3:[No Name] }|
|
]])
@ -1012,7 +1012,7 @@ describe("'winhighlight' highlight", function()
{3:[No Name] }|
{1: }|
{2:~ }|
{14:[No Name] }|
{4:[No Name] }|
|
]])
@ -1022,10 +1022,10 @@ describe("'winhighlight' highlight", function()
{7: }|
{8:~ }|
{8:~ }|
{13:[No Name] }|
{4:[No Name] }|
{1:^ }|
{2:~ }|
{11:[No Name] }|
{3:[No Name] }|
|
]])
@ -1037,7 +1037,7 @@ describe("'winhighlight' highlight", function()
{3:[No Name] }|
{5: }|
{6:~ }|
{12:[No Name] }|
{4:[No Name] }|
|
]])
end)

View File

@ -0,0 +1,287 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, insert = helpers.clear, helpers.insert
local command = helpers.command
local meths = helpers.meths
local iswin = helpers.iswin
local nvim_dir = helpers.nvim_dir
local thelpers = require('test.functional.terminal.helpers')
describe('ext_hlstate detailed highlights', function()
local screen
before_each(function()
clear()
command('syntax on')
screen = Screen.new(40, 8)
screen:attach({ext_hlstate=true})
end)
after_each(function()
screen:detach()
end)
it('work with combined UI and syntax highlights', function()
insert([[
these are some lines
with colorful text]])
meths.buf_add_highlight(0, -1, "String", 0 , 10, 14)
meths.buf_add_highlight(0, -1, "Statement", 1 , 5, -1)
command("/th co")
screen:expect([[
these are {1:some} lines |
^wi{2:th }{4:co}{3:lorful text} |
{5:~ }|
{5:~ }|
{5:~ }|
{5:~ }|
{5:~ }|
{6:search hit BOTTOM, continuing at TOP} |
]], {
[1] = {{foreground = Screen.colors.Magenta},
{{hi_name = "Constant", kind = "syntax"}}},
[2] = {{background = Screen.colors.Yellow},
{{hi_name = "Search", ui_name = "Search", kind = "ui"}}},
[3] = {{bold = true, foreground = Screen.colors.Brown},
{{hi_name = "Statement", kind = "syntax"}}},
[4] = {{bold = true, background = Screen.colors.Yellow, foreground = Screen.colors.Brown}, {3, 2}},
[5] = {{bold = true, foreground = Screen.colors.Blue1},
{{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}},
[6] = {{foreground = Screen.colors.Red},
{{hi_name = "WarningMsg", ui_name = "WarningMsg", kind = "ui"}}},
})
end)
it('work with cleared UI highlights', function()
screen:set_default_attr_ids({
[1] = {{}, {{hi_name = "VertSplit", ui_name = "VertSplit", kind = "ui"}}},
[2] = {{bold = true, foreground = Screen.colors.Blue1},
{{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}},
[3] = {{bold = true, reverse = true},
{{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}} ,
[4] = {{reverse = true},
{{hi_name = "StatusLineNC", ui_name = "StatusLineNC" , kind = "ui"}}},
[5] = {{}, {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}},
[6] = {{}, {{hi_name = "StatusLineNC", ui_name = "StatusLineNC", kind = "ui"}}},
})
command("hi clear VertSplit")
command("vsplit")
screen:expect([[
^ {1:} |
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{3:[No Name] }{4:[No Name] }|
|
]])
command("hi clear StatusLine | hi clear StatuslineNC")
screen:expect([[
^ {1:} |
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{5:[No Name] }{6:[No Name] }|
|
]])
-- redrawing is done even if visible highlights didn't change
command("wincmd w")
screen:expect([[
{1:}^ |
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{2:~ }{1:}{2:~ }|
{6:[No Name] }{5:[No Name] }|
|
]])
end)
it("work with window-local highlights", function()
screen:set_default_attr_ids({
[1] = {{foreground = Screen.colors.Brown}, {{hi_name = "LineNr", ui_name = "LineNr", kind = "ui"}}},
[2] = {{bold = true, foreground = Screen.colors.Blue1}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}},
[3] = {{bold = true, reverse = true}, {{hi_name = "StatusLine", ui_name = "StatusLine", kind = "ui"}}},
[4] = {{reverse = true}, {{hi_name = "StatusLineNC", ui_name = "StatusLineNC", kind = "ui"}}},
[5] = {{background = Screen.colors.Red, foreground = Screen.colors.Grey100}, {{hi_name = "ErrorMsg", ui_name = "LineNr", kind = "ui"}}},
[6] = {{bold = true, reverse = true}, {{hi_name = "MsgSeparator", ui_name = "Normal", kind = "ui"}}},
[7] = {{foreground = Screen.colors.Brown, bold = true, reverse = true}, {6, 1}},
[8] = {{foreground = Screen.colors.Blue1, bold = true, reverse = true}, {6, 2}},
[9] = {{bold = true, foreground = Screen.colors.Brown}, {{hi_name = "Statement", ui_name = "NormalNC", kind = "ui"}}},
[10] = {{bold = true, foreground = Screen.colors.Brown}, {9, 1}},
[11] = {{bold = true, foreground = Screen.colors.Blue1}, {9, 2}}
})
command("set number")
command("split")
-- NormalNC is not applied if not set, to avoid spurious redraws
screen:expect([[
{1: 1 }^ |
{2:~ }|
{2:~ }|
{3:[No Name] }|
{1: 1 } |
{2:~ }|
{4:[No Name] }|
|
]])
command("set winhl=LineNr:ErrorMsg")
screen:expect([[
{5: 1 }^ |
{2:~ }|
{2:~ }|
{3:[No Name] }|
{1: 1 } |
{2:~ }|
{4:[No Name] }|
|
]])
command("set winhl=Normal:MsgSeparator,NormalNC:Statement")
screen:expect([[
{7: 1 }{6:^ }|
{8:~ }|
{8:~ }|
{3:[No Name] }|
{1: 1 } |
{2:~ }|
{4:[No Name] }|
|
]])
command("wincmd w")
screen:expect([[
{10: 1 }{9: }|
{11:~ }|
{11:~ }|
{4:[No Name] }|
{1: 1 }^ |
{2:~ }|
{3:[No Name] }|
|
]])
end)
it("work with :terminal", function()
screen:set_default_attr_ids({
[1] = {{}, {{hi_name = "TermCursorNC", ui_name = "TermCursorNC", kind = "ui"}}},
[2] = {{special = Screen.colors.Grey0, foreground = 52479}, {{kind = "term"}}},
[3] = {{special = Screen.colors.Grey0, bold = true, foreground = 52479}, {{kind = "term"}}},
[4] = {{special = Screen.colors.Grey0, foreground = 52479}, {2, 1}},
[5] = {{special = Screen.colors.Grey0, foreground = 4259839}, {{kind = "term"}}},
[6] = {{special = Screen.colors.Grey0, foreground = 4259839}, {5, 1}},
})
command('enew | call termopen(["'..nvim_dir..'/tty-test"])')
screen:expect([[
^tty ready |
{1: } |
|
|
|
|
|
|
]])
thelpers.feed_data('x ')
thelpers.set_fg(45)
thelpers.feed_data('y ')
thelpers.set_bold()
thelpers.feed_data('z\n')
-- TODO(bfredl): check if this distinction makes sense
if iswin() then
screen:expect([[
^tty ready |
x {5:y z} |
{1: } |
|
|
|
|
|
]])
else
screen:expect([[
^tty ready |
x {2:y }{3:z} |
{1: } |
|
|
|
|
|
]])
end
thelpers.feed_termcode("[A")
thelpers.feed_termcode("[2C")
if iswin() then
screen:expect([[
^tty ready |
x {6:y}{5: z} |
|
|
|
|
|
|
]])
else
screen:expect([[
^tty ready |
x {4:y}{2: }{3:z} |
|
|
|
|
|
|
]])
end
end)
it("can use independent cterm and rgb colors", function()
-- tell test module to save all attributes (doesn't change nvim options)
screen:set_hlstate_cterm(true)
screen:set_default_attr_ids({
[1] = {{bold = true, foreground = Screen.colors.Blue1}, {foreground = 12}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}},
[2] = {{reverse = true, foreground = Screen.colors.Red}, {foreground = 10, italic=true}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}},
})
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
|
]])
command("hi NonText guifg=Red gui=reverse ctermfg=Green cterm=italic")
screen:expect([[
^ |
{2:~ }|
{2:~ }|
{2:~ }|
{2:~ }|
{2:~ }|
{2:~ }|
|
]])
end)
end)

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,10 @@ function Screen.new(width, height)
_default_attr_ignore = nil,
_mouse_enabled = true,
_attrs = {},
_hl_info = {},
_attr_table = {[0]={{},{}}},
_clear_attrs = {},
_new_attrs = false,
_cursor = {
row = 1, col = 1
},
@ -159,10 +163,19 @@ function Screen:set_default_attr_ignore(attr_ignore)
self._default_attr_ignore = attr_ignore
end
function Screen:set_hlstate_cterm(val)
self._hlstate_cterm = val
end
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 +189,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.
@ -210,6 +224,11 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any)
end
local ids = attr_ids or self._default_attr_ids
local ignore = attr_ignore or self._default_attr_ignore
local id_to_index
if self._options.ext_hlstate then
id_to_index = self:hlstate_check_attrs(ids or {})
end
self._new_attrs = false
self:wait(function()
if condition ~= nil then
local status, res = pcall(condition)
@ -223,9 +242,14 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any)
.. ') differs from configured height(' .. self._height .. ') of Screen.')
end
if self._options.ext_hlstate and self._new_attrs then
id_to_index = self:hlstate_check_attrs(ids or {})
end
local info = self._options.ext_hlstate and id_to_index or ids
local actual_rows = {}
for i = 1, self._height do
actual_rows[i] = self:_row_repr(self._rows[i], ids, ignore)
actual_rows[i] = self:_row_repr(self._rows[i], info, ignore)
end
if expected == nil then
@ -339,7 +363,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, hl_id = 0})
end
table.insert(rows, cols)
end
@ -353,14 +377,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 +407,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
@ -425,6 +465,7 @@ function Screen:_handle_scroll(count)
for j = left, right do
target[j].text = source[j].text
target[j].attrs = source[j].attrs
target[j].hl_id = source[j].hl_id
end
end
@ -434,6 +475,28 @@ 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._hl_info[id] = info
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
@ -442,9 +505,32 @@ function Screen:_handle_put(str)
local cell = self._rows[self._cursor.row][self._cursor.col]
cell.text = str
cell.attrs = self._attrs
cell.hl_id = -1
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
local hl_id = 0
for _,item in ipairs(items) do
local text, hl_id_cell, count = unpack(item)
if hl_id_cell ~= nil then
hl_id = hl_id_cell
hl = self._attr_table[hl_id]
end
for _ = 1, (count or 1) do
local cell = line[colpos]
cell.text = text
cell.hl_id = hl_id
cell.attrs = hl
colpos = colpos+1
end
end
end
function Screen:_handle_bell()
self.bell = true
end
@ -498,7 +584,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 +592,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
@ -573,22 +663,33 @@ function Screen:print_snapshot(attrs, ignore)
if ignore == nil then
ignore = self._default_attr_ignore
end
local id_to_index = {}
if attrs == nil then
attrs = {}
if self._default_attr_ids ~= nil then
for i, a in pairs(self._default_attr_ids) do
attrs[i] = a
end
if self._options.ext_hlstate then
id_to_index = self:hlstate_check_attrs(attrs)
end
end
if ignore ~= true then
for i = 1, self._height do
local row = self._rows[i]
for j = 1, self._width do
local attr = row[j].attrs
if self:_attr_index(attrs, attr) == nil and self:_attr_index(ignore, attr) == nil then
if not self:_equal_attrs(attr, {}) then
table.insert(attrs, attr)
if self._options.ext_hlstate then
local hl_id = row[j].hl_id
if hl_id ~= 0 then
self:_insert_hl_id(attrs, id_to_index, hl_id)
end
else
local attr = row[j].attrs
if self:_attr_index(attrs, attr) == nil and self:_attr_index(ignore, attr) == nil then
if not self:_equal_attrs(attr, {}) then
table.insert(attrs, attr)
end
end
end
end
@ -597,8 +698,9 @@ function Screen:print_snapshot(attrs, ignore)
end
local rv = {}
local info = self._options.ext_hlstate and id_to_index or attrs
for i = 1, self._height do
table.insert(rv, " "..self:_row_repr(self._rows[i],attrs, ignore).."|")
table.insert(rv, " "..self:_row_repr(self._rows[i], info, ignore).."|")
end
local attrstrs = {}
local alldefault = true
@ -606,7 +708,12 @@ function Screen:print_snapshot(attrs, ignore)
if self._default_attr_ids == nil or self._default_attr_ids[i] ~= a then
alldefault = false
end
local dict = "{"..self:_pprint_attrs(a).."}"
local dict
if self._options.ext_hlstate then
dict = self:_pprint_hlstate(a)
else
dict = "{"..self:_pprint_attrs(a).."}"
end
table.insert(attrstrs, "["..tostring(i).."] = "..dict)
end
local attrstr = "{"..table.concat(attrstrs, ", ").."}"
@ -620,6 +727,117 @@ function Screen:print_snapshot(attrs, ignore)
io.stdout:flush()
end
function Screen:_insert_hl_id(attrs, id_to_index, hl_id)
if id_to_index[hl_id] ~= nil then
return id_to_index[hl_id]
end
local raw_info = self._hl_info[hl_id]
local info = {}
if #raw_info > 1 then
for i, item in ipairs(raw_info) do
info[i] = self:_insert_hl_id(attrs, id_to_index, item.id)
end
else
info[1] = {}
for k, v in pairs(raw_info[1]) do
if k ~= "id" then
info[1][k] = v
end
end
end
local entry = self._attr_table[hl_id]
local attrval
if self._hlstate_cterm then
attrval = {entry[1], entry[2], info} -- unpack() doesn't work
else
attrval = {entry[1], info}
end
table.insert(attrs, attrval)
id_to_index[hl_id] = #attrs
return #attrs
end
function Screen:hlstate_check_attrs(attrs)
local id_to_index = {}
for i = 1,#self._attr_table do
local iinfo = self._hl_info[i]
local matchinfo = {}
if #iinfo > 1 then
for k,item in ipairs(iinfo) do
matchinfo[k] = id_to_index[item.id]
end
else
matchinfo = iinfo
end
for k,v in pairs(attrs) do
local attr, info, attr_rgb, attr_cterm
if self._hlstate_cterm then
attr_rgb, attr_cterm, info = unpack(v)
attr = {attr_rgb, attr_cterm}
else
attr, info = unpack(v)
end
if self:_equal_attr_def(attr, self._attr_table[i]) then
if #info == #matchinfo then
local match = false
if #info == 1 then
if self:_equal_info(info[1],matchinfo[1]) then
match = true
end
else
match = true
for j = 1,#info do
if info[j] ~= matchinfo[j] then
match = false
end
end
end
if match then
id_to_index[i] = k
end
end
end
end
end
return id_to_index
end
function Screen:_pprint_hlstate(item)
--print(require('inspect')(item))
local attrdict = "{"..self:_pprint_attrs(item[1]).."}, "
local attrdict2, hlinfo
if self._hlstate_cterm then
attrdict2 = "{"..self:_pprint_attrs(item[2]).."}, "
hlinfo = item[3]
else
attrdict2 = ""
hlinfo = item[2]
end
local descdict = "{"..self:_pprint_hlinfo(hlinfo).."}"
return "{"..attrdict..attrdict2..descdict.."}"
end
function Screen:_pprint_hlinfo(states)
if #states == 1 then
local items = {}
for f, v in pairs(states[1]) do
local desc = tostring(v)
if type(v) == type("") then
desc = '"'..desc..'"'
end
table.insert(items, f.." = "..desc)
end
return "{"..table.concat(items, ", ").."}"
else
return table.concat(states, ", ")
end
end
function Screen:_pprint_attrs(attrs)
local items = {}
for f, v in pairs(attrs) do
@ -643,32 +861,53 @@ local function backward_find_meaningful(tbl, from) -- luacheck: no unused
return from
end
function Screen:_get_attr_id(attr_ids, ignore, attrs)
function Screen:_get_attr_id(attr_ids, ignore, attrs, hl_id)
if not attr_ids then
return
end
for id, a in pairs(attr_ids) do
if self:_equal_attrs(a, attrs) then
return id
end
if self._options.ext_hlstate then
local id = attr_ids[hl_id]
if id ~= nil or hl_id == 0 then
return id
end
return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][1])
else
for id, a in pairs(attr_ids) do
if self:_equal_attrs(a, attrs) then
return id
end
end
if self:_equal_attrs(attrs, {}) or
ignore == true or self:_attr_index(ignore, attrs) ~= nil then
-- ignore this attrs
return nil
end
return "UNEXPECTED "..self:_pprint_attrs(attrs)
end
if self:_equal_attrs(attrs, {}) or
ignore == true or self:_attr_index(ignore, attrs) ~= nil then
-- ignore this attrs
return nil
end
function Screen:_equal_attr_def(a, b)
if self._hlstate_cterm then
return self:_equal_attrs(a[1],b[1]) and self:_equal_attrs(a[2],b[2])
else
return self:_equal_attrs(a,b[1])
end
return "UNEXPECTED "..self:_pprint_attrs(attrs)
end
function Screen:_equal_attrs(a, b)
return a.bold == b.bold and a.standout == b.standout and
a.underline == b.underline and a.undercurl == b.undercurl and
a.italic == b.italic and a.reverse == b.reverse and
a.foreground == b.foreground and
a.background == b.background and
a.foreground == b.foreground and a.background == b.background and
a.special == b.special
end
function Screen:_equal_info(a, b)
return a.kind == b.kind and a.hi_name == b.hi_name and
a.ui_name == b.ui_name
end
function Screen:_attr_index(attrs, attr)
if not attrs then
return nil

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)