This is a follow-up to my question about using multi-line regex in Vim.
A quick explanation: I have blocks of text tagged with #tags
and separated by a blank line. I want to filter out all blocks that include a specific tag. I was helped with regex here (thanks!), so I constructed the following command:
command -nargs=1 TagSearch g/^\(.\+\n\)\{-}.*#<args>.*/y a | vnew | put a | %s/^#.*<args>.*\n/&\r
Hence, doing a :TagSearch tag
should:
- Search for the
#tag
, - paste all corresponding Text Blocks into a new vertical buffer,
- Add a blank line between the Text Blocks in the new buffer.
Q1: When I do each step separately, everything works. But the multi-command only pastes the first matching text block into the new buffer. Why?
Q2: How could I make the command accept multiple arguments (in case I want to search for many #tags
at a time)? Thanks!
One can use the following implementation.
command! -nargs=* -bar TagSearch call CollectParagraphs([<f-args>])
function! CollectParagraphs(tags) range
let tags = len(a:tags) > 0 ? a:tags : [expand('<cword>')]
let pats = map(copy(tags), '"\\.\\*#" . escape(v:val, "\\")')
let v = winsaveview()
let [sr, @/; lines] = [@/, '\V' . join(pats, '\&')]
g//call extend(lines, getline(search('\n\n\zs', 'bnW'), line("'}")))
let @/ = sr
call winrestview(v)
exe 'vnew' escape(join(tags), ' %#|\')
set buftype=nofile bufhidden=hide noswapfile
call setline(1, lines)
endfunction
I had a good old play around with this and learnt quite a bit in the process! Looks like there are a few problems going on here.
Here's how I worked through to get it working, although as there are so many approaches with vim there is probably a tidier way somehow.
The g command is of the form g/pattern/command. What I think was happening was in your original form, the | vnew | put a | %s... part of the command was being executed linewise with the g command, rather than once as part of the TagSearch command. I changed the g command to being 'executed' instead which solves the problem - I'd be interested to know if there is a way to specify what the pipe applies to without using execute, I couldnt get it working (brackets don't work for example). This would have been why you were only getting the first line. This gives us (fixing a typo in your '%s' command):
command! -nargs=1 TagSearch execute 'g/^\(.\+\n\)\{-}.*#<args>.*/y a' | vnew | put a | %s/^.*#<args>.*\n/&\r
This seems to reverse the problem though, and we now only get the last line in the new buffer. The other problem with the g command, is that when you do /y a, it overwrites the a register each time. There is a way to get vim to append to a register instead, using the capitalized register name (/y A) (see :help quotea). Doing it this way we need to initialize the register to blank first, using let. That gives us this:
command! -nargs=1 TagSearch let @a='' | execute 'g/^\(.\+\n\)\{-}.*#<args>.*/y A' | vnew | put a | %s/^.*#<args>.*\n/&\r
Finally to get it to execture with multiple tags, I just fiddled with the <args>
a bit (calling TagSearch tag1 tag2, args would literally be the string 'tag1 tag2') to get it to fit in the regex as below:
command! -nargs=* TagSearch let @a='' | execute 'g/^\(.\+\n\)\{-}.*#\(' . substitute('<args>', ' ', '\\|', 'g') . '\).*/y A' | vnew | put a | execute '%s/^.*#\(' . substitute('<args>', ' ', '\\|', 'g') . '\).*\n/&\r'
If you add any more features to this you might want to try playing around with a little vimscript function or something instead, otherwise it might get pretty hard to maintain! You'd be able to grab your text blocks into nice lists and potentially process them a bit more easily rather than having to do everything as if you were actually typing. Take a look at :help functions for things that are available (although there may be a better starting point in the help for vimscript somewhere else).
来源:https://stackoverflow.com/questions/10596502/vim-multi-command-for-filtering-out-blocks-of-text