3

I have a vimscript which needs to switch to a particular buffer. That buffer will be specified by either full path, partial path, or just its name.

For example:

I am in the directory /home/user/code and I have 3 vim buffers open foo.py src/foo.py and src/bar.py.

  • If the script was told to switch to buffer /home/user/code/foo.py it would switch to buffer foo.py.

  • If it were told to switch to user/code/src/foo.py it would switch to buffer src/foo.py

  • If it were told to switch to foo.py it would switch to buffer foo.py

  • If it were told to swith to bar.py it would switch to buffer src/bar.py

The simplest solution I can see is to somehow get a list of the buffers stored in a variable and use trial and error.

It would be nice if the solution was cross platform, but it needs to at least run on Linux.

3 Answers 3

7

The bufname() / bufnr() functions can lookup loaded buffers by partial filename. You can anchor the match to the end by appending a $, like this:

echo bufnr('/src/foo.py$')
Sign up to request clarification or add additional context in comments.

1 Comment

wry grin I knew vim would have a function to do this. Thanks.
0

I found a way to do this using python in a vimscript. With python I was able to get the names of all the buffers from vim.buffers[i].name and used os.path and os.sep to process which buffer to switch to.

In the end, I decided that it would be more helpful for it to refuse to do anything if the buffer it was requested to switch to was ambiguous.

Here it is:

"Given a file, full path, or partial path, this will try to change to the
"buffer which may match that file. If no buffers match, it returns 1. If
"multiple buffers match, it returns 2. It returns 0 on success
function s:GotoBuffer(buf)
python << EOF
import vim, os
buf = vim.eval("a:buf")

#split the paths into lists of their components and reverse.
#e.g. foo/bar/baz.py becomes ['foo', 'bar', 'baz.py']
buf_path = os.path.normpath(buf).split(os.sep)[::-1]
buffers = [os.path.normpath(b.name).split(os.sep)[::-1] for b in vim.buffers]
possible_buffers = range(len(buffers))

#start eliminating incorrect buffers by their filenames and paths
for component in xrange(len(buf_path)):
    for b in buffers:
        if len(b)-1 >= component and b[component] != buf_path[component]:
            #This buffer doesn't match. Eliminate it as a posibility.
            i = buffers.index(b)
            if i in possible_buffers: possible_buffers.remove(i)

if len(possible_buffers) > 1: vim.command("return 2")
#delete the next line to allow ambiguous switching
elif not possible_buffers: vim.command("return 1") 
else:
    vim.command("buffer " + str(possible_buffers[-1] + 1))
EOF
endfunction

EDIT: The above code seems to have some bugs. I am not going to fix them because there is another answer which is much better.

Comments

0

Note on how to escape file path in bufname argument:

let bufnr_val = bufnr('^'..escape(full_or_partial_path, '\.*?~,^${}[]')..'$')

Note that this still match either full or partial path. If you want to match full path only do this

if bufnr_val >= 0 && expand('#'..bufnr_val..':p') != full_path
    let bufnr_val = -1
endif

This is because the documentation contains the following:

bufnr([{buf} [, {create}]])             *bufnr()*
        The result is the number of a buffer, as it is displayed by
        the `:ls` command.  For the use of {buf}, see |bufname()|
        above.
bufname([{buf}])                    *bufname()*
        The result is the name of a buffer.  Mostly as it is displayed
        by the `:ls` command, but not using special names such as
        "[No Name]".
        If {buf} is omitted the current buffer is used.
        If {buf} is a Number, that buffer number's name is given.
        Number zero is the alternate buffer for the current window.
        If {buf} is a String, it is used as a |file-pattern| to match
        with the buffer names.  This is always done like 'magic' is
        set and 'cpoptions' is empty.  When there is more than one
        match an empty string is returned.

        A full match is preferred, otherwise a match at the start, end
        or middle of the buffer name is accepted.  If you only want a
        full match then put "^" at the start and "$" at the end of the
        pattern.

                            *file-pattern*
The pattern is interpreted like mostly used in file names:
    *   matches any sequence of characters; Unusual: includes path
        separators
    ?   matches any single character
    \?  matches a '?'
    .   matches a '.'
    ~   matches a '~'
    ,   separates patterns
    \,  matches a ','
    { } like \( \) in a |pattern|
    ,   inside { }: like \| in a |pattern|
    \}  literal }
    \{  literal {
    \\\{n,m\}  like \{n,m} in a |pattern|
    \   special meaning like in a |pattern|
    [ch]    matches 'c' or 'h'
    [^ch]   match any character but 'c' and 'h'

Note that for all systems the '/' character is used for path separator (even
for MS-Windows).  This was done because the backslash is difficult to use in a
pattern and to make the autocommands portable across different systems.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.