0

I am trying to execute the following:

exe 'v/' .. g:pattern1 .. '/ | v/' .. g:pattern2 .. '//'

where g:pattern1 and g:pattern2 are regular expressions, but what happen is that in the returned text only g:pattern1 is excluded.

If I run them individually i.e.:

exe 'v/' .. g:pattern1 .. '/' 
exe 'v/' .. g:pattern2 .. '/'

they both work.

I wonder if it is possible to chain multiple :v (and also g:) commands through |.

EDIT: To better explain what I want to achieve consider the following text:

banana, apple
apple, banana

apple

and assume that it is loaded in the current buffer.

I want that:

  1. the returned text from a command that include a chain of v similar to the one I posted at the beginning of this question has: a) all the blank lines removed; b) all the lined starting with apple removed
  2. The returned text as per point 1. shall be redirected to a register (say register a)

The solution that I was thinking looked like the following

vim9script

var pattern1 = '^\s*$' 
var pattern2 = '^apple'

redir @a  
silent exe 'v/' .. pattern1 .. '/ | v/' .. pattern2 .. '//'
redir END 

that that does not work as it resemble the case I wrote at the beginning of the post that, as said, does not work.

I am not interested in alternative solutions that:

a) Modify the buffer,

b) Require the creation of scratch or temporary buffer/file.

3
  • What is your goal, exactly?
    – romainl
    Apr 11, 2023 at 14:38
  • I edited the question. I hope it is now more clear.
    – Barzi2001
    Apr 11, 2023 at 15:51
  • XY for the loss.
    – romainl
    Apr 11, 2023 at 16:42

3 Answers 3

1

Your problem description:

  1. remove all the empty lines
  2. remove all the lines starting with apple
  3. I don't want to modify the buffer
  4. I don't want to create any scratch or temporary buffer/file
  5. I want to store the text with the removed lines a register (say register a)

is a mess and I believe this is because you can't think clearly about what you are doing:

  • you mention "empty lines" while your pattern says "blank line",
  • #1 and #2 are in direct contradiction with #3,
  • you rely almost solely on (poorly understood) Ex commands in a script,
  • and the worst sin of all… you include your broken solution, making the whole thing a clear XY problem.

Anyway, here is a slightly reworked description of your problem, without the misguided step that you took to solve it:

I want to store the text of the current buffer, without the blank lines and without the lines that start with apple, into a register (say register a).

Now that we have a clear problem, we can try to figure out how to solve it in a smart way. And, since we are in a script, we will try to do it with, well… scripting.

It is generally a good idea to split a large, difficult problem into a bunch of smaller, easier ones so that's what we are going to do…

Problem #1:

I want to store the text of the current buffer, without the blank lines and without the lines that start with apple, into a register (say register a).

Storing arbitrary text in a register is done with :help setreg():

vim9script
var myText = 'foobar'
setreg('a', myText)

It is even possible to give a list to setreg() to store multiline text without having to mess with EOLs and such:

vim9script
var myLines = ['foo', bar']
setreg('a', myLines)

Problem #2:

I want to store the text of the current buffer, without the blank lines and without the lines that start with apple, into a register (say register a).

Getting the content of the current buffer in a list is done with :help getline():

vim9script
var myLines = getline(1, '$')

Problem #3:

I want to store the text of the current buffer, without the blank lines and without the lines that start with apple, into a register (say register a).

Removing items from a list if they match a given criteria is done with :help filter(). Our list is a list of lines so we can use it to remove unwanted lines:

vim9script
var myLines = getline(1, '$')
    \ ->filter('v:val !~ "^apple"')
    \ ->filter('v:val !~ "^\s*$"')

Solution

Storing the filtered content of the current buffer in a register is thus as simple as:

vim9script
var myLines = getline(1, '$')
    \ ->filter('v:val !~ "^apple"')
    \ ->filter('v:val !~ "^\s*$"')
setreg('a', myLines)
4
  • Thanks. Btw, having confusing ideas and expressing poorly are often tied by causality, but unfortunately it is not always the case. I am going to edit my question to improve clarity. But again, thanks for your reply. I was thinking to use pure script but I don't think I could ever find the :h filter() function. Now I know.
    – Barzi2001
    Apr 11, 2023 at 18:09
  • Well, actually that was my biggest wondering about scripting: how to manipulate list, dictionaries, etc. I learned how to move stuff around through the various setline(), getbufline(), etc. but I never understood how to change stuff, so I always relied on exe or normal. Now I learned this filter function. Good.
    – Barzi2001
    Apr 11, 2023 at 18:33
  • Don't "wonder", ask Vim: :help function-list, :help list-functions, :help dict-functions, etc.
    – romainl
    Apr 11, 2023 at 18:41
  • I know. The majority I learned is from there. But the docs is huge and overwhelming. I checked under text functions and strings function before…
    – Barzi2001
    Apr 11, 2023 at 20:00
0

I believe you miss the second exe:

exe 'v/' .. g:pattern1 .. '/' | exe 'v/' .. g:pattern2 .. '/'
3
  • Hum, it does not seem to work... This will consider twice the current buffer, whereas I want the output of the first v to be used as input of the second v without changing the current buffer. I edited my question.
    – Barzi2001
    Apr 11, 2023 at 14:14
  • I'm not sure I understand your demand correctly. Even if you don't chain the :v command the first already modify the buffer. Maybe an example would help us. Apr 11, 2023 at 14:48
  • 1
    I'll prepare an example and I will post it (not now though!). ;)
    – Barzi2001
    Apr 11, 2023 at 14:57
0

tl;dr: use @romainl solution

Is it possible to chain an arbitrary number of v[global]?

Sort of. The | is right out. From :h E147:

When the command is used recursively, it only works on one line.  Giving a
range is then not allowed. This is useful to find all lines that match a
pattern and do not match another pattern: >
    :g/found/v/notfound/{cmd}
This first finds all lines containing "found", but only executes {cmd} when
there is no match for "notfound".

So for your case you can technically do:

:v/^\s*$/v/^apple/

Can we do a bit better?

We can do a bit better by flipping some of the logic slightly. From not matching an empty line to match a line with a non-whitespace character, \S. Now it becomes

:g/\S/v/^apple/

You can then use your :redir @a tricks to capture the content into a register.

There are some other considerations though. Often using ex commands like this will modify various bits of Vim's state. In this case the current search pattern, @/, register. Which means you need to use :keeppatterns

A more interesting question?!

I think the more interesting question is can you do this with a single pattern and :global/:vglobal command

Vim has some interesting pattern matching operators as well as look aheads/behinds. Using \& and a negative lookbehind you can do this in a single pattern

As fun as it would be to create such a pattern and explain it, Vim also ships with :LogiPat which can aid in making such patterns. See :h :LogiPat

Your pattern can be created using :LogiPat "\S"&!"^apple" which sets the search register to:

\%(.*\S.*\&^\%(\%(^apple\)\@!.\)*$\)

You can also use the LogiPat() function if you rather not mutate the search register.

Note: :LogiPat has some limitations so not every set of patterns will work

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.