Vim - writing to filename in vimscript variable - vim

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.

Related

Escaping Shell Command Arguments in Vim

I'm trying to fully understand the following command in Vim:
:exe "grep -R " . shellescape(expand("<cWORD>")) . " ."<cr>
I got the use of expand function (force the expansion of into the actual string
before it gets passed to shellescape) and shellescape command itself ( from Vim help page: Escape {string} for use as a shell command argument).
What I do not understand, from help itself either, is that use of dots, one before and one after shellescape command.
Again, both of the dots are preceeded and followed by an empty space.
And if I use :
:exe "grep -R "shellescape(expand("<cWORD>"))" ."<cr>
which is the same command without those dots, I (apparently) get the same result.
Can anybody give a detailed explanation?
Thank you
:help :execute already explains that.
As you can see from the :exe[cute] {expr1} .. syntax, it takes multiple arguments.
Multiple arguments are concatenated, with a space in
between. To avoid the extra space use the "."
operator to concatenate strings into one argument.
:help expr-. explains that the operator for String concatenation in Vimscript is . (not + like in many other languages; in Vimscript, this solely is for adding numbers or Lists). The empty space around it is optional, but often given for better readability.
In summary, if you need to concatenate Strings with spaces, you can either use . and include the space inside one of the Strings, or pass separate arguments to :execute and let it add the spaces implicitly, or mix both approaches within the same command (readability should be the first priority here).

Vim substitute() function flags order changes its behavior?

I have the following line made of tab separated strings ; I have sometimes multiple sequential <Tab>:
zer<Tab><Tab>abc<Tab>def<Tab><Tab>iop<Tab><Tab>
I want to insert the 'null' string between 2 consecutive <Tab> ; I run the following command:
:s/\t\(\t\)\#=/\tnull/eg
which give me as I expected:
zer<Tab>null<Tab>abc<Tab>def<Tab>null<Tab>iop<Tab>null<Tab>
The equivalent substitute function to the above command is (I echoed its result):
:echo substitute(getline('.'),'\t\(\t\)\#=','\tnull','eg')
which insert a <Tab> only between the first two <Tab>s:
zer<Tab>null<Tab>abc<Tab>def<Tab><Tab>iop<Tab><Tab>
whereas if I change the order of the substitute flags in the substitute function call ('eg' replaced by 'ge'):
:echo substitute(getline('.'),'\t\(\t\)\#=','\tnull','ge')
Then I get the expected result:
zer<Tab>null<Tab>abc<Tab>def<Tab>null<Tab>iop<Tab>null<Tab>
It seems that the order of the flags in the substitute() function change its behavior while it has no effect for the substitute command.
Does anyone have an idea why that ?
From my limited understanding of C, it looks like Vim only cares about the {flags} argument if its first character is g:
do_all = (flags[0] == 'g');
[…]
if (!do_all)
break;
This may explain the fact that :help substitute() only mentions g when explaining {flags}:
When {flags} is "g", all matches of {pat} in {expr} are
replaced. Otherwise {flags} should be "".
The :substitute command can take many flags, but the substitute() function only supports the g flag. Flags like c (for interactivity) or e (for error suppression) do not apply to the low-level function.

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

Vim convert expression to file

I'm trying to write a function which takes the file opened by vim and moves it to a standard directory. My script checks out the name of the current buffer, takes the head of the path of the current file, then transforms it into an expression, stores it in a variable, and then manipulates the expression so that it becomes the path I want.
Afterwards, I am trying to use saveas to save the buffer at a new location.
However, saveas receives an argument of type {file}.
How can I convert an expression to type file?
You need to use execute:
exe 'saveas ' . g:filepath
See h :exe for details.
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'
Escaping
For arguments like filenames, you have to consider that some characters are special (:help cmdline-special; e.g. % is replaced with the current buffer name), and whitespace should be escaped. Therefore, you need to process your variable through the fnameescape() function:
:execute 'saveas' fnameescape(g:filepath)

Pass a variable to sleep in vimscript

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'

Resources