1

Working on a larger project, I tend to switch from topic to topic using git. Some times I want to run a command on all the buffers that are related to the topic at hand.

I can do a :bufdo, but this would run on many loaded buffers that are not related to what I am doing currently (and will suggest reloading the file, which might not exist in this branch...).

Therefore, I would like to limit the scope of :bufdo (or something similar) to only the active/visible buffers.

I know that I can list the active buffers using :ls a, but how to use the output?

4
  • 1
    Reimplement :bufdo with a function which uses :h :redir to write the output of :ls in a variable, turn it into a list, and switch to each buffer to execute your command? Or use :h :windo or :h :tabdo to execute only on buffer in the current window or in all tabs? Or put the output of :ls a in the quickfix list and use :h :cfdo? – statox Apr 9 at 12:45
  • Thanks for the tips. – clem steredenn Apr 9 at 12:54
  • In addition to those commands, see argdo (and the myriad of ways to fill the arg list, including :args `git ls-files` – D. Ben Knoble Apr 9 at 12:54
  • @D.BenKnoble I thought about :argdo, but could not find how to populate :args with the output of :ls a. – clem steredenn Apr 9 at 12:57
5

Interesting question. I had to think about how to do this as I wanted to avoid using :ls a. (Parsing :ls output isn't ideal in much the same way that parsing the output of the shell command ls is non-ideal.) I've got it down to a single Ex command line, though one that could use a bit of explanation depending on your familiarity with Vim.

Per :h active-buffer, an active buffer is one that is currently being displayed in a Vim window. I figured that if that is an easily obtainable piece of information then it's likely that it would be provided by the function that is a one-stop shop for all your buffer info needs: getbufinfo().

Specifically, I have in mind the windows property that is included in getbufinfo() output. This property contains a list of all the windows the associated buffer currently appears in. If it's in a window then it's active, right?

Cutting to the chase here's the command that displays the buffers appearing in windows, i.e. the active buffers:

:echo filter(getbufinfo({'bufloaded':1}), {v -> len(v:val['windows'])})

Breaking it down:

  • filter(list_or_dict, condition) : takes a List or Dictionary and removes some subset of elements from it based on a condition. The condition is specified in the second param as an expression or function which is evaluated for each element. A non-zero result means the element is kept, otherwise it is dropped (filtered out). As for the specific parameters being used...
  • getbufinfo({'bufloaded':1}) : As mentioned above this function provides a bunch of info about a buffer or buffers. The Dictionary we're passing in as a param is telling the function to only return buffers that are loaded. By definition an active buffer is a loaded buffer so this allows us to narrow things down a bit. The returned value is a List of Dictionary objects, one for each loaded buffer.
  • {v -> len(v:val['windows'])} : For each buffer-associated Dictionary retrieved from getbufinfo() we want to determine how many elements are in the windows property (which is itself a List) and drop buffers with none. We need to evaluate to zero for those elements we want dropped and this works out nicely as we just make a call to the length function len() and return whatever it returns.

If you're not familiar with the -> notation it's basically just a shorthand way of calling a function, in this case one that takes a single parameter. Explaining this "lambda expression" further is probably beyond the scope of this answer but I think it's otherwise pretty clear what's going on here.

Here's an example use of the command. You can replace the echo call with whatever work you want to do against each buffer. The buffer number is available so you can address each buffer by that (among other possibilities). If you want me to get into that a bit further let me know.

for buf in filter(getbufinfo({'bufloaded':1}), {v -> len(v:val['windows'])})
    echo buf.bufnr buf.name
endfor
8
  • 1
    Thanks, that's indeed a nice alternative to parsing :ls a, which I was hoping to avoid (as per my own answer). – clem steredenn Apr 9 at 13:43
  • You bet. Let me know if you have any questions about it. – B Layer Apr 9 at 13:43
  • @clemsteredenn Don't forget to accept the answer you believe is the best solution to your problem. (Or provide feedback if you feel something remains unaddressed.) Thanks. – B Layer Apr 24 at 2:27
  • I find it hard to pick, sorry. I like your well informed answer. I certainly learned from it. But I find that the combination of tabdo and windo is the most practical for me. And since I was the one writing it... – clem steredenn May 5 at 8:12
  • 1
    @clemsteredenn Given how conflicted you were I am extra appreciative of the accept. :) – B Layer May 5 at 18:36
3

Following from @statox comment, it is possible to

  • Use :redir in a function, like

    function! s:runCmdActiveBuffer(cmd)
      :redir => lbufs_str
      :ls a
      :redir END
      :let lbufs = split(lbufs_str, '\n')
      :for i in lbufs
        :let bufnr = split(i)[0]
        :execute 'b '.bufnr
        :execute a:cmd
      :endfor
    endfunction
    
    command! -args=+ ActBufdo :call s:runCmdActiveBuffer(<f-args>)
    

    or populate the quickfix list, e.g. using :cexpr to use :cfdo (or as suggested by D.Ben Knobble, pack the file name to the arg-list and run :argdo)

  • combine :tabdo and :windo as

    :tabdo windo {cmd}
    

There are probably many ways to improve those.

1
  • 2
    Just a quick tip: You don't need to prefix all of your commands with : in your vimscript, vimscript already knows that they are ex commands :) – statox Apr 9 at 13:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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