I try to create a user-defined command in vim that is supposed to return an uuid.
command -range CreateUUID !python3 -c 'import uuid; print(uuid.uuid4())'<cr>
The command works but it just outputs to the terminal.
I have tried
:. CreateUUID
:% CreateUUID
In VimScript commands never return anything, as they are not even considered as expressions. You must declare a function instead. For example,
function! UUID() abort
python3 import uuid
return py3eval('str(uuid.uuid4())')
endfunction
:put =UUID()
An alternative is to capture the command's output by using a specialized function like execute() (for an Ex command) or system() (for an external tool):
:put =system('uuidgen')
Yet another thing is "read-space-bang" command (:h :r!), so you can do
:r !python3 -c 'import uuid; print(uuid.uuid4())'
But, 1) again, it only inserts an external tool's stdout, not an Ex-command's output; 2) it's not the way to compose two arbitrary Ex-commands, as "read-space-bang" counts as a single command with a very unique syntax.
Related
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.
I have a an interactive Perl script, which prints prompts to STDERR and reads lines from STDIN. The final output of this script is an IP address, printed to STDOUT. Here's a numpty version of such a script as an example.
my #pieces;
for (1..4) {
print STDERR "please enter piece $_ of the IP:"; chomp(my $in = <>);
push #pieces, $in;
}
print join '.', #pieces;
print "\n";
I use the vim-fireplace vim plugin. This plugin has a feature where I can say:
:Connect nrepl://127.0.0.1:9999
I want to know how to configure vim so that when I issue a particular command, let's say:
:InteractiveConnect
it will do the following:
Run the Perl script, allowing me to enter 4 pieces of the IP address.
Capture the IP address output by the Perl script.
Interpolate the IP address into the :Connect command
Run the :Connect command.
A bit more info based on some of the responses:
If I call this script using:
:!/path/to/myscript.pl
Then it executes fine and I am able to see the result from it printed in the vim window, followed by
Press ENTER or type command to continue
If the output of the script is being saved in some buffer after execution via !, is it possible to get access to that buffer in vimscript and just capture the bit I want (the last line) with a regex?
Okay, there's probably a more elegant way to do this, but how about this:
function! <SID>InteractiveConnect()
let tempfile=tempname()
exe '!/path/to/your/script.pl >' . shellescape(tempfile)
try
exe 'Connect nrepl://' . readfile(tempfile, '', -1)[0]
finally
call delete(tempfile)
endtry
endfunction
command! -nargs=0 InteractiveConnect call <SID>InteractiveConnect()
This creates a temporary file, writes to it with the script (using system() doesn't work because it doesn't wait for input), reads the last line in the tempfile to the Connect command, and then finally deletes the tempfile.
Maybe something like:
exec 'Connect nrepl://' . matchstr(system('your/script.pl'), '^.\+\%$')
(Untested.) This runs the script using system() then matches the output against the regular expression ^.\+\%$, (where \%$ means end-of-file; if your file is terminated with a newline, an additional \n might be neccessary before it) and feeds the matched str to the Connect command. .
I frequently send files to Vim from Visual Studio. I have it set up as an external tool with the following parameter:
"+call cursor($(CurLine), $(CurCol))"
However, I also want to be able to call my own function as well. When I'm editing a file from VS I want the window to be large, so I expected to be able to do something like this:
"+call cursor($(CurLine), $(CurCol)); +call Embiggen()"
However, that doesn't work. I've tried a few variations (e.g. , call Embiggen(), etc).
Obviously I could write my own PlaceCursorAndEmbiggen function, but I don't really want to do that. Is there any way to call multiple functions on Vim startup?
Eureka!
Simply pass two strings:
"+call cursor($(CurLine), $(CurCol));" "+call Embiggen()"
Maybe the solution would have been easier to find had you used the alternative, more commonplace syntax: -c "cmd" instead of "+cmd". According to :help -c, you can pass up to 10 of these.
These exact commands can be combined into one using pipe symbol:
"+call cursor($(CurLine), $(CurCol)|call Embiggen()"
. There are much more that can be combined this way, but some like :normal can’t, use #Ingo Karkat’s or your own answer for them. If you are short* on +commands and still don’t want to create a .vim file you can use either :execute
vim -c "execute 'normal! 1' | execute 'normal! 2'"
or (bash/zsh) -S with process substitution:
vim -S <(echo '
normal! 1
normal! 2
')
. Though most of time it is better to just create a .vim file.
* You can pass up to 10 + or -c (they are equivalent and they are not counted separately) and 10 other --cmd, though letter is less useful.
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.
I am using the Ack plugin in Vim, which helps me to quickly search for strings in my project. However, sometimes I want to replace all or some occurrences of the found strings. You can do some kind of global search and replace using the Vim arglist like this (source)
:
:args app/views/*/*
:argdo %s/, :expire.*)/)/ge | update
But instead of using args, I would prefer to do a search via Ack and then do the replace in all files that have been found. Is there a way to do it similar to the argdo command?
I've decided to use ack and perl to solve this problem outside of Vim so I could use the more powerful Perl regular expressions instead of the GNU subset. You could map this to a key stroke in your .vimrc.
ack -l 'pattern' | xargs perl -pi -E 's/pattern/replacement/g'
Explanation
ack
ack is an awesome command line tool that is a mix of grep, find, and full Perl regular expressions (not just the GNU subset). It's written in pure Perl, it's fast, it has match highlighting, it works on Windows and it's friendlier to programmers than the traditional command line tools. Install it on Ubuntu with sudo apt-get install ack-grep.
xargs
xargs is an old Unix command line tool. It reads items from standard input and executes the command specified followed by the items read for standard input. So basically the list of files generated by ack are being appended to the end of the perl -pi -E 's/pattern/replacement/g' command.
perl -pi -E
Perl is a programming language.
The -p option causes Perl to create a loop around your program which iterates over filename arguments.
The -i option causes Perl to edit the file in place. You can modify this to create backups.
The -E option causes Perl to execute the one line of code specified as the program. In our case the program is just a Perl regex substitution.
For more information on Perl command line options, see perldoc perlrun. For more information on Perl, see http://www.perl.org/.
Now, Vim has this new command cdo that will run the given command to each line of the quickfix list.
So you can use
:Ack pattern
:cdo s/pattern/newpattern/g
I don't believe there's a built in way of doing this, but it should be easy to make one.
What you need to do is create a command that calls a custom function. The function should then use the getqflist() function to get all of the entries in the quickfix list and exe to do the dirty work. Be careful what you pass as an argument!
" Define a command to make it easier to use
command! -nargs=+ QFDo call QFDo(<q-args>)
" Function that does the work
function! QFDo(command)
" Create a dictionary so that we can
" get the list of buffers rather than the
" list of lines in buffers (easy way
" to get unique entries)
let buffer_numbers = {}
" For each entry, use the buffer number as
" a dictionary key (won't get repeats)
for fixlist_entry in getqflist()
let buffer_numbers[fixlist_entry['bufnr']] = 1
endfor
" Make it into a list as it seems cleaner
let buffer_number_list = keys(buffer_numbers)
" For each buffer
for num in buffer_number_list
" Select the buffer
exe 'buffer' num
" Run the command that's passed as an argument
exe a:command
" Save if necessary
update
endfor
endfunction
You could using ack by this way
:args `ack -l User app/`
:argdo %s/, :expire.*)/)/ge | update
Or use ag
:args `ag -l User app/`
:argdo %s/, :expire.*)/)/gec | w
I use MacVim (activated with mvim in a shell). I pipe the results of ack to mvim:
mvim -f $(ack -l $#)
Then in MacVim, I search/replace using bufdo:
:bufdo %s/SEARCH/REPLACE/gce | update
Omit the c option if confirmation is not needed.