mirror of
https://github.com/neovim/neovim.git
synced 2026-09-05 13:40:57 +00:00
Merge #41529 from justinmk/cmdatom
This commit is contained in:
@@ -485,11 +485,18 @@ All the global variables are declared in `globals.h`.
|
||||
|
||||
THE MAIN EVENT-LOOP
|
||||
|
||||
The main loop is implemented in state_enter. The basic idea is that Vim waits
|
||||
The main loop is implemented in state_enter. The basic idea is that Nvim waits
|
||||
for the user to type a character and processes it until another character is
|
||||
needed. Thus there are several places where Vim waits for a character to be
|
||||
needed. Thus there are several places where Nvim waits for a character to be
|
||||
typed. The `vgetc()` function is used for this. It also handles mapping.
|
||||
|
||||
Internal logic "stuffs" keys (readahead) only for a consumer on the current
|
||||
call stack: `exec_stuffed()`, or a nested reader it invokes. See "Stuffing"
|
||||
in input.c. (This is simpler and more predictable than Vim, where a command
|
||||
may stuff chars, and let the main loop deal with them at some arbitrary future
|
||||
point, thus requiring global flags checked at the right time, scattered
|
||||
throughout the codebase.)
|
||||
|
||||
What we consider the "Nvim event loop" is actually a wrapper around `uv_run` to
|
||||
handle both the `fast_events` queue and possibly (a suitable subset of) deferred
|
||||
events. Therefore "raw" `vim.uv.run()` is often not enough to yield from Lua;
|
||||
|
||||
@@ -4708,7 +4708,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const
|
||||
}
|
||||
cmdline_row = msg_row;
|
||||
|
||||
stuffReadbuffSpec(defstr);
|
||||
stuffReadbuffSpecial(defstr);
|
||||
|
||||
const int save_ex_normal_busy = ex_normal_busy;
|
||||
ex_normal_busy = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// input.c: The input engine "bytes layer" (input_cmdatom.c. is the "policy layer").
|
||||
// input.c: The input engine "bytes layer" (input_cmdatom.c is the "policy layer").
|
||||
//
|
||||
// - Get a character from the user, a script file, or the keybufs described below.
|
||||
// - Apply mappings and abbreviations to typed keys (the :map tables live in mapping.c; the
|
||||
@@ -8,7 +8,12 @@
|
||||
//
|
||||
// Concepts:
|
||||
// - "Stuffing" = when some internal logic pushes keys to execute next. This is how a cmd
|
||||
// "translates" into another cmd: "x" stuffs "dl" (nv_optrans()), "." stuffs the redo buf.
|
||||
// "translates" into another cmd: "x" stuffs "dl", "." stuffs the redo buf.
|
||||
// - XXX NOTE: stuffed keys must have a consumer in the current codepath (not "later on the main
|
||||
// loop"). There are 3 different rituals, depending on the consumer:
|
||||
// 1. basic translation: stuff, then exec_stuffed(); runs as CmdFrames of the stuffing command.
|
||||
// 2. input staged for a nested reader (getcmdline(), edit()): stuff only.
|
||||
// 3. main-loop wakeup (K_NOP): stuffed so it precedes input (unlike an event).
|
||||
// - TYPEAHEAD vs READ-AHEAD:
|
||||
// - typeahead = external input that arrived faster than can be executed (user typed fast).
|
||||
// - readahead = Nvim's own self-input.
|
||||
@@ -18,10 +23,14 @@
|
||||
// (KeyStuffed).
|
||||
//
|
||||
// These buffers are used:
|
||||
// - stuff buffers (`readbuf1`, `readbuf2`) are readahead buffers.
|
||||
// - TWO stuff buffers, because stuffing nests: a cmd executed FROM redo keys (readbuf2) may
|
||||
//
|
||||
// input ──► [ typebuf │ readbuf2 (redo) │ readbuf1 (stuff) ] ──► vgetc() ──► exec
|
||||
// ▲ mappings expand at typebuf front
|
||||
//
|
||||
// - typeahead (`typebuf`).
|
||||
// - readahead/stuff buffers (`readbuf1`, `readbuf2`).
|
||||
// - Two stuff buffers, because stuffing nests: a cmd executed FROM redo keys (readbuf2) may
|
||||
// itself stuff a translation (readbuf1), which must be consumed before the remaining redo.
|
||||
// - `typebuf`: typeahead (see below).
|
||||
// - `redobuff` (RedoState): the current + previous change.
|
||||
// - `recordbuff`: accumulates the keys of a recording ("q").
|
||||
//
|
||||
@@ -750,7 +759,7 @@ void stuffReadbuffLen(const char *s, ptrdiff_t len)
|
||||
/// Stuff "s" into the stuff buffer, leaving special key codes unmodified and
|
||||
/// escaping other K_SPECIAL bytes.
|
||||
/// Change CR, LF and ESC into a space.
|
||||
void stuffReadbuffSpec(const char *s)
|
||||
void stuffReadbuffSpecial(const char *s)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
while (*s != NUL) {
|
||||
|
||||
@@ -78,14 +78,12 @@ static uint64_t frame_id = 0;
|
||||
/// The executing command's frame; its `parent` chain spans nested `normal_execute()`.
|
||||
static CmdFrame *cur_frame = NULL;
|
||||
|
||||
/// The pending atom, while it spans CmdFrames (a mapping's commands, a stuffed continuation, an
|
||||
/// operator awaiting its motion). See `vatom` for Visual composite.
|
||||
/// The pending atom, while it spans CmdFrames (a mapping's commands, an operator awaiting its
|
||||
/// motion, i_CTRL-O). See `vatom` for Visual composite.
|
||||
///
|
||||
/// Also used by undo, to restore cursor position.
|
||||
static struct {
|
||||
bool open; ///< True from the atom's first toplevel frame until it resolves.
|
||||
bool stuffed; ///< Last frame had stuffed keys pending (next frame continues the atom).
|
||||
bool redo; ///< The continuation completes the prepped redo ("!ip" stuffs ":.,.+1!").
|
||||
CmdOrigin origin; ///< State at the atom's start.
|
||||
CmdAtomVec atoms; ///< Subatoms of the mapping/macro.
|
||||
char *lhs; ///< Label: mapping LHS or macro "@x" (NULL: not collecting).
|
||||
@@ -235,14 +233,6 @@ pos_T atom_origin_pos(buf_T *buf)
|
||||
return curwin->w_cursor;
|
||||
}
|
||||
|
||||
/// Decides the "origin" state. Usually `frame->origin`, except for "stuffed" cases.
|
||||
/// A "stuffed" continuation belongs to its stuffing frame (`x` stuffs `dl`: the `dl` atom's origin
|
||||
/// is at `x`).
|
||||
static CmdOrigin frame_origin(const CmdFrame *frame)
|
||||
{
|
||||
return frame->cont ? composite.origin : frame->origin;
|
||||
}
|
||||
|
||||
/// Composes a CmdSpec into `redo_keys` format.
|
||||
/// @return Allocated key sequence.
|
||||
static char *atom_redo_keys(CmdSpec spec)
|
||||
@@ -822,10 +812,11 @@ void atom_typed_del(size_t len)
|
||||
kv_size(typed.keys) -= MIN(len, kv_size(typed.keys));
|
||||
}
|
||||
|
||||
/// Discards the redo-atom: new or invalid command. Only at toplevel (not nested ":normal!").
|
||||
/// Discards the redo-atom: new or invalid command. Only at toplevel: a nested frame (":normal!",
|
||||
/// an exec_stuffed() drain) must not disturb the enclosing command's.
|
||||
static void atom_redo_reset(void)
|
||||
{
|
||||
if (!atom_is_user_cmd()) {
|
||||
if (!atom_is_user_cmd() || (cur_frame != NULL && cur_frame->parent != NULL)) {
|
||||
return;
|
||||
}
|
||||
curcmd.redo_frame = 0;
|
||||
@@ -875,7 +866,7 @@ void atom_macro_start(int regname)
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts accumulating a composite for a command that stuffs its "translation" ("x" => "dl").
|
||||
/// Starts accumulating composite for a cmd that stuffs its translation ("x" => "dl", "." => redo).
|
||||
void atom_stuff_start(const cmdarg_T *cap)
|
||||
{
|
||||
// Not while another composite collects: a mapping's own label wins ("nnoremap <F6> xw").
|
||||
@@ -1017,7 +1008,7 @@ static bool atom_visual_end_suffix(char *suffix, const CmdSpec *spec, bool redoa
|
||||
};
|
||||
if (spec != NULL) {
|
||||
kv_push(vatom.atoms, ((CmdAtom){ .type = kAOperator, .spec = *spec, .keys = suffix,
|
||||
.origin = frame_origin(cur_frame) }));
|
||||
.origin = cur_frame->origin }));
|
||||
} else {
|
||||
xfree(suffix);
|
||||
}
|
||||
@@ -1068,7 +1059,7 @@ void atom_capture_op(oparg_T *oap, cmdarg_T *cap, bool redo_yank)
|
||||
spec.cmd2 = operand ? NUL : cap->nchar;
|
||||
spec.cmdarg = operand ? cap->nchar : NUL;
|
||||
CmdAtom op_atom = atom_from_spec(kAOperator, spec);
|
||||
op_atom.origin = frame_origin(cur_frame);
|
||||
op_atom.origin = cur_frame->origin;
|
||||
atom_stage_set(&op_atom);
|
||||
}
|
||||
} else if (!Visual.active || oap->motion_force) {
|
||||
@@ -1169,7 +1160,7 @@ InsSession atom_ins_start(int cmd, long count, VisualIns vis, bool vblock)
|
||||
// A consumed selection opens the redo body, so the atom starts where the selection did.
|
||||
// Else the CmdFrame origin, from before the entry moved the cursor (a/A/…).
|
||||
.origin = vis == kVInsKeys ? vatom.origin
|
||||
: cur_frame != NULL ? frame_origin(cur_frame) : atom_origin(),
|
||||
: cur_frame != NULL ? cur_frame->origin : atom_origin(),
|
||||
};
|
||||
if (vis != kVInsNone && !mc_replaying()) {
|
||||
if (vis == kVInsKeys && !(atom_visual_replayable() && (vatom.state & kVatomTyped))) {
|
||||
@@ -1226,14 +1217,9 @@ static void atom_ins_push(const InsSession *session, bool cascade)
|
||||
/// Toplevel entry: starts a new atom. Samples the pre-cmd state (`origin`); pushes the frame.
|
||||
void atom_cmd_start(CmdFrame *old)
|
||||
{
|
||||
// A stuffed continuation frame is one cmd with its stuffing frame: `v2e".p` stuffs `c…`, `!ip`
|
||||
// stuffs `:.,.+1!`. KeyStuffed=false if error/CTRL-C aborted, `composite.stuffed` is then stale.
|
||||
old->cont = KeyStuffed && cur_frame == NULL && composite.open && composite.stuffed;
|
||||
old->origin = atom_origin();
|
||||
if (cur_frame == NULL && !composite.open) {
|
||||
composite.open = true;
|
||||
composite.stuffed = false;
|
||||
composite.redo = false;
|
||||
composite.origin = old->origin;
|
||||
}
|
||||
old->visual = Visual;
|
||||
@@ -1251,11 +1237,7 @@ void atom_cmd_start(CmdFrame *old)
|
||||
old->parent = cur_frame;
|
||||
cur_frame = old;
|
||||
curcmd.op_global = false;
|
||||
if (old->cont && composite.redo) {
|
||||
curcmd.redo_frame = old->id; // The continuation completes the prepped redo.
|
||||
} else {
|
||||
atom_redo_reset();
|
||||
}
|
||||
atom_redo_reset();
|
||||
}
|
||||
|
||||
/// Captures the typed command's atom: one atom per command, produced from the CmdFrame diff and
|
||||
@@ -1313,7 +1295,7 @@ static void atom_capture_cmd(cmdarg_T *ca, CmdFrame *old)
|
||||
// Decided once, at session start.
|
||||
vatom.state = (old->keytyped || (atom_composite_active() && atom_is_user_cmd()))
|
||||
? kVatomTyped : kVatomFed;
|
||||
vatom.origin = frame_origin(old);
|
||||
vatom.origin = old->origin;
|
||||
}
|
||||
// Decided by the session (not atom_capturable()), so fed selections (":normal! vjd") still
|
||||
// accumulate for redo-prep. Recording/replay commands are meta (not part of the edit).
|
||||
@@ -1382,7 +1364,7 @@ static void atom_capture_cmd(cmdarg_T *ca, CmdFrame *old)
|
||||
// did neither is a no-op (vim-surround "ysa[" whose surround char was <Esc>).
|
||||
bool effect = changed || reg_max_ts(true) > old->reg_ts;
|
||||
if (atom.keys != NULL && *atom.keys != NUL) {
|
||||
atom.origin = frame_origin(old);
|
||||
atom.origin = old->origin;
|
||||
atom_push(effect, &atom);
|
||||
} else {
|
||||
atom_free(&atom);
|
||||
@@ -1391,14 +1373,14 @@ static void atom_capture_cmd(cmdarg_T *ca, CmdFrame *old)
|
||||
&& !(vis && unchanged)) {
|
||||
// Payload typed in the cmdline ("/pat<CR>"). Emit-only. Not if pattern was not found.
|
||||
CmdAtom atom = atom_from_cmdline(kAMotion, ca, ca->searchbuf);
|
||||
atom.origin = frame_origin(old);
|
||||
atom.origin = old->origin;
|
||||
atom_push(false, &atom);
|
||||
} else if (!vis && curcmd.cmdline != NULL && (ca->cmdchar == ':' || ca->cmdchar == K_COMMAND)) {
|
||||
// Same for ":cnext<CR>" or "<Cmd>cnext<CR>". Never a Visual subatom.
|
||||
CmdAtom atom = atom_from_cmdline(kAExcmd, ca, curcmd.cmdline);
|
||||
// Payload read during the cmdline execution (`ds)` getchar() => ")").
|
||||
atom_payload_append(&atom, old);
|
||||
atom.origin = frame_origin(old);
|
||||
atom.origin = old->origin;
|
||||
atom_push(false, &atom);
|
||||
} else if (replayable && (!vis || (keycls & kKeyPayload) == 0)) {
|
||||
// Non-redoable command (u, zz, q=): never cascaded as an edit.
|
||||
@@ -1408,7 +1390,7 @@ static void atom_capture_cmd(cmdarg_T *ca, CmdFrame *old)
|
||||
spec.regname = 0;
|
||||
}
|
||||
CmdAtom atom = atom_from_spec(motion ? kAMotion : jump_cmd ? kAJump : kANormal, spec);
|
||||
atom.origin = frame_origin(old);
|
||||
atom.origin = old->origin;
|
||||
atom_push(follow, &atom);
|
||||
} else if ((scroll_cmd || mouse_cmd) && !atom_composite_active()) {
|
||||
// Emit-only (viewport-dependent).
|
||||
@@ -1418,7 +1400,7 @@ static void atom_capture_cmd(cmdarg_T *ca, CmdFrame *old)
|
||||
spec.count = 0;
|
||||
}
|
||||
CmdAtom atom = atom_from_spec(scroll_cmd ? kAScroll : kAMouse, spec);
|
||||
atom.origin = frame_origin(old);
|
||||
atom.origin = old->origin;
|
||||
atom_push(false, &atom);
|
||||
}
|
||||
if (vis && kv_size(vatom.atoms) == collected && !unchanged) {
|
||||
@@ -1448,8 +1430,9 @@ void atom_cmd_end(cmdarg_T *ca, CmdFrame *old)
|
||||
}
|
||||
|
||||
// The clock edge. Only at toplevel: cascading from a nested normal_execute() would recurse.
|
||||
// Deferred while a mapping executes (its keys are still in typebuf), so its commands collapse as
|
||||
// one unit; likewise for a macro's LAST command, which may stuff a translation ("x" => "dl").
|
||||
// Deferred while a mapping executes (keys in typebuf), so its commands collapse as one unit;
|
||||
// likewise while stuffed keys are pending (i_CTRL-O dance, or exec_stuffed() deferred under
|
||||
// textlock).
|
||||
if (old->parent == NULL && !mc_replaying() && typebuf_typed() && stuff_empty()) {
|
||||
mc_clock_edge(map_edit);
|
||||
map_edit = false;
|
||||
@@ -1460,13 +1443,5 @@ void atom_cmd_end(cmdarg_T *ca, CmdFrame *old)
|
||||
composite.open = false; // Resolved.
|
||||
}
|
||||
}
|
||||
if (old->parent == NULL) {
|
||||
// Stuffed keys pending: the next frame continues the atom.
|
||||
composite.stuffed = !stuff_empty();
|
||||
// The continuation also completes the redo-prep.
|
||||
// Except Visual: stuffing is captured in its payload.
|
||||
composite.redo = composite.stuffed && curcmd.redo_frame == old->id
|
||||
&& !Visual.active && !old->visual.active;
|
||||
}
|
||||
cur_frame = old->parent;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ extern CmdAtomVec g_atoms;
|
||||
typedef struct CmdFrame CmdFrame;
|
||||
struct CmdFrame {
|
||||
CmdOrigin origin; ///< State at entry.
|
||||
bool cont; ///< Stuffed continuation frame: the atom began earlier.
|
||||
VisualState visual; ///< Visual-mode state (active/start/mode are diffed).
|
||||
bool keytyped; ///< KeyTyped
|
||||
uint64_t captures; ///< Capture counter.
|
||||
|
||||
@@ -492,6 +492,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent)
|
||||
stuffcharReadbuff('y');
|
||||
stuffcharReadbuff(K_MIDDLEMOUSE);
|
||||
}
|
||||
exec_stuffed(oap);
|
||||
return false;
|
||||
}
|
||||
// The rest is below jump_to_mouse()
|
||||
|
||||
@@ -4561,6 +4561,9 @@ static void nv_replace(cmdarg_T *cap)
|
||||
stuffcharReadbuff('R');
|
||||
stuffcharReadbuff('\t');
|
||||
stuffcharReadbuff(ESC);
|
||||
if (exec_stuffed(cap->oap)) {
|
||||
cap->retval |= CA_COMMAND_BUSY;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4868,7 +4871,7 @@ static void nv_abbrev(cmdarg_T *cap)
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate a command into another command.
|
||||
/// Translate a command into another command, and execute it.
|
||||
static void nv_optrans(cmdarg_T *cap)
|
||||
{
|
||||
static const char *(ar[]) = { "dl", "dh", "d$", "c$", "cl", "cc", "yy",
|
||||
@@ -4881,6 +4884,9 @@ static void nv_optrans(cmdarg_T *cap)
|
||||
stuffnumReadbuff(cap->count0);
|
||||
}
|
||||
stuffReadbuff(ar[strchr(str, (char)cap->cmdchar) - str]);
|
||||
if (exec_stuffed(cap->oap)) {
|
||||
cap->retval |= CA_COMMAND_BUSY;
|
||||
}
|
||||
}
|
||||
cap->opcount = 0;
|
||||
}
|
||||
@@ -5650,6 +5656,7 @@ static void nv_g_cmd(cmdarg_T *cap)
|
||||
case K_LEFTMOUSE:
|
||||
if (do_mouse(oap, cap->nchar, BACKWARD, cap->count1, 0)) {
|
||||
stuffcharReadbuff(Ctrl_RSB);
|
||||
exec_stuffed(oap);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -5772,6 +5779,11 @@ static void nv_dot(cmdarg_T *cap)
|
||||
atom_stuff_start(cap);
|
||||
if (start_redo(cap->count0, restart_edit != 0 && Ins.moved == kInsNone) == false) {
|
||||
clearopbeep(cap->oap);
|
||||
return;
|
||||
}
|
||||
// Execute the redo keys here: the whole replay resolves within this "." command.
|
||||
if (exec_stuffed(cap->oap)) {
|
||||
cap->retval |= CA_COMMAND_BUSY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6699,6 +6711,12 @@ static void nv_event(cmdarg_T *cap)
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes one normal-mode command from pending input, outside the main state machine.
|
||||
///
|
||||
/// Called in a loop; a count/register prefix or a pending operator travels into the next call via
|
||||
/// `oap` ("3dl" is three calls, one command).
|
||||
///
|
||||
/// @param toplevel `NormalState.toplevel` (full interactive-command treatment).
|
||||
void normal_cmd(oparg_T *oap, bool toplevel)
|
||||
{
|
||||
NormalState s;
|
||||
@@ -6709,3 +6727,31 @@ void normal_cmd(oparg_T *oap, bool toplevel)
|
||||
normal_execute(&s.state, safe_vgetc());
|
||||
*oap = s.oa;
|
||||
}
|
||||
|
||||
/// Executes the pending readahead (see "Stuffing", input.c).
|
||||
///
|
||||
/// During "textlock" the stuffed keys are left for the main loop instead (for CmdAtom/multicursor
|
||||
/// purposes, that's fine: if stuff_empty()=false, the pending CmdAtom stays open and will collect
|
||||
/// the effects later).
|
||||
///
|
||||
/// @param oap The continuing operator state (see `normal_cmd`), or NULL.
|
||||
/// @return True if insert-session-resume is pending (i_CTRL-O): the caller reports
|
||||
/// CA_COMMAND_BUSY, so resume happens after next command instead.
|
||||
bool exec_stuffed(oparg_T *oap)
|
||||
{
|
||||
if (text_locked() || curbuf_locked()) {
|
||||
return false;
|
||||
}
|
||||
oparg_T oa;
|
||||
clear_oparg(&oa);
|
||||
if (oap == NULL) {
|
||||
oap = &oa;
|
||||
}
|
||||
finish_op = false;
|
||||
while (!stuff_empty() && !got_int) {
|
||||
update_topline_cursor();
|
||||
normal_cmd(oap, true);
|
||||
}
|
||||
finish_op = false;
|
||||
return restart_edit != 0;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "nvim/eval/typval_defs.h"
|
||||
#include "nvim/ex_cmds2.h"
|
||||
#include "nvim/ex_cmds_defs.h"
|
||||
#include "nvim/ex_docmd.h"
|
||||
#include "nvim/ex_getln.h"
|
||||
#include "nvim/extmark.h"
|
||||
#include "nvim/file_search.h"
|
||||
@@ -3073,10 +3074,10 @@ void cursor_pos_info(dict_T *dict)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle indent and format operators and visual mode ":".
|
||||
/// Handle indent, format operators and visual mode ":". Stuff the ":[range]…" cmdline and run it
|
||||
/// here. For OP_COLON/OP_FILTER the user types the rest after the stuffed part.
|
||||
static void op_colon(oparg_T *oap)
|
||||
{
|
||||
stuffcharReadbuff(':');
|
||||
if (oap->is_VIsual) {
|
||||
stuffReadbuff("'<,'>");
|
||||
} else {
|
||||
@@ -3126,7 +3127,11 @@ static void op_colon(oparg_T *oap)
|
||||
stuffReadbuff("\n']");
|
||||
}
|
||||
|
||||
// do_cmdline() does the rest
|
||||
do_cmdline(NULL, getexline, NULL, 0);
|
||||
// Keys stuffed past the cmdline ("']" for OP_FORMAT): internal cleanup, not an atom.
|
||||
atom_suppress(true);
|
||||
exec_stuffed(NULL);
|
||||
atom_suppress(false);
|
||||
}
|
||||
|
||||
#ifdef EXITFREE
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "nvim/eval.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/ex_cmds2.h"
|
||||
#include "nvim/ex_docmd.h"
|
||||
#include "nvim/ex_getln.h"
|
||||
#include "nvim/extmark.h"
|
||||
#include "nvim/file_search.h"
|
||||
@@ -1525,6 +1526,9 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the stuffed keys here: the put resolves within the current command.
|
||||
exec_stuffed(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ describe('CmdAtom', function()
|
||||
)
|
||||
end)
|
||||
|
||||
it('"!" operator captures its stuffed cmdline continuation', function()
|
||||
it('"!" operator captures its stuffed cmdline', function()
|
||||
-- ":.,.+1!" + typed "{prg}<CR>" completes the operator atom, like "d/END<CR>". #41447
|
||||
local prg = n.testprg('shell-test') .. ' REP 2 X'
|
||||
fn.setline(1, { 'b', 'a', '', 'e', 'c', 'd' })
|
||||
@@ -358,6 +358,43 @@ describe('CmdAtom', function()
|
||||
)
|
||||
end)
|
||||
|
||||
it('stuffed translations execute within their command (exec_stuffed)', function()
|
||||
-- A register prefix crosses into the "." replay; an explicit register must not touch the
|
||||
-- small-delete register.
|
||||
fn.setline(1, { 'aaa bbb', 'ccc ddd' })
|
||||
feed('gg0dw')
|
||||
eq('aaa ', fn.getreg('-'))
|
||||
feed('j0"z.')
|
||||
eq('ccc ', fn.getreg('z'))
|
||||
eq('aaa ', fn.getreg('-'))
|
||||
eq({ 'bbb', 'ddd' }, get_lines())
|
||||
-- i_CTRL-O inside a stuffed translation's insert session ("S" == "cc"): the session resumes
|
||||
-- after ONE normal command.
|
||||
api.nvim_buf_set_lines(0, 0, -1, true, { 'xxxx', 'yyyy' })
|
||||
feed('ggSabc<C-o>0def<Esc>')
|
||||
eq({ 'defabc', 'yyyy' }, get_lines())
|
||||
-- r<Tab> under 'expandtab'/'smarttab' ("{count}R<Tab><Esc>": edit() does the tab).
|
||||
command('set expandtab shiftwidth=4 tabstop=4')
|
||||
api.nvim_buf_set_lines(0, 0, -1, true, { 'abcdefgh', 'ABCDEFGH' })
|
||||
feed('gg03r<Tab>')
|
||||
eq(' defgh', fn.getline(1))
|
||||
feed('j0.')
|
||||
eq(' DEFGH', fn.getline(2))
|
||||
api.nvim_buf_set_lines(0, 0, -1, true, { 'abcdefgh' })
|
||||
feed('gg0r<C-v><Tab>')
|
||||
eq('\tbcdefgh', fn.getline(1))
|
||||
command('set noexpandtab smarttab shiftwidth=4 tabstop=8')
|
||||
api.nvim_buf_set_lines(0, 0, -1, true, { 'abcdefgh' })
|
||||
feed('gg02r<Tab>')
|
||||
eq('\tcdefgh', fn.getline(1))
|
||||
command('set expandtab& smarttab& shiftwidth& tabstop&')
|
||||
-- "&" and "count&" (":.,.+Ns").
|
||||
api.nvim_buf_set_lines(0, 0, -1, true, { 'blue a', 'blue b', 'blue c', 'blue d' })
|
||||
command('1s/blue/red/')
|
||||
feed('2G&3G2&')
|
||||
eq({ 'red a', 'red b', 'red c', 'red d' }, get_lines())
|
||||
end)
|
||||
|
||||
it('mapping that enters :terminal mode', function()
|
||||
-- Fake picker/fuzzy-finder: a mapping that opens a :terminal UI (like fzf-lua).
|
||||
-- Its composite ends when :terminal is entered.
|
||||
|
||||
@@ -174,6 +174,10 @@ describe('TUI :detach', function()
|
||||
nvim_set .. ' laststatus=2 background=dark',
|
||||
}, { env = env_notermguicolors, cols = opts.cols })
|
||||
tt.override_screen_expect_for_conpty(screen)
|
||||
-- The child's `--listen` socket is created asynchronously wrt its PTY output.
|
||||
t.retry(nil, 2000, function()
|
||||
assert(vim.uv.fs_stat(child_server))
|
||||
end)
|
||||
end
|
||||
|
||||
it('does not stop server', function()
|
||||
|
||||
Reference in New Issue
Block a user