2
votes

You can perform normal mode commands programmatically in Ex mode, via execute normal, e.g.

:execute "normal" "iNEWTEXT\<Esc>0"

This switches to insert mode (i), writes "NEWTEXT", escapes to normal mode (\< Esc>), then moves to the start of the line (0).

However, using a non-constant string, either a register or variable, the behavior is different. For example, suppose you have the same command above saved on a line in any file (not necessarily a vimscript file):

iNEWTEXT\<Esc>0

You can then copy the text into any register (here, z) via "zy$ and execute the register via @z. This time, though, the output is different:

NEWTEXT\<Esc>0

After entering insert mode, the Escape is no longer treated as a special character, and is instead taken literally. Alternative forms like \e don't work either. Is there a way around this?

EDIT: Using Ingo's answer, I created the the following function. Basically, the use is for having a set of normal/insert commands embedded within the text of the file, and being able to execute them. More commonly, something similar is used for running Ex commands from a line of text, but I couldn't find anything that did this exact thing for normal and insert mode.

So, you'd have text like the following in your file:

jy10j10jpO\<Esc>jEll

When on that line, you could call the function or a remap, and the commands would execute (in this example, copying and pasting 10 lines, and moving 2 columns past the first word). Ingo's alternatives are better for serious usage, namely sourcing commands from another file, having the command in the .vimrc, or a file-type specific option. Macros saved by a session would work just as well, and are more practical than having commands scattered throughout a file. In my case, I was syncing across multiple devices, and didn't want to have another file or clutter my vimrc with this very specific command, but didn't mind cluttering this specific file itself. Think of this like a portable macro.

" Execute current line as Vim normal mode commands.
nnoremap <A-y> :call EvaluateLineAsNormalModeCmd()<CR>

function! EvaluateLineAsNormalModeCmd()
    let g:getCurrentLine = getline(".")
    "have to :execute twice: once to get the contents of the 
    "register inserted into a double-quoted string, and then once for 
    "the :normal to evaluate the string.
    execute 'execute "normal" "' . g:getCurrentLine  . '"'
endfunction

EDIT2/3: Here are two functions using Christian Brabandt's answer. They work about the same but can put the user in insert mode at the end (whereas, based on my minimal information, 'i' in the other context is considered an incomplete command and not executed, and :startinsert can't be used in that situation). PS: Please don't ask me what all those single and double quotes are doing, as I can't wrap my head around it O_o

function! EvaluateLineAsNormalModeCmd()
    normal! 0y$
    execute ':call feedkeys("'.@".'", "t")'
endfunction

function! EvaluateLineAsNormalModeCmd()
    let g:getCurrentLine = getline(".")
    execute ':call feedkeys("'.g:getCurrentLine.'", "t")'
endfunction
2
Have you tried changing the \<Esc> sequence with ^[? (that is <C-v><Esc>)Lieven Keersmaekers
Tried this, but it suffers from the same issue: everything is taken literally. Ingo's double execute function solves this.traycerb

2 Answers

4
votes

If you really need this (the use case is dubious), you have to :execute twice: once to get the contents of the register inserted into a double-quoted string, and then once for the :normal to evaluate the string.

:execute 'execute "normal" "' . @z . '"'

PS: Please give more background; what is your final goal? When a question is only about a small technical step, it's difficult to provide a good answer. If you don't tell us why you want this, it's easy to succumb to the XY problem.

2
votes

I would rather use the feedkeys() function. E.g. for your sample, this should work:

exe ':call feedkeys("'.@".'", "t")'

(If you yanked your line into the unnamed register, else adjust the register name accordingly). Note, quoting could get ugly.

To understand what is going on, this is what is done:

exe ':call feedkeys('           - First part of the feedkeys() function call
    "                           - Start of Quote for the first argument
    .                           - String concatenation
    @"                          - content of the unnamed register
    .                           - String concatenation
    '                           - Start of second part of the feedkeys function call
    "                           - End of Quote for the first argument
    , "t")'                     - Second argument of feedkeys() function call

You could also do it in 2 steps like this:

   exe ':let a="'. @". '"'       - Also needs to quote @" correctly.
   call feedkeys(a, 't')

which should be easier to understand. The exe call is only to translate the normalized key notation into literal keys.