Calling substitute() and expand() inside new command definition in Vim - vim

I'm trying to define a new command in Vim that calls an external script with the name of the current file, but slightly modified. Here's how I defined the command:
:command MyNewCommand !/tmp/myscript.sh substitute(expand("%:p"), "-debug", "", 'g')
In other words, myscript.sh takes one parameter, which is the full pathname of the file being edited, with the string -debug in the pathname removed. My command definition doesn't work because rather than passing the pathname, Vim seems to pass to myscript.sh the entire string itself, beginning with the word substitute. How do I define the command to do what I want? Thanks :).

You can use the system() function to execute the script.
Change the command definition as follows:
:command! MyNewCommand call system('/tmp/myscript.sh ' .
\ shellescape(substitute(expand('%:p'), '-debug', '', 'g')))
To see the output of the command, replace call with echo.

You can use solution similar to #ib's one, but with ! which will show you the output of the shell command (and will also handle case when expand('%:p') contains newlines (it can if FS is fully POSIX-compliant)):
command MyNewCommand execute '!/tmp/myscript.sh' shellescape(substitute(expand('%:p'), '-debug', '', 'g'))

The generic approach is to build a string and then execute it. The problem in your command is that substitute(expand(... is not being evaluated and it's passed as is.
So in a generic example
command MyNewCommand OldCommand expand("%:p")
should be converted to
command MyNewCommand execute 'OldCommand '.expand("%:p")
That way MyNewCommand will just invoke execute with the expression 'OldCommand '.expand("%:p"). execute will evaluate the expression and therefore expand() will get evaluated to the filename and concatenated to 'OldCommand ' resulting in a string of the form 'OldCommand myfilename'. That string then gets executed as an Ex command by the same execute.

Related

In Vim, how can I save a file to a path stored in a variable?

This is inside a script:
:let s:submission_path = expand("%:p:h") . '\submissions\test_submission.csv'
:echo s:submission_path
:write s:submission_path
The script raises an error "can't open file for writing", because I'm on windows and ':' is not a valid filename character. If I name the variable 'submission_path' a file with that name is written (edit: and the contents of the file are as I expect, the contents of the buffer the script is editing).
I don't understand this. The echo command before the write does what I expect it to, output the full path to the file I want to write. But write seems to ignore the variable value and use its name instead? Do I need to dereference it somehow?
Try this:
:let s:submission_path = expand("%:p:h") . '\submissions\test_submission.csv'
:echo s:submission_path
:execute "write " . s:submission_path
Execute will allow you to compose an ex command with string operations and then execute it.
vim has very screwy evaluation rules (and you just need to learn them). write does not take an expression it takes a string so s:submission_path is treated literally for write. However echo does evaluate its arguments so s:submission_path is evaluated.
To do what you want you need to use the execute command.
exec 'write' s:submission_path
Environment variables don't have this problem, but you have to dereference them like in unix with a dollar sign:
:let $submission_path = expand("%:p:h") . '\submissions\test_submission.csv'
:echo $submission_path
:write $submission_path
AND it modifies your environment within the vim process, so just be aware of that.

Passing and using arguments to command in Vim script

How can I use functions in a user defined command? As a simple specific example:
How would I write a command that echos the argument passed to it?
I have this:
command -nargs=1 FW execute ":echo ".<args>
But when I run:
:FW something
I get:
E121: Undefined variable: something
E15: Invalid expression: ":echo".something
Because :echo takes an expression, a string must be quoted. This
is so common that Vim has a special notation for it; See :help <q-args>. Now, for
:execute, you'd need another level of quoting (and based on your comments it seems you went down that road):
:command! -nargs=1 FW execute "echo" string(<q-args>)
Also, you don't need to concatenate explicitly with .; the :execute command does that implicitly, and you can leave off the :.
But this double-quoting isn't necessary; you can skip the :execute:
:command! -nargs=1 FW echo <q-args>
You don't need a colon when you pass commands to "execute", it executes as if you were already in command mode.
You also don't need to concatenate strings with "." with execute if you want spaces between them, by default it concatenates multiple arguments with spaces.
I tried escaping args so that it would be concatenated as a string, this seems to work:
command -nargs=1 FW execute "echo" '<args>'
Is this what you were trying to achieve?
:h execute and :h user-commands are good reading.
edit:
some tests on this:
:FW "test"
test
:FW &shellslash
1
:FW 45
45
:FW "2+2"
"2+2"
:FW 2+2
4
As always, "execute" will execute anything you pass to it, so be careful.

what does the number 1 in the shellescape function mean in vim?

A vim command confuses me. I have read and re-read :help shellescape() several times. I still don't understand the meaning of the number 1 in shellescape(expand('%:p'), 1).
Here's the full command I'm trying to understand:
:nnoremap <F4> :exe ':silent !"c:\Program Files\Mozilla Firefox\firefox.exe"'shellescape(expand('%:p'), 1)<CR>
Let's break down this long command piece by piece.
the command is to map an exe command into F4 in the whole.
:exe is to execute some command.
:silent ! is to execute a shell command silently
"c:\Program Files\Mozilla Firefox\firefox.exe" can call my firefox program.
shellescape(), there are blanks to be escaped.
expand('%:p') can get current file name in full expression that is path + filename.
What does this excerpt from the help page mean?
With a |non-zero-arg| {special} and 'shell' containing "csh" in the tail it's
escaped a second time.
Is there some meaning such the same as 1 or 2 in s/(ha.*)(world)/\2\1/g?
please take a simple example in detail.
And i have two questions related to the topic.
1.In which way i can get how many type of shells in my gvim?
2.In which situation can i change 1 into 2 in shellescape()?
:nnoremap <F4> :exe ':silent !"c:\Program Files\Mozilla Firefox\firefox.exe"'shellescape(expand('%:p'), 2)<CR>
There are basically two different uses for shellescape(); one is the Vimscript system() function, the other the :! Ex command. While most escaping needs are identical in both uses (e.g. to deal with whitespace, arguments must be enclosed in quotes), the :! command requires some additional escaping, because on the command-line, Vim assigns special meaning to symbols like % (replacing it with the current file name). This does not happen for system().
Therefore, to tell shellescape() which escaping mode to use, it has the additional {special} argument; if you pass 1 (or any other value that evaluates to "true"), the additional characters are escaped, too.
TL;DR: Pass 1 for :! commands, 0 or omit the argument for use with system().
From the help for shellescape():
shellescape({string} [, {special}]) shellescape()
Escape {string} for use as a shell command argument.
On MS-Windows and MS-DOS, when 'shellslash' is not set, it
will enclose {string} in double quotes and double all double
quotes within {string}.
For other systems, it will enclose {string} in single quotes
and replace all "'" with "'\''".
When the {special} argument is present and it's a non-zero
Number or a non-empty String (non-zero-arg), then special
items such as "!", "%", "#" and "<cword>" will be preceded by
a backslash. This backslash will be removed again by the :!
command.
The 1 in your example simply tells shellescape() to escape special characters with a backslash. If you were to have (say) a ! in the path returned by expand(), it’d be replaced with \!.
shell is an option:
'shell' 'sh' E91
'shell' 'sh' string (default $SHELL or "sh",
MS-DOS and Win32: "command.com" or
"cmd.exe", OS/2: "cmd")
global
Name of the shell to use for ! and :! commands.
— :help 'shell'
“With a non-zero-arg {special} and 'shell' containing "csh" in the tail it's escaped a second time” means that if, as in your example, shellescape() is given a non-zero argument for special (your example has a 1), it will check shell for that "csh", and if it finds it (if your shell is set to something containing csh) it will double-escape it.
EDIT to specifically answer two questions added to (edited) original post:
You can get your shell setting (referred to in the shellescape() help quote) using :echo &shell.
2 is a Number and is non-zero. You should therefore be able to substitute 2 for the 1 in your example and get the same result.

function failed when call it from a command in vim

When I find the word in the current file, I need to first type "/keyword", but I can't see all the matched rows, So I tried to use the following command to do a shortcut, but it doesn't work, could you please help check why it failed?
function! FindCurrentFile(pattern)
echo a:pattern
execute ":vimgrep" . a:pattern . " %"
execute ":cw"
endfunction
command! -nargs=1 Fi call FindCurrentFile(<args>)
By the way, if you just need a quick overview over the matches you can simply use
:g//print
or
:g//p
(You may even leave out the p completely, since :print is the default operation for the :global command.)
When the current buffer has line numbers turned off, the results produced by :g//p can be difficult to take in fast. In that case use :g//# to show the matches with the line numbers.
Another trick that works for keywords is the normal mode command [I. It shows a quick overview of all the instances of the keyword under the cursor in the current buffer. See :h [I.
try to change the line in your function into this:
execute ':vimgrep "' . a:pattern . '" ' . expand("%")
<args> is replace with the command argument as is - that means that if you write:
Fi keyword
the command will run:
call FindCurrentFile(keyword)
which is wrong - because you want to pass the string "keyword", not a variable named keyword.
What you need is <q-args>, which quotes the argument.
BTW, if you wanted more than one argument, you had to use <f-args>, which quotes multiple arguments and separates them with ,.

How can I save each match in a different file with Vim

I would like to save the output of g/pattern1/,/pattern2/ to a file (for each match, a different file).
e.g.
def
.......
end
def
.......
end
you would end up with a file for each "def...end".
Tried using tempname() like so:
g/pattern1/,/pattern2/exe 'w ' . tempname() but this fails with no range allowed for exe
also tried
g/pattern1/,/pattern2/w `tempname()`
to get tempname() evaluated but this failed with a "too many filenames" error.
What am I missing? Can this be done by using global and other commands, or would you need vimscript to do it?
g/pattern1/,/pattern2/execute "w ".fnameescape(tempname())<CR>
Use execute whenever you want to insert variable into command-line if it is a mapping. If it is not, try using
g/pattern1/,/pattern2/w <C-r>=fn<Tab>e<Tab>te<Tab>)<CR><CR>
Here fn<Tab> with wildmode=longest,list:full will expand to fname, fnamee<Tab> will expand to fnameescape(, te<Tab> will expand to tempname(), so this is a short way to input <C-r>=fnameescape(tempname())<CR>. You can omit fnameescape if you are sure that tempname will not return filename with special characters.
And note that backticks will not execute vimscript function, they execute shell command, so `tempname()` tries to call tempname() in a shell and substitute filename with the result of this call. According to the help, you should have written `=tempname()`.
Try :g/pattern1/normal! :.,/pattern2/w `tempname()`^M with ^M entered as CTRL-V then ENTER

Resources