Pass a variable to sleep in vimscript - vim

I've been using vim for too many years to count, but I never have really learned vimscript very well. I'm trying now.
Anyway, I would like to pass a variable amount of time to the sleep function. I also want to manipulate that value before I pass it along. Here's a simple example.
function! wait(mil)
let timetowait = mil . "m"
sleep timetowait
endfunction
Even if I try prefixing timetowait with l: it says, "Invalid argument: l:timetowait".
What's the right way of passing the value of a variable to sleep?

There are a couple of problems:
Your method should start with a capitalized name
You need to access your argument with a:
You have to have a space between the time to sleep and m
You have to execute the sleep indirectly using execute
Here's an example on how one could do this:
function! Wait(mil)
let timetowait = a:mil . " m"
exe 'sleep '.timetowait
endfunction

Daan's answer is correct; here's some more background info:
Vimscript is evaluated exactly like the Ex commands typed in the : command-line. There were no variables in ex, so there's no way to specify them. When typing a command interactively, you'd probably use <C-R>= to insert variable contents:
:sleep <C-R>=timetowait<CR>m<CR>
... but in a script, :execute must be used. All the literal parts of the Ex command must be quoted (single or double quotes), and then concatenated with the variables:
execute 'sleep' timetowait . 'm'

Related

Dequoting a vim argument

I want to remove the quotes around a vim argument [<f-args>][0].
The problem I'm having is that when I define a new command and call it with an argument say: MyCommand Blah, this gets called like :MyCommand "Blah". The thing is, I want the argument Blah to be dequoted because I have an enviroment variable that I want to prefix the argument with $ so that the full command actually reads something like :MyCommand $Blah.
How do I dequote the argument?
<f-args> is for passing custom command arguments to a Vimscript function; there, you need quoting to pass those arguments as strings.
If you want to pass arguments to another (built-in or custom) command, just use <args>, which passes the arguments as-is.
If you need to pick apart the arguments, pass some to command X and others to command Y, this again is best done not inline in the :command definition, but in a function, so the <f-args> approach would work just fine.
Example
command! -nargs=* Test call TestFunc(<f-args>)
function! TestFunc( ... )
echomsg 'argument 1 is' a:1
echomsg 'arguments 2, 3 are' join(a:000[1:2])
endfunction

Returning to Normal Mode from Insert Mode after "execute normal"

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
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.
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.

Vim - writing to filename in vimscript variable

I'm doing something like
:let foo="bar"
:echom foo
bar
:w foo
"foo" [New File] 0 lines, 0 characters written
I am expecting/hoping to write a file named "bar", not a file named "foo". Assuming that I have a string stored in a variable "foo", how can I write the current buffer to a file with the name being that string?
As an aside, can someone explain what :w foo and :echom foo are doing different with regards to foo?
Vimscript evaluation rules
Vimscript is evaluated exactly like the Ex commands typed in the : command-line. There were no variables in ex, so there's no way to specify them. When typing a command interactively, you'd probably use <C-R>= to insert variable contents:
:sleep <C-R>=timetowait<CR>m<CR>
... but in a script, :execute must be used. All the literal parts of the Ex command must be quoted (single or double quotes), and then concatenated with the variables:
execute 'sleep' timetowait . 'm'
Like :execute above, the :echo[msg command is particular in that it takes a variable argument, whereas most commands (like :write) do not, and treat the argument(s) literally.
Your particular problem
As above, your issue is best resolved via execute:
:execute 'write' foo
However, note that if foo contains any regular filename, it still needs to be escaped for the :write function, which understands some special notation (e.g. % stands for the current buffer name), and likes to have spaces escaped:
:execute 'write' fnameescape(foo)
Only
:execute 'write ' . foo<CR>
and
:write <C-r>=foo<CR><CR>
do what you want.
Variables can be used in a concatenation, case 1, or in an expression, case 2.

Comment out code using vimscript

Hi Im trying to write my first vim script. I want to write a function that will comment out PHP code by the block or curly brackets.
Here is what I've come up with, but I can't get it to work:
:function Mycom()
:let b = line(".")
:echo "this is "b
// trying to grab the matching bracket, not sure wheather this is ok
:%
//keeps missing and going to end og file
:let e = line(".")
:echo "here is end" e
//here is where i want to comment out the block
:echo b,e s%^%//%
:endfunction
You shouldn’t put a leading : on each line — unless you’re writing the function in the Vim command line, Vim will add the : automatically for you. (It‘d be better to write your script in a file, though; that way it’s easier to modify and test.)
Comments in Vimscript start with " (a double quote), not //.
If you want to execute a normal mode command, like % or dd, you can use normal! % or normal! dd.
echo b,e s%... won’t work. If you want to echo the text, try echo b.','.e.' s%^%//%'.
Also, consider using echom instead of echo. Because echom saves the message in the message history, you can re-read it later using :mess.
P.S. If your cursor is on an open { (I saw you’re trying to use % in your script), you can comment the block using
ctrl-v%I//<esc>

VIM: how to put result of a colon command into a variable?

In VIM script, i want to check if VIM was started with command-line arguments or wthout. For this, i want to check a result of :args command that prints arguments. But how to put a result of :args inside an if() or a variable. Following wll not work:
let s:MyArgs = execute( "args" )
You need to play with :redir. I have encapsulated this operation in a function there.
BTW, argc() should also answer your original need in a simpler way. (:h argc())

Resources