├── .gitignore
├── .travis.yml
├── INSTALLATION.md
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── contrib
├── completion.bash
└── completion.fish
├── images
├── demo1.gif
└── demo2.gif
├── nvr
├── __init__.py
└── nvr.py
├── setup.py
└── tests
└── test_nvr.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | neovim_remote.egg-info/
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: xenial
2 |
3 | language: python
4 |
5 | python:
6 | - 3.5
7 | - 3.6
8 | - 3.7
9 | - 3.8
10 |
11 | install:
12 | - pip3 install .
13 | - wget https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.tar.gz
14 | - tar zxf nvim-linux64.tar.gz
15 |
16 | before_script:
17 | - export PATH=$PWD/nvim-linux64/bin:$PATH
18 |
19 | script:
20 | - pytest -v
21 |
22 | jobs:
23 | include:
24 | - stage: Deploy
25 | if: tag =~ v[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+
26 | python: 3.6
27 | install: true
28 | before_script: true
29 | script: true
30 | deploy:
31 | provider: pypi
32 | distributions: "sdist bdist_wheel"
33 | on:
34 | all_branches: true
35 | user: mhinz
36 | password:
37 | secure: "MngOQJaKlbiW2utrRyJ6Dk/9gUCNpiwxl0rV9v2Ke0ZPvr6WVzO35ykvDVTxBaw68lmZ7h+5CcRebts4Ru4hdZI6y4MSqhM2TOEst8z8Dr3wznmpN8XVki2jLhOBf4FFNC0uaHaQ5/uRfbNpn5p/EQ4mXtfQuL/Zq8noizTG0PE6PUHC+8DpazwvXUBKD7hnNolFxGSFC+7ahZZCgbhcLLQt7CAH72OD8zsYGHxSZSk/Vssom34df0Lqgzy5KLdIvCEg6vPD6LpqR4ATGwuE5T5wKdgQYA7h/DrSb87uaVFRzXwSCbELnjPTPIouu7haTAXS5PaBLZWQUJ45DhKrwdHvqud3ECOA5NgZSiBS8YPDXfDFNKx2jBI5Gytol+mnpl+gK16pv3aP1UbVPn/+OYj/ypGMuWIX3v48OGirT4T+xNdVykI35IJK7jJ943xMBS97MJNofMcIx8Bhi91unndDQSoJ222dpjPkn1D4IFhN2OR1lois1JwoV3ZfPx0K9YXRpQGqLSHy9lN1uKQREczQB2A23sr3N8611tgE0eb7cMkX4jgwZsyCkar4E6UcjYylZ+OMquIq4D7/9uZQtx7fR4TmKMEGiDKI3eI28k/sbKbIvB2H6ra++4VPZ0HQwUibpgzUs3I4HGOMQWVAJ8QvrTTcP9lZ9d+JHpi+h9M="
38 |
39 | notifications:
40 | email:
41 | on_success: never
42 |
--------------------------------------------------------------------------------
/INSTALLATION.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## The normal way
4 |
5 | $ pip3 install neovim-remote
6 |
7 | On most systems this will install to `/usr/local/bin`, which is usually in $PATH
8 | already. You're done.
9 |
10 | If you get a _permission denied_ error, e.g. because it tried to install to
11 | `/usr/bin`, **do not** use `sudo` to force it!
12 |
13 | Use this instead:
14 |
15 | $ pip3 install --user neovim-remote
16 |
17 | This will install to `~/.local/bin` (Linux) or `~/Library/Python/3.x/bin`
18 | (macOS) which needs to be added to $PATH.
19 |
20 | This will give you the correct location:
21 |
22 | $ python3 -c 'import site; print(site.USER_BASE)'
23 |
24 | If `nvr` is not in your path and you installed Python with `asdf`, you might need
25 | to reshim:
26 |
27 | $ pip3 install neovim-remote
28 | $ asdf reshim python
29 |
30 | ## From repo
31 |
32 | If you want to test your own changes, it makes sense to put your local repo into
33 | _develop mode_. That way your Python environment will use the repo directly and
34 | you don't have to reinstall the package after each change:
35 |
36 | $ git clone https://github.com/mhinz/neovim-remote
37 | $ cd neovim-remote
38 | $ pip3 install -e .
39 |
40 | Now, `pip3 list` will show the path to the local repo after the package version.
41 |
42 | ## From zip
43 |
44 | Download the [zipped
45 | sources](https://github.com/mhinz/neovim-remote/archive/master.zip) and install
46 | install them with either `pip3 install master.zip` or by running `python3
47 | setup.py install` in the unzipped directory.
48 |
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Marco Hinz
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include tests *
2 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | default: install
2 |
3 | install:
4 | python3 setup.py install
5 |
6 | test:
7 | pytest -v tests
8 |
9 | upload: clean
10 | python3 setup.py sdist bdist_wheel
11 | twine upload --verbose dist/*
12 |
13 | clean:
14 | rm -rf build dist neovim_remote.egg-info
15 |
16 | .PHONY: install test upload
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/mhinz/neovim-remote)
2 | [](https://pypi.python.org/pypi/neovim-remote)
3 |
4 |
5 |
neovim-remote
6 |
7 |
8 | This package provides an executable called **nvr** which solves these cases:
9 |
10 | - Controlling nvim processes from the shell. E.g. opening files in another
11 | terminal window.
12 | - Opening files from within `:terminal` without starting a nested nvim process.
13 |
14 | ---
15 |
16 | - [Installation](#installation)
17 | - [Theory](#theory)
18 | - [First steps](#first-steps)
19 | - [Typical use cases](#typical-use-cases)
20 | - [Demos](#demos)
21 | - [FAQ](#faq)
22 |
23 | ---
24 |
25 | ## Installation
26 |
27 | pip3 install neovim-remote
28 |
29 | If you encounter any issues, e.g. permission denied errors or you can't find the
30 | `nvr` executable, read [INSTALLATION.md](INSTALLATION.md).
31 |
32 | ## Theory
33 |
34 | **Nvim** always starts a server. Get its address with `:echo v:servername`. Or
35 | specify an address at startup: `nvim --listen /tmp/nvimsocket`.
36 |
37 | **nvr** (the client) will use any address given to it via `--servername`,
38 | `$NVIM_LISTEN_ADDRESS` (obsolete in nvim but still supported in nvr), or
39 | defaults to `/tmp/nvimsocket`.
40 |
41 | If the targeted address does not exist, **nvr** starts a new process by running
42 | "nvim". You can change the command by setting `$NVR_CMD`. _(This requires
43 | forking, so it won't work on Windows.)_
44 |
45 | ## First steps
46 |
47 | Start a nvim process (which acts as a server) in one shell:
48 |
49 | nvim --listen /tmp/nvimsocket
50 |
51 | And do this in another shell:
52 |
53 | ```sh
54 | # nvr uses /tmp/nvimsocket by default, so we're good.
55 |
56 | # Open two files:
57 | nvr --remote file1 file2
58 |
59 | # Send keys to the current buffer:
60 | nvr --remote-send 'iabc'
61 | # Enter insert mode, insert 'abc', and go back to normal mode again.
62 |
63 | # Evaluate any VimL expression, e.g. get the current buffer:
64 | nvr --remote-expr 'bufname("")'
65 | README.md
66 | ```
67 |
68 |
69 | click here to see all nvr options
70 |
71 | ```
72 | $ nvr -h
73 | usage: nvr [arguments]
74 |
75 | Remote control Neovim processes.
76 |
77 | If no process is found, a new one will be started.
78 |
79 | $ nvr --remote-send 'iabc'
80 | $ nvr --remote-expr 'map([1,2,3], "v:val + 1")'
81 |
82 | Any arguments not consumed by options will be fed to --remote-silent:
83 |
84 | $ nvr --remote-silent file1 file2
85 | $ nvr file1 file2
86 |
87 | All --remote options take optional commands.
88 | Exception: --remote-expr, --remote-send.
89 |
90 | $ nvr +10 file
91 | $ nvr +'echomsg "foo" | echomsg "bar"' file
92 | $ nvr --remote-tab-wait +'set bufhidden=delete' file
93 |
94 | Open files in a new window from a terminal buffer:
95 |
96 | $ nvr -cc split file1 file2
97 |
98 | Use nvr from git to edit commit messages:
99 |
100 | $ git config --global core.editor 'nvr --remote-wait-silent'
101 |
102 | optional arguments:
103 | -h, --help show this help message and exit
104 | --remote [ [ ...]]
105 | Use :edit to open files. If no process is found, throw
106 | an error and start a new one.
107 | --remote-wait [ [ ...]]
108 | Like --remote, but block until all buffers opened by
109 | this option get deleted or the process exits.
110 | --remote-silent [ [ ...]]
111 | Like --remote, but throw no error if no process is
112 | found.
113 | --remote-wait-silent [ [ ...]]
114 | Combines --remote-wait and --remote-silent.
115 | --remote-tab [ [ ...]]
116 | Like --remote, but use :tabedit.
117 | --remote-tab-wait [ [ ...]]
118 | Like --remote-wait, but use :tabedit.
119 | --remote-tab-silent [ [ ...]]
120 | Like --remote-silent, but use :tabedit.
121 | --remote-tab-wait-silent [ [ ...]]
122 | Like --remote-wait-silent, but use :tabedit.
123 | --remote-send Send key presses.
124 | --remote-expr Evaluate expression and print result in shell.
125 | --servername Set the address to be used. This overrides the default
126 | "/tmp/nvimsocket" and $NVIM_LISTEN_ADDRESS.
127 | --serverlist Print the TCPv4 and Unix domain socket addresses of
128 | all nvim processes.
129 | -cc Execute a command before every other option.
130 | -c Execute a command after every other option.
131 | -d Diff mode. Use :diffthis on all to be opened buffers.
132 | -l Change to previous window via ":wincmd p".
133 | -o [ ...]
134 | Open files via ":split".
135 | -O [ ...]
136 | Open files via ":vsplit".
137 | -p [ ...]
138 | Open files via ":tabedit".
139 | -q Read errorfile into quickfix list and display first
140 | error.
141 | -s Silence "no server found" message.
142 | -t Jump to file and position of given tag.
143 | --nostart If no process is found, do not start a new one.
144 | --version Show the nvr version.
145 |
146 | Development: https://github.com/mhinz/neovim-remote
147 |
148 | Happy hacking!
149 | ```
150 |
151 |
152 | ## Typical use cases
153 |
154 | - **Open files from within `:terminal` without starting a nested nvim process.**
155 |
156 | Easy-peasy! Just `nvr file`.
157 |
158 | This works without any prior setup, because `$NVIM` is always set for all
159 | children of the nvim process, including `:terminal`, and `nvr` will default
160 | to that address.
161 |
162 | I often work with two windows next to each other. If one contains the
163 | terminal, I can use `nvr -l foo` to open the file in the other window.
164 |
165 | - **Open files always in the same nvim process no matter which terminal you're in.**
166 |
167 | Just `nvr -s` starts a new nvim process with the server address set to
168 | `/tmp/nvimsocket`.
169 |
170 | Now, no matter which terminal you are in, `nvr file` will always work on
171 | that nvim process. That is akin to `emacsclient` from Emacs.
172 |
173 | - **Use nvr in plugins.**
174 |
175 | Some plugins rely on the `--remote` family of options from Vim. Nvim had to
176 | remove those when they switched to outsource a lot of manual code to libuv.
177 | These options are [planned to be added back](https://github.com/neovim/neovim/issues/1750), though.
178 |
179 | In these cases nvr can be used as a drop-in replacement. E.g.
180 | [vimtex](https://github.com/lervag/vimtex) can be configured to use nvr to
181 | jump to a certain file and line: [read](https://github.com/lervag/vimtex/blob/80b96c13fe9edc5261e9be104fe15cf3bdc3173d/doc/vimtex.txt#L1702-L1708).
182 |
183 | - **Use nvr as git editor.**
184 |
185 | Imagine Neovim is set as your default editor via `$VISUAL` or `$EDITOR`.
186 |
187 | Running `git commit` in a regular shell starts a nvim process. But in a
188 | terminal buffer (`:terminal`), a new nvim process starts as well. Now you
189 | have one nvim nested within another.
190 |
191 | If you do not want this, put this in your vimrc:
192 |
193 | ```vim
194 | if has('nvim')
195 | let $GIT_EDITOR = 'nvr -cc split --remote-wait'
196 | endif
197 | ```
198 |
199 | That way, you get a new window for inserting the commit message instead of a
200 | nested nvim process. But git still waits for nvr to finish, so make sure to
201 | delete the buffer after saving the commit message: `:w | bd`.
202 |
203 | If you don't like using `:w | bd` and prefer the good old `:wq` (or `:x`),
204 | put the following in your vimrc:
205 |
206 | ```vim
207 | autocmd FileType gitcommit,gitrebase,gitconfig set bufhidden=delete
208 | ```
209 |
210 | To use nvr from a regular shell as well:
211 |
212 | $ git config --global core.editor 'nvr --remote-wait-silent'
213 |
214 | - **Use nvr as git mergetool.**
215 |
216 | If you want to use nvr for `git difftool` and `git mergetool`, put this in
217 | your gitconfig:
218 |
219 | ```
220 | [diff]
221 | tool = nvr
222 | [difftool "nvr"]
223 | cmd = nvr -s -d $LOCAL $REMOTE
224 | [merge]
225 | tool = nvr
226 | [mergetool "nvr"]
227 | cmd = nvr -s -d $LOCAL $BASE $REMOTE $MERGED -c 'wincmd J | wincmd ='
228 | ```
229 |
230 | `nvr -d` is a shortcut for `nvr -d -O` and acts like `vim -d`, thus it uses
231 | `:vsplit` to open the buffers. If you want them to be opened via `:split`
232 | instead, use `nvr -d -o`.
233 |
234 | When used as mergetool and all four buffers got opened, the cursor is in the
235 | window containing the $MERGED buffer. We move it to the bottom via `:wincmd
236 | J` and then equalize the size of all windows via `:wincmd =`.
237 |
238 | - **Use nvr for scripting.**
239 |
240 | You might draw some inspiration from [this Reddit
241 | thread](https://www.reddit.com/r/neovim/comments/aex45u/integrating_nvr_and_tmux_to_use_a_single_tmux_per).
242 |
243 | ## Demos
244 |
245 | _(Click on the GIFs to watch them full-size.)_
246 |
247 | Using nvr from another shell: 
248 |
249 | Using nvr from within `:terminal`: 
250 |
251 | ## FAQ
252 |
253 | - **How to open directories?**
254 |
255 | `:e /tmp` opens a directory view via netrw. Netrw works by hooking into certain
256 | events, `BufEnter` in this case (see `:au FileExplorer` for all of them).
257 |
258 | Unfortunately Neovim's API doesn't trigger any autocmds on its own, so simply
259 | `nvr /tmp` won't work. Meanwhile you can work around it like this:
260 |
261 | $ nvr /tmp -c 'doautocmd BufEnter'
262 |
263 | - **Reading from stdin?**
264 |
265 | Yes! E.g. `echo "foo\nbar" | nvr -o -` and `cat file | nvr --remote -` work just
266 | as you would expect them to work.
267 |
268 | - **Exit code?**
269 |
270 | If you use a [recent enough
271 | Neovim](https://github.com/neovim/neovim/commit/d2e8c76dc22460ddfde80477dd93aab3d5866506), nvr will use the same exit code as the linked nvim.
272 |
273 | E.g. `nvr --remote-wait ` and then `:cquit` in the linked nvim will make
274 | nvr return with 1.
275 |
276 | - **How to send a message to all waiting clients?**
277 |
278 | If you open a buffer with any of the _wait_ options, that buffer will get a
279 | variable `b:nvr`. The variable contains a list of channels wheres each
280 | channel is a waiting nvr client.
281 |
282 | Currently nvr only understands the `Exit` message. You could use it to
283 | disconnect all waiting nvr clients at once:
284 |
285 | ```vim
286 | command! DisconnectClients
287 | \ if exists('b:nvr')
288 | \| for client in b:nvr
289 | \| silent! call rpcnotify(client, 'Exit', 1)
290 | \| endfor
291 | \| endif
292 | ```
293 |
294 | - **Can I have auto-completion for bash/fish?**
295 |
296 | If you want basic auto-completion for bash, you can source [this
297 | script](contrib/completion.bash) in your .bashrc.
298 |
299 | This also completes server names with the `--servername` option.
300 |
301 | If you want auto-completion for fish, you can add [this
302 | file](contrib/completion.fish) to your fish completions dir.
303 |
--------------------------------------------------------------------------------
/contrib/completion.bash:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # bash command completion for neovim remote.
3 | # Source that file in your bashrc to use it.
4 |
5 | _nvr_opts_completions()
6 | {
7 | local cur prev opts
8 | cur=${COMP_WORDS[COMP_CWORD]}
9 | prev=${COMP_WORDS[COMP_CWORD-1]}
10 | opts=(
11 | -h
12 | -cc
13 | -c
14 | -d
15 | -l
16 | -o
17 | -O
18 | -p
19 | -q
20 | -s
21 | -t
22 | --nostart
23 | --version
24 | --serverlist
25 | --servername
26 | --remote
27 | --remote-wait
28 | --remote-silent
29 | --remote-wait-silent
30 | --remote-tab
31 | --remote-tab-wait
32 | --remote-tab-silent
33 | --remote-tab-wait-silent
34 | --remote-send
35 | --remote-expr
36 | )
37 | case "${prev}" in
38 | --servername)
39 | srvlist=$(nvr --serverlist)
40 | COMPREPLY=( $(compgen -W "${srvlist}" -- "$cur") )
41 | return 0
42 | ;;
43 | -[oOpq])
44 | # These options require at least one argument.
45 | COMPREPLY=( $(compgen -A file -- "$cur") )
46 | return 0
47 | ;;
48 | esac
49 | if [[ "$cur" =~ ^- ]]; then
50 | COMPREPLY=( $(compgen -W "${opts[*]}" -- "$cur") )
51 | else
52 | COMPREPLY=( $(compgen -A file -- "$cur") )
53 | fi
54 | return 0
55 | }
56 |
57 | complete -o default -F _nvr_opts_completions nvr
58 |
--------------------------------------------------------------------------------
/contrib/completion.fish:
--------------------------------------------------------------------------------
1 | # Completions for nvr in fish shell
2 | # To install, save file to completions directory of fish config (e.g. ~/.config/fish/completions/nvr.fish)
3 |
4 | complete --command=nvr --long-option=remote --description='Use :edit to open files. If no process is found, throw an error and start a new one'
5 | complete --command=nvr --long-option=remote-wait --description='Like --remote, but block until all buffers opened by this option get deleted or the process exits'
6 | complete --command=nvr --long-option=remote-silent --description='Like --remote, but throw no error if no process is found'
7 | complete --command=nvr --long-option=remote-wait-silent --description='Combines --remote-wait and --remote-silent'
8 | complete --command=nvr --long-option=remote-tab --description='Like --remote, but use :tabedit'
9 | complete --command=nvr --long-option=remote-tab-wait --description='Like --remote-wait, but use :tabedit'
10 | complete --command=nvr --long-option=remote-tab-silent --description='Like --remote-silent, but use :tabedit'
11 | complete --command=nvr --long-option=remote-tab-wait-silent --description='Like --remote-wait-silent, but use :tabedit'
12 | complete --command=nvr --long-option=remote-send --no-files --description='Send key presses'
13 | complete --command=nvr --long-option=remote-expr --no-files --description='Evaluate expression and print result in shell'
14 | complete --command=nvr --long-option=servername --no-files --arguments='(nvr --serverlist)' --description='Set the address to be used. This overrides the default "/tmp/nvimsocket" and $NVIM_LISTEN_ADDRESS'
15 | complete --command=nvr --long-option=serverlist --description='Print the TCPv4 and Unix domain socket addresses of all nvim processes'
16 | complete --command=nvr --short-option=h --long-option=help --description='show help message and exit'
17 | complete --command=nvr --short-option=c --no-files --description='Execute a command after every other option'
18 | complete --command=nvr --short-option=d --description='Diff mode. Use :diffthis on all to be opened buffers'
19 | complete --command=nvr --short-option=l --description='Change to previous window via ":wincmd p"'
20 | complete --command=nvr --short-option=o --description='Open files via ":split"'
21 | complete --command=nvr --short-option=O --description='Open files via ":vsplit"'
22 | complete --command=nvr --short-option=p --description='Open files via ":tabedit"'
23 | complete --command=nvr --short-option=q --description='Read errorfile into quickfix list and display first error'
24 | complete --command=nvr --short-option=s --description='Silence "no server found" message'
25 | complete --command=nvr --short-option=t --no-files --description='Jump to file and position of given tag'
26 | complete --command=nvr --long-option=nostart --description='If no process is found, do not start a new one'
27 | complete --command=nvr --long-option=version --description='Show the nvr version'
28 | complete --command=nvr --old-option=cc --description='Execute a command before every other option'
29 |
--------------------------------------------------------------------------------
/images/demo1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhinz/neovim-remote/1004d41696a3de12f0911b1949327c3dbe4a62ab/images/demo1.gif
--------------------------------------------------------------------------------
/images/demo2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhinz/neovim-remote/1004d41696a3de12f0911b1949327c3dbe4a62ab/images/demo2.gif
--------------------------------------------------------------------------------
/nvr/__init__.py:
--------------------------------------------------------------------------------
1 | from .nvr import main
2 |
--------------------------------------------------------------------------------
/nvr/nvr.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | """
4 | Copyright (c) 2015 - present Marco Hinz
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 | """
24 |
25 | import argparse
26 | import multiprocessing
27 | import os
28 | import re
29 | import sys
30 | import textwrap
31 | import time
32 | import traceback
33 |
34 | import psutil
35 | import pynvim
36 |
37 | class Nvr():
38 | def __init__(self, address, silent=False):
39 | self.address = address
40 | self.server = None
41 | self.silent = silent
42 | self.wait = 0
43 | self.started_new_process = False
44 | self.handled_first_buffer = False
45 | self.diffmode = False
46 |
47 | def attach(self):
48 | try:
49 | socktype, address, port = parse_address(self.address)
50 | if socktype == 'tcp':
51 | self.server = pynvim.attach('tcp', address=address, port=int(port))
52 | else:
53 | self.server = pynvim.attach('socket', path=address)
54 | except OSError:
55 | # Ignore invalid addresses.
56 | pass
57 |
58 | def try_attach(self, args, nvr, options, arguments):
59 | for i in range(10):
60 | self.attach()
61 | if self.server:
62 | self.started_new_process = True
63 | return proceed_after_attach(nvr, options, arguments)
64 | time.sleep(0.2)
65 | print(f'[!] Unable to attach to the new nvim process. Is `{" ".join(args)}` working?')
66 | sys.exit(1)
67 |
68 | def execute_new_nvim_process(self, silent, nvr, options, arguments):
69 | if not silent:
70 | print(textwrap.dedent('''\
71 | [*] Starting new nvim process using $NVR_CMD or 'nvim'.
72 |
73 | Use --nostart to avoid starting a new process.
74 | '''))
75 |
76 | args = os.environ.get('NVR_CMD')
77 | args = args.split(' ') if args else ['nvim']
78 | args.extend(['--listen', self.address])
79 |
80 | multiprocessing.Process(target=self.try_attach, args=(args[0], nvr, options, arguments)).start()
81 |
82 | try:
83 | os.execvpe(args[0], args, os.environ)
84 | except FileNotFoundError:
85 | print(f'[!] Can\'t start new nvim process: `{args[0]}` is not in $PATH.')
86 | sys.exit(1)
87 |
88 | def read_stdin_into_buffer(self, cmd):
89 | self.server.command(cmd)
90 | for line in sys.stdin:
91 | self.server.funcs.append('$', line[:-1])
92 | self.server.command('silent 1delete _ | set nomodified')
93 |
94 | def fnameescaped_command(self, cmd, path):
95 | if not is_netrw_protocol(path):
96 | path = os.path.abspath(path)
97 | path = self.server.funcs.fnameescape(path)
98 | shortmess = self.server.options['shortmess']
99 | self.server.options['shortmess'] = shortmess.replace('F', '')
100 | self.server.command(f'{cmd} {path}')
101 | self.server.options['shortmess'] = shortmess
102 |
103 | def diffthis(self):
104 | if self.diffmode:
105 | self.server.command('diffthis')
106 | if not self.started_new_process:
107 | self.wait_for_current_buffer()
108 |
109 | def wait_for_current_buffer(self):
110 | bvars = self.server.current.buffer.vars
111 | chanid = self.server.channel_id
112 |
113 | self.server.command('augroup nvr')
114 | self.server.command(f'autocmd BufDelete silent! call rpcnotify({chanid}, "BufDelete")')
115 | self.server.command(f'autocmd VimLeave * if exists("v:exiting") && v:exiting > 0 | silent! call rpcnotify({chanid}, "Exit", v:exiting) | endif')
116 | self.server.command('augroup END')
117 |
118 | if 'nvr' in bvars:
119 | if chanid not in bvars['nvr']:
120 | bvars['nvr'] = [chanid] + bvars['nvr']
121 | else:
122 | bvars['nvr'] = [chanid]
123 |
124 | self.wait += 1
125 |
126 | def execute(self, arguments, cmd='edit', silent=False, wait=False):
127 | cmds, files = split_cmds_from_files(arguments)
128 |
129 | for fname in files:
130 | if fname == '-':
131 | self.read_stdin_into_buffer(stdin_cmd(cmd))
132 | else:
133 | try:
134 | if self.started_new_process and not self.handled_first_buffer:
135 | self.fnameescaped_command('edit', fname)
136 | self.handled_first_buffer = True
137 | else:
138 | self.fnameescaped_command(cmd, fname)
139 | except pynvim.api.nvim.NvimError as e:
140 | if not re.search('E37', e.args[0].decode()):
141 | traceback.print_exc()
142 | sys.exit(1)
143 | self.diffthis()
144 |
145 | if wait:
146 | self.wait_for_current_buffer()
147 |
148 | for cmd in cmds:
149 | self.server.command(cmd if cmd else '$')
150 |
151 | return len(files)
152 |
153 |
154 | def stdin_cmd(cmd):
155 | return {
156 | 'edit': 'enew',
157 | 'split': 'new',
158 | 'vsplit': 'vnew',
159 | 'tabedit': 'tabnew',
160 | }[cmd]
161 |
162 |
163 | def is_netrw_protocol(path):
164 | protocols = [
165 | re.compile('^davs?://*'),
166 | re.compile('^file://*'),
167 | re.compile('^ftp://*'),
168 | re.compile('^https?://*'),
169 | re.compile('^rcp://*'),
170 | re.compile('^rsync://*'),
171 | re.compile('^scp://*'),
172 | re.compile('^sftp://*'),
173 | ]
174 |
175 | return True if any(prot.match(path) for prot in protocols) else False
176 |
177 |
178 | def parse_args(argv):
179 | form_class = argparse.RawDescriptionHelpFormatter
180 | usage = argv[0] + ' [arguments]'
181 | epilog = 'Development: https://github.com/mhinz/neovim-remote\n\nHappy hacking!'
182 | desc = textwrap.dedent("""
183 | Remote control Neovim processes.
184 |
185 | If no process is found, a new one will be started.
186 |
187 | $ nvr --remote-send 'iabc'
188 | $ nvr --remote-expr 'map([1,2,3], \"v:val + 1\")'
189 |
190 | Any arguments not consumed by options will be fed to --remote-silent:
191 |
192 | $ nvr --remote-silent file1 file2
193 | $ nvr file1 file2
194 |
195 | All --remote options take optional commands.
196 | Exception: --remote-expr, --remote-send.
197 |
198 | $ nvr +10 file
199 | $ nvr +'echomsg "foo" | echomsg "bar"' file
200 | $ nvr --remote-tab-wait +'set bufhidden=delete' file
201 |
202 | Open files in a new window from a terminal buffer:
203 |
204 | $ nvr -cc split file1 file2
205 |
206 | Use nvr from git to edit commit messages:
207 |
208 | $ git config --global core.editor 'nvr --remote-wait-silent'
209 | """)
210 |
211 | parser = argparse.ArgumentParser(
212 | formatter_class = form_class,
213 | usage = usage,
214 | epilog = epilog,
215 | description = desc)
216 |
217 | parser.add_argument('--remote',
218 | nargs = '*',
219 | metavar = '',
220 | help = 'Use :edit to open files. If no process is found, throw an error and start a new one.')
221 | parser.add_argument('--remote-wait',
222 | nargs = '*',
223 | metavar = '',
224 | help = 'Like --remote, but block until all buffers opened by this option get deleted or the process exits.')
225 | parser.add_argument('--remote-silent',
226 | nargs = '*',
227 | metavar = '',
228 | help = 'Like --remote, but throw no error if no process is found.')
229 | parser.add_argument('--remote-wait-silent',
230 | nargs = '*',
231 | metavar = '',
232 | help = 'Combines --remote-wait and --remote-silent.')
233 |
234 | parser.add_argument('--remote-tab',
235 | nargs = '*',
236 | metavar = '',
237 | help = 'Like --remote, but use :tabedit.')
238 | parser.add_argument('--remote-tab-wait',
239 | nargs = '*',
240 | metavar = '',
241 | help = 'Like --remote-wait, but use :tabedit.')
242 | parser.add_argument('--remote-tab-silent',
243 | nargs = '*',
244 | metavar = '',
245 | help = 'Like --remote-silent, but use :tabedit.')
246 | parser.add_argument('--remote-tab-wait-silent',
247 | nargs = '*',
248 | metavar = '',
249 | help = 'Like --remote-wait-silent, but use :tabedit.')
250 |
251 | parser.add_argument('--remote-send',
252 | metavar = '',
253 | help = 'Send key presses.')
254 | parser.add_argument('--remote-expr',
255 | metavar = '',
256 | help = 'Evaluate expression and print result in shell.')
257 |
258 | parser.add_argument('--servername',
259 | metavar = '',
260 | help = 'Set the address to be used. This overrides the default "/tmp/nvimsocket" and $NVIM_LISTEN_ADDRESS.')
261 | parser.add_argument('--serverlist',
262 | action = 'store_true',
263 | help = 'Print the TCPv4 and Unix domain socket addresses of all nvim processes.')
264 |
265 | parser.add_argument('-cc',
266 | action = 'append',
267 | metavar = '',
268 | help = 'Execute a command before every other option.')
269 | parser.add_argument('-c',
270 | action = 'append',
271 | metavar = '',
272 | help = 'Execute a command after every other option.')
273 | parser.add_argument('-d',
274 | action = 'store_true',
275 | help = 'Diff mode. Use :diffthis on all to be opened buffers.')
276 | parser.add_argument('-l',
277 | action = 'store_true',
278 | help = 'Change to previous window via ":wincmd p".')
279 | parser.add_argument('-o',
280 | nargs = '+',
281 | metavar = '',
282 | help = 'Open files via ":split".')
283 | parser.add_argument('-O',
284 | nargs = '+',
285 | metavar = '',
286 | help = 'Open files via ":vsplit".')
287 | parser.add_argument('-p',
288 | nargs = '+',
289 | metavar = '',
290 | help = 'Open files via ":tabedit".')
291 | parser.add_argument('-q',
292 | metavar = '',
293 | help = 'Read errorfile into quickfix list and display first error.')
294 | parser.add_argument('-s',
295 | action = 'store_true',
296 | help = 'Silence "no server found" message.')
297 | parser.add_argument('-t',
298 | metavar = '',
299 | help = 'Jump to file and position of given tag.')
300 | parser.add_argument('--nostart',
301 | action = 'store_true',
302 | help = 'If no process is found, do not start a new one.')
303 | parser.add_argument('--version',
304 | action = 'store_true',
305 | help = 'Show the nvr version.')
306 |
307 | return parser.parse_known_args(argv[1:])
308 |
309 |
310 | def show_message(address):
311 | print(textwrap.dedent(f'''
312 | [!] Can't connect to: {address}
313 |
314 | The server (nvim) and client (nvr) have to use the same address.
315 |
316 | Server:
317 |
318 | Specify the server address when starting nvim:
319 |
320 | $ nvim --listen {address}
321 |
322 | Use `:echo v:servername` to verify the address.
323 |
324 | Alternatively, run nvr without arguments. It defaults to
325 | starting a new nvim process with the server name set to
326 | "/tmp/nvimsocket".
327 |
328 | Security: When using an Unix domain socket, that socket should
329 | have proper permissions so that it is only accessible by your
330 | user.
331 |
332 | Client:
333 |
334 | Expose $NVIM_LISTEN_ADDRESS (obsolete in nvim but still
335 | supported by nvr) to the environment before using nvr or use
336 | its --servername option. If neither is given, nvr assumes
337 | \"/tmp/nvimsocket\".
338 |
339 | $ NVIM_LISTEN_ADDRESS={address} nvr file1 file2
340 | $ nvr --servername {address} file1 file2
341 | $ nvr --servername 127.0.0.1:6789 file1 file2
342 |
343 | Use -s to suppress this message.
344 | '''))
345 |
346 |
347 | def split_cmds_from_files(args):
348 | cmds = []
349 | files = []
350 | for _ in range(len(args)):
351 | if args[0][0] == '+':
352 | cmds.append(args.pop(0)[1:])
353 | elif args[0] == '--':
354 | args.pop(0)
355 | files += args
356 | break
357 | else:
358 | files.append(args.pop(0))
359 | return cmds, files
360 |
361 |
362 | def print_versions():
363 | import pkg_resources
364 | print('nvr ' + pkg_resources.require("neovim-remote")[0].version)
365 | print('pynvim ' + pkg_resources.require('pynvim')[0].version)
366 | print('psutil ' + pkg_resources.require('psutil')[0].version)
367 | print('Python ' + sys.version.split('\n')[0])
368 |
369 |
370 | def print_addresses():
371 | addresses = []
372 | errors = []
373 |
374 | for proc in psutil.process_iter(attrs=['name']):
375 | if proc.info['name'] == 'nvim':
376 | try:
377 | for conn in proc.connections('inet4'):
378 | addresses.insert(0, ':'.join(map(str, conn.laddr)))
379 | for conn in proc.connections('inet6'):
380 | addresses.insert(0, ':'.join(map(str, conn.laddr)))
381 | try:
382 | for conn in proc.connections('unix'):
383 | if conn.laddr:
384 | addresses.insert(0, conn.laddr)
385 | except FileNotFoundError:
386 | # Windows does not support Unix domain sockets and WSL1
387 | # does not implement /proc/net/unix
388 | pass
389 | except psutil.AccessDenied:
390 | errors.insert(0, f'Access denied for nvim ({proc.pid})')
391 |
392 | for addr in sorted(addresses):
393 | print(addr)
394 | for error in sorted(errors):
395 | print(error, file=sys.stderr)
396 |
397 |
398 | def parse_address(address):
399 | try:
400 | host, port = address.rsplit(':', 1)
401 | if port.isdigit():
402 | return 'tcp', host, port
403 | raise ValueError
404 | except ValueError:
405 | return 'socket', address, None
406 |
407 |
408 | def main(argv=sys.argv, env=os.environ):
409 | options, arguments = parse_args(argv)
410 |
411 | if options.version:
412 | print_versions()
413 | return
414 |
415 | if options.serverlist:
416 | print_addresses()
417 | return
418 |
419 | address = options.servername or env.get('NVIM') or env.get('NVIM_LISTEN_ADDRESS')
420 | if not address:
421 | # Since before build 17063 windows doesn't support unix socket, we need another way
422 | address = '127.0.0.1:6789' if os.name == 'nt' else '/tmp/nvimsocket'
423 |
424 | nvr = Nvr(address, options.s)
425 | nvr.attach()
426 |
427 | if not nvr.server:
428 | if os.path.exists(nvr.address):
429 | print(textwrap.dedent(f'''
430 | [!] A file {nvr.address} exists, but we failed to attach to it.
431 |
432 | Is it a Unix domain socket? Either remove that file and try
433 | again or choose another address with --servername.
434 | '''))
435 | return
436 |
437 | silent = options.remote_silent or options.remote_wait_silent or options.remote_tab_silent or options.remote_tab_wait_silent or options.s
438 | if not silent:
439 | show_message(address)
440 | if options.nostart:
441 | sys.exit(1)
442 | nvr.execute_new_nvim_process(silent, nvr, options, arguments)
443 |
444 | proceed_after_attach(nvr, options, arguments)
445 |
446 |
447 | def proceed_after_attach(nvr, options, arguments):
448 | if options.d:
449 | nvr.diffmode = True
450 |
451 | if options.cc:
452 | for cmd in options.cc:
453 | if cmd == '-':
454 | cmd = sys.stdin.read()
455 | nvr.server.command(cmd)
456 |
457 | if options.l:
458 | nvr.server.command('wincmd p')
459 |
460 | if options.remote is not None:
461 | nvr.execute(options.remote + arguments, 'edit')
462 | elif options.remote_wait is not None:
463 | nvr.execute(options.remote_wait + arguments, 'edit', wait=True)
464 | elif options.remote_silent is not None:
465 | nvr.execute(options.remote_silent + arguments, 'edit', silent=True)
466 | elif options.remote_wait_silent is not None:
467 | nvr.execute(options.remote_wait_silent + arguments, 'edit', silent=True, wait=True)
468 | elif options.remote_tab is not None:
469 | nvr.execute(options.remote_tab + arguments, 'tabedit')
470 | elif options.remote_tab_wait is not None:
471 | nvr.execute(options.remote_tab_wait + arguments, 'tabedit', wait=True)
472 | elif options.remote_tab_silent is not None:
473 | nvr.execute(options.remote_tab_silent + arguments, 'tabedit', silent=True)
474 | elif options.remote_tab_wait_silent is not None:
475 | nvr.execute(options.remote_tab_wait_silent + arguments, 'tabedit', silent=True, wait=True)
476 | elif arguments and options.d:
477 | # Emulate `vim -d`.
478 | options.O = arguments
479 | arguments = []
480 |
481 | if options.remote_send:
482 | nvr.server.input(options.remote_send)
483 |
484 | if options.remote_expr:
485 | result = ''
486 | if options.remote_expr == '-':
487 | options.remote_expr = sys.stdin.read()
488 | try:
489 | result = nvr.server.eval(options.remote_expr)
490 | except:
491 | print(textwrap.dedent(f"""
492 | No valid expression: {options.remote_expr}
493 | Test it in Neovim: :echo eval('...')
494 | If you want to execute a command, use -c or -cc instead.
495 | """))
496 | if type(result) is bytes:
497 | print(result.decode())
498 | elif type(result) is list:
499 | print(list(map(lambda x: x.decode() if type(x) is bytes else x, result)))
500 | elif type(result) is dict:
501 | print({ (k.decode() if type(k) is bytes else k): v for (k,v) in result.items() })
502 | else:
503 | result = str(result)
504 | if not result.endswith(os.linesep):
505 | result += os.linesep
506 | print(result, end='', flush=True)
507 |
508 | if options.o:
509 | args = options.o + arguments
510 | if nvr.diffmode and not nvr.started_new_process:
511 | nvr.execute(args[:1], 'tabedit', silent=True, wait=False)
512 | nvr.execute(args[1:], 'split', silent=True, wait=False)
513 | else:
514 | nvr.execute(args, 'split', silent=True, wait=False)
515 | nvr.server.command('wincmd =')
516 | elif options.O:
517 | args = options.O + arguments
518 | if nvr.diffmode and not nvr.started_new_process:
519 | nvr.execute(args[:1], 'tabedit', silent=True, wait=False)
520 | nvr.execute(args[1:], 'vsplit', silent=True, wait=False)
521 | else:
522 | nvr.execute(args, 'vsplit', silent=True, wait=False)
523 | nvr.server.command('wincmd =')
524 | elif options.p:
525 | nvr.execute(options.p + arguments, 'tabedit', silent=True, wait=False)
526 | else:
527 | # Act like --remote-silent by default.
528 | nvr.execute(arguments, 'edit', silent=True)
529 |
530 | if options.t:
531 | try:
532 | nvr.server.command('tag ' + options.t)
533 | except nvr.server.error as e:
534 | print(e)
535 | sys.exit(1)
536 |
537 | if options.q:
538 | path = nvr.server.funcs.fnameescape(os.environ['PWD'])
539 | nvr.server.command('lcd ' + path)
540 | nvr.server.funcs.setqflist([])
541 | if options.q == '-':
542 | for line in sys.stdin:
543 | nvr.server.command("caddexpr '{}'".
544 | format(line.rstrip().replace("'", "''").replace('|', r'\|')))
545 | else:
546 | with open(options.q, 'r') as f:
547 | for line in f.readlines():
548 | nvr.server.command("caddexpr '{}'".
549 | format(line.rstrip().replace("'", "''").replace('|', r'\|')))
550 | nvr.server.command('silent lcd -')
551 | nvr.server.command('cfirst')
552 |
553 | if options.c:
554 | for cmd in options.c:
555 | if cmd == '-':
556 | cmd = sys.stdin.read()
557 | nvr.server.command(cmd)
558 |
559 | wait_for_n_buffers = nvr.wait
560 | if wait_for_n_buffers > 0:
561 | exitcode = 0
562 |
563 | def notification_cb(msg, args):
564 | nonlocal wait_for_n_buffers
565 | nonlocal exitcode
566 |
567 | if msg == 'BufDelete':
568 | wait_for_n_buffers -= 1
569 | if wait_for_n_buffers == 0:
570 | nvr.server.stop_loop()
571 | exitcode = 0 if len(args) == 0 else args[0]
572 | elif msg == 'Exit':
573 | nvr.server.stop_loop()
574 | exitcode = args[0]
575 |
576 | def err_cb(error):
577 | nonlocal exitcode
578 | print(error, file=sys.stderr)
579 | nvr.server.stop_loop()
580 | exitcode = 1
581 |
582 | nvr.server.run_loop(None, notification_cb, None, err_cb)
583 | nvr.server.close()
584 | sys.exit(exitcode)
585 |
586 | nvr.server.close()
587 |
588 |
589 | if __name__ == '__main__':
590 | main()
591 |
592 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | from os import path
3 |
4 | here = path.abspath(path.dirname(__file__))
5 |
6 | with open(path.join(here, 'README.md'), encoding='utf-8') as f:
7 | long_description = f.read()
8 |
9 | setup(
10 | name = 'neovim-remote',
11 | author = 'Marco Hinz',
12 | author_email = 'mh.codebro@gmail.com',
13 | url = 'https://github.com/mhinz/neovim-remote',
14 | description = 'Control nvim processes using "nvr" commandline tool',
15 | long_description = long_description,
16 | long_description_content_type = 'text/markdown',
17 | python_requires = '>=3.7',
18 | install_requires = ['pynvim', 'psutil', 'setuptools'],
19 | entry_points = {
20 | 'console_scripts': ['nvr = nvr.nvr:main']
21 | },
22 | packages = ['nvr'],
23 | version = '2.5.1',
24 | license = 'MIT',
25 | keywords = 'neovim nvim nvr remote helper',
26 | classifiers = [
27 | 'Topic :: Utilities',
28 | 'License :: OSI Approved :: MIT License',
29 | 'Development Status :: 5 - Production/Stable',
30 | 'Intended Audience :: End Users/Desktop',
31 | 'Intended Audience :: Developers',
32 | 'Programming Language :: Python :: 3',
33 | ],
34 | )
35 |
36 |
--------------------------------------------------------------------------------
/tests/test_nvr.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | import time
5 | import subprocess
6 | import uuid
7 | import nvr
8 |
9 | # Helper functions
10 |
11 | def run_nvim(env):
12 | nvim = subprocess.Popen(['nvim', '-nu', 'NORC', '--headless'], env=env)
13 | time.sleep(1)
14 | return nvim
15 |
16 | def run_nvr(cmdlines, env):
17 | for cmdline in cmdlines:
18 | nvr.main(cmdline, env)
19 |
20 | def setup_env():
21 | env = {'NVIM_LISTEN_ADDRESS': 'pytest_socket_{}'.format(uuid.uuid4())}
22 | env.update(os.environ)
23 | return env
24 |
25 | # Tests
26 |
27 | def test_remote_send(capsys):
28 | env = setup_env()
29 | nvim = run_nvim(env)
30 | cmdlines = [['nvr', '--nostart', '--remote-send', 'iabc'],
31 | ['nvr', '--nostart', '--remote-expr', 'getline(1)']]
32 | run_nvr(cmdlines, env)
33 | nvim.terminate()
34 | out, err = capsys.readouterr()
35 | assert out == 'abc\n'
36 |
37 | # https://github.com/mhinz/neovim-remote/issues/77
38 | def test_escape_filenames_properly(capsys):
39 | filename = 'a b|c'
40 | env = setup_env()
41 | nvim = run_nvim(env)
42 | cmdlines = [['nvr', '-s', '--nostart', '-o', filename],
43 | ['nvr', '-s', '--nostart', '--remote-expr', 'fnamemodify(bufname(""), ":t")']]
44 | run_nvr(cmdlines, env)
45 | nvim.terminate()
46 | out, err = capsys.readouterr()
47 | assert filename == out.rstrip()
48 |
49 | def test_escape_single_quotes_in_filenames(capsys):
50 | filename = "foo'bar'quux"
51 | env = setup_env()
52 | nvim = run_nvim(env)
53 | cmdlines = [['nvr', '-s', '--nostart', '-o', filename],
54 | ['nvr', '-s', '--nostart', '--remote-expr', 'fnamemodify(bufname(""), ":t")']]
55 | run_nvr(cmdlines, env)
56 | nvim.terminate()
57 | out, err = capsys.readouterr()
58 | assert filename == out.rstrip()
59 |
60 | def test_escape_double_quotes_in_filenames(capsys):
61 | filename = 'foo"bar"quux'
62 | env = setup_env()
63 | nvim = run_nvim(env)
64 | cmdlines = [['nvr', '-s', '--nostart', '-o', filename],
65 | ['nvr', '-s', '--nostart', '--remote-expr', 'fnamemodify(bufname(""), ":t")']]
66 | run_nvr(cmdlines, env)
67 | nvim.terminate()
68 | out, err = capsys.readouterr()
69 | assert filename == out.rstrip()
70 |
--------------------------------------------------------------------------------