diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | runtime/autoload/remote/define.vim | 32 | ||||
-rw-r--r-- | runtime/doc/api.txt | 2 | ||||
-rw-r--r-- | runtime/doc/msgpack_rpc.txt | 199 | ||||
-rw-r--r-- | runtime/doc/ui.txt | 290 | ||||
-rwxr-xr-x | src/clint.py | 1 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 33 | ||||
-rw-r--r-- | src/nvim/api/ui_events.in.h | 16 | ||||
-rw-r--r-- | src/nvim/buffer.c | 2 | ||||
-rw-r--r-- | src/nvim/eval.c | 44 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 353 | ||||
-rw-r--r-- | src/nvim/globals.h | 7 | ||||
-rw-r--r-- | src/nvim/msgpack_rpc/channel.c | 36 | ||||
-rw-r--r-- | src/nvim/screen.c | 23 | ||||
-rw-r--r-- | src/nvim/testdir/Makefile | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_retab.vim | 77 | ||||
-rw-r--r-- | src/nvim/version.c | 2 | ||||
-rw-r--r-- | src/nvim/window.c | 8 | ||||
-rw-r--r-- | test/functional/api/server_requests_spec.lua | 61 | ||||
-rw-r--r-- | test/functional/ex_cmds/ctrl_c_spec.lua | 2 | ||||
-rw-r--r-- | test/functional/terminal/window_spec.lua | 55 | ||||
-rw-r--r-- | test/functional/ui/cmdline_spec.lua | 525 |
22 files changed, 1380 insertions, 391 deletions
@@ -13,7 +13,7 @@ [](https://neovim.io/doc/reports/clang) [](https://neovim.io/doc/reports/pvs) -<a href="https://buildd.debian.org/neovim"><img src="https://www.debian.org/logos/openlogo-nd-25.png" width="13" height="15">Debian</a> +[](https://buildd.debian.org/neovim) [](https://github.com/neovim/neovim/releases/) Neovim is a project that seeks to aggressively refactor Vim in order to: diff --git a/runtime/autoload/remote/define.vim b/runtime/autoload/remote/define.vim index b5c976c823..2688a62a82 100644 --- a/runtime/autoload/remote/define.vim +++ b/runtime/autoload/remote/define.vim @@ -169,14 +169,40 @@ function! remote#define#FunctionOnChannel(channel, method, sync, name, opts) exe function_def endfunction +let s:busy = {} +let s:pending_notifications = {} function! s:GetRpcFunction(sync) - if a:sync - return 'rpcrequest' + if a:sync ==# 'urgent' + return 'rpcnotify' + elseif a:sync + return 'remote#define#request' endif - return 'rpcnotify' + return 'remote#define#notify' endfunction +function! remote#define#notify(chan, ...) + if get(s:busy, a:chan, 0) > 0 + let pending = get(s:pending_notifications, a:chan, []) + call add(pending, deepcopy(a:000)) + let s:pending_notifications[a:chan] = pending + else + call call('rpcnotify', [a:chan] + a:000) + endif +endfunction + +function! remote#define#request(chan, ...) + let s:busy[a:chan] = get(s:busy, a:chan, 0)+1 + let val = call('rpcrequest', [a:chan]+a:000) + let s:busy[a:chan] -= 1 + if s:busy[a:chan] == 0 + for msg in get(s:pending_notifications, a:chan, []) + call call('rpcnotify', [a:chan] + msg) + endfor + let s:pending_notifications[a:chan] = [] + endif + return val +endfunction function! s:GetCommandPrefix(name, opts) return 'command!'.s:StringifyOpts(a:opts, ['nargs', 'complete', 'range', diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 8aead087db..159dd93c5e 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -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 diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt index 98a74ccfd6..7af6e401da 100644 --- a/runtime/doc/msgpack_rpc.txt +++ b/runtime/doc/msgpack_rpc.txt @@ -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: diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt new file mode 100644 index 0000000000..deac1609c0 --- /dev/null +++ b/runtime/doc/ui.txt @@ -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: diff --git a/src/clint.py b/src/clint.py index 4a41650ec4..e63175a69b 100755 --- a/src/clint.py +++ b/src/clint.py @@ -2531,6 +2531,7 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): r'(?<!\bkhash_t)' r'(?<!\bkbtree_t)' r'(?<!\bkbitr_t)' + r'(?<!\bPMap)' r'\((?:const )?(?:struct )?[a-zA-Z_]\w*(?: *\*(?:const)?)*\)' r' +' r'-?(?:\*+|&)?(?:\w+|\+\+|--|\()', cast_line) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index e736e29e2d..2944925a9c 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -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. /// diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 1b5d17584f..65357d008a 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -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 diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index fc5bb90973..9d42fbc1b3 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1466,7 +1466,7 @@ void enter_buffer(buf_T *buf) if (buf->terminal) { terminal_resize(buf->terminal, - (uint16_t)curwin->w_width, + (uint16_t)(MAX(0, curwin->w_width - win_col_off(curwin))), (uint16_t)curwin->w_height); } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index aab777955c..c7cb51ac29 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -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; @@ -16723,9 +16724,10 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } + uint16_t term_width = MAX(0, curwin->w_width - win_col_off(curwin)); TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit, true, false, false, cwd); - data->proc.pty.width = curwin->w_width; + data->proc.pty.width = term_width; data->proc.pty.height = curwin->w_height; data->proc.pty.term_name = xstrdup("xterm-256color"); if (!common_job_start(data, rettv)) { @@ -16733,7 +16735,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } TerminalOptions topts; topts.data = data; - topts.width = curwin->w_width; + topts.width = term_width; topts.height = curwin->w_height; topts.write_cb = term_write; topts.resize_cb = term_resize; @@ -19642,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. @@ -19815,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); @@ -19867,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; } @@ -19901,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) @@ -20193,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>". diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 54e5bcb9ff..e79476ab53 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -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); } } + diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 300e506854..0f3e132bc0 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -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--")); diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 88232a55de..5efdb9a194 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -56,7 +56,6 @@ typedef struct { typedef struct { uint64_t id; size_t refcount; - size_t pending_requests; PMap(cstr_t) *subscribed_events; bool closed; ChannelType type; @@ -71,7 +70,6 @@ typedef struct { } data; uint64_t next_request_id; kvec_t(ChannelCallFrame *) call_stack; - kvec_t(WBuffer *) delayed_notifications; MultiQueue *events; } Channel; @@ -205,14 +203,7 @@ bool channel_send_event(uint64_t id, const char *name, Array args) } if (channel) { - if (channel->pending_requests) { - // Pending request, queue the notification for later sending. - const String method = cstr_as_string((char *)name); - WBuffer *buffer = serialize_request(id, 0, method, args, &out_buffer, 1); - kv_push(channel->delayed_notifications, buffer); - } else { - send_event(channel, name, args); - } + send_event(channel, name, args); } else { broadcast_event(name, args); } @@ -248,10 +239,8 @@ Object channel_send_call(uint64_t id, // Push the frame ChannelCallFrame frame = { request_id, false, false, NIL }; kv_push(channel->call_stack, &frame); - channel->pending_requests++; LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, frame.returned); (void)kv_pop(channel->call_stack); - channel->pending_requests--; if (frame.errored) { if (frame.result.type == kObjectTypeString) { @@ -276,10 +265,6 @@ Object channel_send_call(uint64_t id, api_free_object(frame.result); } - if (!channel->pending_requests) { - send_delayed_notifications(channel); - } - decref(channel); return frame.errored ? NIL : frame.result; @@ -704,11 +689,7 @@ static void broadcast_event(const char *name, Array args) for (size_t i = 0; i < kv_size(subscribed); i++) { Channel *channel = kv_A(subscribed, i); - if (channel->pending_requests) { - kv_push(channel->delayed_notifications, buffer); - } else { - channel_write(channel, buffer); - } + channel_write(channel, buffer); } end: @@ -786,7 +767,6 @@ static void free_channel(Channel *channel) pmap_free(cstr_t)(channel->subscribed_events); kv_destroy(channel->call_stack); - kv_destroy(channel->delayed_notifications); if (channel->type != kChannelTypeProc) { multiqueue_free(channel->events); } @@ -811,11 +791,9 @@ static Channel *register_channel(ChannelType type, uint64_t id, rv->closed = false; rv->unpacker = msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE); rv->id = id > 0 ? id : next_chan_id++; - rv->pending_requests = 0; rv->subscribed_events = pmap_new(cstr_t)(); rv->next_request_id = 1; kv_init(rv->call_stack); - kv_init(rv->delayed_notifications); pmap_put(uint64_t)(channels, rv->id, rv); ILOG("new channel %" PRIu64 " (%s): %s", rv->id, @@ -912,16 +890,6 @@ static WBuffer *serialize_response(uint64_t channel_id, return rv; } -static void send_delayed_notifications(Channel* channel) -{ - for (size_t i = 0; i < kv_size(channel->delayed_notifications); i++) { - WBuffer *buffer = kv_A(channel->delayed_notifications, i); - channel_write(channel, buffer); - } - - kv_size(channel->delayed_notifications) = 0; -} - static void incref(Channel *channel) { channel->refcount++; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index f5730cf70a..41e900eb4c 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -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; } @@ -692,12 +693,18 @@ static void win_update(win_T *wp) if (wp->w_nrwidth != i) { type = NOT_VALID; wp->w_nrwidth = i; - } else if (buf->b_mod_set && buf->b_mod_xlines != 0 && wp->w_redraw_top != 0) { - /* - * When there are both inserted/deleted lines and specific lines to be - * redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw - * everything (only happens when redrawing is off for while). - */ + + if (buf->terminal) { + terminal_resize(buf->terminal, + (uint16_t)(MAX(0, curwin->w_width - win_col_off(curwin))), + (uint16_t)curwin->w_height); + } + } else if (buf->b_mod_set + && buf->b_mod_xlines != 0 + && wp->w_redraw_top != 0) { + // When there are both inserted/deleted lines and specific lines to be + // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw + // everything (only happens when redrawing is off for while). type = NOT_VALID; } else { /* diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index fb68292c77..ccf999c7ee 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -64,6 +64,7 @@ NEW_TESTS ?= \ test_normal.res \ test_profile.res \ test_quickfix.res \ + test_retab.res \ test_search.res \ test_signs.res \ test_smartindent.res \ diff --git a/src/nvim/testdir/test_retab.vim b/src/nvim/testdir/test_retab.vim new file mode 100644 index 0000000000..f11a32bade --- /dev/null +++ b/src/nvim/testdir/test_retab.vim @@ -0,0 +1,77 @@ +" Test :retab +func SetUp() + new + call setline(1, "\ta \t b c ") +endfunc + +func TearDown() + bwipe! +endfunc + +func Retab(bang, n) + let l:old_tabstop = &tabstop + let l:old_line = getline(1) + exe "retab" . a:bang . a:n + let l:line = getline(1) + call setline(1, l:old_line) + if a:n > 0 + " :retab changes 'tabstop' to n with argument n > 0. + call assert_equal(a:n, &tabstop) + exe 'set tabstop=' . l:old_tabstop + else + " :retab does not change 'tabstop' with empty or n <= 0. + call assert_equal(l:old_tabstop, &tabstop) + endif + return l:line +endfunc + +func Test_retab() + set tabstop=8 noexpandtab + call assert_equal("\ta\t b c ", Retab('', '')) + call assert_equal("\ta\t b c ", Retab('', 0)) + call assert_equal("\ta\t b c ", Retab('', 8)) + call assert_equal("\ta\t b\t c\t ", Retab('!', '')) + call assert_equal("\ta\t b\t c\t ", Retab('!', 0)) + call assert_equal("\ta\t b\t c\t ", Retab('!', 8)) + + call assert_equal("\t\ta\t\t\tb c ", Retab('', 4)) + call assert_equal("\t\ta\t\t\tb\t\t c\t ", Retab('!', 4)) + + call assert_equal(" a\t\tb c ", Retab('', 10)) + call assert_equal(" a\t\tb c ", Retab('!', 10)) + + set tabstop=8 expandtab + call assert_equal(" a b c ", Retab('', '')) + call assert_equal(" a b c ", Retab('', 0)) + call assert_equal(" a b c ", Retab('', 8)) + call assert_equal(" a b c ", Retab('!', '')) + call assert_equal(" a b c ", Retab('!', 0)) + call assert_equal(" a b c ", Retab('!', 8)) + + call assert_equal(" a b c ", Retab(' ', 4)) + call assert_equal(" a b c ", Retab('!', 4)) + + call assert_equal(" a b c ", Retab(' ', 10)) + call assert_equal(" a b c ", Retab('!', 10)) + + set tabstop=4 noexpandtab + call assert_equal("\ta\t\tb c ", Retab('', '')) + call assert_equal("\ta\t\tb\t\t c\t ", Retab('!', '')) + call assert_equal("\t a\t\t\tb c ", Retab('', 3)) + call assert_equal("\t a\t\t\tb\t\t\tc\t ", Retab('!', 3)) + call assert_equal(" a\t b c ", Retab('', 5)) + call assert_equal(" a\t b\t\t c\t ", Retab('!', 5)) + + set tabstop=4 expandtab + call assert_equal(" a b c ", Retab('', '')) + call assert_equal(" a b c ", Retab('!', '')) + call assert_equal(" a b c ", Retab('', 3)) + call assert_equal(" a b c ", Retab('!', 3)) + call assert_equal(" a b c ", Retab('', 5)) + call assert_equal(" a b c ", Retab('!', 5)) +endfunc + +func Test_retab_error() + call assert_fails('retab -1', 'E487:') + call assert_fails('retab! -1', 'E487:') +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index e8d82d3b87..a31381eddf 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -898,7 +898,7 @@ static const int included_patches[] = { 209, 208, // 207, - // 206, + 206, 205, // 204, // 203 NA diff --git a/src/nvim/window.c b/src/nvim/window.c index c2d0a9b3b1..2d64409a1c 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -3747,7 +3747,9 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, do_autochdir(); if (curbuf->terminal) { - terminal_resize(curbuf->terminal, curwin->w_width, curwin->w_height); + terminal_resize(curbuf->terminal, + (uint16_t)(MAX(0, curwin->w_width - win_col_off(curwin))), + (uint16_t)curwin->w_height); } } @@ -4946,7 +4948,9 @@ void win_new_width(win_T *wp, int width) if (wp->w_buffer->terminal) { if (wp->w_height != 0) { - terminal_resize(wp->w_buffer->terminal, wp->w_width, 0); + terminal_resize(wp->w_buffer->terminal, + (uint16_t)(MAX(0, curwin->w_width - win_col_off(curwin))), + 0); } } } diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index 9f245d913b..4380e52b8b 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -109,7 +109,28 @@ describe('server -> client', function() end) describe('requests and notifications interleaved', function() - -- This tests that the following scenario won't happen: + it('does not delay notifications during pending request', function() + local received = false + local function on_setup() + eq("retval", funcs.rpcrequest(cid, "doit")) + stop() + end + local function on_request(method) + if method == "doit" then + funcs.rpcnotify(cid, "headsup") + eq(true,received) + return "retval" + end + end + local function on_notification(method) + if method == "headsup" then + received = true + end + end + run(on_request, on_notification, on_setup) + end) + + -- This tests the following scenario: -- -- server->client [request ] (1) -- client->server [request ] (2) triggered by (1) @@ -124,36 +145,38 @@ describe('server -> client', function() -- only deals with one server->client request at a time. (In other words, -- the client cannot send a response to a request that is not at the top -- of nvim's request stack). - -- - -- But above scenario shoudn't happen by the way notifications are dealt in - -- Nvim: they are only sent after there are no pending server->client - -- request(the request stack fully unwinds). So (3) is only sent after the - -- client returns (6). - it('works', function() - local expected = 300 - local notified = 0 + pending('will close connection if not properly synchronized', function() local function on_setup() eq('notified!', eval('rpcrequest('..cid..', "notify")')) end local function on_request(method) - eq('notify', method) - eq(1, eval('rpcnotify('..cid..', "notification")')) - return 'notified!' + if method == "notify" then + eq(1, eval('rpcnotify('..cid..', "notification")')) + return 'notified!' + elseif method == "nested" then + -- do some busywork, so the first request will return + -- before this one + for _ = 1, 5 do + eq(2, eval("1+1")) + end + eq(1, eval('rpcnotify('..cid..', "nested_done")')) + return 'done!' + end end local function on_notification(method) - eq('notification', method) - if notified == expected then - stop() - return + if method == "notification" then + eq('done!', eval('rpcrequest('..cid..', "nested")')) + elseif method == "nested_done" then + -- this should never have been sent + ok(false) end - notified = notified + 1 - eq('notified!', eval('rpcrequest('..cid..', "notify")')) end run(on_request, on_notification, on_setup) - eq(expected, notified) + -- ignore disconnect failure, otherwise detected by after_each + clear() end) end) diff --git a/test/functional/ex_cmds/ctrl_c_spec.lua b/test/functional/ex_cmds/ctrl_c_spec.lua index 091a008814..8f76099f79 100644 --- a/test/functional/ex_cmds/ctrl_c_spec.lua +++ b/test/functional/ex_cmds/ctrl_c_spec.lua @@ -47,7 +47,7 @@ describe("CTRL-C (mapped)", function() end -- The test is time-sensitive. Try different sleep values. - local ms_values = {1, 10, 100, 1000, 10000} + local ms_values = {100, 1000, 10000} for i, ms in ipairs(ms_values) do if i < #ms_values then local status, _ = pcall(test_ctrl_c, ms) diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index 0f705cfe40..231618c5da 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local feed, clear = helpers.feed, helpers.clear local wait = helpers.wait +local iswin = helpers.iswin describe('terminal window', function() local screen @@ -11,9 +12,53 @@ describe('terminal window', function() screen = thelpers.screen_setup() end) - describe('with colorcolumn set', function() + describe("with 'number'", function() + it('wraps text', function() + feed([[<C-\><C-N>]]) + feed([[:set numberwidth=1 number<CR>i]]) + screen:expect([[ + {7:1 }tty ready | + {7:2 }rows: 6, cols: 48 | + {7:3 }{1: } | + {7:4 } | + {7:5 } | + {7:6 } | + {3:-- TERMINAL --} | + ]]) + thelpers.feed_data({'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'}) + screen:expect([[ + {7:1 }tty ready | + {7:2 }rows: 6, cols: 48 | + {7:3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV| + {7:4 }WXYZ{1: } | + {7:5 } | + {7:6 } | + {3:-- TERMINAL --} | + ]]) + + if iswin() then + return -- win: :terminal resize is unreliable #7007 + end + + -- numberwidth=9 + feed([[<C-\><C-N>]]) + feed([[:set numberwidth=9 number<CR>i]]) + thelpers.feed_data({' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'}) + screen:expect([[ + {7: 1 }tty ready | + {7: 2 }rows: 6, cols: 48 | + {7: 3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO| + {7: 4 }WXYZ abcdefghijklmnopqrstuvwxyzABCDEFGHIJ| + {7: 5 }KLMNOPQRSTUVWXYZrows: 6, cols: 41 | + {7: 6 }{1: } | + {3:-- TERMINAL --} | + ]]) + end) + end) + + describe("with 'colorcolumn'", function() before_each(function() - feed('<c-\\><c-n>') + feed([[<C-\><C-N>]]) screen:expect([[ tty ready | {2:^ } | @@ -23,7 +68,7 @@ describe('terminal window', function() | | ]]) - feed(':set colorcolumn=20<cr>i') + feed(':set colorcolumn=20<CR>i') end) it('wont show the color column', function() @@ -41,7 +86,7 @@ describe('terminal window', function() describe('with fold set', function() before_each(function() - feed('<c-\\><c-n>:set foldenable foldmethod=manual<cr>i') + feed([[<C-\><C-N>:set foldenable foldmethod=manual<CR>i]]) thelpers.feed_data({'line1', 'line2', 'line3', 'line4', ''}) screen:expect([[ tty ready | @@ -55,7 +100,7 @@ describe('terminal window', function() end) it('wont show any folds', function() - feed('<c-\\><c-n>ggvGzf') + feed([[<C-\><C-N>ggvGzf]]) wait() screen:expect([[ ^tty ready | diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua new file mode 100644 index 0000000000..0f8302b036 --- /dev/null +++ b/test/functional/ui/cmdline_spec.lua @@ -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) |