refactor(exmode): Ex-mode as cmdwin + Lua #40991

Problem:
POSIX-compatible Ex-mode requires special-cases all over the codebase to
match various quirks that don't actually matter to users.
- The main utility of *interactive* Ex-mode is its REPL behavior, and
  that can be achieved with `cmdwin`, which also gains extra UX
  benefits.
- The main utility of *non-interactive* `nvim -es` is for shell
  scripting, where Ex-mode quirks are mostly unhelpful (e.g. the
  "Entering Ex mode" message).

Solution:
- Reimplement *interactive* Ex-mode as a "persistent, insert-mode
  cmdwin" in Lua.
  - "nvim -e/-E" is simply an alias to "gQ".
- Reframe *non-interactive* Ex-mode (`nvim -es`) as "script mode".
  - Drop POSIX Ex-mode quirks.

Improvements:
- "nvim -V1 -es" output ends with a final newline!
- "nvim -V1 -es" no longer shows the "Entering Ex mode" msg. (This was
  pointless noise, unwanted for scripting purposes.)
- stdin is no longer typeahead. Scripts (":lua io.read()") can read
  stdin as data.
- Empty line is a no-op: a stray blank line no longer moves the cursor
  (deviates from POSIX ex "+1"), no longer exits 1 at EOF (E501).

Preserved behavior:
- cursor starts at "$"
- mode()=="cv" (for non-interactive)
- multiline commands (:append/:function/heredoc pull continuation lines)
- bare-range print
- :print=>stdout
- -V1=>stderr
- CRLF input
- continue-after-error and exit codes

Dropped (regressed) POSIX behavior (non-interactive):
- Event loop only ticks while/between commands, not while blocked
  waiting for a stdin line.
- ":g/pat/visual...Q"
- input()/getchar()/":s/x/y/c" no longer consume stdin lines as
  answers: Nvim stops at end-of-input, skipping the rest of the script,
  exit 0. Use ":lua io.read()" instead.
  - If users care about this they should use interactive Ex-mode (`gQ`).
- ":@r" stops at end of the register instead of continuing to read
  cmdline input from stdin.
This commit is contained in:
Justin M. Keyes 2026-07-27 06:25:21 -04:00 committed by GitHub
parent faaa4d57f1
commit 359459dec6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 900 additions and 1056 deletions

View File

@ -153,7 +153,7 @@ Overview >vim
Methods of the "vim" module
vim.command(str) *python-command*
Executes the vim (ex-mode) command str. Returns None.
Executes the Ex command str. Returns None.
Examples: >vim
:py vim.command("set tw=72")
:py vim.command("%s/aaa/bbb/g")

View File

@ -2026,7 +2026,7 @@ containing only a ".". Watch out for lines starting with a backslash, see
|line-continuation|.
Text typed after a "|" command separator is used first. So the following
command in ex mode: >
command in Ex mode: >
:a|one
two
.

View File

@ -276,8 +276,7 @@ Vim has seven BASIC modes:
"!". |Cmdline-mode|
- Ex mode: Like Command-line mode, but after entering a command
you remain in Ex mode. Very limited editing of the
command line. |Ex-mode|
you remain in |Ex-mode|.
*Terminal-mode*
- Terminal mode: In Terminal mode all input (except CTRL-\) is sent to
@ -380,19 +379,26 @@ CTRL-O in Insert mode you get a beep but you are still in Insert mode, type
*v_CTRL-\_CTRL-N* *t_CTRL-\_CTRL-N*
Additionally the command CTRL-\ CTRL-N or <C-\><C-N> can be used to go to
Normal mode from any other mode. This can be used to make sure Vim is in
Normal mode, without causing a beep like <Esc> would. However, this does not
work in Ex mode. When used after a command that takes an argument, such as
|f| or |m|, the timeout set with 'ttimeoutlen' applies.
Normal mode, without causing a beep like <Esc> would. When used after
a command that takes an argument, such as |f| or |m|, the timeout set with
'ttimeoutlen' applies.
*CTRL-\_CTRL-G* *i_CTRL-\_CTRL-G* *c_CTRL-\_CTRL-G* *v_CTRL-\_CTRL-G*
CTRL-\ CTRL-G works the same as |CTRL-\_CTRL-N| for backward compatibility.
*gQ* *mode-Ex* *Ex-mode* *Ex* *EX* *E501*
gQ Switch to Ex mode. This is like typing ":" commands
one after another, except:
- You don't have to keep pressing ":".
- The screen doesn't get updated after each command.
Use the `:vi` command (|:visual|) to exit this mode.
one after another, except you don't have to keep
pressing ":". Use |:visual| to exit this mode.
Implementation details: a persistent |cmdwin| where
<Enter> executes the current line as a command
(against the window from which Ex mode was entered)
and stays open, in insert-mode. Command output is
presented as comment (") lines. After a command that
moves the cursor or changes the buffer, the current
line is printed. An empty line moves to (and prints)
the next line.
==============================================================================
Window contents *window-contents*

View File

@ -93,6 +93,21 @@ DIAGNOSTICS
EDITOR
• Interactive |Ex-mode| (|gQ|, `nvim -e`) is a persistent |cmdwin|. This is much
more flexible and makes "Ex mode" more useful as a kind of "cmdline REPL",
with the tradeoff of dropping some POSIX Ex-mode quirks.
• |mode()| returns "n" or "i"; "cv" indicates |-es| (script mode) only.
• ":global/pat/visual" no longer switches to Normal mode per matching line.
• Non-interactive Ex-mode (|-es|, "script mode") executes stdin as Ex commands
directly. It is a "script host", not "POSIX Ex mode".
• Output ends with a final newline.
• |:visual| is |:edit|: it does not switch to reading stdin as Normal-mode
commands, and ":global/pat/visual" does not enter Normal mode per
matching line.
• Commands that wait for typed input (|input()|, |getchar()|, …) stop Nvim
at end-of-input instead of consuming the next stdin line.
• An empty line is a no-op instead of advancing the cursor (or failing
with |E501| at end-of-file).
• On Windows, the |trust| db now stores paths with "/" slashes. This means the
trust store will be reset (one time).
• `stdpath("log")` moved to `stdpath("state")/logs`.
@ -242,7 +257,7 @@ EDITOR
real-time, instead of only on |FocusGained| or |:checktime|.
• During |complete()|-triggered completion, CTRL-N and CTRL-P are now subject
to insert-mode mappings.
• Multi-byte characters, translated by 'langmap', now invoke correct
• Multibyte characters, translated by 'langmap', now invoke correct
mappings.
Example: >vim
set langmap=õ]

View File

@ -1019,7 +1019,7 @@ Short explanation of each option: *option-list*
|:version| :ve[rsion] show version information
|:normal| :norm[al][!] {commands}
execute Normal mode commands
|gQ| gQ switch to "Ex" mode
|gQ| gQ switch to |Ex-mode|
|:redir| :redir >{file} redirect messages to {file}
|:silent| :silent[!] {command} execute {command} silently
@ -1144,7 +1144,7 @@ Context-sensitive completion on the command-line:
|-+| +[num] put the cursor at line [num] (default: last line)
|-+c| +{command} execute {command} after loading the file
|-+/| +/{pat} {file} .. put the cursor at the first occurrence of {pat}
|-e| -e Ex mode, start vim in Ex mode
|-e| -e |Ex-mode|
|-R| -R Read-only mode, implies -n
|-m| -m modifications not allowed (resets 'write' option)
|-d| -d |diff-mode|

View File

@ -102,12 +102,6 @@ This replaces all occurrences of "pat" with "PAT". The same can be done with: >
:%s/pat/PAT/g
Which is two characters shorter!
When using "global" in Ex mode, a special case is using ":visual" as a
command. This will move to a matching line, go to Normal mode to let you
execute commands there until you use |gQ| to return to Ex mode. This will be
repeated for each matching line. While doing this you cannot use ":global".
To abort this type CTRL-C twice.
==============================================================================
Complex repeats *complex-repeat*

View File

@ -182,7 +182,7 @@ argument.
changes and writing.
-e *-e* *-E*
-E Start Nvim in Ex mode |gQ|, see |Ex-mode|.
-E Start Nvim in |Ex-mode|.
If stdin is not a TTY:
• -e reads/executes stdin as Ex commands.
@ -209,6 +209,11 @@ argument.
With |:verbose| or 'verbose', other commands display on stderr: >
nvim -es +"verbose echo 'foo'"
nvim -V1 -es +"echo 'foo'"
<
Commands that wait for typed input (|input()|, |getchar()|, …)
stop Nvim on EOF; they do not consume stdin. Lua can read
stdin as data: >
printf 'lua vim.g.b = io.read()\nfoo\nput =g:b\np' | nvim -es
<
Skips user |config| unless |-u| was given.
Disables |shada| unless |-i| was given.

View File

@ -232,8 +232,8 @@ gx Opens the current filepath, URL (decided by
will be aborted as if <Esc> or <C-C> was typed.
This implies that an insert command must be completed
(to start Insert mode, see |:startinsert|). A ":"
command must be completed as well. And you can't use
"gQ" to start Ex mode.
command must be completed as well. And you can't
start |Ex-mode|.
The display is not updated while ":normal" is busy.

View File

@ -334,6 +334,7 @@ Commands:
- |:uptime|
Editor:
- Interactive |Ex-mode| is implemented as persistent, insert-mode |cmdwin|.
- |prompt-buffer| supports multiline input/paste, undo/redo, and o/O normal
commands.
- |i_CTRL-R| inserts named/clipboard registers (A-Z,a-z,0-9+) literally, like
@ -482,10 +483,11 @@ Signs:
Startup:
- |-e| and |-es| invoke the same "improved Ex mode" as -E and -Es.
- |-E| and |-Es| read stdin as text (into buffer 1).
- |-es| and |-Es| have improved behavior:
- Quits automatically, don't need "-c qa!".
- |-es| and |-Es| act as a generic "script mode":
- Exits automatically, don't need "-c qa!".
- Skips swap-file dialog.
- Optimized for non-interactive scripts: disables swapfile, shada.
- Disables swapfile, shada.
- Various "POSIX Ex" quirks were dropped.
- |-l| Executes Lua scripts non-interactively.
- |-s| reads Normal commands from stdin if the script name is "-".
- Reading text (instead of commands) from stdin |--|:

View File

@ -4126,7 +4126,7 @@ getpos({expr}) *getpos()*
'x Position of mark x (if the mark is not set, 0 is
returned for all values).
w0 First line visible in current window (one if the
display isn't updated, e.g. in silent Ex mode).
display isn't updated, e.g. in |silent-mode|).
w$ Last line visible in current window (this is one
less than "w0" if no lines are visible).
v End of the current Visual selection (unlike |'<|
@ -7329,8 +7329,7 @@ mode([{expr}]) *mode()*
Rvx Virtual Replace mode |i_CTRL-X| completion
c Command-line editing
cr Command-line editing overstrike mode |c_<Insert>|
cv Vim Ex mode |gQ|
cvr Vim Ex mode while in overstrike mode |c_<Insert>|
cv Non-interactive Ex mode |-es|
r Hit-enter prompt
rm The -- more -- prompt
r? A |:confirm| query of some sort

View File

@ -302,7 +302,7 @@ do
local function cmd(opts)
local ok, err = pcall(vim.api.nvim_cmd, opts, {})
if not ok then
vim.api.nvim_echo({ { err:sub(#'Vim:' + 1) } }, true, { err = true })
vim.api.nvim_echo({ { require('vim._core.util').cmd_errmsg(err) } }, true, { err = true })
end
end

View File

@ -0,0 +1,223 @@
--- Ex mode.
--- - `run()`: non-interactive (`nvim -es`): executes stdin as Ex commands.
--- - `open()`: interactive (`gQ`, `nvim -e`), a keep-open "cmdwin" REPL.
local api = vim.api
local N_ = vim.fn.gettext
local util = require('vim._core.util')
local M = {}
--- Commands that end Ex mode before executing (in the caller window).
local exit_cmds = {
cquit = true,
exit = true,
quit = true,
quitall = true,
restart = true,
view = true,
visual = true,
wq = true,
wqall = true,
xall = true,
xit = true,
}
--- Continuation lines (of a multiline cmd), for the statuscolumn.
--- @type table<integer, true>
local cont_lines = {}
--- Non-interactive "script mode" ("nvim -es", fka "Ex-mode", but POSIX behavior was dropped):
--- Executes stdin as Ex commands, until EOF.
---
--- Stdin is not consumed as typeahead: fd 0 is read directly (`io.lines()`), so `:lua` script lines
--- can also read stdin as data via `io.read()`.
function M.run()
local lines = io.lines()
local getline = function()
local line = lines()
-- Strip trailing CR so CRLF input (e.g. Windows pipes) works like LF.
return line and (line:gsub('\r$', '')) or nil
end
-- Instead of per-line `nvim_exec2`, this runs one `do_cmdline` per logical command with
-- `getline` as the line-getter, in order to preserve these behaviors:
-- - :append/:function/:if/heredocs pull continuation lines from stdin. #7679
-- - Errors are emitted, not thrown (nvim_exec2 turns the first error into an exception):
-- execution continues with the next command, `ex_exitval` still sets the exit code, and
-- "-V1" shows errors on stderr.
-- - Output (:print, :set, …) to stdout as each command executes.
--
-- TODO: could enhance nvim_exec2 instead?
while vim._core.ex_docmd(getline) do
vim.wait(0) -- Run pending events (vim.schedule() callbacks, RPC).
end
end
--- Whether `lines` form a complete command block: no `:append` text, `:function` body, heredoc, or
--- :if/:while/:for/:try block is awaiting continuation lines. Feeds the lines to do_cmdline(),
--- which groups multiline constructs even when skipping (false `:if`), without executing anything.
--- If the construct is still open it consumes the sentinel "endif" and asks the getter for more.
--- @param lines string[]
--- @return boolean
local function block_complete(lines)
-- :append/etc. not grouped by the skipped-:if below: collect until the "." terminator.
local ok, p = pcall(api.nvim_parse_cmd, lines[1], {})
if ok and (p.cmd == 'append' or p.cmd == 'insert' or p.cmd == 'change') then
return vim.list_contains({ unpack(lines, 2) }, '.')
end
local feed = { 'if v:false' }
vim.list_extend(feed, lines)
feed[#feed + 1] = 'endif'
local i = 0
local complete = true
-- Silenced: probing an incomplete block emits e.g. E126/E990 when the reader hits EOF.
vim._with({ silent = true, emsg_silent = true }, function()
vim._core.ex_docmd(function()
i = i + 1
if i > #feed then
complete = false
return nil
end
return feed[i]
end)
end)
return complete
end
--- Ex-mode 'statuscolumn': ":", or blank for continuation lines (`:func` body, `:append` text, …).
function M._statuscolumn()
return cont_lines[vim.v.lnum] and ' ' or ':'
end
--- Interactive Ex-mode: keep-open cmdwin REPL. `<CR>` executes the current line against the caller
--- window and keeps the cmdwin open; command output is presented as comment ('"') lines. Multiline
--- commands (`:append`, etc.) are collected until the block is complete.
function M.open()
local cmdwin = require('vim._core.cmdwin')
local caller = api.nvim_get_current_win()
cmdwin.open(':')
if api.nvim_get_current_win() == caller then
return -- cmdwin failed to open (E1292 already echoed).
end
local buf = api.nvim_get_current_buf()
local block_start = api.nvim_buf_line_count(buf)
cont_lines = {}
vim.wo[0][0].statuscolumn = '%#NonText#%{v:lua.require("vim._core.exmode")._statuscolumn()}'
--- Formats `text` (possibly multiline) as transcript comment lines.
--- @param out string[]
--- @param text string
local function put(out, text)
for _, l in ipairs(vim.split(text, '\n', { trimempty = true })) do
out[#out + 1] = '" ' .. l
end
end
api.nvim_echo({ { N_('Entering Ex mode. Type "visual" to go to Normal mode.') } }, true, {})
local function run()
if not api.nvim_win_is_valid(caller) then
cmdwin._cleanup() -- Caller window is gone: end Ex mode.
return
end
local line = api.nvim_get_current_line()
local lnum = api.nvim_win_get_cursor(0)[1]
if lnum >= block_start then
-- Multiline commands: collect continuation lines until the block is complete.
local block = api.nvim_buf_get_lines(buf, block_start - 1, lnum, false)
if not block_complete(block) then
cont_lines[lnum + 1] = true -- Mark the continuation line.
api.nvim_buf_set_lines(buf, lnum, lnum, false, { '' })
api.nvim_win_set_cursor(0, { lnum + 1, 0 })
return
end
line = table.concat(block, '\n')
end
local parse_ok, parsed = pcall(api.nvim_parse_cmd, line:match('^[^\n]*'), {})
if parse_ok and not line:find('\n') and exit_cmds[parsed.cmd] then
vim.fn.histadd('cmd', line)
cmdwin._cleanup() -- Focuses the caller window.
vim.cmd.stopinsert()
if parsed.cmd ~= 'visual' and parsed.cmd ~= 'view' or #parsed.args > 0 then
--- @type boolean, string?
local ok, err = pcall(vim.cmd --[[@as function]], line) -- May exit Nvim.
if not ok then
util.echo_err(util.cmd_errmsg(tostring(err)))
end
end
return
end
local out = {} --- @type string[]
local prev_lnum = api.nvim_win_get_cursor(caller)[1]
local prev_tick = vim.b[api.nvim_win_get_buf(caller)].changedtick
if line == '' then
-- Empty line: advance one line (POSIX ex "+"); auto-print below shows it.
if prev_lnum >= api.nvim_buf_line_count(api.nvim_win_get_buf(caller)) then
put(out, N_('E501: At end-of-file'))
else
api.nvim_win_set_cursor(caller, { prev_lnum + 1, 0 })
end
else
local ok, res = pcall(api.nvim_win_call, caller, function()
return api.nvim_exec2(line, { output = true })
end)
if ok then
put(out, res.output)
else
put(out, util.cmd_errmsg(tostring(res)))
end
vim.fn.histadd('cmd', line)
end
if not api.nvim_win_is_valid(caller) then
-- The command closed the caller window (e.g. ":close"): continue Ex mode against a
-- remaining window, or end it.
local wins = vim.tbl_filter(
--- @param w integer
function(w)
return api.nvim_win_get_buf(w) ~= buf
end,
api.nvim_list_wins()
)
if #wins == 0 then
vim.cmd.quit() -- Only the cmdwin is left: exit.
return
end
caller = wins[1]
end
-- Ex-mode feature: auto-print the current line after a cursor move or buffer change, unless the
-- cmd already printed something (e.g. ":5,6print" should not redundantly auto-print).
local typed_text = parse_ok
and (parsed.cmd == 'append' or parsed.cmd == 'insert' or parsed.cmd == 'change')
local cbuf = api.nvim_win_get_buf(caller)
local cur = api.nvim_win_get_cursor(caller)[1]
if
#out == 0
and not typed_text
-- Bare range (":1") prints even when the cursor is already on line 1.
and (
(parse_ok and parsed.cmd == '')
or cur ~= prev_lnum
or vim.b[cbuf].changedtick ~= prev_tick
)
then
put(out, api.nvim_buf_get_lines(cbuf, cur - 1, cur, false)[1] or '')
end
-- Keep-open: record the transcript and park the cursor on a fresh last line.
out[#out + 1] = ''
api.nvim_buf_set_lines(buf, -1, -1, false, out)
block_start = api.nvim_buf_line_count(buf)
api.nvim_win_set_cursor(0, { block_start, 0 })
end
vim.keymap.set({ 'n', 'i' }, '<CR>', run, { buffer = buf })
vim.keymap.set({ 'n', 'i' }, '<NL>', run, { buffer = buf })
-- Enter Insert mode before any already-typed keys, so `gQcmd<CR>` types "cmd" into the REPL.
api.nvim_feedkeys('i', 'ni', false)
end
return M

View File

@ -210,9 +210,8 @@ function M.ex_session_restart(eap, extra)
vim.fs.rm(session, { force = true })
-- Trim error message to be equivalent to `:restart!`
local trimmed_msg = msg:match('Vim:.*$')
if trimmed_msg then
util.echo_err(trimmed_msg:sub(5))
if msg:find('Vim:') then
util.echo_err(util.cmd_errmsg(msg))
else
error(msg)
end

View File

@ -166,6 +166,18 @@ function M.get_forge_url(repo, target, target_type)
return ('%s/%s/%s'):format(repo, middle, target)
end
--- Gets a scrubbed message from a pcall'd command error (drops Lua context/traceback):
--- "…/editor.lua:123: Vim(put):E484: xx" => "E484: xx"
---
--- @param err string
--- @return string
function M.cmd_errmsg(err)
err = err:match('^[^\n]*') or err
--- @type string
err = err:match('Vim%b():%s*(.*)') or err:match('Vim:%s*(.*)') or (err:gsub('^.-:%d+:%s*', ''))
return (err:gsub('^Lua:%s*', ''))
end
--- Utility function for displaying vim error codes (EXX)
--- @param msg string
function M.echo_err(msg)

View File

@ -202,6 +202,13 @@ function vim._core.ui_flush() end
--- @return boolean
function vim._core.check_interrupt() end
--- @nodoc
--- Executes one Ex command line obtained from `getline`, which is also called for any
--- continuation lines (`:append` text, `:function` body, heredoc, …). See `vim._core.exmode`.
--- @param getline fun(): string?
--- @return boolean # false if {getline} returned nil before any line was read (EOF).
function vim._core.ex_docmd(getline) end
--- @nodoc
--- Parses `keys` (internal representation) into a list of key chords. See |vim.keycode()|.
--- @param keys string

View File

@ -3644,7 +3644,7 @@ function vim.fn.getpid() end
--- 'x Position of mark x (if the mark is not set, 0 is
--- returned for all values).
--- w0 First line visible in current window (one if the
--- display isn't updated, e.g. in silent Ex mode).
--- display isn't updated, e.g. in |silent-mode|).
--- w$ Last line visible in current window (this is one
--- less than "w0" if no lines are visible).
--- v End of the current Visual selection (unlike |'<|
@ -6573,8 +6573,7 @@ function vim.fn.mkdir(name, flags, prot) end
--- Rvx Virtual Replace mode |i_CTRL-X| completion
--- c Command-line editing
--- cr Command-line editing overstrike mode |c_<Insert>|
--- cv Vim Ex mode |gQ|
--- cvr Vim Ex mode while in overstrike mode |c_<Insert>|
--- cv Non-interactive Ex mode |-es|
--- r Hit-enter prompt
--- rm The -- more -- prompt
--- r? A |:confirm| query of some sort

View File

@ -1717,13 +1717,11 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
}
const int save_did_emsg = did_emsg;
const bool save_ex_pressedreturn = get_pressedreturn();
// Execute the autocmd. The `getnextac` callback handles iteration.
do_cmdline(NULL, getnextac, &patcmd, DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT);
did_emsg += save_did_emsg;
set_pressedreturn(save_ex_pressedreturn);
if (nesting == 1) {
// restore cursor and topline, unless they were changed

View File

@ -1013,10 +1013,6 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
write_info.bw_conv_error_lnum = 0;
write_info.bw_iconv_fd = (iconv_t)-1;
// After writing a file changedtick changes but we don't want to display
// the line.
ex_no_reprint = true;
// If there is no file name yet, use the one for the written file.
// BF_NOTEDITED is set to reflect this (in case the write fails).
// Don't do this when the write is for a filter command.

View File

@ -370,9 +370,8 @@ void screen_resize(int width, int height)
// - While at the more prompt or executing an external command, don't
// redraw, but position the cursor.
// - While editing the command line, only redraw that. TODO: lies
// - in Ex mode, don't redraw anything.
// - Otherwise, redraw right now, and position the cursor.
if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || exmode_active
if (State == MODE_ASKMORE || State == MODE_EXTERNCMD
|| ((State & MODE_CMDLINE) && get_cmdline_info()->one_key)) {
if (State & MODE_CMDLINE) {
update_screen();

View File

@ -5022,7 +5022,6 @@ void timer_due_cb(TimeWatcher *tw, void *data)
timer_T *timer = (timer_T *)data;
int save_did_emsg = did_emsg;
const int called_emsg_before = called_emsg;
const bool save_ex_pressedreturn = get_pressedreturn();
if (timer->stopped || timer->paused) {
return;
@ -5049,7 +5048,6 @@ void timer_due_cb(TimeWatcher *tw, void *data)
}
}
did_emsg = save_did_emsg;
set_pressedreturn(save_ex_pressedreturn);
if (timer->emsg_count >= 3) {
timer_stop(timer);

View File

@ -4487,7 +4487,7 @@ M.funcs = {
'x Position of mark x (if the mark is not set, 0 is
returned for all values).
w0 First line visible in current window (one if the
display isn't updated, e.g. in silent Ex mode).
display isn't updated, e.g. in |silent-mode|).
w$ Last line visible in current window (this is one
less than "w0" if no lines are visible).
v End of the current Visual selection (unlike |'<|
@ -7943,8 +7943,7 @@ M.funcs = {
Rvx Virtual Replace mode |i_CTRL-X| completion
c Command-line editing
cr Command-line editing overstrike mode |c_<Insert>|
cv Vim Ex mode |gQ|
cvr Vim Ex mode while in overstrike mode |c_<Insert>|
cv Non-interactive Ex mode |-es|
r Hit-enter prompt
rm The -- more -- prompt
r? A |:confirm| query of some sort

View File

@ -2881,10 +2881,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
} else {
beginline(BL_SOL | BL_FIX);
}
} else { // no line number, go to last line in Ex mode
if (exmode_active) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
}
} else {
beginline(BL_WHITE | BL_FIX);
}
}
@ -3001,6 +2998,7 @@ void ex_append(exarg_T *eap)
lnum = 0;
}
const int save_State = State;
State = MODE_INSERT; // behave like in Insert mode
if (curbuf->b_p_iminsert == B_IMODE_LMAP) {
State |= MODE_LANGMAP;
@ -3039,13 +3037,13 @@ void ex_append(exarg_T *eap)
}
eap->nextcmd = p;
} else {
int save_State = State;
int getline_State = State;
// Set State to avoid the cursor shape to be set to MODE_INSERT
// state when getline() returns.
State = MODE_CMDLINE;
theline = eap->ea_getline(eap->cstack->cs_looplevel > 0 ? -1 : NUL,
eap->cookie, indent, true);
State = save_State;
State = getline_State;
}
lines_left = Rows - 1;
if (theline == NULL) {
@ -3092,7 +3090,7 @@ void ex_append(exarg_T *eap)
empty = false;
}
}
State = MODE_NORMAL;
State = save_State;
ui_cursor_shape();
if (eap->forceit) {
@ -3117,7 +3115,6 @@ void ex_append(exarg_T *eap)
beginline(BL_SOL | BL_FIX);
need_wait_return = false; // don't use wait_return() now
ex_no_reprint = true;
}
/// ":change"
@ -3268,7 +3265,6 @@ void ex_z(exarg_T *eap)
curwin->w_cursor.lnum = curs;
curwin->w_cursor.col = 0;
}
ex_no_reprint = true;
}
/// @return true if the secure flag is set and also give an error message.
@ -3946,111 +3942,72 @@ static int do_sub(exarg_T *eap, proftime_T tm, const int cmdpreview_ns,
// Loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed.
while (subflags.do_ask) {
if (exmode_active) {
print_line_no_prefix(lnum, subflags.do_number, subflags.do_list);
String orig_line = STRING_INIT;
int len_change = 0;
const bool save_p_lz = p_lz;
int save_p_fen = curwin->w_p_fen;
colnr_T sc, ec;
getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL, 0);
curwin->w_cursor.col = MAX(regmatch.endpos[0].col - 1, 0);
curwin->w_p_fen = false;
// Invert the matched string.
// Remove the inversion afterwards.
int temp = RedrawingDisabled;
RedrawingDisabled = 0;
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec, 0);
curwin->w_cursor.col = regmatch.startpos[0].col;
if (subflags.do_number || curwin->w_p_nu) {
int numw = number_width(curwin) + 1;
sc += numw;
ec += numw;
}
// avoid calling update_screen() in vgetorpeek()
p_lz = false;
char *prompt = xmallocz((size_t)ec + 1);
memset(prompt, ' ', (size_t)sc);
memset(prompt + sc, '^', (size_t)(ec - sc) + 1);
char *resp = getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL,
CALLBACK_NONE, false, NULL);
if (!ui_has(kUIMessages)) {
msg_putchar('\n');
}
xfree(prompt);
if (resp != NULL) {
typed = (uint8_t)(*resp);
xfree(resp);
} else {
// getcmdline_prompt() returns NULL if there is no command line to return.
typed = NUL;
}
// When ":normal" runs out of characters we get
// an empty line. Use "q" to get out of the
// loop.
if (ex_normal_busy && typed == NUL) {
typed = 'q';
}
} else {
String orig_line = STRING_INIT;
int len_change = 0;
const bool save_p_lz = p_lz;
int save_p_fen = curwin->w_p_fen;
if (new_start.data != NULL) {
// There already was a substitution, we would
// like to show this to the user. We cannot
// really update the line, it would change
// what matches. Temporarily replace the line
// and change it back afterwards.
orig_line = cbuf_to_string(ml_get(lnum), (size_t)ml_get_len(lnum));
String new_line = {
.data = concat_str(new_start.data, sub_firstline.data + copycol),
.size = new_start.size + (sub_firstline.size - (size_t)copycol),
};
curwin->w_p_fen = false;
// Invert the matched string.
// Remove the inversion afterwards.
int temp = RedrawingDisabled;
RedrawingDisabled = 0;
// Position the cursor relative to the end of the line, the
// previous substitute may have inserted or deleted characters
// before the cursor.
len_change = (int)new_line.size - (int)orig_line.size;
curwin->w_cursor.col += len_change;
ml_replace(lnum, new_line.data, false);
}
// avoid calling update_screen() in vgetorpeek()
p_lz = false;
Search.match_lines = regmatch.endpos[0].lnum - regmatch.startpos[0].lnum;
Search.match_endcol = regmatch.endpos[0].col + len_change;
if (Search.match_lines == 0 && Search.match_endcol == 0) {
// highlight at least one character for /^/
Search.match_endcol = 1;
}
Search.hl_match = true;
if (new_start.data != NULL) {
// There already was a substitution, we would
// like to show this to the user. We cannot
// really update the line, it would change
// what matches. Temporarily replace the line
// and change it back afterwards.
orig_line = cbuf_to_string(ml_get(lnum), (size_t)ml_get_len(lnum));
String new_line = {
.data = concat_str(new_start.data, sub_firstline.data + copycol),
.size = new_start.size + (sub_firstline.size - (size_t)copycol),
};
update_topline(curwin);
validate_cursor(curwin);
redraw_later(curwin, UPD_SOME_VALID);
show_cursor_info_later(true);
update_screen();
redraw_later(curwin, UPD_SOME_VALID);
// Position the cursor relative to the end of the line, the
// previous substitute may have inserted or deleted characters
// before the cursor.
len_change = (int)new_line.size - (int)orig_line.size;
curwin->w_cursor.col += len_change;
ml_replace(lnum, new_line.data, false);
}
curwin->w_p_fen = save_p_fen;
Search.match_lines = regmatch.endpos[0].lnum - regmatch.startpos[0].lnum;
Search.match_endcol = regmatch.endpos[0].col + len_change;
if (Search.match_lines == 0 && Search.match_endcol == 0) {
// highlight at least one character for /^/
Search.match_endcol = 1;
}
Search.hl_match = true;
char *p = _("replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)");
snprintf(IObuff, IOSIZE, p, sub);
p = xstrdup(IObuff);
typed = prompt_for_input(p, HLF_R, true, NULL);
Search.hl_match = false;
xfree(p);
update_topline(curwin);
validate_cursor(curwin);
redraw_later(curwin, UPD_SOME_VALID);
show_cursor_info_later(true);
update_screen();
redraw_later(curwin, UPD_SOME_VALID);
msg_didout = false; // don't scroll up
gotocmdline(true);
p_lz = save_p_lz;
RedrawingDisabled = temp;
curwin->w_p_fen = save_p_fen;
char *p = _("replace with %s? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)");
snprintf(IObuff, IOSIZE, p, sub);
p = xstrdup(IObuff);
typed = prompt_for_input(p, HLF_R, true, NULL);
Search.hl_match = false;
xfree(p);
msg_didout = false; // don't scroll up
gotocmdline(true);
p_lz = save_p_lz;
RedrawingDisabled = temp;
// restore the line
if (orig_line.data != NULL) {
ml_replace(lnum, orig_line.data, false);
}
// restore the line
if (orig_line.data != NULL) {
ml_replace(lnum, orig_line.data, false);
}
need_wait_return = false; // no hit-return prompt

View File

@ -145,7 +145,6 @@ static const char e_no_script_file_name_to_substitute_for_script[]
= N_("E1274: No script file name to substitute for \"<script>\"");
static int quitmore = 0;
static bool ex_pressedreturn = false;
// Struct for storing a line inside a while/for loop
typedef struct {
@ -263,77 +262,6 @@ static bool is_other_file(int fnum, char *ffname)
return otherfile(ffname);
}
/// Repeatedly get commands for Ex mode, until the ":vi" command is given.
void do_exmode(void)
{
exmode_active = true;
State = MODE_NORMAL;
may_trigger_modechanged();
// When using ":global /pat/ visual" and then "Q" we return to continue
// the :global command.
if (global_busy) {
return;
}
int save_msg_scroll = msg_scroll;
RedrawingDisabled++; // don't redisplay the window
no_wait_return++; // don't wait for return
msg(_("Entering Ex mode. Type \"visual\" to go to Normal mode."), 0);
while (exmode_active) {
// Check for a ":normal" command and no more characters left.
if (ex_normal_busy > 0 && typebuf.tb_len == 0) {
exmode_active = false;
break;
}
msg_scroll = true;
need_wait_return = false;
ex_pressedreturn = false;
ex_no_reprint = false;
varnumber_T changedtick = buf_get_changedtick(curbuf);
int prev_msg_row = msg_row;
linenr_T prev_line = curwin->w_cursor.lnum;
cmdline_row = msg_row;
do_cmdline(NULL, getexline, NULL, 0);
lines_left = Rows - 1;
if ((prev_line != curwin->w_cursor.lnum
|| changedtick != buf_get_changedtick(curbuf)) && !ex_no_reprint) {
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
emsg(_(e_empty_buffer));
} else {
if (ex_pressedreturn) {
// Make sure the message overwrites the right line and isn't throttled.
msg_scroll_flush();
// go up one line, to overwrite the ":<CR>" line, so the
// output doesn't contain empty lines.
msg_row = prev_msg_row;
if (prev_msg_row == Rows - 1) {
msg_row--;
}
}
msg_col = 0;
print_line_no_prefix(curwin->w_cursor.lnum, false, false);
msg_clr_eos();
}
} else if (ex_pressedreturn && !ex_no_reprint) { // must be at EOF
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
emsg(_(e_empty_buffer));
} else {
emsg(_("E501: At end-of-file"));
}
}
}
RedrawingDisabled--;
no_wait_return--;
redraw_all_later(UPD_NOT_VALID);
update_screen();
need_wait_return = false;
msg_scroll = save_msg_scroll;
}
/// Print the executed command for when 'verbose' is set.
///
/// @param lnum if 0, only print the command.
@ -1545,8 +1473,6 @@ bool parse_cmdline(char **cmdline, exarg_T *eap, cmdmod_T *cmod, const char **er
{
char *after_modifier = NULL;
bool retval = false;
// parsing the command modifiers may set ex_pressedreturn
const bool save_ex_pressedreturn = ex_pressedreturn;
// parsing the command range may require moving the cursor
const pos_T save_cursor = curwin->w_cursor;
// parsing the command range may set the last search pattern
@ -1694,7 +1620,6 @@ end:
if (!retval) {
undo_cmdmod(cmod);
}
ex_pressedreturn = save_ex_pressedreturn;
curwin->w_cursor = save_cursor;
restore_last_search_pattern();
return retval;
@ -2258,7 +2183,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
// When global command is busy, don't ask, will fail below.
if (!global_busy && ea.line1 > ea.line2) {
if (msg_silent == 0) {
if ((flags & DOCMD_VERBOSE) || exmode_active) {
if ((flags & DOCMD_VERBOSE) || silent_mode) {
errormsg = _("E493: Backwards range given");
goto doend;
}
@ -2480,17 +2405,13 @@ char *ex_errmsg(const char *const msg, ...)
return ex_error_buf;
}
/// The "+" string used in place of an empty command in Ex mode.
/// This string is used in pointer comparison.
static const char exmode_plus[] = "+";
/// Handle a range without a command.
/// Returns an error message on failure.
static char *ex_range_without_command(exarg_T *eap)
{
char *errormsg = NULL;
if (*eap->cmd == '|' || (exmode_active && eap->cmd != exmode_plus + 1)) {
if (*eap->cmd == '|') {
eap->cmdidx = CMD_print;
eap->argt = EX_RANGE | EX_COUNT | EX_TRLBAR;
if ((errormsg = invalid_range(eap)) == NULL) {
@ -2517,11 +2438,9 @@ static char *ex_range_without_command(exarg_T *eap)
/// Parse and skip over command modifiers:
/// - update eap->cmd
/// - store flags in "cmod".
/// - Set ex_pressedreturn for an empty command line.
///
/// @param skip_only if false, undo_cmdmod() must be called later to free
/// any cmod_filter_pat and cmod_filter_regmatch.regprog,
/// and ex_pressedreturn may be set.
/// any cmod_filter_pat and cmod_filter_regmatch.regprog.
/// @param[out] errormsg potential error message.
///
/// Call apply_cmdmod() to get the side effects of the modifiers:
@ -2535,7 +2454,6 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod,
{
char *orig_cmd = eap->cmd;
char *cmd_start = NULL;
bool use_plus_cmd = false;
bool has_visual_range = false;
CLEAR_POINTER(cmod);
@ -2561,18 +2479,6 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod,
eap->cmd++;
}
// in ex mode, an empty command (after modifiers) works like :+
if (*eap->cmd == NUL && exmode_active
&& getline_equal(eap->ea_getline, eap->cookie, getexline)
&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
eap->cmd = (char *)exmode_plus;
use_plus_cmd = true;
if (!skip_only) {
ex_pressedreturn = true;
}
break; // no modifiers following
}
// ignore comment and empty lines
if (*eap->cmd == '"') {
// a comment ends at a NL
@ -2587,9 +2493,6 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod,
return FAIL;
}
if (*eap->cmd == NUL) {
if (!skip_only) {
ex_pressedreturn = true;
}
return FAIL;
}
@ -2798,31 +2701,13 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod,
// Since the modifiers have been parsed put the colon on top of the
// space: "'<,'>mod cmd" -> "mod:'<,'>cmd
// Put eap->cmd after the colon.
if (use_plus_cmd) {
size_t len = strlen(cmd_start);
// Special case: empty command uses "+":
// "'<,'>mods" -> "mods *+
// Use "*" instead of "'<,'>" to avoid the command getting
// longer, in case is was allocated.
memmove(orig_cmd, cmd_start, len);
xmemcpyz(orig_cmd + len, S_LEN(" *+"));
} else {
memmove(cmd_start - 5, cmd_start, (size_t)(eap->cmd - cmd_start));
eap->cmd -= 5;
memmove(eap->cmd - 1, ":'<,'>", 6);
}
memmove(cmd_start - 5, cmd_start, (size_t)(eap->cmd - cmd_start));
eap->cmd -= 5;
memmove(eap->cmd - 1, ":'<,'>", 6);
} else {
// No modifiers, move the pointer back.
// Special case: change empty command to "+".
if (use_plus_cmd) {
eap->cmd = "'<,'>+";
} else {
eap->cmd = orig_cmd;
}
eap->cmd = orig_cmd;
}
} else if (use_plus_cmd) {
eap->cmd = (char *)exmode_plus;
}
return OK;
@ -5557,8 +5442,6 @@ static void ex_print(exarg_T *eap)
curwin->w_cursor.lnum = eap->line2;
beginline(BL_SOL | BL_FIX);
}
ex_no_reprint = true;
}
static void ex_goto(exarg_T *eap)
@ -6149,43 +6032,6 @@ static void ex_edit(exarg_T *eap)
/// @param old_curwin curwin before doing a split or NULL
void do_exedit(exarg_T *eap, win_T *old_curwin)
{
// ":vi" command ends Ex mode.
if (exmode_active && (eap->cmdidx == CMD_visual
|| eap->cmdidx == CMD_view)) {
exmode_active = false;
ex_pressedreturn = false;
if (ui_has(kUICmdline)) {
ui_ext_cmdline_block_leave();
}
if (*eap->arg == NUL) {
// Special case: ":global/pat/visual\NLvi-commands"
if (global_busy) {
if (eap->nextcmd != NULL) {
stuffReadbuff(eap->nextcmd);
eap->nextcmd = NULL;
}
const int save_rd = RedrawingDisabled;
RedrawingDisabled = 0;
const int save_nwr = no_wait_return;
no_wait_return = 0;
need_wait_return = false;
const int save_ms = msg_scroll;
msg_scroll = 0;
redraw_all_later(UPD_NOT_VALID);
pending_exmode_active = true;
normal_enter(true);
pending_exmode_active = false;
RedrawingDisabled = save_rd;
no_wait_return = save_nwr;
msg_scroll = save_ms;
}
return;
}
}
if ((eap->cmdidx == CMD_new
|| eap->cmdidx == CMD_tabnew
|| eap->cmdidx == CMD_tabedit
@ -6267,8 +6113,6 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
&& (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
old_curwin->w_alt_fnum = curbuf->b_fnum;
}
ex_no_reprint = true;
}
/// ":gui" and ":gvim" when there is no GUI.
@ -6348,8 +6192,6 @@ static void ex_syncbind(exarg_T *eap)
static void ex_read(exarg_T *eap)
{
int empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
if (eap->usefilter) { // :r!cmd
do_bang(1, eap, false, false, true);
return;
@ -6378,24 +6220,6 @@ static void ex_read(exarg_T *eap)
semsg(_(e_notopen), eap->arg);
}
} else {
if (empty && exmode_active) {
// Delete the empty line that remains. Historically ex does
// this but vi doesn't.
linenr_T lnum;
if (eap->line2 == 0) {
lnum = curbuf->b_ml.ml_line_count;
} else {
lnum = 1;
}
if (*ml_get(lnum) == NUL && u_savedel(lnum, 1) == OK) {
ml_delete(lnum);
if (curwin->w_cursor.lnum > 1
&& curwin->w_cursor.lnum >= lnum) {
curwin->w_cursor.lnum--;
}
deleted_lines_mark(lnum, 1);
}
}
redraw_curbuf_later(UPD_VALID);
}
}
@ -6817,7 +6641,6 @@ void ex_may_print(exarg_T *eap)
if (eap->flags != 0) {
print_line(curwin->w_cursor.lnum, (eap->flags & EXFLAG_NR),
(eap->flags & EXFLAG_LIST), true);
ex_no_reprint = true;
}
}
@ -8237,17 +8060,6 @@ bool is_loclist_cmd(int cmdidx)
return cmdnames[cmdidx].cmd_name[0] == 'l';
}
bool get_pressedreturn(void)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return ex_pressedreturn;
}
void set_pressedreturn(bool val)
{
ex_pressedreturn = val;
}
/// ":checkhealth [plugins]"
static void ex_checkhealth(exarg_T *eap)
{

View File

@ -860,7 +860,7 @@ static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear
// Redraw the statusline in case it uses the current mode using the mode()
// function.
if (!cmd_silent && !exmode_active) {
if (!cmd_silent) {
bool found_one = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@ -1000,10 +1000,6 @@ theend:
char *p = ccline.cmdbuff;
if (ui_has(kUICmdline)) {
// Emit cmdline_block in Ex mode unless cmdbuff is NULL (happens with <C-\><C-N> #39021).
if (exmode_active && p != NULL) {
ui_ext_cmdline_block_append(0, p);
}
ui_ext_cmdline_hide(s->gotesc);
}
if (!cmd_silent) {
@ -1374,8 +1370,7 @@ static int command_line_execute(VimState *state, int key)
// Don't ignore it for the input() function.
if ((s->c == Ctrl_C)
&& s->firstc != '@'
// do clear got_int in Ex mode to avoid infinite Ctrl-C loop
&& (!s->break_ctrl_c || exmode_active)
&& !s->break_ctrl_c
&& !global_busy) {
got_int = false;
}
@ -1481,30 +1476,19 @@ static int command_line_execute(VimState *state, int key)
|| s->c == K_KENTER
|| (s->c == ESC
&& (!KeyTyped || vim_strchr(p_cpo, kCpoEsc) != NULL))) {
// In Ex mode a backslash escapes a newline.
if (exmode_active
&& s->c != ESC
&& ccline.cmdpos == ccline.cmdlen
&& ccline.cmdpos > 0
&& ccline.cmdbuff[ccline.cmdpos - 1] == '\\') {
if (s->c == K_KENTER) {
s->c = '\n';
}
} else {
s->gotesc = false; // Might have typed ESC previously, don't
// truncate the cmdline now.
if (ccheck_abbr(s->c + ABBR_OFF)) {
return command_line_changed(s);
}
if (!cmd_silent) {
if (!ui_has(kUICmdline)) {
msg_cursor_goto(msg_row, 0);
}
ui_flush();
}
return 0;
s->gotesc = false; // Might have typed ESC previously, don't
// truncate the cmdline now.
if (ccheck_abbr(s->c + ABBR_OFF)) {
return command_line_changed(s);
}
if (!cmd_silent) {
if (!ui_has(kUICmdline)) {
msg_cursor_goto(msg_row, 0);
}
ui_flush();
}
return 0;
}
// Completion for 'wildchar', 'wildcharm', and wildtrigger()
@ -1752,8 +1736,8 @@ static int command_line_erase_chars(CommandLineState *s)
redrawcmd();
} else if (ccline.cmdlen == 0 && s->c != Ctrl_W
&& ccline.cmdprompt == NULL && s->indent == 0) {
// In ex and debug mode it doesn't make sense to return.
if (exmode_active || ccline.cmdfirstc == '>') {
// In debug mode it doesn't make sense to return.
if (ccline.cmdfirstc == '>') {
return CMDLINE_NOT_CHANGED;
}
@ -2085,11 +2069,8 @@ static int command_line_handle_key(CommandLineState *s)
case ESC: // get here if p_wc != ESC or when ESC typed twice
case Ctrl_C:
// In exmode it doesn't make sense to return. Except when
// ":normal" runs out of characters. Also when highlight callback is active
// <C-c> should interrupt only it.
if ((exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0))
|| (getln_interrupted_highlight && s->c == Ctrl_C)) {
// When the highlight callback is active <C-c> should interrupt only it.
if (getln_interrupted_highlight && s->c == Ctrl_C) {
getln_interrupted_highlight = false;
return command_line_not_changed(s);
}
@ -2824,7 +2805,6 @@ static int command_line_changed(CommandLineState *s)
if (s->firstc == ':'
&& current_sctx.sc_sid == 0 // only if interactive
&& *p_icm != NUL // 'inccommand' is set
&& !exmode_active // not in ex mode
&& cmdline_star == 0 // not typing a password
&& !vpeekc_any()
&& cmdpreview_may_show(s)) {
@ -4013,7 +3993,7 @@ void redrawcmd(void)
void compute_cmdrow(void)
{
if (exmode_active || msg_scrolled != 0) {
if (msg_scrolled != 0) {
cmdline_row = Rows - 1;
} else {
win_T *wp = lastwin_nofloating(NULL);

View File

@ -277,10 +277,6 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
using_b_ffname = (fname == curbuf->b_ffname) || (sfname == curbuf->b_ffname);
using_b_fname = (fname == curbuf->b_fname) || (sfname == curbuf->b_fname);
// After reading a file the cursor line changes but we don't want to
// display the line.
ex_no_reprint = true;
// don't display the file info for another buffer now
need_fileinfo = false;
@ -1855,13 +1851,8 @@ failed:
u_clearline(curbuf); // cannot use "U" command after adding lines
// In Ex mode: cursor at last new line.
// Otherwise: cursor at first new line.
if (exmode_active) {
curwin->w_cursor.lnum = from + linecnt;
} else {
curwin->w_cursor.lnum = from + 1;
}
// Cursor at first new line.
curwin->w_cursor.lnum = from + 1;
check_cursor_lnum(curwin);
beginline(BL_WHITE | BL_FIX); // on first non-blank

View File

@ -445,6 +445,8 @@ EXTERN int sandbox INIT( = 0);
/// Batch-mode: "-es", "-Es", "-l" commandline argument was given.
EXTERN bool silent_mode INIT( = false);
/// Non-interactive Ex mode ("-es"): stdin is executed as Ex commands. mode() returns "cv".
EXTERN bool exmode_active INIT( = false);
/// Per-subsystem state for Visual/Select mode; see normal_defs.h.
EXTERN VisualState Visual INIT( = { .mode = 'v' });
@ -505,14 +507,6 @@ EXTERN bool finish_op INIT( = false); // true while an operator is pending
EXTERN int opcount INIT( = 0); // count for pending operator
EXTERN int motion_force INIT( = 0); // motion force for pending operator
// Ex Mode (Q) state
EXTERN bool exmode_active INIT( = false); // true if Ex mode is active
/// Flag set when normal_check() should return 0 when entering Ex mode.
EXTERN bool pending_exmode_active INIT( = false);
EXTERN bool ex_no_reprint INIT( = false); // No need to print after z or p.
// 'inccommand' command preview state
EXTERN bool cmdpreview INIT( = false);

View File

@ -644,7 +644,6 @@ static int grid_char_needs_redraw(ScreenGrid *grid, int col, size_t off_to, int
|| linebuf_attr[col] != grid->attrs[off_to]
|| (cols > 1 && linebuf_char[col + 1] == 0
&& linebuf_char[col + 1] != grid->chars[off_to + 1]))
|| exmode_active // TODO(bfredl): what in the actual fuck
|| rdb_flags & kOptRdbFlagNodelta));
}

View File

@ -2956,11 +2956,6 @@ static int vgetorpeek(bool advance)
typebuf_was_empty = true;
}
// return 0 in normal_check()
if (pending_exmode_active) {
exmode_active = true;
}
// no chars to block abbreviations for
typebuf.tb_no_abbr_cnt = 0;
@ -2987,7 +2982,7 @@ static int vgetorpeek(bool advance)
// to the user with showcmd.
int showcmd_idx = 0;
bool showing_partial = false;
if (typebuf.tb_len > 0 && advance && !exmode_active) {
if (typebuf.tb_len > 0 && advance) {
if (((State & (MODE_NORMAL | MODE_INSERT)) || State == MODE_LANGMAP)
&& State != MODE_HITRETURN) {
// this looks nice when typing a dead character map

View File

@ -99,6 +99,11 @@ typedef struct {
size_t size;
} ModuleDef;
typedef struct {
LuaRef getline; ///< Lua function returning the next input line, or nil at EOF.
bool got_line; ///< True if at least one line was read.
} ExDocmdCookie;
#include "lua/executor.c.generated.h"
#include "lua/vim_module.generated.h"
@ -598,6 +603,44 @@ static int nlua_check_interrupt(lua_State *lstate)
return 1;
}
/// LineGetter for nlua_ex_docmd(): calls the Lua `getline` function.
static char *nlua_ex_getline(int c, void *cookie, int indent, bool do_concat)
{
ExDocmdCookie *ecd = cookie;
lua_State *lstate = global_lstate;
nlua_pushref(lstate, ecd->getline);
if (nlua_pcall(lstate, 0, 1)) {
nlua_error(lstate, _("Error executing Ex-mode line getter: %.*s"));
return NULL;
}
if (lua_type(lstate, -1) != LUA_TSTRING) { // nil: EOF.
lua_pop(lstate, 1);
return NULL;
}
char *line = xstrdup(lua_tostring(lstate, -1));
lua_pop(lstate, 1);
ecd->got_line = true;
return line;
}
/// For exmode.lua: Executes one cmdline obtained from `getline`, which is also called for any
/// continuation lines (`:append` text, `:function` body, heredoc, …). Unlike `nvim_exec2()`, errors
/// are emitted (not thrown), so the caller can continue with the next command. #40966
static int nlua_ex_docmd(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
luaL_checktype(lstate, 1, LUA_TFUNCTION);
ExDocmdCookie cookie = { .getline = nlua_ref_global(lstate, 1) };
msg_scroll = true;
need_wait_return = false;
no_wait_return++; // Don't wait for return, e.g. after "-V1" messages.
do_cmdline(NULL, nlua_ex_getline, &cookie, 0);
no_wait_return--;
nlua_unref_global(lstate, cookie.getline);
lua_pushboolean(lstate, cookie.got_line);
return 1;
}
static int nlua_os_exit(lua_State *lstate)
{
if (in_fast_callback > 0) {
@ -900,7 +943,7 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
nlua_common_vim_init(lstate, false);
// vim._core wait helpers
// Bindings for `vim.wait()`.
lua_getfield(lstate, -1, "_core");
lua_pushcfunction(lstate, &nlua_loop_poll);
lua_setfield(lstate, -2, "loop_poll");
@ -908,6 +951,10 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_setfield(lstate, -2, "ui_flush");
lua_pushcfunction(lstate, &nlua_check_interrupt);
lua_setfield(lstate, -2, "check_interrupt");
// Misc vim._core bindings.
lua_pushcfunction(lstate, &nlua_ex_docmd);
lua_setfield(lstate, -2, "ex_docmd");
lua_pop(lstate, 1);
// patch require() (only for --startuptime)

View File

@ -172,13 +172,11 @@ void event_init(void)
static bool event_teardown(void)
{
if (!main_loop.events) {
input_stop();
return true;
}
multiqueue_process_events(main_loop.events);
loop_poll_events(&main_loop, 0); // Drain thread_events, fast_events.
input_stop();
server_teardown();
channel_teardown();
proc_teardown(&main_loop);
@ -417,11 +415,6 @@ int main(int argc, char **argv)
// Set the break level after the terminal is initialized.
debug_break_level = params.use_debug_break_level;
// Read ex-commands if invoked with "-es".
if (!stdin_isatty && !params.input_istext && silent_mode && exmode_active) {
input_start();
}
// Wait for UIs to set up Nvim or show early messages
// and prompts (--cmd, swapfile dialog, …).
bool use_remote_ui = (embedded_mode && !headless_mode);
@ -546,9 +539,7 @@ int main(int argc, char **argv)
//
starting = NO_BUFFERS;
no_wait_return = false;
if (!exmode_active) {
msg_scroll = false;
}
msg_scroll = false;
// Read file (text, not commands) from stdin if:
// - stdin is not a tty
@ -577,7 +568,7 @@ int main(int argc, char **argv)
set_vim_var_string(VV_SWAPCOMMAND, NULL, -1);
// Ex starts at last line of the file.
if (exmode_active) {
if (params.exmode) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
}
@ -676,13 +667,35 @@ int main(int argc, char **argv)
msg_didout = false;
}
getout(lua_ok ? 0 : 1);
} else if (silent_mode) {
// Non-interactive Ex mode (-es): vim._core.exmode.run() executes Ex commands from stdin.
msg_scroll = true;
no_wait_return++; // No hit-enter prompts in batch mode, e.g. after "-V1" messages.
// Read stdin as commands iff a pipe/file: "nvim -es +cmd" in a tty executes and exits, it
// doesn't wait for input. ("-Es" reads stdin as text; already consumed by read_stdin().)
if (!params.input_istext && !stdin_isatty) {
DLOG("executing stdin as Ex commands");
typval_T args[] = { { .v_type = VAR_UNKNOWN } };
nlua_call_typval("vim._core.exmode", "run", args, NULL);
}
if (msg_didout) {
msg_putchar('\n'); // Terminate the last output line.
msg_didout = false;
}
getout(0); // getout() adds `ex_exitval` for "-es".
}
if (params.exmode) {
// Interactive Ex mode (-e/-E): cmdwin REPL. #40962
typval_T args[] = { { .v_type = VAR_UNKNOWN } };
nlua_call_typval("vim._core.exmode", "open", args, NULL);
}
TIME_MSG("before starting main loop");
ILOG("starting main loop");
// Main loop: never returns.
normal_enter(false);
normal_enter();
#if defined(MSWIN) && !defined(MAKE_LIB)
xfree(argv);
@ -723,9 +736,6 @@ void os_exit(int r)
} else {
ml_close_all(true); // remove all memfiles
}
if (used_stdin) {
stream_set_blocking(STDIN_FILENO, true); // normalize stream (#2598)
}
ILOG("Nvim exit: %d", r);
@ -746,7 +756,7 @@ void getout(int exitval)
// make sure startuptimes have been flushed
time_finish();
// On error during Ex mode, exit with a non-zero code.
// On error in "-es" (or explicit ":quit"), exit with a non-zero code.
// POSIX requires this, although it's not 100% clear from the standard.
if (exmode_active) {
exitval += ex_exitval;
@ -882,10 +892,6 @@ void preserve_exit(const char *errmsg)
// Prevent repeated calls into this method.
if (really_exiting) {
if (used_stdin) {
// normalize stream (#2598)
stream_set_blocking(STDIN_FILENO, true);
}
exit(2);
}
@ -1080,7 +1086,7 @@ static bool edit_stdin(mparm_T *parmp)
{
bool implicit = !headless_mode
&& !(embedded_mode && stdin_fd <= 0)
&& (!exmode_active || parmp->input_istext)
&& (!parmp->exmode || parmp->input_istext)
&& !stdin_isatty
&& parmp->edit_type <= EDIT_STDIN
&& parmp->scriptin == NULL; // `-s -` was not given.
@ -1119,9 +1125,10 @@ static void command_line_scan(mparm_T *parmp)
char c = argv[0][argv_idx++];
switch (c) {
case NUL: // "nvim -" read from stdin
if (exmode_active) {
if (parmp->exmode) {
// "nvim -e -" silent mode
silent_mode = true;
exmode_active = true;
parmp->no_swap_file = true;
} else {
if (parmp->edit_type > EDIT_STDIN) {
@ -1214,10 +1221,10 @@ static void command_line_scan(mparm_T *parmp)
parmp->diff_mode = true;
break;
case 'e': // "-e" Ex mode
exmode_active = true;
parmp->exmode = true;
break;
case 'E': // "-E" Ex mode
exmode_active = true;
parmp->exmode = true;
parmp->input_istext = true;
break;
case 'f': // "-f" GUI: run in foreground.
@ -1282,8 +1289,9 @@ static void command_line_scan(mparm_T *parmp)
recoverymode = 1;
break;
case 's':
if (exmode_active) { // "-es" silent (batch) Ex-mode
if (parmp->exmode) { // "-es" silent (batch) mode
silent_mode = true;
exmode_active = true;
parmp->no_swap_file = true;
if (p_shadafile == NULL || *p_shadafile == NUL) {
set_option_value_give_err(kOptShadafile, STATIC_CSTR_AS_OPTVAL("NONE"), 0);
@ -2025,9 +2033,7 @@ static void exe_commands(mparm_T *parmp)
curwin->w_cursor.lnum = 1;
}
if (!exmode_active) {
msg_scroll = false;
}
msg_scroll = false;
// When started with "-q errorfile" jump to first error again.
if (parmp->edit_type == EDIT_QF) {

View File

@ -29,6 +29,7 @@ typedef struct {
char *tagname; // tag from -t argument
char *use_ef; // 'errorfile' from -q argument
bool exmode; // "-e"/"-E": Ex mode
bool input_istext; // stdin is text, not executable (-E/-Es)
int no_swap_file; // "-n" argument used

View File

@ -502,7 +502,7 @@ char *msg_strtrunc(const char *s, int force)
// May truncate message to avoid a hit-return prompt
if ((!msg_scroll && !need_wait_return && shortmess(kShmTruncall)
&& !exmode_active && msg_silent == 0 && !ui_has(kUIMessages))
&& msg_silent == 0 && !ui_has(kUIMessages))
|| force) {
int room;
int len = vim_strsize(s);
@ -1072,7 +1072,7 @@ char *msg_may_trunc(bool force, char *s)
// just in case.
int room = (Rows - cmdline_row - 1) * Columns + sc_col - 1;
if (room > 0
&& (force || (shortmess(kShmTrunc) && !exmode_active))
&& (force || shortmess(kShmTrunc))
&& (int)strlen(s) - room > 0) {
int size = vim_strsize(s);
@ -1436,9 +1436,7 @@ void wait_return(int redraw)
}
need_wait_return = true;
if (no_wait_return) {
if (!exmode_active) {
cmdline_row = msg_row;
}
cmdline_row = msg_row;
return;
}
@ -1448,10 +1446,6 @@ void wait_return(int redraw)
c = CAR; // just pretend CR was hit
quit_more = false;
got_int = false;
} else if (exmode_active) {
msg_puts(" "); // make sure the cursor is on the right line
c = CAR; // no need for a return in ex mode
got_int = false;
} else if (!stuff_empty()) {
// When there are stuffed characters, the next stuffed character will
// dismiss the hit-enter prompt immediately and have to be put back, so
@ -1567,9 +1561,7 @@ void wait_return(int redraw)
// If the user hits ':', '?' or '/' we get a command line from the next
// line.
if (c == ':' || c == '?' || c == '/') {
if (!exmode_active) {
cmdline_row = msg_row;
}
cmdline_row = msg_row;
skip_redraw = true; // skip redraw once
do_redraw = false;
}
@ -2539,7 +2531,7 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse
inc_msg_scrolled();
need_wait_return = true; // may need wait_return() in main()
redraw_cmdline = true;
if (cmdline_row > 0 && !exmode_active) {
if (cmdline_row > 0) {
cmdline_row--;
}
@ -2550,7 +2542,7 @@ static void msg_puts_display(const char *str, int maxlen, int hl_id, int recurse
}
if (p_more && lines_left == 0 && State != MODE_HITRETURN
&& !msg_no_more && !exmode_active) {
&& !msg_no_more) {
if (do_more_prompt(NUL)) {
s = confirm_buttons;
}
@ -3317,7 +3309,7 @@ static void msg_moremsg(bool full)
}
/// Repeat the message for the current mode: MODE_ASKMORE, MODE_EXTERNCMD,
/// confirm() prompt or exmode_active.
/// or confirm() prompt.
void repeat_message(void)
{
if (ui_has(kUIMessages)) {

View File

@ -101,8 +101,6 @@ typedef struct {
bool need_flushbuf;
bool set_prevcount;
bool previous_got_int; // `got_int` was true
bool noexmode; // true if the normal mode was pushed from
// ex mode (:global or :visual for example)
bool toplevel; // top-level normal mode
oparg_T oa; // operator arguments
cmdarg_T ca; // command arguments
@ -502,22 +500,16 @@ bool op_pending(void)
&& current_oap->regname == NUL);
}
/// Normal state entry point. This is called on:
///
/// - Startup, In this case the function never returns.
/// - The :visual command is called from :global in ex mode, `:global/PAT/visual`
/// for example. Returns when re-entering ex mode (because ex mode recursion is
/// not allowed)
/// Normal state entry point: the main loop. Called on startup, never returns.
///
/// This used to be called main_loop() on main.c
void normal_enter(bool noexmode)
void normal_enter(void)
{
NormalState state;
normal_state_init(&state);
oparg_T *prev_oap = current_oap;
current_oap = &state.oa;
state.noexmode = noexmode;
state.toplevel = !noexmode;
state.toplevel = true;
state_enter(&state.state);
current_oap = prev_oap;
}
@ -1265,25 +1257,13 @@ static void normal_check_stuff_buffer(NormalState *s)
static void normal_check_interrupt(NormalState *s)
{
// Reset "got_int" now that we got back to the main loop. Except when
// inside a ":g/pat/cmd" command, then the "got_int" needs to abort
// the ":g" command.
// For ":g/pat/vi" we reset "got_int" when used once. When used
// a second time we go back to Ex mode and abort the ":g" command.
// Reset "got_int" now that we got back to the main loop.
if (got_int) {
if (s->noexmode && global_busy && !exmode_active
&& s->previous_got_int) {
// Typed two CTRL-C in a row: go back to ex mode as if "Q" was
// used and keep "got_int" set, so that it aborts ":g".
exmode_active = true;
State = MODE_NORMAL;
} else if (!global_busy || !exmode_active) {
if (!quit_more) {
// flush all buffers
vgetc();
}
got_int = false;
if (!quit_more) {
// flush all buffers
vgetc();
}
got_int = false;
s->previous_got_int = true;
} else {
s->previous_got_int = false;
@ -1410,9 +1390,7 @@ static int normal_check(VimState *state)
discard_current_exception();
}
if (!exmode_active) {
msg_scroll = false;
}
msg_scroll = false;
quit_more = false;
state_no_longer_safe(NULL);
@ -1420,7 +1398,7 @@ static int normal_check(VimState *state)
// If skip redraw is set (for ":" in wait_return()), don't redraw now.
// If there is nothing in the stuff_buffer or do_redraw is true,
// update cursor and redraw.
if (skip_redraw || exmode_active) {
if (skip_redraw) {
skip_redraw = false;
setcursor();
} else if (do_redraw || stuff_empty()) {
@ -1470,21 +1448,13 @@ static int normal_check(VimState *state)
// only at the very toplevel. Otherwise we may be using a List or
// Dict internally somewhere.
// "may_garbage_collect" is reset in vgetc() which is invoked through
// do_exmode() and normal_cmd().
may_garbage_collect = !s->noexmode;
// normal_cmd().
may_garbage_collect = true;
// Update w_curswant if w_set_curswant has been set.
// Postponed until here to avoid computing w_virtcol too often.
update_curswant();
if (exmode_active) {
if (s->noexmode) {
return 0;
}
do_exmode();
return -1;
}
normal_prepare(s);
return 1;
}
@ -5706,7 +5676,13 @@ static void nv_g_cmd(cmdarg_T *cap)
// "gQ": improved Ex mode
case 'Q':
if (!check_text_locked(cap->oap) && !checkclearopq(oap)) {
do_exmode();
if (ex_normal_busy > 0 || global_busy) {
// Ex mode cannot run inside ":normal" or ":global"; discard the rest of the command.
flush_buffers(FLUSH_TYPEAHEAD);
} else if (!silent_mode) {
typval_T args[] = { { .v_type = VAR_UNKNOWN } };
nlua_call_typval("vim._core.exmode", "open", args, NULL);
}
}
break;

View File

@ -17,8 +17,6 @@
#include "nvim/eval_defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/rstream.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/input.h"
#include "nvim/insexpand.h"
@ -40,39 +38,16 @@
#define READ_BUFFER_SIZE 0xfff
#define INPUT_BUFFER_SIZE ((READ_BUFFER_SIZE * 4) + MAX_KEY_CODE_LEN)
static RStream read_stream = { .s.closed = true }; // Input before UI starts.
static char input_buffer[INPUT_BUFFER_SIZE];
static char *input_read_pos = input_buffer;
static char *input_write_pos = input_buffer;
static bool input_eof = false;
static bool blocking = false;
static int cursorhold_time = 0; ///< time waiting for CursorHold event
static int cursorhold_tb_change_cnt = 0; ///< tb_change_cnt when waiting started
#include "os/input.c.generated.h"
void input_start(void)
{
if (!read_stream.s.closed) {
return;
}
used_stdin = true;
rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO);
rstream_start(&read_stream, input_read_cb, NULL);
}
void input_stop(void)
{
if (read_stream.s.closed) {
return;
}
rstream_stop(&read_stream);
rstream_may_close(&read_stream);
}
static void cursorhold_event(void **argv)
{
event_T event = State & MODE_INSERT ? EVENT_CURSORHOLDI : EVENT_CURSORHOLD;
@ -83,7 +58,7 @@ static void cursorhold_event(void **argv)
static void create_cursorhold_event(bool events_enabled)
{
// If events are enabled and the queue has any items, this function should not
// have been called (`inbuf_poll` would return `kTrue`).
// have been called (`inbuf_poll` would return true).
// TODO(tarruda): Cursorhold should be implemented as a timer set during the
// `state_check` callback for the states where it can be triggered.
assert(!events_enabled || multiqueue_empty(main_loop.events));
@ -139,9 +114,8 @@ int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
ctrl_c_interrupts = false;
}
TriState result; ///< inbuf_poll result.
if (ms >= 0) {
if ((result = inbuf_poll(ms, events)) == kFalse) {
if (!inbuf_poll(ms, events)) {
return 0;
}
} else {
@ -164,10 +138,10 @@ int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
int64_t delay_left = p_acl - ins_compl_autocomplete_elapsed();
wait_time = MIN(MAX(delay_left, 0), wait_time);
}
if ((result = inbuf_poll((int)wait_time, events)) == kFalse) {
if (read_stream.s.closed && silent_mode) {
// Drained eventloop & initial input; exit silent/batch-mode (-es/-Es).
read_error_exit();
if (!inbuf_poll((int)wait_time, events)) {
if (silent_mode) {
// Ran out of input: exit silent/batch-mode (-es/-Es).
getout(0);
}
// The 'autocompletedelay' expired: trigger the popup. When
// 'updatetime' is shorter, fall through to CursorHold instead.
@ -191,7 +165,7 @@ int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
create_cursorhold_event(events == main_loop.events);
} else {
before_blocking();
result = inbuf_poll(-1, events);
inbuf_poll(-1, events);
}
} else {
cursorhold_time += (int)((os_hrtime() - wait_start) / 1000000);
@ -212,10 +186,6 @@ int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
return push_event_key(buf, maxlen);
}
if (result == kNone && ms != 0) {
read_error_exit();
}
return 0;
#undef TRY_READ
@ -224,7 +194,7 @@ int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
// Check if a character is available for reading
bool os_char_avail(void)
{
return inbuf_poll(0, NULL) == kTrue;
return inbuf_poll(0, NULL);
}
/// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed.
@ -550,48 +520,31 @@ bool input_blocking(void)
///
/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait.
/// @param events (optional) Queue to check for pending events.
/// @return TriState:
/// - kTrue: Input/events available
/// - kFalse: No input/events
/// - kNone: EOF reached on the input stream
static TriState inbuf_poll(int ms, MultiQueue *events)
/// @return true if input or events are available.
static bool inbuf_poll(int ms, MultiQueue *events)
{
if (os_input_ready(events)) {
return kTrue;
return true;
}
if (do_profiling == PROF_YES && ms) {
prof_input_start();
}
if ((ms == -1 || ms > 0) && events != main_loop.events && !input_eof) {
if ((ms == -1 || ms > 0) && events != main_loop.events) {
// The pending input provoked a blocking wait. Do special events now. #6247
blocking = true;
multiqueue_process_events(ch_before_blocking_events);
}
DLOG("blocking... events=%s", !!events ? "true" : "false");
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, os_input_ready(events) || input_eof);
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, os_input_ready(events));
blocking = false;
if (do_profiling == PROF_YES && ms) {
prof_input_end();
}
if (os_input_ready(events)) {
return kTrue;
}
return input_eof ? kNone : kFalse;
}
static size_t input_read_cb(RStream *stream, const char *buf, size_t c, void *data, bool at_eof)
{
if (at_eof) {
input_eof = true;
}
assert(input_space() >= c);
input_enqueue_raw(buf, c);
return c;
return os_input_ready(events);
}
static void process_ctrl_c(void)
@ -645,16 +598,6 @@ bool os_input_ready(MultiQueue *events)
|| pending_events(events)); // Events must be processed
}
// Exit because of an input read error.
static void read_error_exit(void)
FUNC_ATTR_NORETURN
{
if (silent_mode) { // Normal way to exit for "nvim -es".
getout(0);
}
preserve_exit(_("Nvim: Error reading input, exiting...\n"));
}
static bool pending_events(MultiQueue *events)
{
return events && !multiqueue_empty(events);

View File

@ -5,8 +5,5 @@
#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/event/defs.h" // IWYU pragma: keep
#include "nvim/macros_defs.h"
EXTERN bool used_stdin INIT( = false);
#include "os/input.h.generated.h"

View File

@ -5317,30 +5317,16 @@ describe('API', function()
)
end)
it('does not interfere with printing line in Ex mode #19400', function()
local screen = Screen.new(60, 7)
local screen = Screen.new(60, 12)
insert([[
foo
bar]])
feed('gQ1')
screen:expect([[
foo |
bar |
{1:~ }|*2
{3: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:1^ |
]])
screen:expect({ any = vim.pesc('{1::}1^') })
eq('Parsing command-line', pcall_err(api.nvim_parse_cmd, '', {}))
-- Executing the line still auto-prints it.
feed('<CR>')
screen:expect([[
foo |
bar |
{3: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:1 |
foo |
:^ |
]])
screen:expect({ any = vim.pesc('" foo') })
end)
it('does not move cursor or change search history/pattern #19878 #19890', function()
api.nvim_buf_set_lines(0, 0, -1, true, { 'foo', 'bar', 'foo', 'bar' })

View File

@ -204,6 +204,7 @@ describe('vim._core', function()
'vim._core.defaults',
'vim._core.editor',
'vim._core.ex_cmd',
'vim._core.exmode',
'vim._core.exrc',
'vim._core.help',
'vim._core.log',

View File

@ -681,6 +681,43 @@ describe('startup', function()
)
-- without `-u`
eq(' encoding=utf-8\n', fn.system({ nvim_prog, '-n', '-es' }, { 'set encoding', '' }))
-- CRLF input (e.g. Windows pipes) works like LF.
eq(' encoding=utf-8\n', fn.system({ nvim_prog, '-n', '-es' }, 'set encoding\r\n'))
-- Stdin is not consumed as typeahead: `:lua` can read the next stdin line as data.
eq(
'thedata\n',
fn.system(
{ nvim_prog, '-n', '-u', 'NONE', '-i', 'NONE', '-es' },
{ 'lua vim.g.d = io.read()', 'thedata', 'put =g:d', 'print', '' }
)
)
-- Cursor starts at the last line (POSIX ex): ed-style "a" appends at end-of-file.
write_file('Xesfile', 'L1\nL2\n')
finally(function()
os.remove('Xesfile')
end)
eq(
'L1\nL2\nX\n',
fn.system(
{ nvim_prog, '-n', '-u', 'NONE', '-i', 'NONE', '-es', 'Xesfile' },
{ 'a', 'X', '.', '%print', '' }
)
)
-- In a terminal, "-es" executes +cmds and exits; it doesn't wait for input.
local screen = Screen.new(60, 6)
fn.jobstart({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '-es', '-V1', '+echo "hello"' }, {
term = true,
env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
})
-- "nvim -es" output ends with a final newline.
screen:expect([[
^hello |
|
[Process exited 0] |
|*3
]])
end)
it('-es/-Es disables swapfile/shada/config #8540', function()
@ -731,14 +768,10 @@ describe('startup', function()
'--clean',
'-es',
'-c',
-- 'showcmd' leads to a char_avail() call just after the 'Q' (no more input).
[[set showcmd | exe "g/^/vi|Vgg:w>>Xoutput.txt\rgQ"]],
[[g/^/.w >>Xoutput.txt]],
'Xinput.txt',
})
eq(
'OUT\nline1\nline1\nline2\nline1\nline2\nline3\nline1\nline2\nline3\nline4\n',
read_file('Xoutput.txt')
)
eq('OUT\nline1\nline2\nline3\nline4\n', read_file('Xoutput.txt'))
end)
it('ENTER dismisses early message #7967', function()
@ -829,35 +862,43 @@ end)
describe('startup', function()
it('-e/-E interactive #7679', function()
clear('-e')
local screen = Screen.new(25, 3)
local screen = Screen.new(40, 12)
feed("put ='from -e'<CR>")
screen:expect([[
:put ='from -e' |
from -e |
:^ |
|
from -e |
{1:~ }|*6
{2:[No Name] [+] }|
{1::}^ |
{3:[Command Line] }|
{5:-- INSERT --} |
]])
clear('-E')
screen = Screen.new(25, 3)
screen = Screen.new(40, 12)
feed("put ='from -E'<CR>")
screen:expect([[
:put ='from -E' |
from -E |
:^ |
|
from -E |
{1:~ }|*6
{2:[No Name] [+] }|
{1::}^ |
{3:[Command Line] }|
{5:-- INSERT --} |
]])
end)
it('-e sets ex mode', function()
it('-e enters Ex mode at startup', function()
clear('-e')
local screen = Screen.new(25, 3)
-- Verify we set the proper mode both before and after :vi.
feed('put =mode(1)<CR>vi<CR>:put =mode(1)<CR>')
screen:expect([[
cv |
^n |
:put =mode(1) |
]])
local screen = Screen.new(40, 12)
-- The keep-open cmdwin REPL is focused, in Insert mode. #40962
screen:expect({ any = vim.pesc('[Command Line]') })
eq(':', fn.getcmdwintype())
feed('visual<CR>')
eq('n', fn.mode(1))
eq('', fn.getcmdwintype())
-- Scripts can detect batch mode ("-es"): mode() is "cv".
eq('cv\n', fn.system({ nvim_prog, '-n', '-es' }, { 'put =mode(1)', 'print', '' }))
end)
@ -1855,44 +1896,3 @@ describe('user session', function()
eq(1, eval('g:lua_session'))
end)
end)
describe('inccommand on ex mode', function()
it('should not preview', function()
clear()
local screen
screen = Screen.new(60, 10)
local id = fn.jobstart({
nvim_prog,
'-u',
'NONE',
'-i',
'NONE',
'-c',
'set termguicolors background=dark',
'-E',
'README.md',
}, {
term = true,
env = { VIMRUNTIME = os.getenv('VIMRUNTIME') },
})
fn.chansend(id, '%s/N')
screen:add_extra_attr_ids({
[101] = {
background = Screen.colors.NvimDarkGrey4,
foreground = Screen.colors.NvimLightGray2,
},
[102] = {
background = Screen.colors.NvimDarkGray2,
foreground = Screen.colors.NvimLightGray2,
},
})
screen:expect([[
{102:^ }|
{102: }|*5
{101: }|
{102:Entering Ex mode. Type "visual" to go to Normal mode. }|
{102::%s/N }|
|
]])
end)
end)

View File

@ -15,6 +15,7 @@ before_each(clear)
describe(':normal!', function()
it('can get out of Insert mode if called from Ex mode #17924', function()
feed('gQnormal! Ifoo<CR>')
feed('visual<CR>')
expect('foo')
end)

View File

@ -6,171 +6,124 @@ local describe, it, before_each = t.describe, t.it, t.before_each
local clear = n.clear
local command = n.command
local eq = t.eq
local eval = n.eval
local feed = n.feed
local api = n.api
local poke_eventloop = n.poke_eventloop
local fn = n.fn
before_each(clear)
-- Interactive Ex mode (gQ) is a keep-open |cmdwin| REPL. #40962
describe('Ex mode', function()
it('supports command line editing', function()
local function test_ex_edit(expected, cmd)
feed('gQ' .. cmd .. '<C-b>"<CR>')
local ret = eval('@:[1:]') -- Remove leading quote.
feed('visual<CR>')
eq(api.nvim_replace_termcodes(expected, true, true, true), ret)
end
command('set sw=2')
test_ex_edit('bar', 'foo bar<C-u>bar')
test_ex_edit('1<C-u>2', '1<C-v><C-u>2')
test_ex_edit('213', '1<C-b>2<C-e>3')
test_ex_edit('2013', '01<Home>2<End>3')
test_ex_edit('0213', '01<Left>2<Right>3')
test_ex_edit('0342', '012<Left><Left><Insert>3<Insert>4')
test_ex_edit('foo ', 'foo bar<C-w>')
test_ex_edit('foo', 'fooba<Del><Del>')
test_ex_edit('foobar', 'foo<Tab>bar')
test_ex_edit('abbreviate', 'abbrev<Tab>')
test_ex_edit('1<C-t><C-t>', '1<C-t><C-t>')
test_ex_edit('1<C-t><C-t>', '1<C-t><C-t><C-d>')
test_ex_edit(' foo', ' foo<C-d>')
test_ex_edit(' foo0', ' foo0<C-d>')
test_ex_edit(' foo^', ' foo^<C-d>')
test_ex_edit('foo', '<BS><C-H><Del><kDel>foo')
-- default wildchar <Tab> interferes with this test
command('set wildchar=<c-e>')
test_ex_edit('a\tb', 'a\t\t<C-H>b')
test_ex_edit('\tm<C-T>n', '\tm<C-T>n')
command('set wildchar&')
it('executes commands against the caller window, auto-prints, records history', function()
local screen = Screen.new(60, 10)
command([[call setline(1, ['line1', 'line2', 'line3'])]])
feed('gQ')
screen:expect([[
line1 |
{2:[No Name] [+] }|
{1::}^ |
{1:~ }|*5
{3:[Command Line] }|
{5:-- INSERT --} |
]])
t.matches('Entering Ex mode%.', n.exec_capture('messages'))
feed("put ='inserted'<CR>")
feed('visual<CR>')
eq('n', fn.mode(1))
eq({ 'line1', 'inserted', 'line2', 'line3' }, fn.getline(1, '$'))
eq('visual', fn.histget('cmd', -1))
eq("put ='inserted'", fn.histget('cmd', -2))
end)
it('bare address and empty line move the cursor and auto-print', function()
local screen = Screen.new(60, 8)
command([[call setline(1, ['line1', 'line2'])]])
feed('gQ')
feed('1<CR>') -- Bare address: move and auto-print.
screen:expect({ any = vim.pesc('" line1') })
feed('<CR>') -- Empty line: advance and auto-print (POSIX ex "+").
screen:expect({ any = vim.pesc('" line2') })
feed('<CR>') -- At end-of-file.
screen:expect({ any = 'E501' })
feed('visual<CR>')
eq(2, fn.line('.'))
end)
it('explicit :print is not doubled by auto-print', function()
command([[call setline(1, ['line1', 'line2'])]])
feed('gQ')
feed('1,2print<CR>')
-- Transcript records each printed line once, not the cursor line again.
eq({ '1,2print', '" line1', '" line2', '' }, n.api.nvim_buf_get_lines(0, 0, -1, false))
feed('visual<CR>')
end)
it('substitute confirmation prompt', function()
command('set noincsearch nohlsearch inccommand=')
local screen = Screen.new(60, 6)
command([[call setline(1, repeat(['foo foo'], 4))]])
command([[set number]])
local screen = Screen.new(60, 8)
command('set inccommand=')
command([[call setline(1, repeat(['foo foo'], 2))]])
feed('gQ')
screen:expect([[
{8: 1 }foo foo |
{8: 2 }foo foo |
{8: 3 }foo foo |
{3: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:^ |
]])
feed('%s/foo/bar/gc<CR>')
screen:expect([[
{8: 1 }foo foo |
{3: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:%s/foo/bar/gc |
{8: 1 }foo foo |
^^^^ |
]])
feed('N<CR>')
screen:expect([[
Entering Ex mode. Type "visual" to go to Normal mode. |
:%s/foo/bar/gc |
{8: 1 }foo foo |
^^^N |
{8: 1 }foo foo |
^^^^ |
]])
feed('n<CR>')
screen:expect([[
{8: 1 }foo foo |
^^^N |
{8: 1 }foo foo |
^^^n |
{8: 1 }foo foo |
^^^^ |
]])
feed('y<CR>')
feed('q<CR>')
screen:expect([[
{8: 1 }foo foo |
^^^y |
{8: 2 }foo foo |
^^^q |
{8: 2 }foo foo |
:^ |
]])
-- Pressing enter in ex mode should print the current line
feed('<CR>')
screen:expect([[
^^^y |
{8: 2 }foo foo |
^^^q |
{8: 2 }foo foo |
{8: 3 }foo foo |
:^ |
]])
-- The printed line should overwrite the colon
feed('<CR>')
screen:expect([[
{8: 2 }foo foo |
^^^q |
{8: 2 }foo foo |
{8: 3 }foo foo |
{8: 4 }foo foo |
:^ |
]])
feed(':vi<CR>')
screen:expect([[
{8: 1 }foo bar |
{8: 2 }foo foo |
{8: 3 }foo foo |
{8: 4 }^foo foo |
{1:~ }|
|
]])
screen:expect({ any = vim.pesc('replace with bar? (y)es/(n)o/(a)ll') })
feed('y') -- Replace the first match ...
feed('q') -- ... then quit at the second prompt: back to the REPL.
screen:expect({ any = vim.pesc('{5:-- INSERT --}') })
feed('visual<CR>')
eq({ 'bar foo', 'foo foo' }, fn.getline(1, '$'))
end)
it('pressing Ctrl-C in :append inside a loop in Ex mode does not hang', function()
local screen = Screen.new(60, 6)
it('collects multi-line commands until the block is complete', function()
local screen = Screen.new(60, 10)
feed('gQ')
feed('append<CR>')
-- Continuation lines should not have ":" prefix in 'statuscolumn'.
screen:expect([[
|
{2:[No Name] }|
{1::}append |
{1: }^ |
{1:~ }|*4
{3:[Command Line] }|
{5:-- INSERT --} |
]])
feed('x1<CR>x2<CR>.<CR>')
-- Executing :append does not end the REPL's Insert mode (ex_append restores "State").
eq('i', fn.mode(1))
-- Ending :append with "." does not auto-print the last inserted line.
eq({ 'append', 'x1', 'x2', '.', '' }, n.api.nvim_buf_get_lines(0, 0, -1, false))
feed('let g:x = 0<CR>')
feed('if 1<CR>')
-- Continuation lines should not have ":" prefix in 'statuscolumn'.
screen:expect([[
x1 |
{2:[No Name] [+] }|
{1: }x1 |
{1: }x2 |
{1: }. |
{1::}let g:x = 0 |
{1::}if 1 |
{1: }^ |
{3:[Command Line] }|
{5:-- INSERT --} |
]])
feed('let g:x = 1<CR>endif<CR>')
feed('function! F()<CR>return 42<CR>endfunction<CR>')
feed('let g:h =<lt><lt> trim EOS<CR> hey<CR>EOS<CR>')
feed('visual<CR>')
eq({ 'x1', 'x2' }, fn.getline(1, '$'))
eq(1, n.eval('g:x'))
eq(42, n.eval('F()'))
eq({ 'hey' }, n.eval('g:h'))
end)
it('pressing Ctrl-C in :append inside a loop does not hang', function()
feed('gQ')
feed('for i in range(1)<CR>')
feed('append<CR>')
screen:expect([[
{3: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:for i in range(1) |
|
: append |
^ |
]])
-- CTRL-C cancels the cmdwin (ends Ex mode): back to the ":" cmdline, like |q:|.
feed('<C-C>')
poke_eventloop() -- Wait for input to be flushed
feed('foo<CR>')
screen:expect([[
Entering Ex mode. Type "visual" to go to Normal mode. |
:for i in range(1) |
|
: append |
foo |
^ |
]])
feed('.<CR>')
screen:expect([[
:for i in range(1) |
|
: append |
foo |
. |
: ^ |
]])
feed('endfor<CR>')
feed('vi<CR>')
screen:expect([[
^foo |
{1:~ }|*4
|
]])
eq('', fn.getcmdwintype())
feed('<Esc>')
eq('n', fn.mode(1))
eq({ '' }, fn.getline(1, '$'))
end)
end)

View File

@ -30,17 +30,17 @@ describe(':global', function()
{9:Interrupted} |
]])
-- Also test in Ex mode
-- Also test in Ex mode (keep-open cmdwin REPL)
feed('gQg/foo/norm :<C-V>;<CR>')
poke_eventloop() -- Wait for :sleep to start
feed('<C-C>')
screen:expect([[
{3: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:g/foo/norm :; |
|
{9:Interrupted} |
:^ |
foo |
{2:[No Name] [+] }|
{1::}" Keyboard interrupt |
{1::}^ |
{3:[Command Line] }|
{5:-- INSERT --} |
]])
end)
end)

View File

@ -523,13 +523,13 @@ end)
describe('buffer cursor position is correct in terminal without number column', function()
local screen
local function setup_ex_register(str)
local function setup_register_and_prompt(str, prompt)
prompt = ':' .. (prompt or '')
screen = tt.setup_child_nvim({
'-u',
'NONE',
'-i',
'NONE',
'-E',
'--cmd',
string.format('let @r = "%s"', str),
-- <Left> and <Right> don't always work
@ -539,16 +539,16 @@ describe('buffer cursor position is correct in terminal without number column',
'cnoremap <C-O> <Right>',
'--cmd',
'set notermguicolors',
-- A blocked cmdline prompt at the last row, as a stable text-entry to test cursor tracking.
('+call input("%s")'):format(prompt),
}, {
cols = 70,
})
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:^ |
screen:expect(([[
|*5
%s^%s|
{5:-- TERMINAL --} |
]])
]]):format(prompt, (' '):rep(70 - #prompt)))
end
before_each(function()
@ -558,24 +558,20 @@ describe('buffer cursor position is correct in terminal without number column',
describe('in a line with no multibyte chars or trailing spaces,', function()
before_each(function()
setup_ex_register('aaaaaaaa')
setup_register_and_prompt('aaaaaaaa')
end)
it('at the end', function()
feed('<C-R>r')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:aaaaaaaa^ |
{5:-- TERMINAL --} |
]])
eq({ 6, 9 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:aaaaaaa^a |
|
]])
@ -585,18 +581,14 @@ describe('buffer cursor position is correct in terminal without number column',
it('near the end', function()
feed('<C-R>r<C-X><C-X>')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:aaaaaa^aa |
{5:-- TERMINAL --} |
]])
eq({ 6, 7 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:aaaaa^aaa |
|
]])
@ -606,18 +598,14 @@ describe('buffer cursor position is correct in terminal without number column',
it('near the start', function()
feed('<C-R>r<C-B><C-O>')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:a^aaaaaaa |
{5:-- TERMINAL --} |
]])
eq({ 6, 2 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:^aaaaaaaa |
|
]])
@ -627,24 +615,20 @@ describe('buffer cursor position is correct in terminal without number column',
describe('in a line with single-cell multibyte chars and no trailing spaces,', function()
before_each(function()
setup_ex_register('µµµµµµµµ')
setup_register_and_prompt('µµµµµµµµ')
end)
it('at the end', function()
feed('<C-R>r')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:µµµµµµµµ^ |
{5:-- TERMINAL --} |
]])
eq({ 6, 17 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:µµµµµµµ^µ |
|
]])
@ -654,18 +638,14 @@ describe('buffer cursor position is correct in terminal without number column',
it('near the end', function()
feed('<C-R>r<C-X><C-X>')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:µµµµµµ^µµ |
{5:-- TERMINAL --} |
]])
eq({ 6, 13 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:µµµµµ^µµµ |
|
]])
@ -675,18 +655,14 @@ describe('buffer cursor position is correct in terminal without number column',
it('near the start', function()
feed('<C-R>r<C-B><C-O>')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:µ^µµµµµµµ |
{5:-- TERMINAL --} |
]])
eq({ 6, 3 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:^µµµµµµµµ |
|
]])
@ -696,24 +672,20 @@ describe('buffer cursor position is correct in terminal without number column',
describe('in a line with single-cell composed multibyte chars and no trailing spaces,', function()
before_each(function()
setup_ex_register('µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳')
setup_register_and_prompt('µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳')
end)
it('at the end', function()
feed('<C-R>r')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳^ |
{5:-- TERMINAL --} |
]])
eq({ 6, 33 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳ |
|
]])
@ -724,18 +696,14 @@ describe('buffer cursor position is correct in terminal without number column',
skip(is_os('win'))
feed('<C-R>r<C-X><C-X>')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:µ̳µ̳µ̳µ̳µ̳µ̳^µ̳µ̳ |
{5:-- TERMINAL --} |
]])
eq({ 6, 25 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:µ̳µ̳µ̳µ̳µ̳^µ̳µ̳µ̳ |
|
]])
@ -746,18 +714,14 @@ describe('buffer cursor position is correct in terminal without number column',
skip(is_os('win'))
feed('<C-R>r<C-B><C-O>')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:µ̳^µ̳µ̳µ̳µ̳µ̳µ̳µ̳ |
{5:-- TERMINAL --} |
]])
eq({ 6, 5 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:^µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳ |
|
]])
@ -767,24 +731,20 @@ describe('buffer cursor position is correct in terminal without number column',
describe('in a line with double-cell multibyte chars and no trailing spaces,', function()
before_each(function()
setup_ex_register('哦哦哦哦哦哦哦哦')
setup_register_and_prompt('哦哦哦哦哦哦哦哦')
end)
it('at the end', function()
feed('<C-R>r')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:^ |
{5:-- TERMINAL --} |
]])
eq({ 6, 25 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:^ |
|
]])
@ -794,18 +754,14 @@ describe('buffer cursor position is correct in terminal without number column',
it('near the end', function()
feed('<C-R>r<C-X><C-X>')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:^ |
{5:-- TERMINAL --} |
]])
eq({ 6, 19 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:^ |
|
]])
@ -815,18 +771,14 @@ describe('buffer cursor position is correct in terminal without number column',
it('near the start', function()
feed('<C-R>r<C-B><C-O>')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:^ |
{5:-- TERMINAL --} |
]])
eq({ 6, 4 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
|*5
:^ |
|
]])
@ -835,39 +787,33 @@ describe('buffer cursor position is correct in terminal without number column',
end)
it('at the end of a line with trailing spaces #16234', function()
setup_ex_register('aaaaaaaa ')
feed('<C-R>r')
-- Use the raw tty-test child: it echoes input verbatim, so the trailing spaces are real
-- terminal cells (a cmdline/TUI child never emits trailing blank cells).
screen = tt.setup_screen(nil, nil, 70)
tt.feed_data('aaaaaaaa ')
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:aaaaaaaa ^ |
tty ready |
aaaaaaaa ^ |
|*4
{5:-- TERMINAL --} |
]])
matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()'))
eq({ 6, 13 }, eval('nvim_win_get_cursor(0)'))
matches('^aaaaaaaa [ ]*$', eval('nvim_get_current_line()'))
eq({ 2, 12 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
|*3
{2: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:aaaaaaaa ^ |
|
]])
eq({ 6, 12 }, eval('nvim_win_get_cursor(0)'))
eq({ 2, 11 }, eval('nvim_win_get_cursor(0)'))
end)
end)
describe('buffer cursor position is correct in terminal with number column', function()
local screen
local function setup_ex_register(str)
local function setup_register_and_prompt(str, prompt)
prompt = ':' .. (prompt or '')
screen = tt.setup_child_nvim({
'-u',
'NONE',
'-i',
'NONE',
'-E',
'--cmd',
string.format('let @r = "%s"', str),
-- <Left> and <Right> don't always work
@ -877,31 +823,33 @@ describe('buffer cursor position is correct in terminal with number column', fun
'cnoremap <C-O> <Right>',
'--cmd',
'set notermguicolors',
-- A blocked cmdline prompt at the last row, as a stable text-entry to test cursor tracking.
('+call input("%s")'):format(prompt),
}, {
cols = 70,
})
screen:expect([[
screen:expect(([[
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 6 }:^ |
{121: 4 } |
{121: 5 } |
{121: 6 }%s^%s|
{5:-- TERMINAL --} |
]])
]]):format(prompt, (' '):rep(66 - #prompt)))
end
before_each(function()
clear()
command('autocmd! nvim.terminal TermOpen')
-- 'number' should be set before the terminal process starts, otherwise the resize
-- from setting 'number' may cause a redraw that removes the "Entering Ex mode".
-- from setting 'number' may cause a redraw that changes the child screen.
command('set number')
end)
describe('in a line with no multibyte chars or trailing spaces,', function()
before_each(function()
setup_ex_register('aaaaaaaa')
setup_register_and_prompt('aaaaaaaa')
end)
it('at the end', function()
@ -910,8 +858,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:aaaaaaaa^ |
{5:-- TERMINAL --} |
]])
@ -921,8 +869,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:aaaaaaa^a |
|
]])
@ -935,8 +883,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:aaaaaa^aa |
{5:-- TERMINAL --} |
]])
@ -946,8 +894,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:aaaaa^aaa |
|
]])
@ -960,8 +908,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:a^aaaaaaa |
{5:-- TERMINAL --} |
]])
@ -971,8 +919,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:^aaaaaaaa |
|
]])
@ -982,7 +930,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
describe('in a line with single-cell multibyte chars and no trailing spaces,', function()
before_each(function()
setup_ex_register('µµµµµµµµ')
setup_register_and_prompt('µµµµµµµµ')
end)
it('at the end', function()
@ -991,8 +939,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:µµµµµµµµ^ |
{5:-- TERMINAL --} |
]])
@ -1002,8 +950,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:µµµµµµµ^µ |
|
]])
@ -1016,8 +964,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:µµµµµµ^µµ |
{5:-- TERMINAL --} |
]])
@ -1027,8 +975,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:µµµµµ^µµµ |
|
]])
@ -1041,8 +989,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:µ^µµµµµµµ |
{5:-- TERMINAL --} |
]])
@ -1052,8 +1000,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:^µµµµµµµµ |
|
]])
@ -1063,7 +1011,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
describe('in a line with single-cell composed multibyte chars and no trailing spaces,', function()
before_each(function()
setup_ex_register('µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳')
setup_register_and_prompt('µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳')
end)
it('at the end', function()
@ -1072,8 +1020,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳^ |
{5:-- TERMINAL --} |
]])
@ -1083,8 +1031,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳ |
|
]])
@ -1098,8 +1046,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳^µ̳µ̳ |
{5:-- TERMINAL --} |
]])
@ -1109,8 +1057,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:µ̳µ̳µ̳µ̳µ̳^µ̳µ̳µ̳ |
|
]])
@ -1124,8 +1072,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:µ̳^µ̳µ̳µ̳µ̳µ̳µ̳µ̳ |
{5:-- TERMINAL --} |
]])
@ -1135,8 +1083,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:^µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳ |
|
]])
@ -1146,7 +1094,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
describe('in a line with double-cell multibyte chars and no trailing spaces,', function()
before_each(function()
setup_ex_register('哦哦哦哦哦哦哦哦')
setup_register_and_prompt('哦哦哦哦哦哦哦哦')
end)
it('at the end', function()
@ -1155,8 +1103,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:^ |
{5:-- TERMINAL --} |
]])
@ -1166,8 +1114,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:^ |
|
]])
@ -1180,8 +1128,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:^ |
{5:-- TERMINAL --} |
]])
@ -1191,8 +1139,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:^ |
|
]])
@ -1205,8 +1153,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:^ |
{5:-- TERMINAL --} |
]])
@ -1216,8 +1164,8 @@ describe('buffer cursor position is correct in terminal with number column', fun
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 4 } |
{121: 5 } |
{121: 6 }:^ |
|
]])
@ -1226,29 +1174,25 @@ describe('buffer cursor position is correct in terminal with number column', fun
end)
it('at the end of a line with trailing spaces #16234', function()
setup_ex_register('aaaaaaaa ')
feed('<C-R>r')
-- Raw tty-test child: trailing spaces are real terminal cells. See the no-number variant.
-- 'number' is set after the terminal opens: setup_screen() asserts the default layout.
command('set nonumber')
screen = tt.setup_screen(nil, nil, 70)
command('set number')
screen:expect({ any = 'rows: 6, cols: 66' })
tt.feed_data('aaaaaaaa ')
screen:expect([[
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 6 }:aaaaaaaa ^ |
{121: 1 }tty ready |
{121: 2 }rows: 6, cols: 66 |
{121: 3 }aaaaaaaa ^ |
{121: 4 } |
{121: 5 } |
{121: 6 } |
{5:-- TERMINAL --} |
]])
matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()'))
eq({ 6, 13 }, eval('nvim_win_get_cursor(0)'))
matches('^aaaaaaaa [ ]*$', eval('nvim_get_current_line()'))
eq({ 3, 12 }, eval('nvim_win_get_cursor(0)'))
feed([[<C-\><C-N>]])
screen:expect([[
{121: 1 } |
{121: 2 } |
{121: 3 } |
{121: 4 }{2: }|
{121: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
{121: 6 }:aaaaaaaa ^ |
|
]])
eq({ 6, 12 }, eval('nvim_win_get_cursor(0)'))
eq({ 3, 11 }, eval('nvim_win_get_cursor(0)'))
end)
end)

View File

@ -690,7 +690,7 @@ describe('TUI :restart', function()
-- Assert the command-line echo so the E37 assertion below doesn't "succeed immediately".
screen:expect({ any = vim.pesc(cmd) })
tt.feed_data('\013')
screen:expect({ any = vim.pesc('Vim(qall):E37: No write since last change') })
screen:expect({ any = vim.pesc('E37: No write since last change') })
assert_exitreason(('QuitPre:%s\nExitPre:%s\n'):format(exitreason, exitreason))
end

View File

@ -782,35 +782,34 @@ local function test_cmdline(linegrid)
end)
it('works with exmode', function()
screen:try_resize(60, 12)
feed('gQ')
screen:expect({
grid = [[
|
{3: }|
Entering Ex mode. Type "|
visual" to go to Normal m|
ode.^ |
]],
cmdline = { { content = { { '' } }, firstc = ':', pos = 0 } },
})
screen:expect([[
|
{1:~ }|
{2:[No Name] }|
{1::}^ |
{1:~ }|*6
{3:[Command Line] }|
{5:-- INSERT --} |
]])
feed('echo "foo"<CR>')
screen:expect({
grid = [[
{3: }|
Entering Ex mode. Type "|
visual" to go to Normal m|
ode. |
foo^ |
]],
cmdline = { { content = { { '' } }, firstc = ':', pos = 0 } },
cmdline_block = { { { 'echo "foo"' } } },
})
-- Shouldn't crash for NULL cmdline_block event after <C-\><C-N> #39021.
screen:expect([[
|
{1:~ }|
{2:[No Name] }|
{1::}echo "foo" |
{1::}" foo |
{1::}^ |
{1:~ }|*4
{3:[Command Line] }|
{5:-- INSERT --} |
]])
feed('<C-\\><C-N>vis<CR>')
screen:expect([[
^ |
{1:~ }|*3
|
^ |
{1:~ }|*10
|
]])
assert_alive()
end)
@ -1218,7 +1217,7 @@ end)
it('tabline is not redrawn in Ex mode #24122', function()
clear()
local screen = Screen.new(60, 5)
local screen = Screen.new(60, 8)
exec([[
set showtabline=2
@ -1234,18 +1233,22 @@ it('tabline is not redrawn in Ex mode #24122', function()
screen:expect([[
{2:foo }|
|
{3: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:^ |
{2:[No Name] }|
{1::}^ |
{1:~ }|*2
{3:[Command Line] }|
{5:-- INSERT --} |
]])
feed('echo 1<CR>')
screen:expect([[
{3: }|
Entering Ex mode. Type "visual" to go to Normal mode. |
:echo 1 |
1 |
:^ |
{2:foo }|
|
{2:[No Name] }|
{1::}echo 1 |
{1::}" 1 |
{1::}^ |
{3:[Command Line] }|
{5:-- INSERT --} |
]])
end)

View File

@ -2090,23 +2090,15 @@ describe('ui/builtin messages', function()
it('prints lines in Ex mode correctly with a burst of carriage returns #19341', function()
command('set number')
api.nvim_buf_set_lines(0, 0, 0, true, { 'aaa', 'bbb', 'ccc' })
feed('gggQ<CR><CR>1<CR><CR>vi')
-- Empty lines advance the cursor and print; a bare address moves and prints.
feed('gggQ<CR><CR>1<CR><CR>')
screen:expect({ any = vim.pesc('" bbb') })
feed('vi<CR>')
screen:expect([[
Entering Ex mode. Type "visual" to go to Normal mode. |
{8: 2 }bbb |
{8: 3 }ccc |
:1 |
{8: 1 }aaa |
{8: 2 }bbb |
:vi^ |
]])
feed('<CR>')
screen:expect([[
{8: 1 }aaa |
{8: 2 }^bbb |
{8: 3 }ccc |
{8: 4 } |
{1:~ }|*2
{1:~ }|*3
|
]])
end)
@ -2600,11 +2592,20 @@ describe('ui/msg_puts_printf', function()
fn.mkdir(locale_dir, 'p')
fn.filecopy(build_dir .. '/src/nvim/po/ja.mo', locale_dir .. '/nvim.mo')
-- "-Es" doesn't read stdin as commands, and the "Entering Ex mode" banner was removed, so
-- ":print" a translated message via "-S" (silent mode suppresses ":echo" output). #40966
t.write_file(
'Xes_ja.vim',
[[call setline(1, gettext("Entering Ex mode. Type \"visual\" to go to Normal mode."))]]
.. '\n'
.. '.print\n'
)
finally(function()
os.remove('Xes_ja.vim')
n.rmdir(vim.fs.dirname(locale_dir))
end)
cmd = cmd .. '"' .. nvim_prog .. '" -u NONE -i NONE -Es -V1'
cmd = cmd .. '"' .. nvim_prog .. '" -u NONE -i NONE -Es -S Xes_ja.vim'
command([[call jobstart(']] .. cmd .. [[',{'term':v:true})]])
screen:expect([[
^Exモードに入ります |

View File

@ -108,6 +108,7 @@ endfunc
" Test for displaying lines from an empty buffer in Ex mode
func Test_Ex_emptybuf()
throw 'Skipped: Nvim implements interactive Ex mode as a cmdwin script'
new
call assert_fails('call feedkeys("Q\<CR>", "xt")', 'E749:')
call setline(1, "abc")
@ -168,6 +169,7 @@ endfunc
" Test for :g/pat/visual to run vi commands in Ex mode
" This used to hang Vim before 8.2.0274.
func Test_Ex_global()
throw 'Skipped: Nvim implements interactive Ex mode as a cmdwin script'
new
call setline(1, ['', 'foo', 'bar', 'foo', 'bar', 'foo'])
call feedkeys("Q\<bs>g/bar/visual\<CR>$rxQ$ryQvisual\<CR>j", "xt")
@ -235,13 +237,15 @@ endfunc
" In Ex-mode, a backslash escapes a newline
func Test_Ex_escape_enter()
throw 'Skipped: Nvim implements interactive Ex mode as a cmdwin script'
call feedkeys("gQlet l = \"a\\\<kEnter>b\"\<cr>vi\<cr>", 'xt')
call assert_equal("a\rb", l)
endfunc
" Test for :append! command in Ex mode
func Test_Ex_append()
throw 'Skipped: Nvim only supports Vim Ex mode'
" feedkeys('x') implies ex_normal_busy; Ex mode cannot run inside :normal.
throw 'Skipped: Nvim implements interactive Ex mode as a cmdwin script'
new
call setline(1, "\t abc")
call feedkeys("Qappend!\npqr\nxyz\n.\nvisual\n", 'xt')
@ -370,6 +374,7 @@ endfunc
" Testing implicit print command
func Test_implicit_print()
throw 'Skipped: Nvim implements interactive Ex mode as a cmdwin script'
new
call setline(1, ['one', 'two', 'three'])
call feedkeys('Q:let a=execute(":1,2")', 'xt')
@ -381,6 +386,8 @@ endfunc
" Test inserting text after the trailing bar
func Test_insert_after_trailing_bar()
" feedkeys('x') implies ex_normal_busy; Ex mode cannot run inside :normal.
throw 'Skipped: Nvim implements interactive Ex mode as a cmdwin script'
new
call feedkeys("Qi|\nfoo\n.\na|bar\nbar\n.\nc|baz\n.", "xt")
call assert_equal(['', 'foo', 'bar', 'baz'], getline(1, '$'))
@ -389,6 +396,7 @@ endfunc
" Test global insert of a newline without terminating period
func Test_global_insert_newline()
throw 'Skipped: Nvim implements interactive Ex mode as a cmdwin script'
new
call setline(1, ['foo'])
call feedkeys("Qg/foo/i\\\n", "xt")
@ -398,6 +406,7 @@ endfunc
" An empty command followed by a newline shouldn't cause E749 in Ex mode.
func Test_ex_empty_command_newline()
throw 'Skipped: Nvim implements interactive Ex mode as a cmdwin script'
let g:var = 0
call feedkeys("gQexecute \"\\nlet g:var = 1\"\r", 'xt')
call assert_equal(1, g:var)

View File

@ -578,9 +578,11 @@ func Test_read_cmd()
new
read Xcmdfile
call assert_equal(['', 'one'], getline(1, '$'))
call deletebufline('', 1, '$')
call feedkeys("Qr Xcmdfile\<CR>visual\<CR>", 'xt')
call assert_equal(['one'], getline(1, '$'))
" Nvim: Ex mode is a cmdwin wrapper; cannot run inside feedkeys('x'), and
" ":read" no longer deletes the empty first line in Ex mode.
"call deletebufline('', 1, '$')
"call feedkeys("Qr Xcmdfile\<CR>visual\<CR>", 'xt')
"call assert_equal(['one'], getline(1, '$'))
bw!
endfunc

View File

@ -814,10 +814,12 @@ func Test_mode()
call assert_equal('c-c', g:current_modes)
call feedkeys(":\<Insert>\<F2>\<CR>", 'xt')
call assert_equal("c-cr", g:current_modes)
call feedkeys("gQ\<F2>vi\<CR>", 'xt')
call assert_equal('c-cv', g:current_modes)
call feedkeys("gQ\<Insert>\<F2>vi\<CR>", 'xt')
call assert_equal("c-cvr", g:current_modes)
" Nvim: interactive Ex mode is a cmdwin wrapper: mode()=n/i ("cv" means
" -es); cannot run inside :normal/feedkeys('x').
"call feedkeys("gQ\<F2>vi\<CR>", 'xt')
"call assert_equal('c-cv', g:current_modes)
"call feedkeys("gQ\<Insert>\<F2>vi\<CR>", 'xt')
"call assert_equal("c-cvr", g:current_modes)
" Commandline mode in Visual mode should return "c-c", never "v-v".
call feedkeys("v\<Cmd>call input('')\<CR>\<F2>\<CR>\<Esc>", 'xt')
@ -825,10 +827,10 @@ func Test_mode()
" Executing commands in Vim Ex mode should return "cv", never "cvr",
" as Cmdline editing has already ended.
call feedkeys("gQcall Save_mode()\<CR>vi\<CR>", 'xt')
call assert_equal('c-cv', g:current_modes)
call feedkeys("gQ\<Insert>call Save_mode()\<CR>vi\<CR>", 'xt')
call assert_equal('c-cv', g:current_modes)
"call feedkeys("gQcall Save_mode()\<CR>vi\<CR>", 'xt')
"call assert_equal('c-cv', g:current_modes)
"call feedkeys("gQ\<Insert>call Save_mode()\<CR>vi\<CR>", 'xt')
"call assert_equal('c-cv', g:current_modes)
" call feedkeys("Qcall Save_mode()\<CR>vi\<CR>", 'xt')
" call assert_equal('c-ce', g:current_modes)

View File

@ -1188,7 +1188,8 @@ endfunc
" Test for the "-E" (improved Ex mode) argument
func Test_E_arg()
let after =<< trim [CODE]
call assert_equal('cv', mode(1))
" Nvim: "-E" enters the cmdwin REPL after startup scripts; mode()="n".
call assert_equal('n', mode(1))
call writefile(v:errors, 'Xtestout')
qall
[CODE]