mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	Merge pull request #8221 from bfredl/hlstate
UI grid protocol revision: line based updates and semantic highlights
This commit is contained in:
		@@ -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
 | 
			
		||||
@@ -198,6 +346,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
 | 
			
		||||
	what will happen, depending on the scroll direction. "=" is used to
 | 
			
		||||
@@ -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*
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
								
							
							
						
						
									
										418
									
								
								src/nvim/highlight.c
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										13
									
								
								src/nvim/highlight.h
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
///
 | 
			
		||||
 
 | 
			
		||||
@@ -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");
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 */
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
@@ -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	      *
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										285
									
								
								src/nvim/ui.c
									
									
									
									
									
								
							
							
						
						
									
										285
									
								
								src/nvim/ui.c
									
									
									
									
									
								
							@@ -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();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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 --}                                    |
 | 
			
		||||
    ]=])
 | 
			
		||||
 
 | 
			
		||||
@@ -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" } },
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										287
									
								
								test/functional/ui/hlstate_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								test/functional/ui/hlstate_spec.lua
									
									
									
									
									
										Normal 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)
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user