Merge #6423 from justinmk/guicursor

This commit is contained in:
Justin M. Keyes 2017-04-02 02:32:36 +02:00 committed by GitHub
commit 58422f17d8
18 changed files with 581 additions and 220 deletions

View File

@ -371,27 +371,6 @@ See
Used to set the 'shell' option, which determines the shell used by the Used to set the 'shell' option, which determines the shell used by the
.Ic :terminal .Ic :terminal
command. command.
.It Ev NVIM_TUI_ENABLE_CURSOR_SHAPE
Set to 0 to prevent Nvim from changing the cursor shape.
Set to 1 to enable non-blinking mode-sensitive cursor (this is the default).
Set to 2 to enable blinking mode-sensitive cursor.
Host terminal must support the DECSCUSR CSI escape sequence.
.Pp
Depending on the terminal emulator, using this option with
.Nm
under
.Xr tmux 1
might require adding the following to
.Pa ~/.tmux.conf :
.Bd -literal -offset indent
set -ga terminal-overrides ',*:Ss=\eE[%p1%d q:Se=\eE[2 q'
.Ed
.Pp
See
.Ic terminal-overrides
in the
.Xr tmux 1
manual page for more information.
.El .El
.Sh FILES .Sh FILES
.Bl -tag -width "~/.config/nvim/init.vim" .Bl -tag -width "~/.config/nvim/init.vim"

View File

@ -250,23 +250,21 @@ connect to another with different type codes.
============================================================================== ==============================================================================
6. Remote UIs *rpc-remote-ui* 6. Remote UIs *rpc-remote-ui*
Nvim allows Graphical user interfaces to be implemented by separate processes GUIs can be implemented as external processes communicating with Nvim over the
communicating with Nvim over the RPC API. Currently the ui model conists of a RPC API. Currently the UI model consists of a terminal-like grid with one
terminal-like grid with one single, monospace font size, with a few elements single, monospace font size. Some elements (UI "widgets") can be drawn
that could be drawn separately from the grid (for the momemnt only the popup separately from the grid.
menu)
After connecting to a nvim instance (typically a spawned, embedded instance) After connecting to Nvim (usually a spawned, embedded instance) use the
use the |nvim_ui_attach|(width, height, options) API method to tell nvim that your |nvim_ui_attach| API method to tell Nvim that your program wants to draw the
program wants to draw the nvim screen on a grid with "width" times Nvim screen on a grid of width × height cells. `options` must be
"height" cells. "options" should be a dictionary with the following (all a dictionary with these (optional) keys:
optional) keys: `rgb` Controls what color format to use.
`rgb`: Controls what color format to use.
Set to true (default) to use 24-bit rgb Set to true (default) to use 24-bit rgb
colors. colors.
Set to false to use terminal color codes (at Set to false to use terminal color codes (at
most 256 different colors). most 256 different colors).
`popupmenu_external`: Instead of drawing the completion popupmenu on `popupmenu_external` Instead of drawing the completion popupmenu on
the grid, Nvim will send higher-level events to the grid, Nvim will send higher-level events to
the ui and let it draw the popupmenu. the ui and let it draw the popupmenu.
Defaults to false. Defaults to false.

View File

@ -2790,21 +2790,17 @@ A jump table for the options with a short description can be found at |Q_op|.
i-ci:ver25-Cursor/lCursor, i-ci:ver25-Cursor/lCursor,
r-cr:hor20-Cursor/lCursor, r-cr:hor20-Cursor/lCursor,
sm:block-Cursor sm:block-Cursor
-blinkwait175-blinkoff150-blinkon175", -blinkwait175-blinkoff150-blinkon175")
for Windows console:
"n-v-c:block,o:hor50,i-ci:hor15,
r-cr:hor30,sm:block")
global global
{only available when compiled with GUI enabled, and Configures the cursor style for each mode. Works in the GUI and some
for Windows console} terminals. Empty means "non-blinking block cursor in all modes": >
This option tells Vim what the cursor should look like in different :set guicursor=
modes. It fully works in the GUI. In a Windows console, only <
the height of the cursor can be changed. This can be done by With tmux you might need this in ~/.tmux.conf (see terminal-overrides
specifying a block cursor, or a percentage for a vertical or in the tmux(1) manual page): >
horizontal cursor. set -ga terminal-overrides ',*:Ss=\E[%p1%d q:Se=\E[2 q'
For a console the 't_SI' and 't_EI' escape sequences are used. <
The option is a comma separated list of parts. Each part consists of a
The option is a comma separated list of parts. Each part consist of a
mode-list and an argument-list: mode-list and an argument-list:
mode-list:argument-list,mode-list:argument-list,.. mode-list:argument-list,mode-list:argument-list,..
The mode-list is a dash separated list of these modes: The mode-list is a dash separated list of these modes:

View File

@ -113,6 +113,7 @@ Some `CTRL-SHIFT-...` key chords are distinguished from `CTRL-...` variants
Options: Options:
'cpoptions' flags: |cpo-_| 'cpoptions' flags: |cpo-_|
'guicursor' works in the terminal
'inccommand' shows interactive results for |:substitute|-like commands 'inccommand' shows interactive results for |:substitute|-like commands
'statusline' supports unlimited alignment sections 'statusline' supports unlimited alignment sections
'tabline' %@Func@foo%X can call any function on mouse-click 'tabline' %@Func@foo%X can call any function on mouse-click

View File

@ -12,6 +12,7 @@
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/popupmnu.h" #include "nvim/popupmnu.h"
#include "nvim/cursor_shape.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.c.generated.h" # include "api/ui.c.generated.h"
@ -69,6 +70,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->clear = remote_ui_clear; ui->clear = remote_ui_clear;
ui->eol_clear = remote_ui_eol_clear; ui->eol_clear = remote_ui_eol_clear;
ui->cursor_goto = remote_ui_cursor_goto; ui->cursor_goto = remote_ui_cursor_goto;
ui->cursor_style_set = remote_ui_cursor_style_set;
ui->update_menu = remote_ui_update_menu; ui->update_menu = remote_ui_update_menu;
ui->busy_start = remote_ui_busy_start; ui->busy_start = remote_ui_busy_start;
ui->busy_stop = remote_ui_busy_stop; ui->busy_stop = remote_ui_busy_stop;
@ -298,6 +300,14 @@ static void remote_ui_scroll(UI *ui, int count)
push_call(ui, "scroll", args); push_call(ui, "scroll", args);
} }
static void remote_ui_cursor_style_set(UI *ui, Dictionary styles)
{
Array args = ARRAY_DICT_INIT;
Object copy = copy_object(DICTIONARY_OBJ(styles));
ADD(args, copy);
push_call(ui, "cursor_style_set", args);
}
static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
{ {
Array args = ARRAY_DICT_INIT; Array args = ARRAY_DICT_INIT;

View File

@ -7,40 +7,74 @@
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/strings.h" #include "nvim/strings.h"
#include "nvim/syntax.h" #include "nvim/syntax.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ui.h"
/* /// Handling of cursor and mouse pointer shapes in various modes.
* Handling of cursor and mouse pointer shapes in various modes.
*/
static cursorentry_T shape_table[SHAPE_IDX_COUNT] = static cursorentry_T shape_table[SHAPE_IDX_COUNT] =
{ {
/* The values will be filled in from the 'guicursor' and 'mouseshape' // Values are set by 'guicursor' and 'mouseshape'.
* defaults when Vim starts. // Adjust the SHAPE_IDX_ defines when changing this!
* Adjust the SHAPE_IDX_ defines when making changes! */ { "normal", 0, 0, 0, 700L, 400L, 250L, 0, 0, "n", SHAPE_CURSOR+SHAPE_MOUSE },
{0, 0, 0, 700L, 400L, 250L, 0, 0, "n", SHAPE_CURSOR+SHAPE_MOUSE}, { "visual", 0, 0, 0, 700L, 400L, 250L, 0, 0, "v", SHAPE_CURSOR+SHAPE_MOUSE },
{0, 0, 0, 700L, 400L, 250L, 0, 0, "v", SHAPE_CURSOR+SHAPE_MOUSE}, { "insert", 0, 0, 0, 700L, 400L, 250L, 0, 0, "i", SHAPE_CURSOR+SHAPE_MOUSE },
{0, 0, 0, 700L, 400L, 250L, 0, 0, "i", SHAPE_CURSOR+SHAPE_MOUSE}, { "replace", 0, 0, 0, 700L, 400L, 250L, 0, 0, "r", SHAPE_CURSOR+SHAPE_MOUSE },
{0, 0, 0, 700L, 400L, 250L, 0, 0, "r", SHAPE_CURSOR+SHAPE_MOUSE}, { "cmdline_normal", 0, 0, 0, 700L, 400L, 250L, 0, 0, "c", SHAPE_CURSOR+SHAPE_MOUSE },
{0, 0, 0, 700L, 400L, 250L, 0, 0, "c", SHAPE_CURSOR+SHAPE_MOUSE}, { "cmdline_insert", 0, 0, 0, 700L, 400L, 250L, 0, 0, "ci", SHAPE_CURSOR+SHAPE_MOUSE },
{0, 0, 0, 700L, 400L, 250L, 0, 0, "ci", SHAPE_CURSOR+SHAPE_MOUSE}, { "cmdline_replace", 0, 0, 0, 700L, 400L, 250L, 0, 0, "cr", SHAPE_CURSOR+SHAPE_MOUSE },
{0, 0, 0, 700L, 400L, 250L, 0, 0, "cr", SHAPE_CURSOR+SHAPE_MOUSE}, { "operator", 0, 0, 0, 700L, 400L, 250L, 0, 0, "o", SHAPE_CURSOR+SHAPE_MOUSE },
{0, 0, 0, 700L, 400L, 250L, 0, 0, "o", SHAPE_CURSOR+SHAPE_MOUSE}, { "visual_select", 0, 0, 0, 700L, 400L, 250L, 0, 0, "ve", SHAPE_CURSOR+SHAPE_MOUSE },
{0, 0, 0, 700L, 400L, 250L, 0, 0, "ve", SHAPE_CURSOR+SHAPE_MOUSE}, { "cmdline_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "e", SHAPE_MOUSE },
{0, 0, 0, 0L, 0L, 0L, 0, 0, "e", SHAPE_MOUSE}, { "statusline_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "s", SHAPE_MOUSE },
{0, 0, 0, 0L, 0L, 0L, 0, 0, "s", SHAPE_MOUSE}, { "statusline_drag", 0, 0, 0, 0L, 0L, 0L, 0, 0, "sd", SHAPE_MOUSE },
{0, 0, 0, 0L, 0L, 0L, 0, 0, "sd", SHAPE_MOUSE}, { "vsep_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "vs", SHAPE_MOUSE },
{0, 0, 0, 0L, 0L, 0L, 0, 0, "vs", SHAPE_MOUSE}, { "vsep_drag", 0, 0, 0, 0L, 0L, 0L, 0, 0, "vd", SHAPE_MOUSE },
{0, 0, 0, 0L, 0L, 0L, 0, 0, "vd", SHAPE_MOUSE}, { "more", 0, 0, 0, 0L, 0L, 0L, 0, 0, "m", SHAPE_MOUSE },
{0, 0, 0, 0L, 0L, 0L, 0, 0, "m", SHAPE_MOUSE}, { "more_lastline", 0, 0, 0, 0L, 0L, 0L, 0, 0, "ml", SHAPE_MOUSE },
{0, 0, 0, 0L, 0L, 0L, 0, 0, "ml", SHAPE_MOUSE}, { "showmatch", 0, 0, 0, 100L, 100L, 100L, 0, 0, "sm", SHAPE_CURSOR },
{0, 0, 0, 100L, 100L, 100L, 0, 0, "sm", SHAPE_CURSOR},
}; };
/* /// Converts cursor_shapes into a Dictionary of dictionaries
* Parse the 'guicursor' option ("what" is SHAPE_CURSOR) or 'mouseshape' /// @return dictionary of the form {"normal" : { "cursor_shape": ... }, ...}
* ("what" is SHAPE_MOUSE). Dictionary cursor_shape_dict(void)
* Returns error message for an illegal option, NULL otherwise. {
*/ Dictionary all = ARRAY_DICT_INIT;
for (int i = 0; i < SHAPE_IDX_COUNT; i++) {
Dictionary dic = ARRAY_DICT_INIT;
cursorentry_T *cur = &shape_table[i];
if (cur->used_for & SHAPE_MOUSE) {
PUT(dic, "mouse_shape", INTEGER_OBJ(cur->mshape));
}
if (cur->used_for & SHAPE_CURSOR) {
String shape_str;
switch (cur->shape) {
case SHAPE_BLOCK: shape_str = cstr_to_string("block"); break;
case SHAPE_VER: shape_str = cstr_to_string("vertical"); break;
case SHAPE_HOR: shape_str = cstr_to_string("horizontal"); break;
default: shape_str = cstr_to_string("unknown");
}
PUT(dic, "cursor_shape", STRING_OBJ(shape_str));
PUT(dic, "cell_percentage", INTEGER_OBJ(cur->percentage));
PUT(dic, "blinkwait", INTEGER_OBJ(cur->blinkwait));
PUT(dic, "blinkon", INTEGER_OBJ(cur->blinkon));
PUT(dic, "blinkoff", INTEGER_OBJ(cur->blinkoff));
PUT(dic, "hl_id", INTEGER_OBJ(cur->id));
PUT(dic, "id_lm", INTEGER_OBJ(cur->id_lm));
}
PUT(dic, "short_name", STRING_OBJ(cstr_to_string(cur->name)));
PUT(all, cur->full_name, DICTIONARY_OBJ(dic));
}
return all;
}
/// Parse the 'guicursor' option
///
/// @param what SHAPE_CURSOR or SHAPE_MOUSE ('mouseshape')
///
/// @returns error message for an illegal option, NULL otherwise.
char_u *parse_shape_opt(int what) char_u *parse_shape_opt(int what)
{ {
char_u *modep; char_u *modep;
@ -59,10 +93,11 @@ char_u *parse_shape_opt(int what)
* First round: check for errors; second round: do it for real. * First round: check for errors; second round: do it for real.
*/ */
for (round = 1; round <= 2; ++round) { for (round = 1; round <= 2; ++round) {
/* // Repeat for all comma separated parts.
* Repeat for all comma separated parts.
*/
modep = p_guicursor; modep = p_guicursor;
if (*p_guicursor == NUL) {
modep = (char_u *)"a:block-blinkon0";
}
while (*modep != NUL) { while (*modep != NUL) {
colonp = vim_strchr(modep, ':'); colonp = vim_strchr(modep, ':');
if (colonp == NULL) if (colonp == NULL)
@ -71,19 +106,18 @@ char_u *parse_shape_opt(int what)
return (char_u *)N_("E546: Illegal mode"); return (char_u *)N_("E546: Illegal mode");
commap = vim_strchr(modep, ','); commap = vim_strchr(modep, ',');
/* // Repeat for all modes before the colon.
* Repeat for all mode's before the colon. // For the 'a' mode, we loop to handle all the modes.
* For the 'a' mode, we loop to handle all the modes.
*/
all_idx = -1; all_idx = -1;
assert(modep < colonp); assert(modep < colonp);
while (modep < colonp || all_idx >= 0) { while (modep < colonp || all_idx >= 0) {
if (all_idx < 0) { if (all_idx < 0) {
/* Find the mode. */ // Find the mode
if (modep[1] == '-' || modep[1] == ':') if (modep[1] == '-' || modep[1] == ':') {
len = 1; len = 1;
else } else {
len = 2; len = 2;
}
if (len == 1 && TOLOWER_ASC(modep[0]) == 'a') { if (len == 1 && TOLOWER_ASC(modep[0]) == 'a') {
all_idx = SHAPE_IDX_COUNT - 1; all_idx = SHAPE_IDX_COUNT - 1;
@ -100,11 +134,11 @@ char_u *parse_shape_opt(int what)
modep += len + 1; modep += len + 1;
} }
if (all_idx >= 0) if (all_idx >= 0) {
idx = all_idx--; idx = all_idx--;
else if (round == 2) { } else if (round == 2) {
{ {
/* Set the defaults, for the missing parts */ // Set the defaults, for the missing parts
shape_table[idx].shape = SHAPE_BLOCK; shape_table[idx].shape = SHAPE_BLOCK;
shape_table[idx].blinkwait = 700L; shape_table[idx].blinkwait = 700L;
shape_table[idx].blinkon = 400L; shape_table[idx].blinkon = 400L;
@ -208,6 +242,23 @@ char_u *parse_shape_opt(int what)
shape_table[SHAPE_IDX_VE].id_lm = shape_table[SHAPE_IDX_V].id_lm; shape_table[SHAPE_IDX_VE].id_lm = shape_table[SHAPE_IDX_V].id_lm;
} }
} }
ui_cursor_style_set();
return NULL; return NULL;
} }
/// Map cursor mode from string to integer
///
/// @param mode Fullname of the mode whose id we are looking for
/// @return -1 in case of failure, else the matching SHAPE_ID* integer
int cursor_mode_str2int(const char *mode)
{
for (int current_mode = 0; current_mode < SHAPE_IDX_COUNT; current_mode++) {
if (strcmp(shape_table[current_mode].full_name, mode) == 0) {
return current_mode;
}
}
ELOG("Unknown mode %s", mode);
return -1;
}

View File

@ -1,32 +1,34 @@
#ifndef NVIM_CURSOR_SHAPE_H #ifndef NVIM_CURSOR_SHAPE_H
#define NVIM_CURSOR_SHAPE_H #define NVIM_CURSOR_SHAPE_H
/* /// struct to store values from 'guicursor' and 'mouseshape'
* struct to store values from 'guicursor' and 'mouseshape' /// Indexes in shape_table[]
*/ typedef enum {
/* Indexes in shape_table[] */ SHAPE_IDX_N = 0, ///< Normal mode
#define SHAPE_IDX_N 0 /* Normal mode */ SHAPE_IDX_V = 1, ///< Visual mode
#define SHAPE_IDX_V 1 /* Visual mode */ SHAPE_IDX_I = 2, ///< Insert mode
#define SHAPE_IDX_I 2 /* Insert mode */ SHAPE_IDX_R = 3, ///< Replace mode
#define SHAPE_IDX_R 3 /* Replace mode */ SHAPE_IDX_C = 4, ///< Command line Normal mode
#define SHAPE_IDX_C 4 /* Command line Normal mode */ SHAPE_IDX_CI = 5, ///< Command line Insert mode
#define SHAPE_IDX_CI 5 /* Command line Insert mode */ SHAPE_IDX_CR = 6, ///< Command line Replace mode
#define SHAPE_IDX_CR 6 /* Command line Replace mode */ SHAPE_IDX_O = 7, ///< Operator-pending mode
#define SHAPE_IDX_O 7 /* Operator-pending mode */ SHAPE_IDX_VE = 8, ///< Visual mode with 'selection' exclusive
#define SHAPE_IDX_VE 8 /* Visual mode with 'selection' exclusive */ SHAPE_IDX_CLINE = 9, ///< On command line
#define SHAPE_IDX_CLINE 9 /* On command line */ SHAPE_IDX_STATUS = 10, ///< On status line
#define SHAPE_IDX_STATUS 10 /* A status line */ SHAPE_IDX_SDRAG = 11, ///< dragging a status line
#define SHAPE_IDX_SDRAG 11 /* dragging a status line */ SHAPE_IDX_VSEP = 12, ///< On vertical separator line
#define SHAPE_IDX_VSEP 12 /* A vertical separator line */ SHAPE_IDX_VDRAG = 13, ///< dragging a vertical separator line
#define SHAPE_IDX_VDRAG 13 /* dragging a vertical separator line */ SHAPE_IDX_MORE = 14, ///< Hit-return or More
#define SHAPE_IDX_MORE 14 /* Hit-return or More */ SHAPE_IDX_MOREL = 15, ///< Hit-return or More in last line
#define SHAPE_IDX_MOREL 15 /* Hit-return or More in last line */ SHAPE_IDX_SM = 16, ///< showing matching paren
#define SHAPE_IDX_SM 16 /* showing matching paren */ SHAPE_IDX_COUNT = 17
#define SHAPE_IDX_COUNT 17 } MouseMode;
#define SHAPE_BLOCK 0 /* block cursor */ typedef enum {
#define SHAPE_HOR 1 /* horizontal bar cursor */ SHAPE_BLOCK = 0, ///< block cursor
#define SHAPE_VER 2 /* vertical bar cursor */ SHAPE_HOR = 1, ///< horizontal bar cursor
SHAPE_VER = 2 ///< vertical bar cursor
} CursorShape;
#define MSHAPE_NUMBERED 1000 /* offset for shapes identified by number */ #define MSHAPE_NUMBERED 1000 /* offset for shapes identified by number */
#define MSHAPE_HIDE 1 /* hide mouse pointer */ #define MSHAPE_HIDE 1 /* hide mouse pointer */
@ -35,16 +37,17 @@
#define SHAPE_CURSOR 2 /* used for text cursor shape */ #define SHAPE_CURSOR 2 /* used for text cursor shape */
typedef struct cursor_entry { typedef struct cursor_entry {
int shape; /* one of the SHAPE_ defines */ char *full_name; ///< mode description
int mshape; /* one of the MSHAPE defines */ CursorShape shape; ///< cursor shape: one of the SHAPE_ defines
int percentage; /* percentage of cell for bar */ int mshape; ///< mouse shape: one of the MSHAPE defines
long blinkwait; /* blinking, wait time before blinking starts */ int percentage; ///< percentage of cell for bar
long blinkon; /* blinking, on time */ long blinkwait; ///< blinking, wait time before blinking starts
long blinkoff; /* blinking, off time */ long blinkon; ///< blinking, on time
int id; /* highlight group ID */ long blinkoff; ///< blinking, off time
int id_lm; /* highlight group ID for :lmap mode */ int id; ///< highlight group ID
char *name; /* mode name (fixed) */ int id_lm; ///< highlight group ID for :lmap mode
char used_for; /* SHAPE_MOUSE and/or SHAPE_CURSOR */ char *name; ///< mode short name
char used_for; ///< SHAPE_MOUSE and/or SHAPE_CURSOR
} cursorentry_T; } cursorentry_T;

View File

@ -1000,7 +1000,7 @@ return {
deny_duplicates=true, deny_duplicates=true,
vi_def=true, vi_def=true,
varname='p_guicursor', varname='p_guicursor',
defaults={if_true={vi="n-v-c:block,o:hor50,i-ci:hor15,r-cr:hor30,sm:block"}} defaults={if_true={vi="n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175"}}
}, },
{ {
full_name='guifont', abbreviation='gfn', full_name='guifont', abbreviation='gfn',

View File

@ -42,34 +42,38 @@
static bool did_syntax_onoff = false; static bool did_syntax_onoff = false;
// Structure that stores information about a highlight group. /// Structure that stores information about a highlight group.
// The ID of a highlight group is also called group ID. It is the index in /// The ID of a highlight group is also called group ID. It is the index in
// the highlight_ga array PLUS ONE. /// the highlight_ga array PLUS ONE.
struct hl_group { struct hl_group {
char_u *sg_name; // highlight group name char_u *sg_name; ///< highlight group name
char_u *sg_name_u; // uppercase of sg_name char_u *sg_name_u; ///< uppercase of sg_name
int sg_attr; // Screen attr int sg_attr; ///< Screen attr @see ATTR_ENTRY
int sg_link; // link to this highlight group ID int sg_link; ///< link to this highlight group ID
int sg_set; // combination of SG_* flags int sg_set; ///< combination of flags in \ref SG_SET
scid_T sg_scriptID; // script in which the group was last set scid_T sg_scriptID; ///< script in which the group was last set
// for terminal UIs // for terminal UIs
int sg_cterm; // "cterm=" highlighting attr int sg_cterm; ///< "cterm=" highlighting attr
int sg_cterm_fg; // terminal fg color number + 1 int sg_cterm_fg; ///< terminal fg color number + 1
int sg_cterm_bg; // terminal bg color number + 1 int sg_cterm_bg; ///< terminal bg color number + 1
int sg_cterm_bold; // bold attr was set for light color int sg_cterm_bold; ///< bold attr was set for light color
// for RGB UIs // for RGB UIs
int sg_gui; // "gui=" highlighting attributes int sg_gui; ///< "gui=" highlighting attributes
RgbValue sg_rgb_fg; // RGB foreground color ///< (combination of \ref HL_ATTRIBUTES)
RgbValue sg_rgb_bg; // RGB background color RgbValue sg_rgb_fg; ///< RGB foreground color
RgbValue sg_rgb_sp; // RGB special color RgbValue sg_rgb_bg; ///< RGB background color
uint8_t *sg_rgb_fg_name; // RGB foreground color name RgbValue sg_rgb_sp; ///< RGB special color
uint8_t *sg_rgb_bg_name; // RGB background color name uint8_t *sg_rgb_fg_name; ///< RGB foreground color name
uint8_t *sg_rgb_sp_name; // RGB special color name uint8_t *sg_rgb_bg_name; ///< RGB background color name
uint8_t *sg_rgb_sp_name; ///< RGB special color name
}; };
/// \addtogroup SG_SET
/// @{
#define SG_CTERM 2 // cterm has been set #define SG_CTERM 2 // cterm has been set
#define SG_GUI 4 // gui has been set #define SG_GUI 4 // gui has been set
#define SG_LINK 8 // link has been set #define SG_LINK 8 // link has been set
/// @}
// highlight groups for 'highlight' option // highlight groups for 'highlight' option
static garray_T highlight_ga = GA_EMPTY_INIT_VALUE; static garray_T highlight_ga = GA_EMPTY_INIT_VALUE;
@ -6093,16 +6097,16 @@ int load_colors(char_u *name)
return retval; return retval;
} }
/*
* Handle the ":highlight .." command. /// Handle the ":highlight .." command.
* When using ":hi clear" this is called recursively for each group with /// When using ":hi clear" this is called recursively for each group with
* "forceit" and "init" both TRUE. /// "forceit" and "init" both TRUE.
*/ /// @param init TRUE when called for initializing
void void
do_highlight ( do_highlight(
char_u *line, char_u *line,
int forceit, int forceit,
int init /* TRUE when called for initializing */ int init
) )
{ {
char_u *name_end; char_u *name_end;
@ -6704,12 +6708,10 @@ static garray_T attr_table = GA_EMPTY_INIT_VALUE;
#define ATTR_ENTRY(idx) ((attrentry_T *)attr_table.ga_data)[idx] #define ATTR_ENTRY(idx) ((attrentry_T *)attr_table.ga_data)[idx]
/* /// Return the attr number for a set of colors and font.
* 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
* Add a new entry to the term_attr_table, attr_table or gui_attr_table /// if the combination is new.
* if the combination is new. /// @return 0 for error.
* Return 0 for error.
*/
int get_attr_entry(attrentry_T *aep) int get_attr_entry(attrentry_T *aep)
{ {
garray_T *table = &attr_table; garray_T *table = &attr_table;
@ -6932,7 +6934,7 @@ static int highlight_list_arg(int id, int didh, int type, int iarg, char_u *sarg
/// Check whether highlight group has attribute /// Check whether highlight group has attribute
/// ///
/// @param[in] id Highilght group to check. /// @param[in] id Highlight group to check.
/// @param[in] flag Attribute to check. /// @param[in] flag Attribute to check.
/// @param[in] modec 'g' for GUI, 'c' for term. /// @param[in] modec 'g' for GUI, 'c' for term.
/// ///
@ -7165,12 +7167,13 @@ int syn_namen2id(char_u *linep, int len)
return id; return id;
} }
/* /// Find highlight group name in the table and return it's ID.
* Find highlight group name in the table and return it's ID. /// If it doesn't exist yet, a new entry is created.
* The argument is a pointer to the name and the length of the name. ///
* If it doesn't exist yet, a new entry is created. /// @param pp Highlight group name
* Return 0 for failure. /// @param len length of \p pp
*/ ///
/// @return 0 for failure else the id of the group
int syn_check_group(char_u *pp, int len) int syn_check_group(char_u *pp, int len)
{ {
char_u *name = vim_strnsave(pp, len); char_u *name = vim_strnsave(pp, len);
@ -8244,7 +8247,14 @@ color_name_table_T color_name_table[] = {
{ NULL, 0 }, { NULL, 0 },
}; };
RgbValue name_to_color(uint8_t *name)
/// Translate to RgbValue if \p name is an hex value (e.g. #XXXXXX),
/// else look into color_name_table to translate a color name to its
/// hex value
///
/// @param[in] name string value to convert to RGB
/// return the hex value or -1 if could not find a correct value
RgbValue name_to_color(const uint8_t *name)
{ {
if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2])

View File

@ -5,10 +5,11 @@
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
/*
* Terminal highlighting attribute bits. /// Terminal highlighting attribute bits.
* Attributes above HL_ALL are used for syntax highlighting. /// Attributes above HL_ALL are used for syntax highlighting.
*/ /// \addtogroup HL_ATTRIBUTES
/// @{
#define HL_NORMAL 0x00 #define HL_NORMAL 0x00
#define HL_INVERSE 0x01 #define HL_INVERSE 0x01
#define HL_BOLD 0x02 #define HL_BOLD 0x02
@ -16,6 +17,7 @@
#define HL_UNDERLINE 0x08 #define HL_UNDERLINE 0x08
#define HL_UNDERCURL 0x10 #define HL_UNDERCURL 0x10
#define HL_STANDOUT 0x20 #define HL_STANDOUT 0x20
/// @}
#define HL_CONTAINED 0x01 /* not used on toplevel */ #define HL_CONTAINED 0x01 /* not used on toplevel */
#define HL_TRANSP 0x02 /* has no highlighting */ #define HL_TRANSP 0x02 /* has no highlighting */

View File

@ -31,6 +31,8 @@
#include "nvim/ugrid.h" #include "nvim/ugrid.h"
#include "nvim/tui/input.h" #include "nvim/tui/input.h"
#include "nvim/tui/tui.h" #include "nvim/tui/tui.h"
#include "nvim/cursor_shape.h"
#include "nvim/syntax.h"
// Space reserved in the output buffer to restore the cursor to normal when // Space reserved in the output buffer to restore the cursor to normal when
// flushing. No existing terminal will require 32 bytes to do that. // flushing. No existing terminal will require 32 bytes to do that.
@ -69,13 +71,14 @@ typedef struct {
bool can_use_terminal_scroll; bool can_use_terminal_scroll;
bool mouse_enabled; bool mouse_enabled;
bool busy; bool busy;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
HlAttrs print_attrs; HlAttrs print_attrs;
int showing_mode; int showing_mode;
struct { struct {
int enable_mouse, disable_mouse; int enable_mouse, disable_mouse;
int enable_bracketed_paste, disable_bracketed_paste; int enable_bracketed_paste, disable_bracketed_paste;
int set_cursor_shape_bar, set_cursor_shape_ul, set_cursor_shape_block;
int set_rgb_foreground, set_rgb_background; int set_rgb_foreground, set_rgb_background;
int set_cursor_color;
int enable_focus_reporting, disable_focus_reporting; int enable_focus_reporting, disable_focus_reporting;
} unibi_ext; } unibi_ext;
} TUIData; } TUIData;
@ -97,6 +100,7 @@ UI *tui_start(void)
ui->clear = tui_clear; ui->clear = tui_clear;
ui->eol_clear = tui_eol_clear; ui->eol_clear = tui_eol_clear;
ui->cursor_goto = tui_cursor_goto; ui->cursor_goto = tui_cursor_goto;
ui->cursor_style_set = tui_cursor_style_set;
ui->update_menu = tui_update_menu; ui->update_menu = tui_update_menu;
ui->busy_start = tui_busy_start; ui->busy_start = tui_busy_start;
ui->busy_stop = tui_busy_stop; ui->busy_stop = tui_busy_stop;
@ -129,11 +133,9 @@ static void terminfo_start(UI *ui)
data->showing_mode = 0; data->showing_mode = 0;
data->unibi_ext.enable_mouse = -1; data->unibi_ext.enable_mouse = -1;
data->unibi_ext.disable_mouse = -1; data->unibi_ext.disable_mouse = -1;
data->unibi_ext.set_cursor_color = -1;
data->unibi_ext.enable_bracketed_paste = -1; data->unibi_ext.enable_bracketed_paste = -1;
data->unibi_ext.disable_bracketed_paste = -1; data->unibi_ext.disable_bracketed_paste = -1;
data->unibi_ext.set_cursor_shape_bar = -1;
data->unibi_ext.set_cursor_shape_ul = -1;
data->unibi_ext.set_cursor_shape_block = -1;
data->unibi_ext.enable_focus_reporting = -1; data->unibi_ext.enable_focus_reporting = -1;
data->unibi_ext.disable_focus_reporting = -1; data->unibi_ext.disable_focus_reporting = -1;
data->out_fd = 1; data->out_fd = 1;
@ -146,8 +148,6 @@ static void terminfo_start(UI *ui)
data->ut = unibi_dummy(); data->ut = unibi_dummy();
} }
fix_terminfo(data); fix_terminfo(data);
// Initialize the cursor shape.
unibi_out(ui, data->unibi_ext.set_cursor_shape_block);
// Set 't_Co' from the result of unibilium & fix_terminfo. // Set 't_Co' from the result of unibilium & fix_terminfo.
t_colors = unibi_get_num(data->ut, unibi_max_colors); t_colors = unibi_get_num(data->ut, unibi_max_colors);
// Enter alternate screen and clear // Enter alternate screen and clear
@ -434,6 +434,64 @@ static void tui_cursor_goto(UI *ui, int row, int col)
unibi_goto(ui, row, col); unibi_goto(ui, row, col);
} }
CursorShape tui_cursor_decode_shape(const char *shape_str)
{
CursorShape shape = 0;
if (strcmp(shape_str, "block") == 0) {
shape = SHAPE_BLOCK;
} else if (strcmp(shape_str, "vertical") == 0) {
shape = SHAPE_VER;
} else if (strcmp(shape_str, "horizontal") == 0) {
shape = SHAPE_HOR;
} else {
EMSG2(_(e_invarg2), shape_str);
}
return shape;
}
static cursorentry_T decode_cursor_entry(Dictionary args)
{
cursorentry_T r;
for (size_t i = 0; i < args.size; i++) {
char *keyStr = args.items[i].key.data;
Object value = args.items[i].value;
if (strcmp(keyStr, "cursor_shape") == 0) {
r.shape = tui_cursor_decode_shape(args.items[i].value.data.string.data);
} else if (strcmp(keyStr, "blinkon") == 0) {
r.blinkon = (int)value.data.integer;
} else if (strcmp(keyStr, "blinkoff") == 0) {
r.blinkoff = (int)value.data.integer;
} else if (strcmp(keyStr, "hl_id") == 0) {
r.id = (int)value.data.integer;
}
}
return r;
}
static void tui_cursor_style_set(UI *ui, Dictionary args)
{
TUIData *data = ui->data;
for (size_t i = 0; i < args.size; i++) {
char *mode_name = args.items[i].key.data;
const int mode_id = cursor_mode_str2int(mode_name);
if (mode_id < 0) {
WLOG("Unknown mode '%s'", mode_name);
continue;
}
cursorentry_T r = decode_cursor_entry(args.items[i].value.data.dictionary);
r.full_name = mode_name;
data->cursor_shapes[mode_id] = r;
}
// force redraw
MouseMode cursor_mode = tui_mode2cursor(data->showing_mode);
tui_set_cursor(ui, cursor_mode);
}
static void tui_update_menu(UI *ui) static void tui_update_menu(UI *ui)
{ {
// Do nothing; menus are for GUI only // Do nothing; menus are for GUI only
@ -467,33 +525,101 @@ static void tui_mouse_off(UI *ui)
} }
} }
/// @param mode one of SHAPE_XXX
static void tui_set_cursor(UI *ui, MouseMode mode)
{
TUIData *data = ui->data;
cursorentry_T c = data->cursor_shapes[mode];
int shape = c.shape;
bool inside_tmux = os_getenv("TMUX") != NULL;
unibi_var_t vars[26 + 26] = { { 0 } };
# define TMUX_WRAP(seq) (inside_tmux ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq)
// Support changing cursor shape on some popular terminals.
const char *term_prog = os_getenv("TERM_PROGRAM");
const char *vte_version = os_getenv("VTE_VERSION");
if ((term_prog && !strcmp(term_prog, "Konsole"))
|| os_getenv("KONSOLE_DBUS_SESSION") != NULL) {
// Konsole uses a proprietary escape code to set the cursor shape
// and does not support DECSCUSR.
switch (shape) {
case SHAPE_BLOCK: shape = 0; break;
case SHAPE_VER: shape = 1; break;
case SHAPE_HOR: shape = 3; break;
default: WLOG("Unknown shape value %d", shape); break;
}
data->params[0].i = shape;
data->params[1].i = (c.blinkon == 0);
unibi_format(vars, vars + 26,
TMUX_WRAP("\x1b]50;CursorShape=%p1%d;BlinkingCursorEnabled=%p2%d\x07"),
data->params, out, ui, NULL, NULL);
} else if (!vte_version || atoi(vte_version) >= 3900) {
// Assume that the terminal supports DECSCUSR unless it is an
// old VTE based terminal. This should not get wrapped for tmux,
// which will handle it via its Ss/Se terminfo extension - usually
// according to its terminal-overrides.
switch (shape) {
case SHAPE_BLOCK: shape = 1; break;
case SHAPE_VER: shape = 5; break;
case SHAPE_HOR: shape = 3; break;
default: WLOG("Unknown shape value %d", shape); break;
}
data->params[0].i = shape + (c.blinkon ==0);
unibi_format(vars, vars + 26, "\x1b[%p1%d q",
data->params, out, ui, NULL, NULL);
}
if (c.id != 0 && ui->rgb) {
int attr = syn_id2attr(c.id);
attrentry_T *aep = syn_cterm_attr2entry(attr);
data->params[0].i = aep->rgb_bg_color;
unibi_out(ui, data->unibi_ext.set_cursor_color);
}
}
/// Returns cursor mode from edit mode
static MouseMode tui_mode2cursor(int mode)
{
switch (mode) {
case INSERT: return SHAPE_IDX_I;
case CMDLINE: return SHAPE_IDX_C;
case REPLACE: return SHAPE_IDX_R;
case NORMAL:
default: return SHAPE_IDX_N;
}
}
/// @param mode editor mode
static void tui_mode_change(UI *ui, int mode) static void tui_mode_change(UI *ui, int mode)
{ {
TUIData *data = ui->data; TUIData *data = ui->data;
if (mode == INSERT) { if (mode == INSERT) {
if (data->showing_mode != INSERT) { if (data->showing_mode != INSERT) {
unibi_out(ui, data->unibi_ext.set_cursor_shape_bar); tui_set_cursor(ui, SHAPE_IDX_I);
} }
} else if (mode == CMDLINE) { } else if (mode == CMDLINE) {
if (data->showing_mode != CMDLINE) { if (data->showing_mode != CMDLINE) {
unibi_out(ui, data->unibi_ext.set_cursor_shape_bar); tui_set_cursor(ui, SHAPE_IDX_C);
} }
} else if (mode == REPLACE) { } else if (mode == REPLACE) {
if (data->showing_mode != REPLACE) { if (data->showing_mode != REPLACE) {
unibi_out(ui, data->unibi_ext.set_cursor_shape_ul); tui_set_cursor(ui, SHAPE_IDX_R);
} }
} else { } else {
assert(mode == NORMAL); assert(mode == NORMAL);
if (data->showing_mode != NORMAL) { if (data->showing_mode != NORMAL) {
unibi_out(ui, data->unibi_ext.set_cursor_shape_block); tui_set_cursor(ui, SHAPE_IDX_N);
} }
} }
data->showing_mode = mode; data->showing_mode = mode;
} }
static void tui_set_scroll_region(UI *ui, int top, int bot, int left, static void tui_set_scroll_region(UI *ui, int top, int bot, int left,
int right) int right)
{ {
TUIData *data = ui->data; TUIData *data = ui->data;
ugrid_set_scroll_region(&data->grid, top, bot, left, right); ugrid_set_scroll_region(&data->grid, top, bot, left, right);
@ -831,8 +957,6 @@ static void fix_terminfo(TUIData *data)
goto end; goto end;
} }
bool inside_tmux = os_getenv("TMUX") != NULL;
#define STARTS_WITH(str, prefix) (!memcmp(str, prefix, sizeof(prefix) - 1)) #define STARTS_WITH(str, prefix) (!memcmp(str, prefix, sizeof(prefix) - 1))
if (STARTS_WITH(term, "rxvt")) { if (STARTS_WITH(term, "rxvt")) {
@ -890,42 +1014,10 @@ static void fix_terminfo(TUIData *data)
unibi_set_str(ut, unibi_set_a_background, XTERM_SETAB); unibi_set_str(ut, unibi_set_a_background, XTERM_SETAB);
} }
const char * env_cusr_shape = os_getenv("NVIM_TUI_ENABLE_CURSOR_SHAPE");
if (env_cusr_shape && strncmp(env_cusr_shape, "0", 1) == 0) {
goto end;
}
bool cusr_blink = env_cusr_shape && strncmp(env_cusr_shape, "2", 1) == 0;
#define TMUX_WRAP(seq) (inside_tmux ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq)
// Support changing cursor shape on some popular terminals.
const char *term_prog = os_getenv("TERM_PROGRAM");
const char *vte_version = os_getenv("VTE_VERSION");
if ((term_prog && !strcmp(term_prog, "Konsole"))
|| os_getenv("KONSOLE_DBUS_SESSION") != NULL) {
// Konsole uses a proprietary escape code to set the cursor shape
// and does not support DECSCUSR.
data->unibi_ext.set_cursor_shape_bar = (int)unibi_add_ext_str(ut, NULL,
TMUX_WRAP("\x1b]50;CursorShape=1\x07"));
data->unibi_ext.set_cursor_shape_ul = (int)unibi_add_ext_str(ut, NULL,
TMUX_WRAP("\x1b]50;CursorShape=2\x07"));
data->unibi_ext.set_cursor_shape_block = (int)unibi_add_ext_str(ut, NULL,
TMUX_WRAP("\x1b]50;CursorShape=0\x07"));
} else if (!vte_version || atoi(vte_version) >= 3900) {
// Assume that the terminal supports DECSCUSR unless it is an
// old VTE based terminal. This should not get wrapped for tmux,
// which will handle it via its Ss/Se terminfo extension - usually
// according to its terminal-overrides.
data->unibi_ext.set_cursor_shape_bar =
(int)unibi_add_ext_str(ut, NULL, cusr_blink ? "\x1b[5 q" : "\x1b[6 q");
data->unibi_ext.set_cursor_shape_ul =
(int)unibi_add_ext_str(ut, NULL, cusr_blink ? "\x1b[3 q" : "\x1b[4 q");
data->unibi_ext.set_cursor_shape_block =
(int)unibi_add_ext_str(ut, NULL, cusr_blink ? "\x1b[1 q" : "\x1b[2 q");
}
end: end:
// Fill some empty slots with common terminal strings // Fill some empty slots with common terminal strings
data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(
ut, NULL, "\033]12;#%p1%06x\007");
data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, NULL, data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, NULL,
"\x1b[?1002h\x1b[?1006h"); "\x1b[?1002h\x1b[?1006h");
data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, NULL, data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, NULL,

View File

@ -1,6 +1,8 @@
#ifndef NVIM_TUI_TUI_H #ifndef NVIM_TUI_TUI_H
#define NVIM_TUI_TUI_H #define NVIM_TUI_TUI_H
#include "nvim/cursor_shape.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/tui.h.generated.h" # include "tui/tui.h.generated.h"
#endif #endif

View File

@ -29,6 +29,7 @@
#include "nvim/screen.h" #include "nvim/screen.h"
#include "nvim/syntax.h" #include "nvim/syntax.h"
#include "nvim/window.h" #include "nvim/window.h"
#include "nvim/cursor_shape.h"
#ifdef FEAT_TUI #ifdef FEAT_TUI
# include "nvim/tui/tui.h" # include "nvim/tui/tui.h"
#else #else
@ -179,6 +180,7 @@ void ui_refresh(void)
row = col = 0; row = col = 0;
screen_resize(width, height); screen_resize(width, height);
pum_set_external(pum_external); pum_set_external(pum_external);
ui_cursor_style_set();
} }
static void ui_refresh_event(void **argv) static void ui_refresh_event(void **argv)
@ -376,6 +378,13 @@ void ui_cursor_goto(int new_row, int new_col)
pending_cursor_update = true; pending_cursor_update = true;
} }
void ui_cursor_style_set(void)
{
Dictionary style = cursor_shape_dict();
UI_CALL(cursor_style_set, style);
api_free_dictionary(style);
}
void ui_update_menu(void) void ui_update_menu(void)
{ {
UI_CALL(update_menu); UI_CALL(update_menu);

View File

@ -22,6 +22,7 @@ struct ui_t {
void (*clear)(UI *ui); void (*clear)(UI *ui);
void (*eol_clear)(UI *ui); void (*eol_clear)(UI *ui);
void (*cursor_goto)(UI *ui, int row, int col); void (*cursor_goto)(UI *ui, int row, int col);
void (*cursor_style_set)(UI *ui, Dictionary cursor_shapes);
void (*update_menu)(UI *ui); void (*update_menu)(UI *ui);
void (*busy_start)(UI *ui); void (*busy_start)(UI *ui);
void (*busy_stop)(UI *ui); void (*busy_stop)(UI *ui);

View File

@ -13,6 +13,7 @@
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/ui_bridge.h" #include "nvim/ui_bridge.h"
#include "nvim/ugrid.h" #include "nvim/ugrid.h"
#include "nvim/api/private/helpers.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_bridge.c.generated.h" # include "ui_bridge.c.generated.h"
@ -59,6 +60,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
rv->bridge.clear = ui_bridge_clear; rv->bridge.clear = ui_bridge_clear;
rv->bridge.eol_clear = ui_bridge_eol_clear; rv->bridge.eol_clear = ui_bridge_eol_clear;
rv->bridge.cursor_goto = ui_bridge_cursor_goto; rv->bridge.cursor_goto = ui_bridge_cursor_goto;
rv->bridge.cursor_style_set = ui_bridge_cursor_styleset;
rv->bridge.update_menu = ui_bridge_update_menu; rv->bridge.update_menu = ui_bridge_update_menu;
rv->bridge.busy_start = ui_bridge_busy_start; rv->bridge.busy_start = ui_bridge_busy_start;
rv->bridge.busy_stop = ui_bridge_busy_stop; rv->bridge.busy_stop = ui_bridge_busy_stop;
@ -178,6 +180,23 @@ static void ui_bridge_cursor_goto_event(void **argv)
ui->cursor_goto(ui, PTR2INT(argv[1]), PTR2INT(argv[2])); ui->cursor_goto(ui, PTR2INT(argv[1]), PTR2INT(argv[2]));
} }
static void ui_bridge_cursor_styleset(UI *b, Dictionary style)
{
Object copy = copy_object(DICTIONARY_OBJ(style));
Object *pobj = xmalloc(sizeof(copy));
*pobj = copy;
UI_CALL(b, cursor_styleset, 2, b, pobj);
}
static void ui_bridge_cursor_styleset_event(void **argv)
{
UI *ui = UI(argv[0]);
Object *styles = (Object *)argv[1];
ui->cursor_style_set(ui, styles->data.dictionary);
api_free_object(*styles);
xfree(styles);
}
static void ui_bridge_update_menu(UI *b) static void ui_bridge_update_menu(UI *b)
{ {
UI_CALL(b, update_menu, 1, b); UI_CALL(b, update_menu, 1, b);

View File

@ -0,0 +1,182 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed, meths = helpers.clear, helpers.feed, helpers.meths
local insert, execute = helpers.insert, helpers.execute
local eq, funcs = helpers.eq, helpers.funcs
local command = helpers.command
describe('ui/cursor', function()
local screen
before_each(function()
clear()
screen = Screen.new(25, 5)
screen:attach()
end)
after_each(function()
screen:detach()
end)
it("'guicursor' is published as a UI event", function()
command('redraw')
screen:expect('', nil, nil, nil, true) -- Tickle the event-loop.
local expected_cursor_style = {
cmdline_hover = {
mouse_shape = 0,
short_name = 'e' },
cmdline_insert = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 25,
cursor_shape = 'vertical',
hl_id = 45,
id_lm = 46,
mouse_shape = 0,
short_name = 'ci' },
cmdline_normal = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 0,
cursor_shape = 'block',
hl_id = 45,
id_lm = 46,
mouse_shape = 0,
short_name = 'c' },
cmdline_replace = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 20,
cursor_shape = 'horizontal',
hl_id = 45,
id_lm = 46,
mouse_shape = 0,
short_name = 'cr' },
insert = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 25,
cursor_shape = 'vertical',
hl_id = 45,
id_lm = 46,
mouse_shape = 0,
short_name = 'i' },
more = {
mouse_shape = 0,
short_name = 'm' },
more_lastline = {
mouse_shape = 0,
short_name = 'ml' },
normal = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 0,
cursor_shape = 'block',
hl_id = 45,
id_lm = 46,
mouse_shape = 0,
short_name = 'n' },
operator = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 50,
cursor_shape = 'horizontal',
hl_id = 45,
id_lm = 45,
mouse_shape = 0,
short_name = 'o' },
replace = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 20,
cursor_shape = 'horizontal',
hl_id = 45,
id_lm = 46,
mouse_shape = 0,
short_name = 'r' },
showmatch = {
blinkoff = 150,
blinkon = 175,
blinkwait = 175,
cell_percentage = 0,
cursor_shape = 'block',
hl_id = 45,
id_lm = 45,
short_name = 'sm' },
statusline_drag = {
mouse_shape = 0,
short_name = 'sd' },
statusline_hover = {
mouse_shape = 0,
short_name = 's' },
visual = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 0,
cursor_shape = 'block',
hl_id = 45,
id_lm = 46,
mouse_shape = 0,
short_name = 'v' },
visual_select = {
blinkoff = 250,
blinkon = 400,
blinkwait = 700,
cell_percentage = 35,
cursor_shape = 'vertical',
hl_id = 45,
id_lm = 45,
mouse_shape = 0,
short_name = 've' },
vsep_drag = {
mouse_shape = 0,
short_name = 'vd' },
vsep_hover = {
mouse_shape = 0,
short_name = 'vs' }
}
-- Default 'guicursor' published on startup.
eq(expected_cursor_style, screen._cursor_style)
eq('normal', screen.mode)
-- Event is published ONLY if the cursor style changed.
screen._cursor_style = nil
command('redraw')
screen:expect('', nil, nil, nil, true) -- Tickle the event-loop.
eq(nil, screen._cursor_style)
-- Change the cursor style.
meths.set_option('guicursor', 'n-v-c:ver35-blinkwait171-blinkoff172-blinkon173,ve:hor35,o:ver50,i-ci:block,r-cr:hor90,sm:ver42')
command('redraw')
screen:expect('', nil, nil, nil, true) -- Tickle the event-loop.
eq('vertical', screen._cursor_style.normal.cursor_shape)
eq('horizontal', screen._cursor_style.visual_select.cursor_shape)
eq('vertical', screen._cursor_style.operator.cursor_shape)
eq('block', screen._cursor_style.insert.cursor_shape)
eq('vertical', screen._cursor_style.showmatch.cursor_shape)
eq(171, screen._cursor_style.normal.blinkwait)
eq(172, screen._cursor_style.normal.blinkoff)
eq(173, screen._cursor_style.normal.blinkon)
end)
it("empty 'guicursor' sets cursor_shape=block in all modes", function()
meths.set_option('guicursor', '')
command('redraw')
screen:expect('', nil, nil, nil, true) -- Tickle the event-loop.
for _, m in ipairs({ 'cmdline_insert', 'cmdline_normal', 'cmdline_replace', 'insert',
'showmatch', 'normal', 'replace', 'visual',
'visual_select', }) do
eq('block', screen._cursor_style[m].cursor_shape)
eq(0, screen._cursor_style[m].blinkon)
end
end)
end)

View File

@ -6,7 +6,7 @@ local eq, funcs = helpers.eq, helpers.funcs
if helpers.pending_win32(pending) then return end if helpers.pending_win32(pending) then return end
describe('Mouse input', function() describe('ui/mouse/input', function()
local screen local screen
before_each(function() before_each(function()

View File

@ -313,6 +313,8 @@ function Screen:_redraw(updates)
if handler ~= nil then if handler ~= nil then
handler(self, unpack(update[i])) handler(self, unpack(update[i]))
else else
assert(self._on_event,
"Add Screen:_handle_XXX method or call Screen:set_on_event_handler")
self._on_event(method, update[i]) self._on_event(method, update[i])
end end
end end
@ -343,6 +345,10 @@ function Screen:_handle_resize(width, height)
} }
end end
function Screen:_handle_cursor_style_set(style)
self._cursor_style = style
end
function Screen:_handle_clear() function Screen:_handle_clear()
self:_clear_block(self._scroll_region.top, self._scroll_region.bot, self:_clear_block(self._scroll_region.top, self._scroll_region.bot,
self._scroll_region.left, self._scroll_region.right) self._scroll_region.left, self._scroll_region.right)