6

When I click on a link in thunderbird and firefox is already running, it doesn't open a new window, just a new tab.

I want the same thing for vim: no matter where I am and how I send a bunch of files to it, I want them opened in a single session.

To do this, I've added the following code in my ~/.bashrc and ~/.zshrc.

function nv {

vim --serverlist | grep -q VIM

if [ $? -eq 0 ]; then

    if [ $# -eq 0 ]; then
        vim
    else
        vim --remote "$@"
    fi

else
    vim --servername vim "$@"
fi

}

It defines the function nv which can do 3 things :

  • if no VIM server is running, nv launches one
  • if a VIM server is running and one or more arguments were passed to nv, it sends them to the server
  • if a VIM server is running but no argument was passed to nv, it launches a simple vim session (so that I can still launch a separate vim session by using the same function / alias)

I've recently read that you could redirect the output of a shell command as the quickfix list to vim. For example :

vim -q <(grep -Rn foo *)

It works with vim but not with my function nv.

I would like to use the same syntax so that the output of grep is not opened by a new vim session, but by the VIM server.

When I use nv -q <(grep -Rn foo *), the VIM server doesn't receive the output of grep but a file called -q and another one : /proc/<pid>/fd/11.

I know why it doesn't work, the function was not written with that case in mind.

But then, I tried something simpler : vim --remote -q <(grep -Rn foo *)

And the result is the same, it doesn't work, the server still receives two files : -q and /proc/<pid>/fd/11.

I would like to know if it's possible to edit the code of my nv function so that it works when I use it with the -q switch to remotely populate the quickfix list of an already running vim server, and if so receive some advice on how to do it.

If it's not, at least, I would like to know how to use the -q and --remote switch simultaneously.


Edit : I may be wrong but I don't think -q and --remote can be used simultaneously.

For the moment, I've come up with the following command :

vim --remote-send ":grep -Rn foo *<cr><cr>"

Now I need to edit the nv function to integrate it, but I don't know how to do it.

Edit bis: I don't think it's worth the trouble, I'll stick with nv and the last command when needed.

2
  • 1
    I'll post the answer later on. Let's see if someone else builds a better solution in the meantime.
    – muru
    Nov 26, 2015 at 18:11
  • 1
    I have not much experience with --remote arguments, but you should get something to work, if in your function you check for the argument -q and then use --remote-send ":cexpr $1<cr>" or something like this. That should work, but as I said, I don't know for sure. Nov 26, 2015 at 22:22

1 Answer 1

2

You cannot use --remote with -q, any arguments after --remote are treated as filenames:

--remote    Connect to a Vim server and make it edit the files given in
            the rest of the arguments.  If no server is found a warning
            is given and the files are edited in the current Vim.

That said, you cannot use the result of process substitution (<(cmd)) with a program running elsewhere. If you notice, the file name from process substitution uses /proc/self:

$ echo <(date)
/proc/self/fd/11

The shell sets up the file descriptors of the executed command so that one of them points the substituted process. Naturally, this fd cannot be easily used by a separate process - you'll at least need to translate /proc/self to /proc/<PID>.

Therefore, it would be easier if we could run the command in Vim itself. The cexpr command should help us there: quickfix.txt For Vim version 7.4. Last change: 2015 Sep 08

                                                        :cex :cexpr E777
:cex[pr][!] {expr}      Create a quickfix list using the result of {expr} and
                        jump to the first error.
                        If {expr} is a String, then each new-line terminated
                        line in the String is processed using the global value
                        of 'errorformat' and the result is added to the
                        quickfix list.
                        If {expr} is a List, then each String item in the list
                        is processed and added to the quickfix list.  Non
                        String items in the List are ignored.
                        See :cc for [!].
                        Examples: 
                                :cexpr system('grep -n xyz *')
                                :cexpr getline(1, '$')

Huh, one of the examples uses grep much the same way you do.

Using cexpr, the following function could work:

function nv (
    if vim --serverlist | grep -q VIM; then
        if [ $# -eq 0 ]; then
            vim
        elif [[ $1 == -q ]]; then
            shift
            IFS=' '
            vim --remote-send ":cexpr system('$*')<cr><cr>"
        else
            vim --remote "$@"
        fi
    else
        vim --servername VIM "$@"
    fi
)

Use it thus:

nv -q grep -Rn foo *

That's to say, you pass the command as you would write it to nv -q. The function checks if the first argument is -q, and then uses vim --remote-send to call cexpr and system() on the rest of the arguments.

This bit might need explaining:

shift
IFS=' '
... system('$*') ...

Since the first argument is -q and it's no longer needed, and using $* is more convenient, I simply discard -q.

Now, there are two quick ways of combining the arguments: $@ and $*. A quoted $@ is usually preferred, if you want separate words (we don't). So, we use $*, which combines the arguments using the first character of IFS. What the value of IFS is depends on the shell, so I set it to a space to get the needed effect.

The best effect is if you send a quoted command:

nv -q 'grep -Rn foo *'

Then * won't be expanded by the shell you called it from, thus preventing problems when called by system().

Note

My earlier version of the function used {} to group the command. This version uses (), to create a subshell. This makes it easier to set variables like IFS locally without disturbing the shell.

1
  • Nice explanation ! The only thing hard to understand for me was the special parameter '$*'. There seems to be a sort of chain : original shell > vim > system() function > another shell, and they all inherit the environment of the first one otherwise the last shell wouldn't be able to expand '$*' correctly. Thanks for your thorough explanation.
    – saginaw
    Nov 27, 2015 at 16:03

Your Answer

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

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