I have some specific parts of files with unformated xml code. I need to write a vimscript function that selects the text and calls xmllint over it.
I know I can do this on the command line :'<,'>!xmllint --format -
But I really need to do the same in a vimscript function and I don't know how to make something like normal! call for visual.
I tried this but it does not work correctly :
function! MyFormat()
... stuff done here
let startl = line("'<")
let endl = line("'>")
let line = getline(startl, endl)
let r = system('echo "' . join(line, "") . '" | xmllint --format -')
call setline('.', r)
endfunction
Every line in a Vim script is an Ex command. Since you already have a working Ex command, you might as well use it.
function! MyFormat()
" ... stuff done here
'<,'>!xmllint --format -
" ... more stuff done here
endfunction
But, again, data is missing so this might work… or not, be sufficient… or not, etc.
Related
I have implemented a command in vim which pastes the result of a calculation into your file, i.e. you type
:CalP 34 * 89
and it should paste the result after your cursor.
The code is as follows:
command! -nargs=+ CalP :call Calculator(<q-args>) | normal! p
py from math import *
fun Calculator(arg)
redir #"
execute "py print " a:arg
redir END
let #" = strpart(#", 1)
endfun
This works but is messier than I would like for a simple operation, mainly because:
I don't know a better way to redirect the output of py print ... to the " register
I have to write execute "py print " a:arg because just py print a:arg doesn't work
The let #" = strpart(#", 1) removes the stray newline at the front of the register which py print creates, ideally this should be removed
I think this should be do-able in one line but I don't know enough vimscript.
No scripting is needed for this. In insert mode, you can use <Ctrl-R>=34*89<CR> to insert the result of that calculation.
:help i_CTRL-R
:help expression
I'll second #Amadan's suggestion. If you prefer Python over Vimscript, you can use the pyeval() function, e.g. directly from insert mode:
<C-R>=pyeval('34 * 89')<CR>
If you would like to keep your custom command, that's possible, too:
command! -nargs=+ CalP execute 'normal! a' . pyeval(<q-args>) . "\<Esc>"
I'm using a vim plugin and when I want to look up a function under the cursor in the online api docs, I type '\da'
Here's the vimscipt code for the keymapping:
nnoremap <buffer> <LocalLeader>dda :silent call <SID>OpenURL('http://api.drush.ws/api/function/')<CR><C-L>
When the command is run, it writes the standard output from the shell into the current vim buffer, so the phrase:
"Created new window in existing browser session." will be written into the current buffer.
Also here's the openurl function:
function s:OpenURL(base)
let open = b:Drupal_info.OPEN_COMMAND
if open == ''
return
endif
let func = shellescape(expand('<cword>'))
if a:base == 'api.d.o'
if strlen(b:Drupal_info.CORE)
execute '!' . open . ' http://api.drupal.org/api/search/' .
\ b:Drupal_info.CORE . '/' . func
else
execute '!' . open . ' http://api.drupal.org/' . func
endif
else
execute '!' . open . ' ' . a:base . func
endif
endfun
How do I fix this/redirect stdout?
(I'm using ubuntu/gnome.)
Thanks!
I do not see anything that will put command output into the current buffer. But if you don’t want to observe command output at all, you can do one of two things:
Put silent in front of each ! (note: you must have the space after silent) and add redraw! command just before endfunction.
Replace every execute '!'.<...> with call system(<...>) (in this case having newline in arguments is much likely to cause bugs).
I'm trying to solve a problem with VIM. Here is what I'm trying to achieve:
I have a file with several lines in it. As I move my cursor from line to line, I want to send the current line as an argument to a Ruby script. The result of this script should be redirected to a VIM buffer which will be shown in a split window above the original text.
so far, I have been able to write a function that sends one line to the script and the results show up in a buffer above. I am not sure how to get this function to run each time the cursor moves to a new line and make the results update the same buffer. Any pointers would be appreciated.
My code:
function! BB()
redir => a
let str = getline(".")
let str1 = "\"" . str . "\""
silent execute "!${HOME}/scripts/test.rb " . str1
redir END
new
put! = a
endfunction
command! -nargs=0 BB echo BB()
The first thing that comes to my mind is mapping keys movements. Something like:
map j j:call BB()<CR>
map k k:call BB()<CR>
I have this function:
function! Find(name)
let l:list=system("find . -name '".a:name."' | perl -ne 'print \"$.\\t$_\"'")
let l:num=strlen(substitute(l:list, "[^\n]", "", "g"))
if l:num < 1
echo "'".a:name."' not found"
return
endif
if l:num != 1
echo l:list
let l:input=input("Which ? (CR=nothing)\n")
if strlen(l:input)==0
return
endif
if strlen(substitute(l:input, "[0-9]", "", "g"))>0
echo "Not a number"
return
endif
if l:input<1 || l:input>l:num
echo "Out of range"
return
endif
let l:line=matchstr("\n".l:list, "\n".l:input."\t[^\n]*")
else
let l:line=l:list
endif
let l:line=substitute(l:line, "^[^\t]*\t./", "", "")
execute ":e ".l:line
endfunction
command! -nargs=1 Find :call Find("<args>")
When I try adding a parameter, so the declaration becomes function! Find(name, search_dir), it always tells me i don't have enough parameters when I call the function in vim using :Find x y(where as Find: x would work when ther was only 1 parameter in the function declaration.
Any idea how I can add multiple parameters?
The end goal is to have a Find function that finds in a specified subdirectory.
An addition to #Peter Rincker answer: you should never use "<args>" if you want to pass parameter to a function as there is already a builtins <q-args> and <f-args> which do not need additional quoting and do not introduce a possibility of code injection (try Find ".string(g:)." with your code). First will pass all parameters as one item, second will produce a list of parameters suitable for a function call:
command -nargs=+ Find :call Find(<f-args>)
Another things to consider:
(system() call) Never pass user input to shell as-is, use shellescape(str, 1). You may have unexpected problems here.
strlen(substitute(l:input, "[0-9]", "", "g"))>0 condition is equivalent to input=~#'\D', but is much bigger.
You don't need to specify l:: it is the default scope inside functions.
There is a built-in glob() function: the whole system() line can be replaced with
join(map(split(glob('./*'.escape(a:name, '\`*[]?').'*'), "\n"), 'v:key."\t".v:val'), "\n")
Don't forget to escape everything you execute: execute ":e ".l:line should be written as execute "e" fnameescape(line) (it is the third place where code injection is possible in such a simple code snippet!).
It is better to use lists here, in this case you don't need to use something to add line numbers:
function s:Find(name)
let list=split(glob('./*'.fnameescape(a:name).'*'), "\n")
if empty(list)
echo string(a:name) "not found"
return
endif
let num=len(list)
if num>1
echo map(copy(list), 'v:key."\t".v:val')
let input=input("Which?")
if empty(input)
return
elseif input=~#'\D'
echo "Not a number"
return
elseif input<1 || input>num
echo "Out of range"
return
endif
let line=list[input]
else
let line=list[0]
endif
execute "e" fnameescape(line)
endfunction
command! -nargs=1 Find :call Find(<f-args)
Neither you nor me handle the situation where filename that matches pattern contains a newline. I know how to handle this (you can see my vim-fileutils plugin (deprecated) or os.listdir function of os module of frawor plugin (in alpha stage, not posted to vim.org)). I don't think such situation is likely, so just remember that it is possible.
You need to change -nargs=1 to -nargs=+. This will mean you have to have arguments but does not specify a number. I suggest you change your Find function to Find(...) and use a:0 get the number of arguments to error out if an invalid number of arguments are used.
Example function and command with multiple parameters:
command! -nargs=+ -complete=dir Find call Find(<f-args>)
fun! Find(name, ...)
let dir = getcwd()
if a:0 == 1
let dir = getcwd() . '/' . (a:1 =~ '[/\\]$' ? a:1 : a:1 . '/')
elseif a:0 != 0
echohl ErrorMsg
echo "Must supply 1 or 2 arguments"
echohl NONE
endif
let &efm = "%f"
cexpr []
caddexpr split(glob(dir . '**/*' . escape(a:name, '\`*[]?') . '*'), '\n')
copen
aug Find
au!
au BufLeave <buffer> ccl|aug! Find
aug END
endfun
For more help
:h :command-nargs
:h ...
Edit
Added example of function that excepts multiple parameters as suggest by #ZyX.
I've installed pydiction dictionary in vim so that I'd be able to get a list of python commands when I press tab after partially typed command. Everything is working fine, except every time the menu shows up, there is a file name besides the each command in the list. How do I remove that filename from the menu?
plz, take a look at the picture: http://www.uzbozor.com/uploads/vim.png
(copy and paste the link if clicking doesn't work)
Thanks
I haven't managed to solve this very elegantly, but there's a workaround by writing a custom completion function that simply greps the dictionary file for matches:
function! MyCompleteFunction( findstart, base )
if a:findstart
let line = getline('.')
let start = col('.') - 1
while start > 0 && line[start - 1] =~ '[A-Za-z_]'
let start -= 1
endwhile
return start
else
silent call DictGrep( a:base, 'path\to\dictionary\file' )
let matches = []
for thismatch in getqflist()
call add(matches, thismatch.text)
endfor
return matches
endif
endfunction
Note that I have defined a function DictGrep() that actually performs the vimgrep. This is so I can call it silently and not be troubled by error messages:
function! DictGrep( leader, file )
try
exe "vimgrep /^" . a:leader . ".*/j " . a:file
catch /.*/
echo "no matches"
endtry
endfunction
Then simply define set the completefunc:
setlocal completefunc=MyCompleteFunction()
and then use for insert-mode completion (which could be mapped to replace your current dictionary completion binding).
The vimgrep could be quite a slow operation, but I haven't noticed any problems unless there are hundreds of matches in the dictionary file.
Hope this helps.