Merge #7173 'api/ui: externalize cmdline'

closes #6162
This commit is contained in:
Justin M. Keyes 2017-10-29 02:13:12 +02:00
commit 7b0ceb3726
10 changed files with 1153 additions and 316 deletions

View File

@ -48,7 +48,7 @@ version.api_compatible API is backwards-compatible with this level
version.api_prerelease Declares the current API level as unstable >
(version.api_prerelease && fn.since == version.api_level)
functions API function signatures
ui_events UI event signatures |rpc-remote-ui|
ui_events UI event signatures |ui|
{fn}.since API level where function {fn} was introduced
{fn}.deprecated_since API level where function {fn} was deprecated
types Custom handle types defined by Nvim

View File

@ -243,203 +243,4 @@ Even for statically compiled clients it is good practice to avoid hardcoding
the type codes, because a client may be built against one Nvim version but
connect to another with different type codes.
==============================================================================
6. Remote UIs *rpc-remote-ui*
GUIs can be implemented as external processes communicating with Nvim over the
RPC API. The UI model consists of a terminal-like grid with a single,
monospace font size. Some elements (UI "widgets") can be drawn separately from
the grid ("externalized").
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).
`ext_popupmenu` Externalize the popupmenu. |ui-ext-popupmenu|
`ext_tabline` Externalize the tabline. |ui-ext-tabline|
Externalized widgets will not be drawn by
Nvim; only high-level data will be published
in new UI event kinds.
Nvim will then send msgpack-rpc notifications, with the method name "redraw"
and a single argument, an array of screen updates (described below). These
should be processed in order. Preferably the user should only be able to see
the screen state after all updates in the same "redraw" event are processed
(not any intermediate state after processing only a part of the array).
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
to be forward-compatible. |api-contract|
Screen updates are tuples whose first element is the string name of the update
kind.
["resize", width, height]
The grid is resized to `width` and `height` cells.
["clear"]
Clear the screen.
["eol_clear"]
Clear from the cursor position to the end of the current line.
["cursor_goto", row, col]
Move the cursor to position (row, col). Currently, the same cursor is
used to define the position for text insertion and the visible cursor.
However, only the last cursor position, after processing the entire
array in the "redraw" event, is intended to be a visible cursor
position.
["update_fg", color]
["update_bg", color]
["update_sp", color]
Set the default foreground, background and special colors
respectively.
["highlight_set", attrs]
Set the attributes that the next text put on the screen will have.
`attrs` is a dict with the keys below. Any absent key is reset
to its default value. Color defaults are set by the `update_fg` etc
updates. All boolean keys default to false.
`foreground`: foreground color.
`background`: backround 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.
["put", text]
The (utf-8 encoded) string `text` is put at the cursor position
(and the cursor is advanced), with the highlights as set by the
last `highlight_set` update.
["set_scroll_region", top, bot, left, right]
Define the scroll region used by `scroll` below.
["scroll", count]
Scroll the text in the scroll region. 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 count 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 count 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
+-------------------------+
<
["set_title", title]
["set_icon", icon]
Set the window title, and icon (minimized) window title, respectively.
In windowing systems not distinguishing between the two, "set_icon"
can be ignored.
["mouse_on"]
["mouse_off"]
Tells the client whether mouse support, as determined by |'mouse'|
option, is considered to be active in the current mode. This is mostly
useful for a terminal frontend, or other situations where nvim mouse
would conflict with other usages of the mouse. It is safe for a client
to ignore this and always send mouse events.
["busy_on"]
["busy_off"]
Nvim started or stopped being busy, and possibly not responsible to user
input. This could be indicated to the user by hiding the cursor.
["suspend"]
|:suspend| command or |Ctrl-Z| mapping is used. A terminal client (or other
client where it makes sense) could suspend itself. Other clients can
safely ignore it.
["bell"]
["visual_bell"]
Notify the user with an audible or visual bell, respectively.
["update_menu"]
The menu mappings changed.
["mode_info_set", cursor_style_enabled, mode_info]
`cursor_style_enabled` is a boolean indicating if the UI should set the cursor
style. `mode_info` is a list of mode property maps. The current mode is given
by the `mode_idx` field of the `mode_change` event.
Each mode property map may contain these keys:
KEY DESCRIPTION ~
`cursor_shape`: "block", "horizontal", "vertical"
`cell_percentage`: Cell % occupied by the cursor.
`blinkwait`, `blinkon`, `blinkoff`: See |cursor-blinking|.
`hl_id`: Cursor highlight group.
`hl_lm`: Cursor highlight group if 'langmap' is active.
`short_name`: Mode code name, see 'guicursor'.
`name`: Mode descriptive name.
`mouse_shape`: (To be implemented.)
Some keys are missing in some modes.
["mode_change", mode, mode_idx]
The mode changed. The first parameter `mode` is a string representing the
current mode. `mode_idx` is an index into the array received in the
`mode_info_set` event. UIs should change the cursor style according to the
properties specified in the corresponding item. The set of modes reported will
change in new versions of Nvim, for instance more submodes and temporary
states might be represented as separate modes.
*ui-ext-popupmenu*
["popupmenu_show", items, selected, row, col]
When `popupmenu_external` is set to true, nvim will not draw the
popupmenu on the grid, instead when the popupmenu is to be displayed
this update is sent. `items` is an array of the items to show, the
items are themselves arrays of the form [word, kind, menu, info]
as defined at |complete-items|, except that `word` is replaced by
`abbr` if present. `selected` is the initially selected item, either a
zero-based index into the array of items, or -1 if no item is
selected. `row` and `col` is the anchor position, where the first
character of the completed word will be.
["popupmenu_select", selected]
An item in the currently displayed popupmenu is selected. `selected`
is either a zero-based index into the array of items from the last
`popupmenu_show` event, or -1 if no item is selected.
["popupmenu_hide"]
The popupmenu is hidden.
*ui-ext-tabline*
["tabline_update", curtab, tabs]
Tabline was updated. UIs should present this data in a custom tabline
widget.
curtab: Current Tabpage
tabs: List of Dicts [{ "tab": Tabpage, "name": String }, ...]
==============================================================================
vim:tw=78:ts=8:noet:ft=help:norl:

290
runtime/doc/ui.txt Normal file
View File

@ -0,0 +1,290 @@
*ui.txt* Nvim
NVIM REFERENCE MANUAL
Nvim UI protocol *ui*
Type |gO| to see the table of contents.
==============================================================================
Introduction *ui-intro*
GUIs can be implemented as external processes communicating with Nvim over the
RPC API. The UI model consists of a terminal-like grid with a single,
monospace font size. Some elements (UI "widgets") can be drawn separately from
the grid ("externalized").
*ui-options*
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.
Set true (default) for 24-bit RGB colors.
Set false for terminal colors (max of 256).
*ui-ext-options*
`ext_popupmenu` Externalize the popupmenu. |ui-popupmenu|
`ext_tabline` Externalize the tabline. |ui-tabline|
`ext_cmdline` Externalize the cmdline. |ui-cmdline|
Nvim will then send msgpack-rpc notifications, with the method name "redraw"
and a single argument, an array of screen update events.
Update events are tuples whose first element is the event name and remaining
elements the event parameters.
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 interface.
Nvim optionally sends screen elements "semantically" as structured events
instead of raw grid-lines. Then the UI must decide how to present those
elements itself; Nvim will not draw those elements on the grid. This is
controlled by the |ui-ext-options|.
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
to be forward-compatible. |api-contract|
==============================================================================
Global Events *ui-global*
["set_title", title]
["set_icon", icon]
Set the window title, and icon (minimized) window title, respectively.
In windowing systems not distinguishing between the two, "set_icon"
can be ignored.
["mode_info_set", cursor_style_enabled, mode_info]
`cursor_style_enabled` is a boolean indicating if the UI should set
the cursor style. `mode_info` is a list of mode property maps. The
current mode is given by the `mode_idx` field of the `mode_change`
event.
Each mode property map may contain these keys:
KEY DESCRIPTION ~
`cursor_shape`: "block", "horizontal", "vertical"
`cell_percentage`: Cell % occupied by the cursor.
`blinkwait`, `blinkon`, `blinkoff`: See |cursor-blinking|.
`hl_id`: Cursor highlight group.
`hl_lm`: Cursor highlight group if 'langmap' is active.
`short_name`: Mode code name, see 'guicursor'.
`name`: Mode descriptive name.
`mouse_shape`: (To be implemented.)
Some keys are missing in some modes.
["mode_change", mode, mode_idx]
The mode changed. The first parameter `mode` is a string representing
the current mode. `mode_idx` is an index into the array received in
the `mode_info_set` event. UIs should change the cursor style
according to the properties specified in the corresponding item. The
set of modes reported will change in new versions of Nvim, for
instance more submodes and temporary states might be represented as
separate modes.
["mouse_on"]
["mouse_off"]
Tells the client whether mouse support, as determined by |'mouse'|
option, is considered to be active in the current mode. This is mostly
useful for a terminal frontend, or other situations where nvim mouse
would conflict with other usages of the mouse. It is safe for a client
to ignore this and always send mouse events.
["busy_on"]
["busy_off"]
Nvim started or stopped being busy, and possibly not responsive to
user input. This could be indicated to the user by hiding the cursor.
["suspend"]
|:suspend| command or |Ctrl-Z| mapping is used. A terminal client (or other
client where it makes sense) could suspend itself. Other clients can
safely ignore it.
["update_menu"]
The menu mappings changed.
["bell"]
["visual_bell"]
Notify the user with an audible or visual bell, respectively.
==============================================================================
Grid Events *ui-grid*
["resize", width, height]
The grid is resized to `width` and `height` cells.
["clear"]
Clear the grid.
["eol_clear"]
Clear from the cursor position to the end of the current line.
["cursor_goto", row, col]
Move the cursor to position (row, col). Currently, the same cursor is
used to define the position for text insertion and the visible cursor.
However, only the last cursor position, after processing the entire
array in the "redraw" event, is intended to be a visible cursor
position.
["update_fg", color]
["update_bg", color]
["update_sp", color]
Set the default foreground, background and special colors
respectively.
*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
to its default value. Color defaults are set by the `update_fg` etc
updates. All boolean keys default to false.
`foreground`: foreground color.
`background`: backround 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.
["put", text]
The (utf-8 encoded) string `text` is put at the cursor position
(and the cursor is advanced), with the highlights as set by the
last `highlight_set` update.
["set_scroll_region", top, bot, left, right]
Define the scroll region used by `scroll` below.
["scroll", count]
Scroll the text in the scroll region. 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 count 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 count 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
+-------------------------+
<
==============================================================================
Popupmenu Events *ui-popupmenu*
Only sent if `ext_popupmenu` option is set in |ui-options|
["popupmenu_show", items, selected, row, col]
`items` is an array of the items to show, the
items are themselves arrays of the form [word, kind, menu, info]
as defined at |complete-items|, except that `word` is replaced by
`abbr` if present. `selected` is the initially selected item, either a
zero-based index into the array of items, or -1 if no item is
selected. `row` and `col` is the anchor position, where the first
character of the completed word will be.
["popupmenu_select", selected]
An item in the currently displayed popupmenu is selected. `selected`
is either a zero-based index into the array of items from the last
`popupmenu_show` event, or -1 if no item is selected.
["popupmenu_hide"]
The popupmenu is hidden.
==============================================================================
Tabline Events *ui-tabline*
Only sent if `ext_tabline` option is set in |ui-options|
["tabline_update", curtab, tabs]
Tabline was updated. UIs should present this data in a custom tabline
widget.
curtab: Current Tabpage
tabs: List of Dicts [{ "tab": Tabpage, "name": String }, ...]
==============================================================================
Cmdline Events *ui-cmdline*
Only sent if `ext_cmdline` option is set in |ui-options|
["cmdline_show", content, pos, firstc, prompt, indent, level]
content: List of [attrs, string]
[[{}, "t"], [attrs, "est"], ...]
Triggered when the user types in the cmdline.
The `content` is the full content that should be displayed in the
cmdline, and the `pos` is the position of the cursor that in the
cmdline. The content is divided into chunks with different highlight
attributes represented as a dict (see |ui-event-highlight_set|).
`firstc` and `prompt` are text, that if non-empty should be
displayed in front of the command line. `firstc` always indicates
built-in command lines such as `:` (ex command) and `/` `?` (search),
while `prompt` is an |input()| prompt. `indent` tells how many spaces
the content should be indented.
The Nvim command line can be invoked recursively, for instance by
typing `<c-r>=` at the command line prompt. The `level` field is used
to distinguish different command lines active at the same time. The
first invoked command line has level 1, the next recursively-invoked
prompt has level 2. A command line invoked from the |cmd-line-window|
has a higher level than than the edited command line.
["cmdline_pos", pos, level]
Change the cursor position in the cmdline.
["cmdline_special_char", c, shift, level]
Display a special char in the cmdline at the cursor position. This is
typically used to indicate a pending state, e.g. after |c_CTRL-V|. If
`shift` is true the text after the cursor should be shifted, otherwise
it should overwrite the char at the cursor.
Should be hidden at next cmdline_pos.
["cmdline_hide"]
Hide the cmdline.
["cmdline_block_show", lines]
Show a block of context to the current command line. For example if
the user defines a |:function| interactively: >
:function Foo()
: echo "foo"
:
<
`lines` is a list of lines of highlighted chunks, in the same form as
the "cmdline_show" `contents` parameter.
["cmdline_block_append", line]
Append a line at the end of the currently shown block.
["cmdline_block_hide"]
Hide the block.
==============================================================================
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@ -667,6 +667,22 @@ tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err)
return rv;
}
/// Allocates a String consisting of a single char. Does not support multibyte
/// characters. The resulting string is also NUL-terminated, to facilitate
/// interoperating with code using C strings.
///
/// @param char the char to convert
/// @return the resulting String, if the input char was NUL, an
/// empty String is returned
String cchar_to_string(char c)
{
char buf[] = { c, NUL };
return (String) {
.data = xmemdupz(buf, 1),
.size = (c != NUL) ? 1 : 0
};
}
/// Copies a C string into a String (binary safe string, characters + length).
/// The resulting string is also NUL-terminated, to facilitate interoperating
/// with code using C strings.
@ -687,6 +703,23 @@ String cstr_to_string(const char *str)
};
}
/// Copies buffer to an allocated String.
/// The resulting string is also NUL-terminated, to facilitate interoperating
/// with code using C strings.
///
/// @param buf the buffer to copy
/// @param size length of the buffer
/// @return the resulting String, if the input string was NULL, an
/// empty String is returned
String cbuf_to_string(const char *buf, size_t size)
FUNC_ATTR_NONNULL_ALL
{
return (String) {
.data = xmemdupz(buf, size),
.size = size
};
}
/// Creates a String using the given C string. Unlike
/// cstr_to_string this function DOES NOT copy the C string.
///

View File

@ -68,4 +68,20 @@ void popupmenu_select(Integer selected)
void tabline_update(Tabpage current, Array tabs)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void cmdline_show(Array content, Integer pos, String firstc, String prompt,
Integer indent, Integer level)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void cmdline_pos(Integer pos, Integer level)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void cmdline_special_char(String c, Boolean shift, Integer level)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void cmdline_hide(Integer level)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void cmdline_block_show(Array lines)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void cmdline_block_append(Array lines)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void cmdline_block_hide(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
#endif // NVIM_API_UI_EVENTS_IN_H

View File

@ -11147,17 +11147,18 @@ void get_user_input(const typval_T *const argvars,
cmd_silent = false; // Want to see the prompt.
// Only the part of the message after the last NL is considered as
// prompt for the command line.
const char *p = strrchr(prompt, '\n');
if (p == NULL) {
p = prompt;
} else {
p++;
msg_start();
msg_clr_eos();
msg_puts_attr_len(prompt, p - prompt, echo_attr);
msg_didout = false;
msg_starthere();
// prompt for the command line, unlsess cmdline is externalized
const char *p = prompt;
if (!ui_is_external(kUICmdline)) {
const char *lastnl = strrchr(prompt, '\n');
if (lastnl != NULL) {
p = lastnl+1;
msg_start();
msg_clr_eos();
msg_puts_attr_len(prompt, p - prompt, echo_attr);
msg_didout = false;
msg_starthere();
}
}
cmdline_row = msg_row;
@ -19643,6 +19644,7 @@ void ex_function(exarg_T *eap)
int todo;
hashitem_T *hi;
int sourcing_lnum_off;
bool show_block = false;
/*
* ":function" without argument: list functions.
@ -19816,6 +19818,11 @@ void ex_function(exarg_T *eap)
goto errret_2;
}
if (KeyTyped && ui_is_external(kUICmdline)) {
show_block = true;
ui_ext_cmdline_block_append(0, (const char *)eap->cmd);
}
// find extra arguments "range", "dict", "abort" and "closure"
for (;; ) {
p = skipwhite(p);
@ -19868,7 +19875,9 @@ void ex_function(exarg_T *eap)
if (!eap->skip && did_emsg)
goto erret;
msg_putchar('\n'); /* don't overwrite the function name */
if (!ui_is_external(kUICmdline)) {
msg_putchar('\n'); // don't overwrite the function name
}
cmdline_row = msg_row;
}
@ -19902,6 +19911,9 @@ void ex_function(exarg_T *eap)
EMSG(_("E126: Missing :endfunction"));
goto erret;
}
if (show_block) {
ui_ext_cmdline_block_append(indent, (const char *)theline);
}
/* Detect line continuation: sourcing_lnum increased more than one. */
if (sourcing_lnum > sourcing_lnum_off + 1)
@ -20194,6 +20206,9 @@ ret_free:
xfree(name);
did_emsg |= saved_did_emsg;
need_wait_return |= saved_wait_return;
if (show_block) {
ui_ext_cmdline_block_leave();
}
}
/// Get a function name, translating "<SID>" and "<SNR>".

View File

@ -67,6 +67,30 @@
#include "nvim/api/private/helpers.h"
#include "nvim/highlight_defs.h"
/// Command-line colors: one chunk
///
/// Defines a region which has the same highlighting.
typedef struct {
int start; ///< Colored chunk start.
int end; ///< Colored chunk end (exclusive, > start).
int attr; ///< Highlight attr.
} CmdlineColorChunk;
/// Command-line colors
///
/// Holds data about all colors.
typedef kvec_t(CmdlineColorChunk) CmdlineColors;
/// Command-line coloring
///
/// Holds both what are the colors and what have been colored. Latter is used to
/// suppress unnecessary calls to coloring callbacks.
typedef struct {
unsigned prompt_id; ///< ID of the prompt which was colored last.
char *cmdbuff; ///< What exactly was colored last time or NULL.
CmdlineColors colors; ///< Last colors.
} ColoredCmdline;
/*
* Variables shared between getcmdline(), redrawcmdline() and others.
* These need to be saved when using CTRL-R |, that's why they are in a
@ -91,6 +115,11 @@ struct cmdline_info {
int input_fn; // when TRUE Invoked for input() function
unsigned prompt_id; ///< Prompt number, used to disable coloring on errors.
Callback highlight_callback; ///< Callback used for coloring user input.
ColoredCmdline last_colors; ///< Last cmdline colors
int level; // current cmdline level
struct cmdline_info *prev_ccline; ///< pointer to saved cmdline state
char special_char; ///< last putcmdline char (used for redraws)
bool special_shift; ///< shift of last putcmdline char
};
/// Last value of prompt_id, incremented when doing new prompt
static unsigned last_prompt_id = 0;
@ -143,36 +172,6 @@ typedef struct command_line_state {
struct cmdline_info save_ccline;
} CommandLineState;
/// Command-line colors: one chunk
///
/// Defines a region which has the same highlighting.
typedef struct {
int start; ///< Colored chunk start.
int end; ///< Colored chunk end (exclusive, > start).
int attr; ///< Highlight attr.
} CmdlineColorChunk;
/// Command-line colors
///
/// Holds data about all colors.
typedef kvec_t(CmdlineColorChunk) CmdlineColors;
/// Command-line coloring
///
/// Holds both what are the colors and what have been colored. Latter is used to
/// suppress unnecessary calls to coloring callbacks.
typedef struct {
unsigned prompt_id; ///< ID of the prompt which was colored last.
char *cmdbuff; ///< What exactly was colored last time or NULL.
CmdlineColors colors; ///< Last colors.
} ColoredCmdline;
/// Last command-line colors.
ColoredCmdline last_ccline_colors = {
.cmdbuff = NULL,
.colors = KV_INITIAL_VALUE
};
typedef struct cmdline_info CmdlineInfo;
/* The current cmdline_info. It is initialized in getcmdline() and after that
@ -185,6 +184,8 @@ static int cmd_showtail; /* Only show path tail in lists ? */
static int new_cmdpos; /* position set by set_cmdline_pos() */
static Array cmdline_block; ///< currently displayed block of context
/*
* Type used by call_user_expand_func
*/
@ -239,6 +240,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
}
ccline.prompt_id = last_prompt_id++;
ccline.level++;
ccline.overstrike = false; // always start in insert mode
clearpos(&s->match_end);
s->save_cursor = curwin->w_cursor; // may be restored later
@ -258,6 +260,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
ccline.cmdlen = ccline.cmdpos = 0;
ccline.cmdbuff[0] = NUL;
ccline.last_colors = (ColoredCmdline){ .cmdbuff = NULL,
.colors = KV_INITIAL_VALUE };
// autoindent for :insert and :append
if (s->firstc <= 0) {
memset(ccline.cmdbuff, ' ', s->indent);
@ -408,12 +413,19 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
setmouse();
ui_cursor_shape(); // may show different cursor shape
xfree(s->save_p_icm);
xfree(ccline.last_colors.cmdbuff);
kv_destroy(ccline.last_colors.colors);
{
char_u *p = ccline.cmdbuff;
// Make ccline empty, getcmdline() may try to use it.
ccline.cmdbuff = NULL;
if (ui_is_external(kUICmdline)) {
ui_call_cmdline_hide(ccline.level);
}
ccline.level--;
return p;
}
}
@ -804,7 +816,9 @@ static int command_line_execute(VimState *state, int key)
}
if (!cmd_silent) {
ui_cursor_goto(msg_row, 0);
if (!ui_is_external(kUICmdline)) {
ui_cursor_goto(msg_row, 0);
}
ui_flush();
}
return 0;
@ -1134,7 +1148,7 @@ static int command_line_handle_key(CommandLineState *s)
xfree(ccline.cmdbuff); // no commandline to return
ccline.cmdbuff = NULL;
if (!cmd_silent) {
if (!cmd_silent && !ui_is_external(kUICmdline)) {
if (cmdmsg_rl) {
msg_col = Columns;
} else {
@ -1586,9 +1600,14 @@ static int command_line_handle_key(CommandLineState *s)
s->do_abbr = false; // don't do abbreviation now
// may need to remove ^ when composing char was typed
if (enc_utf8 && utf_iscomposing(s->c) && !cmd_silent) {
draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
msg_putchar(' ');
cursorcmd();
if (ui_is_external(kUICmdline)) {
// TODO(bfredl): why not make unputcmdline also work with true?
unputcmdline();
} else {
draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
msg_putchar(' ');
cursorcmd();
}
}
break;
@ -2346,8 +2365,7 @@ enum { MAX_CB_ERRORS = 1 };
/// Should use built-in command parser or user-specified one. Currently only the
/// latter is supported.
///
/// @param[in] colored_ccline Command-line to color.
/// @param[out] ret_ccline_colors What should be colored. Also holds a cache:
/// @param[in,out] colored_ccline Command-line to color. Also holds a cache:
/// if ->prompt_id and ->cmdbuff values happen
/// to be equal to those from colored_cmdline it
/// will just do nothing, assuming that ->colors
@ -2357,11 +2375,11 @@ enum { MAX_CB_ERRORS = 1 };
///
/// @return true if draw_cmdline may proceed, false if it does not need anything
/// to do.
static bool color_cmdline(const CmdlineInfo *const colored_ccline,
ColoredCmdline *const ret_ccline_colors)
static bool color_cmdline(CmdlineInfo *colored_ccline)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
bool printed_errmsg = false;
#define PRINT_ERRMSG(...) \
do { \
msg_putchar('\n'); \
@ -2370,19 +2388,21 @@ static bool color_cmdline(const CmdlineInfo *const colored_ccline,
} while (0)
bool ret = true;
ColoredCmdline *ccline_colors = &colored_ccline->last_colors;
// Check whether result of the previous call is still valid.
if (ret_ccline_colors->prompt_id == colored_ccline->prompt_id
&& ret_ccline_colors->cmdbuff != NULL
&& STRCMP(ret_ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) {
if (ccline_colors->prompt_id == colored_ccline->prompt_id
&& ccline_colors->cmdbuff != NULL
&& STRCMP(ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) {
return ret;
}
kv_size(ret_ccline_colors->colors) = 0;
kv_size(ccline_colors->colors) = 0;
if (colored_ccline->cmdbuff == NULL || *colored_ccline->cmdbuff == NUL) {
// Nothing to do, exiting.
xfree(ret_ccline_colors->cmdbuff);
ret_ccline_colors->cmdbuff = NULL;
xfree(ccline_colors->cmdbuff);
ccline_colors->cmdbuff = NULL;
return ret;
}
@ -2395,7 +2415,7 @@ static bool color_cmdline(const CmdlineInfo *const colored_ccline,
static unsigned prev_prompt_id = UINT_MAX;
static int prev_prompt_errors = 0;
Callback color_cb = { .type = kCallbackNone };
Callback color_cb = CALLBACK_NONE;
bool can_free_cb = false;
TryState tstate;
Error err = ERROR_INIT;
@ -2504,7 +2524,7 @@ static bool color_cmdline(const CmdlineInfo *const colored_ccline,
goto color_cmdline_error;
}
if (start != prev_end) {
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
.start = prev_end,
.end = start,
.attr = 0,
@ -2533,14 +2553,14 @@ static bool color_cmdline(const CmdlineInfo *const colored_ccline,
}
const int id = syn_name2id((char_u *)group);
const int attr = (id == 0 ? 0 : syn_id2attr(id));
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
.start = start,
.end = end,
.attr = attr,
}));
}
if (prev_end < colored_ccline->cmdlen) {
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
.start = prev_end,
.end = colored_ccline->cmdlen,
.attr = 0,
@ -2552,14 +2572,14 @@ color_cmdline_end:
if (can_free_cb) {
callback_free(&color_cb);
}
xfree(ret_ccline_colors->cmdbuff);
xfree(ccline_colors->cmdbuff);
// Note: errors “output” is cached just as well as regular results.
ret_ccline_colors->prompt_id = colored_ccline->prompt_id;
ccline_colors->prompt_id = colored_ccline->prompt_id;
if (arg_allocated) {
ret_ccline_colors->cmdbuff = (char *)arg.vval.v_string;
ccline_colors->cmdbuff = (char *)arg.vval.v_string;
} else {
ret_ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff,
(size_t)colored_ccline->cmdlen);
ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff,
(size_t)colored_ccline->cmdlen);
}
tv_clear(&tv);
return ret;
@ -2572,7 +2592,7 @@ color_cmdline_error:
(void)printed_errmsg;
prev_prompt_errors++;
kv_size(ret_ccline_colors->colors) = 0;
kv_size(ccline_colors->colors) = 0;
redrawcmdline();
ret = false;
goto color_cmdline_end;
@ -2585,7 +2605,13 @@ color_cmdline_error:
*/
static void draw_cmdline(int start, int len)
{
if (!color_cmdline(&ccline, &last_ccline_colors)) {
if (!color_cmdline(&ccline)) {
return;
}
if (ui_is_external(kUICmdline)) {
ccline.special_char = NUL;
ui_ext_cmdline_show(&ccline);
return;
}
@ -2687,9 +2713,9 @@ static void draw_cmdline(int start, int len)
msg_outtrans_len(arshape_buf, newlen);
} else {
draw_cmdline_no_arabicshape:
if (kv_size(last_ccline_colors.colors)) {
for (size_t i = 0; i < kv_size(last_ccline_colors.colors); i++) {
CmdlineColorChunk chunk = kv_A(last_ccline_colors.colors, i);
if (kv_size(ccline.last_colors.colors)) {
for (size_t i = 0; i < kv_size(ccline.last_colors.colors); i++) {
CmdlineColorChunk chunk = kv_A(ccline.last_colors.colors, i);
if (chunk.end <= start) {
continue;
}
@ -2704,6 +2730,107 @@ draw_cmdline_no_arabicshape:
}
}
static void ui_ext_cmdline_show(CmdlineInfo *line)
{
Array content = ARRAY_DICT_INIT;
if (cmdline_star) {
size_t len = 0;
for (char_u *p = ccline.cmdbuff; *p; mb_ptr_adv(p)) {
len++;
}
char *buf = xmallocz(len);
memset(buf, '*', len);
Array item = ARRAY_DICT_INIT;
ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
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;
if (chunk.attr) {
attrentry_T *aep = syn_cterm_attr2entry(chunk.attr);
// TODO(bfredl): this desicion could be delayed by making attr_code a
// recognized type
HlAttrs rgb_attrs = attrentry2hlattrs(aep, true);
ADD(item, DICTIONARY_OBJ(hlattrs2dict(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, STRING_OBJ(cstr_to_string((char *)(line->cmdbuff))));
ADD(content, ARRAY_OBJ(item));
}
ui_call_cmdline_show(content, line->cmdpos,
cchar_to_string((char)line->cmdfirstc),
cstr_to_string((char *)(line->cmdprompt)),
line->cmdindent,
line->level);
if (line->special_char) {
ui_call_cmdline_special_char(cchar_to_string((char)(line->special_char)),
line->special_shift,
line->level);
}
}
void ui_ext_cmdline_block_append(int indent, const char *line)
{
char *buf = xmallocz(indent + strlen(line));
memset(buf, ' ', indent);
memcpy(buf+indent, line, strlen(line));
Array item = ARRAY_DICT_INIT;
ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
ADD(item, STRING_OBJ(cstr_as_string(buf)));
Array content = ARRAY_DICT_INIT;
ADD(content, ARRAY_OBJ(item));
ADD(cmdline_block, ARRAY_OBJ(content));
if (cmdline_block.size > 1) {
ui_call_cmdline_block_append(copy_array(content));
} else {
ui_call_cmdline_block_show(copy_array(cmdline_block));
}
}
void ui_ext_cmdline_block_leave(void)
{
api_free_array(cmdline_block);
ui_call_cmdline_block_hide();
}
/// Extra redrawing needed for redraw! and on ui_attach
/// assumes "redrawcmdline()" will already be invoked
void cmdline_screen_cleared(void)
{
if (!ui_is_external(kUICmdline)) {
return;
}
if (cmdline_block.size) {
ui_call_cmdline_block_show(copy_array(cmdline_block));
}
int prev_level = ccline.level-1;
CmdlineInfo *prev_ccline = ccline.prev_ccline;
while (prev_level > 0 && prev_ccline) {
if (prev_ccline->level == prev_level) {
// don't redraw a cmdline already shown in the cmdline window
if (prev_level != cmdwin_level) {
ui_ext_cmdline_show(prev_ccline);
}
prev_level--;
}
prev_ccline = prev_ccline->prev_ccline;
}
}
/*
* Put a character on the command line. Shifts the following text to the
* right when "shift" is TRUE. Used for CTRL-V, CTRL-K, etc.
@ -2711,33 +2838,39 @@ draw_cmdline_no_arabicshape:
*/
void putcmdline(int c, int shift)
{
if (cmd_silent)
if (cmd_silent) {
return;
msg_no_more = TRUE;
msg_putchar(c);
if (shift)
draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
msg_no_more = FALSE;
}
if (!ui_is_external(kUICmdline)) {
msg_no_more = true;
msg_putchar(c);
if (shift) {
draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
}
msg_no_more = false;
} else {
ccline.special_char = c;
ccline.special_shift = shift;
ui_call_cmdline_special_char(cchar_to_string((char)(c)), shift,
ccline.level);
}
cursorcmd();
ui_cursor_shape();
}
/*
* Undo a putcmdline(c, FALSE).
*/
/// Undo a putcmdline(c, FALSE).
void unputcmdline(void)
{
if (cmd_silent)
if (cmd_silent) {
return;
msg_no_more = TRUE;
if (ccline.cmdlen == ccline.cmdpos)
}
msg_no_more = true;
if (ccline.cmdlen == ccline.cmdpos && !ui_is_external(kUICmdline)) {
msg_putchar(' ');
else if (has_mbyte)
draw_cmdline(ccline.cmdpos,
(*mb_ptr2len)(ccline.cmdbuff + ccline.cmdpos));
else
draw_cmdline(ccline.cmdpos, 1);
msg_no_more = FALSE;
} else {
draw_cmdline(ccline.cmdpos, mb_ptr2len(ccline.cmdbuff + ccline.cmdpos));
}
msg_no_more = false;
cursorcmd();
ui_cursor_shape();
}
@ -2871,9 +3004,6 @@ void put_on_cmdline(char_u *str, int len, int redraw)
msg_check();
}
static struct cmdline_info prev_ccline;
static int prev_ccline_used = FALSE;
/*
* Save ccline, because obtaining the "=" register may execute "normal :cmd"
* and overwrite it. But get_cmdline_str() may need it, thus make it
@ -2881,15 +3011,12 @@ static int prev_ccline_used = FALSE;
*/
static void save_cmdline(struct cmdline_info *ccp)
{
if (!prev_ccline_used) {
memset(&prev_ccline, 0, sizeof(struct cmdline_info));
prev_ccline_used = TRUE;
}
*ccp = prev_ccline;
prev_ccline = ccline;
*ccp = ccline;
ccline.prev_ccline = ccp;
ccline.cmdbuff = NULL;
ccline.cmdprompt = NULL;
ccline.xpc = NULL;
ccline.special_char = NUL;
}
/*
@ -2897,8 +3024,7 @@ static void save_cmdline(struct cmdline_info *ccp)
*/
static void restore_cmdline(struct cmdline_info *ccp)
{
ccline = prev_ccline;
prev_ccline = *ccp;
ccline = *ccp;
}
/*
@ -3066,17 +3192,25 @@ static void redrawcmdprompt(void)
if (cmd_silent)
return;
if (ccline.cmdfirstc != NUL)
if (ui_is_external(kUICmdline)) {
ui_ext_cmdline_show(&ccline);
return;
}
if (ccline.cmdfirstc != NUL) {
msg_putchar(ccline.cmdfirstc);
}
if (ccline.cmdprompt != NULL) {
msg_puts_attr((const char *)ccline.cmdprompt, ccline.cmdattr);
ccline.cmdindent = msg_col + (msg_row - cmdline_row) * Columns;
/* do the reverse of set_cmdspos() */
if (ccline.cmdfirstc != NUL)
--ccline.cmdindent;
} else
for (i = ccline.cmdindent; i > 0; --i)
// do the reverse of set_cmdspos()
if (ccline.cmdfirstc != NUL) {
ccline.cmdindent--;
}
} else {
for (i = ccline.cmdindent; i > 0; i--) {
msg_putchar(' ');
}
}
}
/*
@ -3087,6 +3221,11 @@ void redrawcmd(void)
if (cmd_silent)
return;
if (ui_is_external(kUICmdline)) {
draw_cmdline(0, ccline.cmdlen);
return;
}
/* when 'incsearch' is set there may be no command line while redrawing */
if (ccline.cmdbuff == NULL) {
ui_cursor_goto(cmdline_row, 0);
@ -3130,6 +3269,11 @@ static void cursorcmd(void)
if (cmd_silent)
return;
if (ui_is_external(kUICmdline)) {
ui_call_cmdline_pos(ccline.cmdpos, ccline.level);
return;
}
if (cmdmsg_rl) {
msg_row = cmdline_row + (ccline.cmdspos / (int)(Columns - 1));
msg_col = (int)Columns - (ccline.cmdspos % (int)(Columns - 1)) - 1;
@ -3147,6 +3291,9 @@ static void cursorcmd(void)
void gotocmdline(int clr)
{
if (ui_is_external(kUICmdline)) {
return;
}
msg_start();
if (cmdmsg_rl)
msg_col = Columns - 1;
@ -5204,13 +5351,15 @@ int get_history_idx(int histype)
*/
static struct cmdline_info *get_ccline_ptr(void)
{
if ((State & CMDLINE) == 0)
if ((State & CMDLINE) == 0) {
return NULL;
if (ccline.cmdbuff != NULL)
} else if (ccline.cmdbuff != NULL) {
return &ccline;
if (prev_ccline_used && prev_ccline.cmdbuff != NULL)
return &prev_ccline;
return NULL;
} else if (ccline.prev_ccline && ccline.prev_ccline->cmdbuff != NULL) {
return ccline.prev_ccline;
} else {
return NULL;
}
}
/*
@ -5646,6 +5795,7 @@ static int ex_window(void)
return K_IGNORE;
}
cmdwin_type = get_cmdline_type();
cmdwin_level = ccline.level;
// Create empty command-line buffer.
buf_open_scratch(0, "[Command Line]");
@ -5698,6 +5848,9 @@ static int ex_window(void)
curwin->w_cursor.col = ccline.cmdpos;
changed_line_abv_curs();
invalidate_botline();
if (ui_is_external(kUICmdline)) {
ui_call_cmdline_hide(ccline.level);
}
redraw_later(SOME_VALID);
// Save the command line info, can be used recursively.
@ -5740,6 +5893,7 @@ static int ex_window(void)
// Restore the command line info.
restore_cmdline(&save_ccline);
cmdwin_type = 0;
cmdwin_level = 0;
exmode_active = save_exmode;
@ -5974,3 +6128,4 @@ static void set_search_match(pos_T *t)
coladvance((colnr_T)MAXCOL);
}
}

View File

@ -981,9 +981,10 @@ EXTERN int fill_diff INIT(= '-');
EXTERN int km_stopsel INIT(= FALSE);
EXTERN int km_startsel INIT(= FALSE);
EXTERN int cedit_key INIT(= -1); /* key value of 'cedit' option */
EXTERN int cmdwin_type INIT(= 0); /* type of cmdline window or 0 */
EXTERN int cmdwin_result INIT(= 0); /* result of cmdline window or 0 */
EXTERN int cedit_key INIT(= -1); ///< key value of 'cedit' option
EXTERN int cmdwin_type INIT(= 0); ///< type of cmdline window or 0
EXTERN int cmdwin_result INIT(= 0); ///< result of cmdline window or 0
EXTERN int cmdwin_level INIT(= 0); ///< cmdline recursion level
EXTERN char_u no_lines_msg[] INIT(= N_("--No lines in buffer--"));

View File

@ -345,8 +345,9 @@ void update_screen(int type)
if (need_highlight_changed)
highlight_changed();
if (type == CLEAR) { /* first clear screen */
screenclear(); /* will reset clear_cmdline */
if (type == CLEAR) { // first clear screen
screenclear(); // will reset clear_cmdline
cmdline_screen_cleared(); // clear external cmdline state
type = NOT_VALID;
}

View File

@ -0,0 +1,525 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed, eq = helpers.clear, helpers.feed, helpers.eq
local source = helpers.source
local ok = helpers.ok
local command = helpers.command
describe('external cmdline', function()
local screen
local last_level = 0
local cmdline = {}
local block = nil
before_each(function()
clear()
cmdline, block = {}, nil
screen = Screen.new(25, 5)
screen:attach({rgb=true, ext_cmdline=true})
screen:set_on_event_handler(function(name, data)
if name == "cmdline_show" then
local content, pos, firstc, prompt, indent, level = unpack(data)
ok(level > 0)
cmdline[level] = {content=content, pos=pos, firstc=firstc,
prompt=prompt, indent=indent}
last_level = level
elseif name == "cmdline_hide" then
local level = data[1]
cmdline[level] = nil
elseif name == "cmdline_special_char" then
local char, shift, level = unpack(data)
cmdline[level].special = {char, shift}
elseif name == "cmdline_pos" then
local pos, level = unpack(data)
cmdline[level].pos = pos
elseif name == "cmdline_block_show" then
block = data[1]
elseif name == "cmdline_block_append" then
block[#block+1] = data[1]
elseif name == "cmdline_block_hide" then
block = nil
end
end)
end)
after_each(function()
screen:detach()
end)
local function expect_cmdline(level, expected)
local attr_ids = screen._default_attr_ids
local attr_ignore = screen._default_attr_ignore
local actual = ''
for _, chunk in ipairs(cmdline[level] and cmdline[level].content or {}) do
local attrs, text = chunk[1], chunk[2]
if screen:_equal_attrs(attrs, {}) then
actual = actual..text
else
local attr_id = screen:_get_attr_id(attr_ids, attr_ignore, attrs)
actual = actual..'{' .. attr_id .. ':' .. text .. '}'
end
end
eq(expected, actual)
end
it('works', function()
feed(':')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq(1, last_level)
eq({{
content = { { {}, "" } },
firstc = ":",
indent = 0,
pos = 0,
prompt = ""
}}, cmdline)
end)
feed('sign')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{
content = { { {}, "sign" } },
firstc = ":",
indent = 0,
pos = 4,
prompt = ""
}}, cmdline)
end)
feed('<Left>')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{
content = { { {}, "sign" } },
firstc = ":",
indent = 0,
pos = 3,
prompt = ""
}}, cmdline)
end)
feed('<bs>')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{
content = { { {}, "sin" } },
firstc = ":",
indent = 0,
pos = 2,
prompt = ""
}}, cmdline)
end)
feed('<Esc>')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({}, cmdline)
end)
end)
it("works with input()", function()
feed(':call input("input", "default")<cr>')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{
content = { { {}, "default" } },
firstc = "",
indent = 0,
pos = 7,
prompt = "input"
}}, cmdline)
end)
feed('<cr>')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({}, cmdline)
end)
end)
it("works with special chars and nested cmdline", function()
feed(':xx<c-r>')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{
content = { { {}, "xx" } },
firstc = ":",
indent = 0,
pos = 2,
prompt = "",
special = {'"', true},
}}, cmdline)
end)
feed('=')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{
content = { { {}, "xx" } },
firstc = ":",
indent = 0,
pos = 2,
prompt = "",
special = {'"', true},
},{
content = { { {}, "" } },
firstc = "=",
indent = 0,
pos = 0,
prompt = "",
}}, cmdline)
end)
feed('1+2')
local expectation = {{
content = { { {}, "xx" } },
firstc = ":",
indent = 0,
pos = 2,
prompt = "",
special = {'"', true},
},{
content = { { {}, "1+2" } },
firstc = "=",
indent = 0,
pos = 3,
prompt = "",
}}
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq(expectation, cmdline)
end)
-- erase information, so we check if it is retransmitted
cmdline = {}
command("redraw!")
-- redraw! forgets cursor position. Be OK with that, as UI should indicate
-- focus is at external cmdline anyway.
screen:expect([[
|
~ |
~ |
~ |
^ |
]], nil, nil, function()
eq(expectation, cmdline)
end)
feed('<cr>')
screen:expect([[
|
~ |
~ |
~ |
^ |
]], nil, nil, function()
eq({{
content = { { {}, "xx3" } },
firstc = ":",
indent = 0,
pos = 3,
prompt = "",
}}, cmdline)
end)
feed('<esc>')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({}, cmdline)
end)
end)
it("works with function definitions", function()
feed(':function Foo()<cr>')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{
content = { { {}, "" } },
firstc = ":",
indent = 2,
pos = 0,
prompt = "",
}}, cmdline)
eq({{{{}, 'function Foo()'}}}, block)
end)
feed('line1<cr>')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{{{}, 'function Foo()'}},
{{{}, ' line1'}}}, block)
end)
block = {}
command("redraw!")
screen:expect([[
|
~ |
~ |
~ |
^ |
]], nil, nil, function()
eq({{{{}, 'function Foo()'}},
{{{}, ' line1'}}}, block)
end)
feed('endfunction<cr>')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq(nil, block)
end)
end)
it("works with cmdline window", function()
feed(':make')
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{
content = { { {}, "make" } },
firstc = ":",
indent = 0,
pos = 4,
prompt = ""
}}, cmdline)
end)
feed('<c-f>')
screen:expect([[
|
[No Name] |
:make^ |
[Command Line] |
|
]], nil, nil, function()
eq({}, cmdline)
end)
-- nested cmdline
feed(':yank')
screen:expect([[
|
[No Name] |
:make^ |
[Command Line] |
|
]], nil, nil, function()
eq({nil, {
content = { { {}, "yank" } },
firstc = ":",
indent = 0,
pos = 4,
prompt = ""
}}, cmdline)
end)
cmdline = {}
command("redraw!")
screen:expect([[
|
[No Name] |
:make |
[Command Line] |
^ |
]], nil, nil, function()
eq({nil, {
content = { { {}, "yank" } },
firstc = ":",
indent = 0,
pos = 4,
prompt = ""
}}, cmdline)
end)
feed("<c-c>")
screen:expect([[
|
[No Name] |
:make^ |
[Command Line] |
|
]], nil, nil, function()
eq({}, cmdline)
end)
feed("<c-c>")
screen:expect([[
|
[No Name] |
:make^ |
[Command Line] |
|
]], nil, nil, function()
eq({{
content = { { {}, "make" } },
firstc = ":",
indent = 0,
pos = 4,
prompt = ""
}}, cmdline)
end)
cmdline = {}
command("redraw!")
screen:expect([[
|
~ |
~ |
~ |
^ |
]], nil, nil, function()
eq({{
content = { { {}, "make" } },
firstc = ":",
indent = 0,
pos = 4,
prompt = ""
}}, cmdline)
end)
end)
it('works with inputsecret()', function()
feed(":call inputsecret('secret:')<cr>abc123")
screen:expect([[
^ |
~ |
~ |
~ |
|
]], nil, nil, function()
eq({{
content = { { {}, "******" } },
firstc = "",
indent = 0,
pos = 6,
prompt = "secret:"
}}, cmdline)
end)
end)
it('works with highlighted cmdline', function()
source([[
highlight RBP1 guibg=Red
highlight RBP2 guibg=Yellow
highlight RBP3 guibg=Green
highlight RBP4 guibg=Blue
let g:NUM_LVLS = 4
function RainBowParens(cmdline)
let ret = []
let i = 0
let lvl = 0
while i < len(a:cmdline)
if a:cmdline[i] is# '('
call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)])
let lvl += 1
elseif a:cmdline[i] is# ')'
let lvl -= 1
call add(ret, [i, i + 1, 'RBP' . ((lvl % g:NUM_LVLS) + 1)])
endif
let i += 1
endwhile
return ret
endfunction
map <f5> :let x = input({'prompt':'>','highlight':'RainBowParens'})<cr>
"map <f5> :let x = input({'prompt':'>'})<cr>
]])
screen:set_default_attr_ids({
RBP1={background = Screen.colors.Red},
RBP2={background = Screen.colors.Yellow},
RBP3={background = Screen.colors.Green},
RBP4={background = Screen.colors.Blue},
EOB={bold = true, foreground = Screen.colors.Blue1},
ERR={foreground = Screen.colors.Grey100, background = Screen.colors.Red},
SK={foreground = Screen.colors.Blue},
PE={bold = true, foreground = Screen.colors.SeaGreen4}
})
feed('<f5>(a(b)a)')
screen:expect([[
^ |
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
|
]], nil, nil, function()
expect_cmdline(1, '{RBP1:(}a{RBP2:(}b{RBP2:)}a{RBP1:)}')
end)
end)
end)