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
.Ic :terminal
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
.Sh FILES
.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*
Nvim allows Graphical user interfaces to be implemented by separate processes
communicating with Nvim over the RPC API. Currently the ui model conists of a
terminal-like grid with one single, monospace font size, with a few elements
that could be drawn separately from the grid (for the momemnt only the popup
menu)
GUIs can be implemented as external processes communicating with Nvim over the
RPC API. Currently the UI model consists of a terminal-like grid with one
single, monospace font size. Some elements (UI "widgets") can be drawn
separately from the grid.
After connecting to a nvim instance (typically a spawned, embedded instance)
use the |nvim_ui_attach|(width, height, options) API method to tell nvim that your
program wants to draw the nvim screen on a grid with "width" times
"height" cells. "options" should be a dictionary with the following (all
optional) keys:
`rgb`: Controls what color format to use.
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 on a grid of width × height cells. `options` must be
a dictionary with these (optional) keys:
`rgb` Controls what color format to use.
Set to true (default) to use 24-bit rgb
colors.
Set to false to use terminal color codes (at
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 ui and let it draw the popupmenu.
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,
r-cr:hor20-Cursor/lCursor,
sm:block-Cursor
-blinkwait175-blinkoff150-blinkon175",
for Windows console:
"n-v-c:block,o:hor50,i-ci:hor15,
r-cr:hor30,sm:block")
-blinkwait175-blinkoff150-blinkon175")
global
{only available when compiled with GUI enabled, and
for Windows console}
This option tells Vim what the cursor should look like in different
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
specifying a block cursor, or a percentage for a vertical or
horizontal cursor.
For a console the 't_SI' and 't_EI' escape sequences are used.
The option is a comma separated list of parts. Each part consist of a
Configures the cursor style for each mode. Works in the GUI and some
terminals. Empty means "non-blinking block cursor in all modes": >
:set guicursor=
<
With tmux you might need this in ~/.tmux.conf (see terminal-overrides
in the tmux(1) manual page): >
set -ga terminal-overrides ',*:Ss=\E[%p1%d q:Se=\E[2 q'
<
The option is a comma separated list of parts. Each part consists of a
mode-list and an argument-list:
mode-list:argument-list,mode-list:argument-list,..
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:
'cpoptions' flags: |cpo-_|
'guicursor' works in the terminal
'inccommand' shows interactive results for |:substitute|-like commands
'statusline' supports unlimited alignment sections
'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/helpers.h"
#include "nvim/popupmnu.h"
#include "nvim/cursor_shape.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# 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->eol_clear = remote_ui_eol_clear;
ui->cursor_goto = remote_ui_cursor_goto;
ui->cursor_style_set = remote_ui_cursor_style_set;
ui->update_menu = remote_ui_update_menu;
ui->busy_start = remote_ui_busy_start;
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);
}
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)
{
Array args = ARRAY_DICT_INIT;

View File

@ -7,40 +7,74 @@
#include "nvim/charset.h"
#include "nvim/strings.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] =
{
/* The values will be filled in from the 'guicursor' and 'mouseshape'
* defaults when Vim starts.
* Adjust the SHAPE_IDX_ defines when making changes! */
{0, 0, 0, 700L, 400L, 250L, 0, 0, "n", SHAPE_CURSOR+SHAPE_MOUSE},
{0, 0, 0, 700L, 400L, 250L, 0, 0, "v", SHAPE_CURSOR+SHAPE_MOUSE},
{0, 0, 0, 700L, 400L, 250L, 0, 0, "i", SHAPE_CURSOR+SHAPE_MOUSE},
{0, 0, 0, 700L, 400L, 250L, 0, 0, "r", SHAPE_CURSOR+SHAPE_MOUSE},
{0, 0, 0, 700L, 400L, 250L, 0, 0, "c", SHAPE_CURSOR+SHAPE_MOUSE},
{0, 0, 0, 700L, 400L, 250L, 0, 0, "ci", SHAPE_CURSOR+SHAPE_MOUSE},
{0, 0, 0, 700L, 400L, 250L, 0, 0, "cr", SHAPE_CURSOR+SHAPE_MOUSE},
{0, 0, 0, 700L, 400L, 250L, 0, 0, "o", SHAPE_CURSOR+SHAPE_MOUSE},
{0, 0, 0, 700L, 400L, 250L, 0, 0, "ve", SHAPE_CURSOR+SHAPE_MOUSE},
{0, 0, 0, 0L, 0L, 0L, 0, 0, "e", SHAPE_MOUSE},
{0, 0, 0, 0L, 0L, 0L, 0, 0, "s", SHAPE_MOUSE},
{0, 0, 0, 0L, 0L, 0L, 0, 0, "sd", SHAPE_MOUSE},
{0, 0, 0, 0L, 0L, 0L, 0, 0, "vs", SHAPE_MOUSE},
{0, 0, 0, 0L, 0L, 0L, 0, 0, "vd", SHAPE_MOUSE},
{0, 0, 0, 0L, 0L, 0L, 0, 0, "m", SHAPE_MOUSE},
{0, 0, 0, 0L, 0L, 0L, 0, 0, "ml", SHAPE_MOUSE},
{0, 0, 0, 100L, 100L, 100L, 0, 0, "sm", SHAPE_CURSOR},
// Values are set by 'guicursor' and 'mouseshape'.
// Adjust the SHAPE_IDX_ defines when changing this!
{ "normal", 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 },
{ "insert", 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 },
{ "cmdline_normal", 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 },
{ "cmdline_replace", 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 },
{ "visual_select", 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 },
{ "statusline_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "s", SHAPE_MOUSE },
{ "statusline_drag", 0, 0, 0, 0L, 0L, 0L, 0, 0, "sd", SHAPE_MOUSE },
{ "vsep_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "vs", SHAPE_MOUSE },
{ "vsep_drag", 0, 0, 0, 0L, 0L, 0L, 0, 0, "vd", SHAPE_MOUSE },
{ "more", 0, 0, 0, 0L, 0L, 0L, 0, 0, "m", SHAPE_MOUSE },
{ "more_lastline", 0, 0, 0, 0L, 0L, 0L, 0, 0, "ml", SHAPE_MOUSE },
{ "showmatch", 0, 0, 0, 100L, 100L, 100L, 0, 0, "sm", SHAPE_CURSOR },
};
/*
* Parse the 'guicursor' option ("what" is SHAPE_CURSOR) or 'mouseshape'
* ("what" is SHAPE_MOUSE).
* Returns error message for an illegal option, NULL otherwise.
*/
/// Converts cursor_shapes into a Dictionary of dictionaries
/// @return dictionary of the form {"normal" : { "cursor_shape": ... }, ...}
Dictionary cursor_shape_dict(void)
{
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 *modep;
@ -59,10 +93,11 @@ char_u *parse_shape_opt(int what)
* First round: check for errors; second round: do it for real.
*/
for (round = 1; round <= 2; ++round) {
/*
* Repeat for all comma separated parts.
*/
// Repeat for all comma separated parts.
modep = p_guicursor;
if (*p_guicursor == NUL) {
modep = (char_u *)"a:block-blinkon0";
}
while (*modep != NUL) {
colonp = vim_strchr(modep, ':');
if (colonp == NULL)
@ -71,19 +106,18 @@ char_u *parse_shape_opt(int what)
return (char_u *)N_("E546: Illegal mode");
commap = vim_strchr(modep, ',');
/*
* Repeat for all mode's before the colon.
* For the 'a' mode, we loop to handle all the modes.
*/
// Repeat for all modes before the colon.
// For the 'a' mode, we loop to handle all the modes.
all_idx = -1;
assert(modep < colonp);
while (modep < colonp || all_idx >= 0) {
if (all_idx < 0) {
/* Find the mode. */
if (modep[1] == '-' || modep[1] == ':')
// Find the mode
if (modep[1] == '-' || modep[1] == ':') {
len = 1;
else
} else {
len = 2;
}
if (len == 1 && TOLOWER_ASC(modep[0]) == 'a') {
all_idx = SHAPE_IDX_COUNT - 1;
@ -100,11 +134,11 @@ char_u *parse_shape_opt(int what)
modep += len + 1;
}
if (all_idx >= 0)
if (all_idx >= 0) {
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].blinkwait = 700L;
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;
}
}
ui_cursor_style_set();
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
#define NVIM_CURSOR_SHAPE_H
/*
* struct to store values from 'guicursor' and 'mouseshape'
*/
/* Indexes in shape_table[] */
#define SHAPE_IDX_N 0 /* Normal mode */
#define SHAPE_IDX_V 1 /* Visual mode */
#define SHAPE_IDX_I 2 /* Insert mode */
#define SHAPE_IDX_R 3 /* Replace mode */
#define SHAPE_IDX_C 4 /* Command line Normal mode */
#define SHAPE_IDX_CI 5 /* Command line Insert mode */
#define SHAPE_IDX_CR 6 /* Command line Replace mode */
#define SHAPE_IDX_O 7 /* Operator-pending mode */
#define SHAPE_IDX_VE 8 /* Visual mode with 'selection' exclusive */
#define SHAPE_IDX_CLINE 9 /* On command line */
#define SHAPE_IDX_STATUS 10 /* A status line */
#define SHAPE_IDX_SDRAG 11 /* dragging a status line */
#define SHAPE_IDX_VSEP 12 /* A vertical separator line */
#define SHAPE_IDX_VDRAG 13 /* dragging a vertical separator line */
#define SHAPE_IDX_MORE 14 /* Hit-return or More */
#define SHAPE_IDX_MOREL 15 /* Hit-return or More in last line */
#define SHAPE_IDX_SM 16 /* showing matching paren */
#define SHAPE_IDX_COUNT 17
/// struct to store values from 'guicursor' and 'mouseshape'
/// Indexes in shape_table[]
typedef enum {
SHAPE_IDX_N = 0, ///< Normal mode
SHAPE_IDX_V = 1, ///< Visual mode
SHAPE_IDX_I = 2, ///< Insert mode
SHAPE_IDX_R = 3, ///< Replace mode
SHAPE_IDX_C = 4, ///< Command line Normal mode
SHAPE_IDX_CI = 5, ///< Command line Insert mode
SHAPE_IDX_CR = 6, ///< Command line Replace mode
SHAPE_IDX_O = 7, ///< Operator-pending mode
SHAPE_IDX_VE = 8, ///< Visual mode with 'selection' exclusive
SHAPE_IDX_CLINE = 9, ///< On command line
SHAPE_IDX_STATUS = 10, ///< On status line
SHAPE_IDX_SDRAG = 11, ///< dragging a status line
SHAPE_IDX_VSEP = 12, ///< On vertical separator line
SHAPE_IDX_VDRAG = 13, ///< dragging a vertical separator line
SHAPE_IDX_MORE = 14, ///< Hit-return or More
SHAPE_IDX_MOREL = 15, ///< Hit-return or More in last line
SHAPE_IDX_SM = 16, ///< showing matching paren
SHAPE_IDX_COUNT = 17
} MouseMode;
#define SHAPE_BLOCK 0 /* block cursor */
#define SHAPE_HOR 1 /* horizontal bar cursor */
#define SHAPE_VER 2 /* vertical bar cursor */
typedef enum {
SHAPE_BLOCK = 0, ///< block 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_HIDE 1 /* hide mouse pointer */
@ -35,16 +37,17 @@
#define SHAPE_CURSOR 2 /* used for text cursor shape */
typedef struct cursor_entry {
int shape; /* one of the SHAPE_ defines */
int mshape; /* one of the MSHAPE defines */
int percentage; /* percentage of cell for bar */
long blinkwait; /* blinking, wait time before blinking starts */
long blinkon; /* blinking, on time */
long blinkoff; /* blinking, off time */
int id; /* highlight group ID */
int id_lm; /* highlight group ID for :lmap mode */
char *name; /* mode name (fixed) */
char used_for; /* SHAPE_MOUSE and/or SHAPE_CURSOR */
char *full_name; ///< mode description
CursorShape shape; ///< cursor shape: one of the SHAPE_ defines
int mshape; ///< mouse shape: one of the MSHAPE defines
int percentage; ///< percentage of cell for bar
long blinkwait; ///< blinking, wait time before blinking starts
long blinkon; ///< blinking, on time
long blinkoff; ///< blinking, off time
int id; ///< highlight group ID
int id_lm; ///< highlight group ID for :lmap mode
char *name; ///< mode short name
char used_for; ///< SHAPE_MOUSE and/or SHAPE_CURSOR
} cursorentry_T;

View File

@ -1000,7 +1000,7 @@ return {
deny_duplicates=true,
vi_def=true,
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',

View File

@ -42,34 +42,38 @@
static bool did_syntax_onoff = false;
// 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 highlight_ga array PLUS ONE.
/// 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 highlight_ga array PLUS ONE.
struct hl_group {
char_u *sg_name; // highlight group name
char_u *sg_name_u; // uppercase of sg_name
int sg_attr; // Screen attr
int sg_link; // link to this highlight group ID
int sg_set; // combination of SG_* flags
scid_T sg_scriptID; // script in which the group was last set
char_u *sg_name; ///< highlight group name
char_u *sg_name_u; ///< uppercase of sg_name
int sg_attr; ///< Screen attr @see ATTR_ENTRY
int sg_link; ///< link to this highlight group ID
int sg_set; ///< combination of flags in \ref SG_SET
scid_T sg_scriptID; ///< script in which the group was last set
// for terminal UIs
int sg_cterm; // "cterm=" highlighting attr
int sg_cterm_fg; // terminal fg 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; ///< "cterm=" highlighting attr
int sg_cterm_fg; ///< terminal fg color number + 1
int sg_cterm_bg; ///< terminal bg color number + 1
int sg_cterm_bold; ///< bold attr was set for light color
// for RGB UIs
int sg_gui; // "gui=" highlighting attributes
RgbValue sg_rgb_fg; // RGB foreground color
RgbValue sg_rgb_bg; // RGB background color
RgbValue sg_rgb_sp; // RGB special color
uint8_t *sg_rgb_fg_name; // RGB foreground color name
uint8_t *sg_rgb_bg_name; // RGB background color name
uint8_t *sg_rgb_sp_name; // RGB special color name
int sg_gui; ///< "gui=" highlighting attributes
///< (combination of \ref HL_ATTRIBUTES)
RgbValue sg_rgb_fg; ///< RGB foreground color
RgbValue sg_rgb_bg; ///< RGB background color
RgbValue sg_rgb_sp; ///< RGB special color
uint8_t *sg_rgb_fg_name; ///< RGB foreground 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_GUI 4 // gui has been set
#define SG_LINK 8 // link has been set
/// @}
// highlight groups for 'highlight' option
static garray_T highlight_ga = GA_EMPTY_INIT_VALUE;
@ -6093,16 +6097,16 @@ int load_colors(char_u *name)
return retval;
}
/*
* Handle the ":highlight .." command.
* When using ":hi clear" this is called recursively for each group with
* "forceit" and "init" both TRUE.
*/
void
do_highlight (
/// Handle the ":highlight .." command.
/// When using ":hi clear" this is called recursively for each group with
/// "forceit" and "init" both TRUE.
/// @param init TRUE when called for initializing
void
do_highlight(
char_u *line,
int forceit,
int init /* TRUE when called for initializing */
int init
)
{
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]
/*
* 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.
*/
/// 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(attrentry_T *aep)
{
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
///
/// @param[in] id Highilght group to check.
/// @param[in] id Highlight group to check.
/// @param[in] flag Attribute to check.
/// @param[in] modec 'g' for GUI, 'c' for term.
///
@ -7165,12 +7167,13 @@ int syn_namen2id(char_u *linep, int len)
return id;
}
/*
* Find highlight group name in the table and return it's ID.
* 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.
* Return 0 for failure.
*/
/// Find highlight group name in the table and return it's ID.
/// If it doesn't exist yet, a new entry is created.
///
/// @param pp Highlight group name
/// @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)
{
char_u *name = vim_strnsave(pp, len);
@ -8244,7 +8247,14 @@ color_name_table_T color_name_table[] = {
{ 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])

View File

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

View File

@ -31,6 +31,8 @@
#include "nvim/ugrid.h"
#include "nvim/tui/input.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
// flushing. No existing terminal will require 32 bytes to do that.
@ -69,13 +71,14 @@ typedef struct {
bool can_use_terminal_scroll;
bool mouse_enabled;
bool busy;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
HlAttrs print_attrs;
int showing_mode;
struct {
int enable_mouse, disable_mouse;
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_cursor_color;
int enable_focus_reporting, disable_focus_reporting;
} unibi_ext;
} TUIData;
@ -97,6 +100,7 @@ UI *tui_start(void)
ui->clear = tui_clear;
ui->eol_clear = tui_eol_clear;
ui->cursor_goto = tui_cursor_goto;
ui->cursor_style_set = tui_cursor_style_set;
ui->update_menu = tui_update_menu;
ui->busy_start = tui_busy_start;
ui->busy_stop = tui_busy_stop;
@ -129,11 +133,9 @@ static void terminfo_start(UI *ui)
data->showing_mode = 0;
data->unibi_ext.enable_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.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.disable_focus_reporting = -1;
data->out_fd = 1;
@ -146,8 +148,6 @@ static void terminfo_start(UI *ui)
data->ut = unibi_dummy();
}
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.
t_colors = unibi_get_num(data->ut, unibi_max_colors);
// 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);
}
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)
{
// 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)
{
TUIData *data = ui->data;
if (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) {
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) {
if (data->showing_mode != REPLACE) {
unibi_out(ui, data->unibi_ext.set_cursor_shape_ul);
tui_set_cursor(ui, SHAPE_IDX_R);
}
} else {
assert(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;
}
static void tui_set_scroll_region(UI *ui, int top, int bot, int left,
int right)
int right)
{
TUIData *data = ui->data;
ugrid_set_scroll_region(&data->grid, top, bot, left, right);
@ -831,8 +957,6 @@ static void fix_terminfo(TUIData *data)
goto end;
}
bool inside_tmux = os_getenv("TMUX") != NULL;
#define STARTS_WITH(str, prefix) (!memcmp(str, prefix, sizeof(prefix) - 1))
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);
}
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:
// 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,
"\x1b[?1002h\x1b[?1006h");
data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, NULL,

View File

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

View File

@ -29,6 +29,7 @@
#include "nvim/screen.h"
#include "nvim/syntax.h"
#include "nvim/window.h"
#include "nvim/cursor_shape.h"
#ifdef FEAT_TUI
# include "nvim/tui/tui.h"
#else
@ -179,6 +180,7 @@ void ui_refresh(void)
row = col = 0;
screen_resize(width, height);
pum_set_external(pum_external);
ui_cursor_style_set();
}
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;
}
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)
{
UI_CALL(update_menu);

View File

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

View File

@ -13,6 +13,7 @@
#include "nvim/memory.h"
#include "nvim/ui_bridge.h"
#include "nvim/ugrid.h"
#include "nvim/api/private/helpers.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# 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.eol_clear = ui_bridge_eol_clear;
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.busy_start = ui_bridge_busy_start;
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]));
}
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)
{
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
describe('Mouse input', function()
describe('ui/mouse/input', function()
local screen
before_each(function()

View File

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