Codebase list python-pynvim / upstream/0.2.6
New upstream version 0.2.6 Víctor Cuadrado Juan 5 years ago
32 changed file(s) with 658 addition(s) and 611 deletion(s). Raw diff Collapse all Expand all
0 Metadata-Version: 1.1
0 Metadata-Version: 2.1
11 Name: neovim
2 Version: 0.2.0
2 Version: 0.2.6
33 Summary: Python client to neovim
44 Home-page: http://github.com/neovim/python-client
55 Author: Thiago de Arruda
66 Author-email: tpadilha84@gmail.com
77 License: Apache
8 Download-URL: https://github.com/neovim/python-client/archive/0.2.0.tar.gz
8 Download-URL: https://github.com/neovim/python-client/archive/0.2.6.tar.gz
99 Description: UNKNOWN
1010 Platform: UNKNOWN
11 Provides-Extra: pyuv
0 ### Python client to [Neovim](https://github.com/neovim/neovim)
0 ### Pynvim: Python client to [Neovim](https://github.com/neovim/neovim)
11
22 [![Build Status](https://travis-ci.org/neovim/python-client.svg?branch=master)](https://travis-ci.org/neovim/python-client)
3 [![Documentation Status](https://readthedocs.org/projects/pynvim/badge/?version=latest)](http://pynvim.readthedocs.io/en/latest/?badge=latest)
34 [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/neovim/python-client/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/neovim/python-client/?branch=master)
45 [![Code Coverage](https://scrutinizer-ci.com/g/neovim/python-client/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/neovim/python-client/?branch=master)
56
6 Implements support for python plugins in Nvim. Also works as a library for
7 Pynvim implements support for python plugins in Nvim. It also works as a library for
78 connecting to and scripting Nvim processes through its msgpack-rpc API.
89
910 #### Installation
1011
11 Supports python 2.7, and 3.3 or later.
12 Supports python 2.7, and 3.4 or later.
1213
1314 ```sh
1415 pip2 install neovim
3536
3637 #### Python Plugin API
3738
38 Neovim has a new mechanism for defining plugins, as well as a number of
39 extensions to the python API. The API extensions are accessible no matter if the
40 traditional `:python` interface or the new mechanism is used, as discussed
41 below.
39 Pynvim supports python _remote plugins_ (via the language-agnostic Nvim rplugin
40 interface), as well as _Vim plugins_ (via the `:python[3]` interface). Thus when
41 pynvim is installed Neovim will report support for the `+python[3]` Vim feature.
4242
43 * `vim.funcs` exposes vimscript functions (both builtin and global user defined
44 functions) as a python namespace. For instance to set the value of a register
45 ```
46 vim.funcs.setreg('0', ["some", "text"], 'l')
47 ```
43 The rplugin interface allows plugins to handle vimL function calls as well as
44 defining commands and autocommands, and such plugins can operate asynchronously
45 without blocking nvim. For details on the new rplugin interface,
46 see the [Remote Plugin](http://pynvim.readthedocs.io/en/latest/usage/remote-plugins.html) documentation.
4847
49 * `vim.api` exposes nvim API methods. For instance to call `nvim_strwidth`,
50 ```
51 result = vim.api.strwidth("some text")
52 ```
53 Note the initial `nvim_` is not included. Also, object methods can be called
54 directly on their object,
55 ```
56 buf = vim.current.buffer
57 len = buf.api.line_count()
58 ```
59 calls `nvim_buf_line_count`. Alternatively msgpack requests can be invoked
60 directly,
61 ```
62 result = vim.request("nvim_strwith", "some text")
63 len = vim.request("nvim_buf_line_count", buf)
64 ```
48 Pynvim defines some extensions over the vim python API:
6549
66 * The API is not thread-safe in general. However, `vim.async_call` allows a
67 spawned thread to schedule code to be executed on the main thread. This method
68 could also be called from `:python` or a synchronous request handler, to defer
69 some execution that shouldn't block nvim.
70 ```
71 :python vim.async_call(myfunc, args...)
50 * Builtin and plugin vimL functions are available as `nvim.funcs`
51 * API functions are available as `vim.api` and for objects such as `buffer.api`
52 * Lua functions can be defined using `vim.exec_lua` and called with `vim.lua`
53 * Support for thread-safety and async requests.
7254
73 ```
74 Note that this code will still block the plugin host if it does long-running
75 computations. Intensive computations should be done in a separate thread (or
76 process), and `vim.async_call` can be used to send results back to nvim.
77
78 * Some methods accept an `async` keyword argument: `vim.eval`, `vim.command`,
79 `vim.request` as well as the `vim.funcs` and `vim.api` wrappers. When
80 `async=True` is passed the client will not wait for nvim to complete the
81 request (which also means that the return value is unavailable).
82
83 #### Remote (new-style) plugins
84
85 Neovim allows python plugins to be defined by placing python files or packages
86 in `rplugin/python3/` (in a runtimepath folder). These follow the structure of
87 this example:
88
89 ```python
90 import neovim
91
92 @neovim.plugin
93 class TestPlugin(object):
94
95 def __init__(self, nvim):
96 self.nvim = nvim
97
98 @neovim.function("TestFunction", sync=True)
99 def testfunction(self, args):
100 return 3
101
102 @neovim.command("TestCommand", range='', nargs='*')
103 def testcommand(self, args, range):
104 self.nvim.current.line = ('Command with args: {}, range: {}'
105 .format(args, range))
106
107 @neovim.autocmd('BufEnter', pattern='*.py', eval='expand("<afile>")', sync=True)
108 def on_bufenter(self, filename):
109 self.nvim.out_write("testplugin is in " + filename + "\n")
110 ```
111
112 If `sync=True` is supplied nvim will wait for the handler to finish (this is
113 required for function return values), but by default handlers are executed
114 asynchronously.
115
116 Normally async handlers (`sync=False`, the default) are blocked while a
117 synchronous handler is running. This ensures that async handlers can call
118 requests without nvim confusing these requests with requests from a synchronous
119 handler. To execute an asynchronous handler even when other handlers are
120 running, add `allow_nested=True` to the decorator. The handler must then not
121 make synchronous nvim requests, but it can make asynchronous requests, i e
122 passing `async=True`.
123
124 You need to run `:UpdateRemotePlugins` in nvim for changes in the specifications
125 to have effect. For details see `:help remote-plugin` in nvim.
55 See the [Python Plugin API](http://pynvim.readthedocs.io/en/latest/usage/python-plugin-api.html) documentation for usage of this new functionality.
12656
12757 #### Development
12858
13161 pip2 install .
13262 pip3 install .
13363 ```
134 for the changes to have effect. Alternatively you could execute neovim
135 with the `$PYTHONPATH` environment variable
136 ```
137 PYTHONPATH=/path/to/python-client nvim
138 ```
139 But note this is not completely reliable as installed packages can appear before
140 `$PYTHONPATH` in the python search path.
141
142 You need to rerun this command if you have changed the code, in order for nvim
143 to use it for the plugin host.
144
145 To run the tests execute
146
147 ```sh
148 nosetests
149 ```
150
151 This will run the tests in an embedded instance of nvim.
152 If you want to test a different version than `nvim` in `$PATH` use
153 ```sh
154 NVIM_CHILD_ARGV='["/path/to/nvim", "-u", "NONE", "--embed"]' nosetests
155 ```
156
157 Alternatively, if you want to see the state of nvim, you could use
158
159 ```sh
160 export NVIM_LISTEN_ADDRESS=/tmp/nvimtest
161 xterm -e "nvim -u NONE"&
162 nosetests
163 ```
164
165 But note you need to restart nvim every time you run the tests! Substitute your
166 favorite terminal emulator for `xterm`.
167
168 #### Troubleshooting
169
170 You can run the plugin host in nvim with logging enabled to debug errors:
171 ```
172 NVIM_PYTHON_LOG_FILE=logfile NVIM_PYTHON_LOG_LEVEL=DEBUG nvim
173 ```
174 As more than one python host process might be started, the log filenames take
175 the pattern `logfile_pyX_KIND` where `X` is the major python version (2 or 3)
176 and `KIND` is either "rplugin" or "script" (for the `:python[3]`
177 script interface).
178
179 If the host cannot start at all, the error could be found in `~/.nvimlog` if
180 `nvim` was compiled with logging.
64 for the changes to have effect. For instructions of testing and troubleshooting,
65 see the [development](http://pynvim.readthedocs.io/en/latest/development.html) documentation.
18166
18267 #### Usage through the python REPL
18368
2020 'shutdown_hook', 'attach', 'setup_logging', 'ErrorResponse')
2121
2222
23 VERSION = Version(major=0, minor=2, patch=0, prerelease='')
23 VERSION = Version(major=0, minor=2, patch=6, prerelease='')
2424
2525
2626 def start_host(session=None):
9595 nvim = attach('socket', path=<path>)
9696 nvim = attach('child', argv=<argv>)
9797 nvim = attach('stdio')
98
99 When the session is not needed anymore, it is recommended to explicitly
100 close it:
101 nvim.close()
102 It is also possible to use the session as a context mangager:
103 with attach('socket', path=thepath) as nvim:
104 print(nvim.funcs.getpid())
105 print(nvim.current.line)
106 This will automatically close the session when you're done with it, or
107 when an error occured.
108
109
98110 """
99111 session = (tcp_session(address, port) if session_type == 'tcp' else
100112 socket_session(path) if session_type == 'socket' else
00 """API for working with a Nvim Buffer."""
11 from .common import Remote
2 from ..compat import IS_PYTHON3
2 from ..compat import IS_PYTHON3, check_async
33
44
55 __all__ = ('Buffer')
9797 return Range(self, start, end)
9898
9999 def add_highlight(self, hl_group, line, col_start=0,
100 col_end=-1, src_id=-1, async=None):
100 col_end=-1, src_id=-1, async_=None,
101 **kwargs):
101102 """Add a highlight to the buffer."""
102 if async is None:
103 async = (src_id != 0)
103 async_ = check_async(async_, kwargs, src_id != 0)
104104 return self.request('nvim_buf_add_highlight', src_id, hl_group,
105 line, col_start, col_end, async=async)
106
107 def clear_highlight(self, src_id, line_start=0, line_end=-1, async=True):
105 line, col_start, col_end, async_=async_)
106
107 def clear_highlight(self, src_id, line_start=0, line_end=-1, async_=None,
108 **kwargs):
108109 """Clear highlights from the buffer."""
110 async_ = check_async(async_, kwargs, True)
109111 self.request('nvim_buf_clear_highlight', src_id,
110 line_start, line_end, async=async)
112 line_start, line_end, async_=async_)
113
114 def update_highlights(self, src_id, hls, clear_start=0, clear_end=-1,
115 clear=False, async_=True):
116 """Add or update highlights in batch to avoid unnecessary redraws.
117
118 A `src_id` must have been allocated prior to use of this function. Use
119 for instance `nvim.new_highlight_source()` to get a src_id for your
120 plugin.
121
122 `hls` should be a list of highlight items. Each item should be a list
123 or tuple on the form `("GroupName", linenr, col_start, col_end)` or
124 `("GroupName", linenr)` to highlight an entire line.
125
126 By default existing highlights are preserved. Specify a line range with
127 clear_start and clear_end to replace highlights in this range. As a
128 shorthand, use clear=True to clear the entire buffer before adding the
129 new highlights.
130 """
131 if clear and clear_start is None:
132 clear_start = 0
133 lua = self._session._get_lua_private()
134 lua.update_highlights(self, src_id, hls, clear_start, clear_end,
135 async_=async_)
111136
112137 @property
113138 def name(self):
6868 It is used to provide a dict-like API to vim variables and options.
6969 """
7070
71 def __init__(self, obj, get_method, set_method=None, self_obj=None):
72 """Initialize a RemoteMap with session, getter/setter and self_obj."""
71 def __init__(self, obj, get_method, set_method=None):
72 """Initialize a RemoteMap with session, getter/setter."""
7373 self._get = functools.partial(obj.request, get_method)
7474 self._set = None
7575 if set_method:
117117
118118 For example, the 'windows' property of the `Nvim` class is a RemoteSequence
119119 sequence instance, and the expression `nvim.windows[0]` is translated to
120 session.request('vim_get_windows')[0].
121
122 It can also receive an optional self_obj that will be passed as first
123 argument of the request. For example, `tabpage.windows[0]` is translated
124 to: session.request('tabpage_get_windows', tabpage_instance)[0].
120 session.request('nvim_list_wins')[0].
125121
126122 One important detail about this class is that all methods will fetch the
127123 sequence into a list and perform the necessary manipulation
129125 """
130126
131127 def __init__(self, session, method):
132 """Initialize a RemoteSequence with session, method and self_obj."""
128 """Initialize a RemoteSequence with session, method."""
133129 self._fetch = functools.partial(session.request, method)
134130
135131 def __len__(self):
00 """Main Nvim interface."""
1 import functools
21 import os
32 import sys
3 from functools import partial
44 from traceback import format_stack
55
66 from msgpack import ExtType
1818
1919 os_chdir = os.chdir
2020
21 lua_module = """
22 local a = vim.api
23 local function update_highlights(buf, src_id, hls, clear_first, clear_end)
24 if clear_first ~= nil then
25 a.nvim_buf_clear_highlight(buf, src_id, clear_first, clear_end)
26 end
27 for _,hl in pairs(hls) do
28 local group, line, col_start, col_end = unpack(hl)
29 if col_start == nil then
30 col_start = 0
31 end
32 if col_end == nil then
33 col_end = -1
34 end
35 a.nvim_buf_add_highlight(buf, src_id, group, line, col_start, col_end)
36 end
37 end
38
39 local chid = ...
40 local mod = {update_highlights=update_highlights}
41 _G["_pynvim_"..chid] = mod
42 """
43
2144
2245 class Nvim(object):
2346
3558 `SubClass.from_nvim(nvim)` where `SubClass` is a subclass of `Nvim`, which
3659 is useful for having multiple `Nvim` objects that behave differently
3760 without one affecting the other.
61
62 When this library is used on python3.4+, asyncio event loop is guaranteed
63 to be used. It is available as the "loop" attribute of this class. Note
64 that asyncio callbacks cannot make blocking requests, which includes
65 accessing state-dependent attributes. They should instead schedule another
66 callback using nvim.async_call, which will not have this restriction.
3867 """
3968
4069 @classmethod
85114 self.current = Current(self)
86115 self.session = CompatibilitySession(self)
87116 self.funcs = Funcs(self)
117 self.lua = LuaFuncs(self)
88118 self.error = NvimError
89119 self._decode = decode
90120 self._err_cb = err_cb
121
122 # only on python3.4+ we expose asyncio
123 if IS_PYTHON3 and os.name != 'nt':
124 self.loop = self._session.loop._loop
91125
92126 def _from_nvim(self, obj, decode=None):
93127 if decode is None:
104138 return ExtType(*obj.code_data)
105139 return obj
106140
141 def _get_lua_private(self):
142 if not getattr(self._session, "_has_lua", False):
143 self.exec_lua(lua_module, self.channel_id)
144 self._session._has_lua = True
145 return getattr(self.lua, "_pynvim_{}".format(self.channel_id))
146
107147 def request(self, name, *args, **kwargs):
108148 r"""Send an API request or notification to nvim.
109149
111151 functions have python wrapper functions. The `api` object can
112152 be also be used to call API functions as methods:
113153
114 vim.api.err_write('ERROR\n', async=True)
154 vim.api.err_write('ERROR\n', async_=True)
115155 vim.current.buffer.api.get_mark('.')
116156
117157 is equivalent to
118158
119 vim.request('nvim_err_write', 'ERROR\n', async=True)
159 vim.request('nvim_err_write', 'ERROR\n', async_=True)
120160 vim.request('nvim_buf_get_mark', vim.current.buffer, '.')
121161
122162
123 Normally a blocking request will be sent. If the `async` flag is
163 Normally a blocking request will be sent. If the `async_` flag is
124164 present and True, a asynchronous notification is sent instead. This
125165 will never block, and the return value or error is ignored.
126166 """
179219 """Stop the event loop being started with `run_loop`."""
180220 self._session.stop()
181221
222 def close(self):
223 """Close the nvim session and release its resources."""
224 self._session.close()
225
226 def __enter__(self):
227 """Enter nvim session as a context manager."""
228 return self
229
230 def __exit__(self, *exc_info):
231 """Exit nvim session as a context manager.
232
233 Closes the event loop.
234 """
235 self.close()
236
182237 def with_decode(self, decode=True):
183238 """Initialize a new Nvim instance."""
184239 return Nvim(self._session, self.channel_id,
226281 def call(self, name, *args, **kwargs):
227282 """Call a vimscript function."""
228283 return self.request('nvim_call_function', name, args, **kwargs)
284
285 def exec_lua(self, code, *args, **kwargs):
286 """Execute lua code.
287
288 Additional parameters are available as `...` inside the lua chunk.
289 Only statements are executed. To evaluate an expression, prefix it
290 with `return`: `return my_function(...)`
291
292 There is a shorthand syntax to call lua functions with arguments:
293
294 nvim.lua.func(1,2)
295 nvim.lua.mymod.myfunction(data, async_=True)
296
297 is equivalent to
298
299 nvim.exec_lua("return func(...)", 1, 2)
300 nvim.exec_lua("mymod.myfunction(...)", data, async_=True)
301
302 Note that with `async_=True` there is no return value.
303 """
304 return self.request('nvim_execute_lua', code, args, **kwargs)
229305
230306 def strwidth(self, string):
231307 """Return the number of display cells `string` occupies.
438514 self._nvim = nvim
439515
440516 def __getattr__(self, name):
441 return functools.partial(self._nvim.call, name)
517 return partial(self._nvim.call, name)
518
519
520 class LuaFuncs(object):
521
522 """Wrapper to allow lua functions to be called like python methods."""
523
524 def __init__(self, nvim, name=""):
525 self._nvim = nvim
526 self.name = name
527
528 def __getattr__(self, name):
529 """Return wrapper to named api method."""
530 prefix = self.name + "." if self.name else ""
531 return LuaFuncs(self._nvim, prefix + name)
532
533 def __call__(self, *args, **kwargs):
534 # first new function after keyword rename, be a bit noisy
535 if 'async' in kwargs:
536 raise ValueError('"async" argument is not allowed. '
537 'Use "async_" instead.')
538 async_ = kwargs.get('async_', False)
539 pattern = "return {}(...)" if not async_ else "{}(...)"
540 code = pattern.format(self.name)
541 return self._nvim.exec_lua(code, *args, **kwargs)
442542
443543
444544 class NvimError(Exception):
00 """Code for supporting compatibility across python versions."""
11
22 import sys
3 import warnings
34 from imp import find_module as original_find_module
45
56
3536 unicode_errors_default = 'strict'
3637
3738 NUM_TYPES = (int, long, float)
39
40
41 def check_async(async_, kwargs, default):
42 """Return a value of 'async' in kwargs or default when async_ is None.
43
44 This helper function exists for backward compatibility (See #274).
45 It shows a warning message when 'async' in kwargs is used to note users.
46 """
47 if async_ is not None:
48 return async_
49 elif 'async' in kwargs:
50 warnings.warn(
51 '"async" attribute is deprecated. Use "async_" instead.',
52 DeprecationWarning,
53 )
54 return kwargs.pop('async')
55 else:
56 return default
2626 1: self._on_response,
2727 2: self._on_notification
2828 }
29 self.loop = msgpack_stream.loop
2930
3031 def threadsafe_call(self, fn):
3132 """Wrapper around `MsgpackStream.threadsafe_call`."""
6869 def stop(self):
6970 """Stop the event loop."""
7071 self._msgpack_stream.stop()
72
73 def close(self):
74 """Close the event loop."""
75 self._msgpack_stream.close()
7176
7277 def _on_message(self, msg):
7378 try:
11
22 Tries to use pyuv as a backend, falling back to the asyncio implementation.
33 """
4 try:
5 # libuv is fully implemented in C, use it when available
6 from .uv import UvEventLoop
7 EventLoop = UvEventLoop
8 except ImportError:
9 # asyncio(trollius on python 2) is pure python and should be more portable
10 # across python implementations
4
5 import os
6
7 from ...compat import IS_PYTHON3
8
9 # on python3 we only support asyncio, as we expose it to plugins
10 if IS_PYTHON3 and os.name != 'nt':
1111 from .asyncio import AsyncioEventLoop
1212 EventLoop = AsyncioEventLoop
13 else:
14 try:
15 # libuv is fully implemented in C, use it when available
16 from .uv import UvEventLoop
17 EventLoop = UvEventLoop
18 except ImportError:
19 # asyncio(trollius on python 2) is pure python and should be more
20 # portable across python implementations
21 from .asyncio import AsyncioEventLoop
22 EventLoop = AsyncioEventLoop
1323
1424
1525 __all__ = ('EventLoop')
3838 def connection_made(self, transport):
3939 """Used to signal `asyncio.Protocol` of a successful connection."""
4040 self._transport = transport
41 self._raw_transport = transport
4142 if isinstance(transport, asyncio.SubprocessTransport):
4243 self._transport = transport.get_pipe_transport(0)
4344
7374 self._loop = loop_cls()
7475 self._queued_data = deque()
7576 self._fact = lambda: self
77 self._raw_transport = None
7678
7779 def _connect_tcp(self, address, port):
7880 coroutine = self._loop.create_connection(self._fact, address, port)
111113 def _stop(self):
112114 self._loop.stop()
113115
116 def _close(self):
117 if self._raw_transport is not None:
118 self._raw_transport.close()
119 self._loop.close()
120
114121 def _threadsafe_call(self, fn):
115122 self._loop.call_soon_threadsafe(fn)
116123
2929 - `_init()`: Implementation-specific initialization
3030 - `_connect_tcp(address, port)`: connect to Nvim using tcp/ip
3131 - `_connect_socket(path)`: Same as tcp, but use a UNIX domain socket or
32 or named pipe.
32 named pipe.
3333 - `_connect_stdio()`: Use stdin/stdout as the connection to Nvim
3434 - `_connect_child(argv)`: Use the argument vector `argv` to spawn an
3535 embedded Nvim that has it's stdin/stdout connected to the event loop.
8484 self._on_data = None
8585 self._error = None
8686 self._init()
87 getattr(self, '_connect_{}'.format(transport_type))(*args)
87 try:
88 getattr(self, '_connect_{}'.format(transport_type))(*args)
89 except Exception as e:
90 self.close()
91 raise e
8892 self._start_reading()
8993
9094 def connect_tcp(self, address, port):
147151 self._stop()
148152 debug('Stopped event loop')
149153
154 def close(self):
155 """Stop the event loop."""
156 self._close()
157 debug('Closed event loop')
158
150159 def _on_signal(self, signum):
151160 msg = 'Received {}'.format(self._signames[signum])
152161 debug(msg)
9696 def _stop(self):
9797 self._loop.stop()
9898
99 def _close(self):
100 pass
101
99102 def _threadsafe_call(self, fn):
100103 self._callbacks.append(fn)
101104 self._async.send()
1818
1919 def __init__(self, event_loop):
2020 """Wrap `event_loop` on a msgpack-aware interface."""
21 self._event_loop = event_loop
22 self._packer = Packer(unicode_errors=unicode_errors_default)
21 self.loop = event_loop
22 self._packer = Packer(encoding='utf-8',
23 unicode_errors=unicode_errors_default)
2324 self._unpacker = Unpacker()
2425 self._message_cb = None
2526
2627 def threadsafe_call(self, fn):
2728 """Wrapper around `BaseEventLoop.threadsafe_call`."""
28 self._event_loop.threadsafe_call(fn)
29 self.loop.threadsafe_call(fn)
2930
3031 def send(self, msg):
3132 """Queue `msg` for sending to Nvim."""
3233 debug('sent %s', msg)
33 self._event_loop.send(self._packer.pack(msg))
34 self.loop.send(self._packer.pack(msg))
3435
3536 def run(self, message_cb):
3637 """Run the event loop to receive messages from Nvim.
3940 a message has been successfully parsed from the input stream.
4041 """
4142 self._message_cb = message_cb
42 self._event_loop.run(self._on_data)
43 self.loop.run(self._on_data)
4344 self._message_cb = None
4445
4546 def stop(self):
4647 """Stop the event loop."""
47 self._event_loop.stop()
48 self.loop.stop()
49
50 def close(self):
51 """Close the event loop."""
52 self.loop.close()
4853
4954 def _on_data(self, data):
5055 self._unpacker.feed(data)
33 from traceback import format_exc
44
55 import greenlet
6
7 from ..compat import check_async
68
79 logger = logging.getLogger(__name__)
810 error, debug, info, warn = (logger.error, logger.debug, logger.info,
2527 self._pending_messages = deque()
2628 self._is_running = False
2729 self._setup_exception = None
30 self.loop = async_session.loop
2831
2932 def threadsafe_call(self, fn, *args, **kwargs):
3033 """Wrapper around `AsyncSession.threadsafe_call`."""
7073 - Run the loop until the response is available
7174 - Put requests/notifications received while waiting into a queue
7275
73 If the `async` flag is present and True, a asynchronous notification is
74 sent instead. This will never block, and the return value or error is
75 ignored.
76 If the `async_` flag is present and True, a asynchronous notification
77 is sent instead. This will never block, and the return value or error
78 is ignored.
7679 """
77 async = kwargs.pop('async', False)
78 if async:
80 async_ = check_async(kwargs.pop('async_', None), kwargs, False)
81 if async_:
7982 self._async_session.notify(method, args)
8083 return
8184
138141 def stop(self):
139142 """Stop the event loop."""
140143 self._async_session.stop()
144
145 def close(self):
146 """Close the event loop."""
147 self._async_session.close()
141148
142149 def _yielding_request(self, method, args):
143150 gr = greenlet.getcurrent()
1313 def plugin(cls):
1414 """Tag a class as a plugin.
1515
16 This decorator is required to make the a class methods discoverable by the
16 This decorator is required to make the class methods discoverable by the
1717 plugin_load method of the host.
1818 """
1919 cls._nvim_plugin = True
5454
5555 if range is not None:
5656 opts['range'] = '' if range is True else str(range)
57 elif count:
57 elif count is not None:
5858 opts['count'] = count
5959
6060 if bang:
4545 self._decode_default = IS_PYTHON3
4646
4747 def _on_async_err(self, msg):
48 self.nvim.err_write(msg, async=True)
48 self.nvim.err_write(msg, async_=True)
4949
5050 def start(self, plugins):
5151 """Start listening for msgpack-rpc requests and notifications."""
2222
2323 if sys.version_info >= (3, 4):
2424 from importlib.machinery import PathFinder
25
26 PYTHON_SUBDIR = 'python3'
27 else:
28 PYTHON_SUBDIR = 'python2'
2529
2630
2731 @plugin
3943 exec('import sys', self.module.__dict__)
4044 self.legacy_vim = LegacyVim.from_nvim(nvim)
4145 sys.modules['vim'] = self.legacy_vim
46
47 # Handle DirChanged. #296
48 nvim.command(
49 'au DirChanged * call rpcnotify({}, "python_chdir", v:event.cwd)'
50 .format(nvim.channel_id), async_=True)
51 # XXX: Avoid race condition.
52 # https://github.com/neovim/python-client/pull/296#issuecomment-358970531
53 # TODO(bfredl): when host initialization has been refactored,
54 # to make __init__ safe again, the following should work:
55 # os.chdir(nvim.eval('getcwd()', async_=False))
56 nvim.command('call rpcnotify({}, "python_chdir", getcwd())'
57 .format(nvim.channel_id), async_=True)
4258
4359 def setup(self, nvim):
4460 """Setup import hooks and global streams.
148164 """Handle the `pyeval` vim function."""
149165 return eval(expr, self.module.__dict__)
150166
167 @rpc_export('python_chdir', sync=False)
168 def python_chdir(self, cwd):
169 """Handle working directory changes."""
170 os.chdir(cwd)
171
151172 def _set_current_range(self, start, stop):
152173 current = self.legacy_vim.current
153174 current.range = current.buffer.range(start, stop)
167188 if IS_PYTHON3:
168189 num_types = (int, float)
169190 else:
170 num_types = (int, long, float)
191 num_types = (int, long, float) # noqa: F821
171192
172193
173194 def num_to_str(obj):
235256
236257 def discover_runtime_directories(nvim):
237258 rv = []
238 for path in nvim.list_runtime_paths():
239 if not os.path.exists(path):
259 for rtp in nvim.list_runtime_paths():
260 if not os.path.exists(rtp):
240261 continue
241 path1 = os.path.join(path, 'pythonx')
242 if IS_PYTHON3:
243 path2 = os.path.join(path, 'python3')
244 else:
245 path2 = os.path.join(path, 'python2')
246 if os.path.exists(path1):
247 rv.append(path1)
248 if os.path.exists(path2):
249 rv.append(path2)
262 for subdir in ['pythonx', PYTHON_SUBDIR]:
263 path = os.path.join(rtp, subdir)
264 if os.path.exists(path):
265 rv.append(path)
250266 return rv
+0
-5
neovim/version.py less more
0 from .util import Version
1
2 VERSION = Version(major=0, minor=1, patch=14, prerelease="dev")
3
4 __all__ = ('VERSION',)
0 Metadata-Version: 1.1
0 Metadata-Version: 2.1
11 Name: neovim
2 Version: 0.2.0
2 Version: 0.2.6
33 Summary: Python client to neovim
44 Home-page: http://github.com/neovim/python-client
55 Author: Thiago de Arruda
66 Author-email: tpadilha84@gmail.com
77 License: Apache
8 Download-URL: https://github.com/neovim/python-client/archive/0.2.0.tar.gz
8 Download-URL: https://github.com/neovim/python-client/archive/0.2.6.tar.gz
99 Description: UNKNOWN
1010 Platform: UNKNOWN
11 Provides-Extra: pyuv
55 neovim/__init__.py
66 neovim/compat.py
77 neovim/util.py
8 neovim/version.py
98 neovim.egg-info/PKG-INFO
109 neovim.egg-info/SOURCES.txt
1110 neovim.egg-info/dependency_links.txt
3231 neovim/plugin/script_host.py
3332 test/test_buffer.py
3433 test/test_client_rpc.py
35 test/test_common.py
3634 test/test_concurrency.py
35 test/test_decorators.py
3736 test/test_events.py
3837 test/test_tabpage.py
3938 test/test_vim.py
0 msgpack-python>=0.4.0
0 msgpack>=0.5.0
11 greenlet
22
33 [pyuv]
00 [flake8]
1 ignore = D211,E731,F821,D401
1 ignore = D211,E731,D401
22
33 [egg_info]
44 tag_build =
44 from setuptools import setup
55
66 install_requires = [
7 'msgpack-python>=0.4.0',
7 'msgpack>=0.5.0',
88 ]
9
10 tests_require = [
11 'pytest>=3.4.0',
12 ]
13
914 extras_require = {
1015 'pyuv': ['pyuv>=1.0.0'],
1116 }
2126 install_requires.append('greenlet')
2227
2328 setup(name='neovim',
24 version='0.2.0',
29 version='0.2.6',
2530 description='Python client to neovim',
2631 url='http://github.com/neovim/python-client',
27 download_url='https://github.com/neovim/python-client/archive/0.2.0.tar.gz',
32 download_url='https://github.com/neovim/python-client/archive/0.2.6.tar.gz',
2833 author='Thiago de Arruda',
2934 author_email='tpadilha84@gmail.com',
3035 license='Apache',
3136 packages=['neovim', 'neovim.api', 'neovim.msgpack_rpc',
3237 'neovim.msgpack_rpc.event_loop', 'neovim.plugin'],
3338 install_requires=install_requires,
39 tests_require=tests_require,
3440 extras_require=extras_require,
3541 zip_safe=False)
00 import os
1 from nose.tools import with_setup, eq_ as eq, ok_ as ok
2 from test_common import vim, cleanup
31
42 from neovim.compat import IS_PYTHON3
53
64
7 @with_setup(setup=cleanup)
8 def test_get_length():
9 eq(len(vim.current.buffer), 1)
5 def test_get_length(vim):
6 assert len(vim.current.buffer) == 1
107 vim.current.buffer.append('line')
11 eq(len(vim.current.buffer), 2)
8 assert len(vim.current.buffer) == 2
129 vim.current.buffer.append('line')
13 eq(len(vim.current.buffer), 3)
10 assert len(vim.current.buffer) == 3
1411 vim.current.buffer[-1] = None
15 eq(len(vim.current.buffer), 2)
12 assert len(vim.current.buffer) == 2
1613 vim.current.buffer[-1] = None
1714 vim.current.buffer[-1] = None
1815 # There's always at least one line
19 eq(len(vim.current.buffer), 1)
16 assert len(vim.current.buffer) == 1
2017
2118
22 @with_setup(setup=cleanup)
23 def test_get_set_del_line():
24 eq(vim.current.buffer[0], '')
19 def test_get_set_del_line(vim):
20 assert vim.current.buffer[0] == ''
2521 vim.current.buffer[0] = 'line1'
26 eq(vim.current.buffer[0], 'line1')
22 assert vim.current.buffer[0] == 'line1'
2723 vim.current.buffer[0] = 'line2'
28 eq(vim.current.buffer[0], 'line2')
24 assert vim.current.buffer[0] == 'line2'
2925 vim.current.buffer[0] = None
30 eq(vim.current.buffer[0], '')
26 assert vim.current.buffer[0] == ''
3127 # __delitem__
3228 vim.current.buffer[:] = ['line1', 'line2', 'line3']
33 eq(vim.current.buffer[2], 'line3')
29 assert vim.current.buffer[2] == 'line3'
3430 del vim.current.buffer[0]
35 eq(vim.current.buffer[0], 'line2')
36 eq(vim.current.buffer[1], 'line3')
31 assert vim.current.buffer[0] == 'line2'
32 assert vim.current.buffer[1] == 'line3'
3733 del vim.current.buffer[-1]
38 eq(vim.current.buffer[0], 'line2')
39 eq(len(vim.current.buffer), 1)
34 assert vim.current.buffer[0] == 'line2'
35 assert len(vim.current.buffer) == 1
4036
4137
42 @with_setup(setup=cleanup)
43 def test_get_set_del_slice():
44 eq(vim.current.buffer[:], [''])
38 def test_get_set_del_slice(vim):
39 assert vim.current.buffer[:] == ['']
4540 # Replace buffer
4641 vim.current.buffer[:] = ['a', 'b', 'c']
47 eq(vim.current.buffer[:], ['a', 'b', 'c'])
48 eq(vim.current.buffer[1:], ['b', 'c'])
49 eq(vim.current.buffer[1:2], ['b'])
50 eq(vim.current.buffer[1:1], [])
51 eq(vim.current.buffer[:-1], ['a', 'b'])
52 eq(vim.current.buffer[1:-1], ['b'])
53 eq(vim.current.buffer[-2:], ['b', 'c'])
42 assert vim.current.buffer[:] == ['a', 'b', 'c']
43 assert vim.current.buffer[1:] == ['b', 'c']
44 assert vim.current.buffer[1:2] == ['b']
45 assert vim.current.buffer[1:1] == []
46 assert vim.current.buffer[:-1] == ['a', 'b']
47 assert vim.current.buffer[1:-1] == ['b']
48 assert vim.current.buffer[-2:] == ['b', 'c']
5449 vim.current.buffer[1:2] = ['a', 'b', 'c']
55 eq(vim.current.buffer[:], ['a', 'a', 'b', 'c', 'c'])
50 assert vim.current.buffer[:] == ['a', 'a', 'b', 'c', 'c']
5651 vim.current.buffer[-1:] = ['a', 'b', 'c']
57 eq(vim.current.buffer[:], ['a', 'a', 'b', 'c', 'a', 'b', 'c'])
52 assert vim.current.buffer[:] == ['a', 'a', 'b', 'c', 'a', 'b', 'c']
5853 vim.current.buffer[:-3] = None
59 eq(vim.current.buffer[:], ['a', 'b', 'c'])
54 assert vim.current.buffer[:] == ['a', 'b', 'c']
6055 vim.current.buffer[:] = None
61 eq(vim.current.buffer[:], [''])
56 assert vim.current.buffer[:] == ['']
6257 # __delitem__
6358 vim.current.buffer[:] = ['a', 'b', 'c']
6459 del vim.current.buffer[:]
65 eq(vim.current.buffer[:], [''])
60 assert vim.current.buffer[:] == ['']
6661 vim.current.buffer[:] = ['a', 'b', 'c']
6762 del vim.current.buffer[:1]
68 eq(vim.current.buffer[:], ['b', 'c'])
63 assert vim.current.buffer[:] == ['b', 'c']
6964 del vim.current.buffer[:-1]
70 eq(vim.current.buffer[:], ['c'])
65 assert vim.current.buffer[:] == ['c']
7166
7267
73 @with_setup(setup=cleanup)
74 def test_vars():
68 def test_vars(vim):
7569 vim.current.buffer.vars['python'] = [1, 2, {'3': 1}]
76 eq(vim.current.buffer.vars['python'], [1, 2, {'3': 1}])
77 eq(vim.eval('b:python'), [1, 2, {'3': 1}])
70 assert vim.current.buffer.vars['python'] == [1, 2, {'3': 1}]
71 assert vim.eval('b:python') == [1, 2, {'3': 1}]
7872
7973
80 @with_setup(setup=cleanup)
81 def test_api():
74 def test_api(vim):
8275 vim.current.buffer.api.set_var('myvar', 'thetext')
83 eq(vim.current.buffer.api.get_var('myvar'), 'thetext')
84 eq(vim.eval('b:myvar'), 'thetext')
85 vim.current.buffer.api.set_lines(0,-1,True,['alpha', 'beta'])
86 eq(vim.current.buffer.api.get_lines(0,-1,True), ['alpha', 'beta'])
87 eq(vim.current.buffer[:], ['alpha', 'beta'])
76 assert vim.current.buffer.api.get_var('myvar') == 'thetext'
77 assert vim.eval('b:myvar') == 'thetext'
78 vim.current.buffer.api.set_lines(0, -1, True, ['alpha', 'beta'])
79 assert vim.current.buffer.api.get_lines(0, -1, True) == ['alpha', 'beta']
80 assert vim.current.buffer[:] == ['alpha', 'beta']
8881
8982
90 @with_setup(setup=cleanup)
91 def test_options():
92 eq(vim.current.buffer.options['shiftwidth'], 8)
83 def test_options(vim):
84 assert vim.current.buffer.options['shiftwidth'] == 8
9385 vim.current.buffer.options['shiftwidth'] = 4
94 eq(vim.current.buffer.options['shiftwidth'], 4)
86 assert vim.current.buffer.options['shiftwidth'] == 4
9587 # global-local option
9688 vim.current.buffer.options['define'] = 'test'
97 eq(vim.current.buffer.options['define'], 'test')
89 assert vim.current.buffer.options['define'] == 'test'
9890 # Doesn't change the global value
99 eq(vim.options['define'], '^\s*#\s*define')
91 assert vim.options['define'] == '^\s*#\s*define'
10092
10193
102 @with_setup(setup=cleanup)
103 def test_number():
94 def test_number(vim):
10495 curnum = vim.current.buffer.number
10596 vim.command('new')
106 eq(vim.current.buffer.number, curnum + 1)
97 assert vim.current.buffer.number == curnum + 1
10798 vim.command('new')
108 eq(vim.current.buffer.number, curnum + 2)
99 assert vim.current.buffer.number == curnum + 2
109100
110101
111 @with_setup(setup=cleanup)
112 def test_name():
102 def test_name(vim):
113103 vim.command('new')
114 eq(vim.current.buffer.name, '')
104 assert vim.current.buffer.name == ''
115105 new_name = vim.eval('resolve(tempname())')
116106 vim.current.buffer.name = new_name
117 eq(vim.current.buffer.name, new_name)
107 assert vim.current.buffer.name == new_name
118108 vim.command('silent w!')
119 ok(os.path.isfile(new_name))
109 assert os.path.isfile(new_name)
120110 os.unlink(new_name)
121111
122112
123 @with_setup(setup=cleanup)
124 def test_valid():
113 def test_valid(vim):
125114 vim.command('new')
126115 buffer = vim.current.buffer
127 ok(buffer.valid)
116 assert buffer.valid
128117 vim.command('bw!')
129 ok(not buffer.valid)
118 assert not buffer.valid
130119
131120
132 @with_setup(setup=cleanup)
133 def test_append():
121 def test_append(vim):
134122 vim.current.buffer.append('a')
135 eq(vim.current.buffer[:], ['', 'a'])
123 assert vim.current.buffer[:] == ['', 'a']
136124 vim.current.buffer.append('b', 0)
137 eq(vim.current.buffer[:], ['b', '', 'a'])
125 assert vim.current.buffer[:] == ['b', '', 'a']
138126 vim.current.buffer.append(['c', 'd'])
139 eq(vim.current.buffer[:], ['b', '', 'a', 'c', 'd'])
127 assert vim.current.buffer[:] == ['b', '', 'a', 'c', 'd']
140128 vim.current.buffer.append(['c', 'd'], 2)
141 eq(vim.current.buffer[:], ['b', '', 'c', 'd', 'a', 'c', 'd'])
129 assert vim.current.buffer[:] == ['b', '', 'c', 'd', 'a', 'c', 'd']
142130 vim.current.buffer.append(b'bytes')
143 eq(vim.current.buffer[:], ['b', '', 'c', 'd', 'a', 'c', 'd', 'bytes'])
131 assert vim.current.buffer[:] == ['b', '', 'c', 'd', 'a', 'c', 'd', 'bytes']
144132
145133
146 @with_setup(setup=cleanup)
147 def test_mark():
134 def test_mark(vim):
148135 vim.current.buffer.append(['a', 'bit of', 'text'])
149136 vim.current.window.cursor = [3, 4]
150137 vim.command('mark V')
151 eq(vim.current.buffer.mark('V'), [3, 0])
138 assert vim.current.buffer.mark('V') == [3, 0]
152139
153 @with_setup(setup=cleanup)
154 def test_invalid_utf8():
140
141 def test_invalid_utf8(vim):
155142 vim.command('normal "=printf("%c", 0xFF)\np')
156 eq(vim.eval("char2nr(getline(1))"), 0xFF)
143 assert vim.eval("char2nr(getline(1))") == 0xFF
157144
158 eq(vim.current.buffer[:], ['\udcff'] if IS_PYTHON3 else ['\xff'])
145 assert vim.current.buffer[:] == ['\udcff'] if IS_PYTHON3 else ['\xff']
159146 vim.current.line += 'x'
160 eq(vim.eval("getline(1)", decode=False), b'\xFFx')
161 eq(vim.current.buffer[:], ['\udcffx'] if IS_PYTHON3 else ['\xffx'])
147 assert vim.eval("getline(1)", decode=False) == b'\xFFx'
148 assert vim.current.buffer[:] == ['\udcffx'] if IS_PYTHON3 else ['\xffx']
162149
163 @with_setup(setup=cleanup)
164 def test_get_exceptions():
150
151 def test_get_exceptions(vim):
165152 try:
166153 vim.current.buffer.options['invalid-option']
167 ok(False)
154 assert False
168155 except vim.error:
169156 pass
170157
171 @with_setup(setup=cleanup)
172 def test_contains():
173 ok(vim.current.buffer in vim.buffers)
174
175 @with_setup(setup=cleanup)
176 def test_set_items_for_range():
158 def test_set_items_for_range(vim):
177159 vim.current.buffer[:] = ['a', 'b', 'c', 'd', 'e']
178160 r = vim.current.buffer.range(1, 3)
179161 r[1:3] = ['foo']*3
180 eq(vim.current.buffer[:], ['a', 'foo', 'foo', 'foo', 'd', 'e'])
162 assert vim.current.buffer[:] == ['a', 'foo', 'foo', 'foo', 'd', 'e']
163
164 # NB: we can't easily test the effect of this. But at least run the lua
165 # function sync, so we know it runs without runtime error with simple args.
166 def test_update_highlights(vim):
167 vim.current.buffer[:] = ['a', 'b', 'c']
168 src_id = vim.new_highlight_source()
169 vim.current.buffer.update_highlights(src_id, [["Comment", 0, 0, -1], ("String", 1, 0, 1)], clear=True, async_=False)
00 # -*- coding: utf-8 -*-
1 from nose.tools import with_setup, eq_ as eq
2 from test_common import vim, cleanup
3
4 cid = vim.channel_id
51
62
7 @with_setup(setup=cleanup)
8 def test_call_and_reply():
3 def test_call_and_reply(vim):
4 cid = vim.channel_id
95 def setup_cb():
106 cmd = 'let g:result = rpcrequest(%d, "client-call", 1, 2, 3)' % cid
117 vim.command(cmd)
12 eq(vim.vars['result'], [4, 5, 6])
8 assert vim.vars['result'] == [4, 5, 6]
139 vim.stop_loop()
1410
1511 def request_cb(name, args):
16 eq(name, 'client-call')
17 eq(args, [1, 2, 3])
12 assert name == 'client-call'
13 assert args == [1, 2, 3]
1814 return [4, 5, 6]
1915
2016 vim.run_loop(request_cb, None, setup_cb)
2117
2218
23 @with_setup(setup=cleanup)
24 def test_call_api_before_reply():
19 def test_call_api_before_reply(vim):
20 cid = vim.channel_id
2521 def setup_cb():
2622 cmd = 'let g:result = rpcrequest(%d, "client-call2", 1, 2, 3)' % cid
2723 vim.command(cmd)
28 eq(vim.vars['result'], [7, 8, 9])
24 assert vim.vars['result'] == [7, 8, 9]
2925 vim.stop_loop()
3026
3127 def request_cb(name, args):
3430
3531 vim.run_loop(request_cb, None, setup_cb)
3632
37 @with_setup(setup=cleanup)
38 def test_async_call():
33 def test_async_call(vim):
3934
4035 def request_cb(name, args):
4136 if name == "test-event":
4338 vim.stop_loop()
4439
4540 # this would have dead-locked if not async
46 vim.funcs.rpcrequest(vim.channel_id, "test-event", async=True)
41 vim.funcs.rpcrequest(vim.channel_id, "test-event", async_=True)
4742 vim.run_loop(request_cb, None, None)
48 eq(vim.vars['result'], 17)
43 assert vim.vars['result'] == 17
4944
5045
51 @with_setup(setup=cleanup)
52 def test_recursion():
46 def test_recursion(vim):
47 cid = vim.channel_id
5348 def setup_cb():
5449 vim.vars['result1'] = 0
5550 vim.vars['result2'] = 0
5752 vim.vars['result4'] = 0
5853 cmd = 'let g:result1 = rpcrequest(%d, "call", %d)' % (cid, 2,)
5954 vim.command(cmd)
60 eq(vim.vars['result1'], 4)
61 eq(vim.vars['result2'], 8)
62 eq(vim.vars['result3'], 16)
63 eq(vim.vars['result4'], 32)
55 assert vim.vars['result1'] == 4
56 assert vim.vars['result2'] == 8
57 assert vim.vars['result3'] == 16
58 assert vim.vars['result4'] == 32
6459 vim.stop_loop()
6560
6661 def request_cb(name, args):
+0
-67
test/test_common.py less more
0 import json
1 import os
2 import sys
3
4 import neovim
5
6 from nose.tools import eq_ as eq
7
8 neovim.setup_logging("test")
9
10 child_argv = os.environ.get('NVIM_CHILD_ARGV')
11 listen_address = os.environ.get('NVIM_LISTEN_ADDRESS')
12 if child_argv is None and listen_address is None:
13 child_argv = '["nvim", "-u", "NONE", "--embed"]'
14
15 if child_argv is not None:
16 vim = neovim.attach('child', argv=json.loads(child_argv))
17 else:
18 vim = neovim.attach('socket', path=listen_address)
19
20 cleanup_func = ''':function BeforeEachTest()
21 set all&
22 redir => groups
23 silent augroup
24 redir END
25 for group in split(groups)
26 exe 'augroup '.group
27 autocmd!
28 augroup END
29 endfor
30 autocmd!
31 tabnew
32 let curbufnum = eval(bufnr('%'))
33 redir => buflist
34 silent ls!
35 redir END
36 let bufnums = []
37 for buf in split(buflist, '\\n')
38 let bufnum = eval(split(buf, '[ u]')[0])
39 if bufnum != curbufnum
40 call add(bufnums, bufnum)
41 endif
42 endfor
43 if len(bufnums) > 0
44 exe 'silent bwipeout! '.join(bufnums, ' ')
45 endif
46 silent tabonly
47 for k in keys(g:)
48 exe 'unlet g:'.k
49 endfor
50 filetype plugin indent off
51 mapclear
52 mapclear!
53 abclear
54 comclear
55 endfunction
56 '''
57
58 vim.input(cleanup_func)
59
60
61 def cleanup():
62 # cleanup nvim
63 vim.command('call BeforeEachTest()')
64 eq(len(vim.tabpages), 1)
65 eq(len(vim.windows), 1)
66 eq(len(vim.buffers), 1)
0 from nose.tools import with_setup, eq_ as eq
1 from test_common import vim, cleanup
20 from threading import Timer
31
4 @with_setup(setup=cleanup)
5 def test_interrupt_from_another_thread():
2
3 def test_interrupt_from_another_thread(vim):
64 timer = Timer(0.5, lambda: vim.async_call(lambda: vim.stop_loop()))
75 timer.start()
8 eq(vim.next_message(), None)
6 assert vim.next_message() == None
97
10 @with_setup(setup=cleanup)
11 def test_exception_in_threadsafe_call():
8
9 def test_exception_in_threadsafe_call(vim):
1210 # an exception in a threadsafe_call shouldn't crash the entire host
1311 msgs = []
1412 vim.async_call(lambda: [vim.eval("3"), undefined_variable])
1513 timer = Timer(0.5, lambda: vim.async_call(lambda: vim.stop_loop()))
1614 timer.start()
1715 vim.run_loop(None, None, err_cb=msgs.append)
18 eq(len(msgs), 1)
16 assert len(msgs) == 1
1917 msgs[0].index('NameError')
2018 msgs[0].index('undefined_variable')
0 from neovim.plugin.decorators import command
1
2
3 def test_command_count():
4 def function():
5 "A dummy function to decorate."
6 return
7
8 # ensure absence with default value of None
9 decorated = command('test')(function)
10 assert 'count' not in decorated._nvim_rpc_spec['opts']
11
12 # ensure absence with explicit value of None
13 count_value = None
14 decorated = command('test', count=count_value)(function)
15 assert 'count' not in decorated._nvim_rpc_spec['opts']
16
17 # Test presesence with value of 0
18 count_value = 0
19 decorated = command('test', count=count_value)(function)
20 assert 'count' in decorated._nvim_rpc_spec['opts']
21 assert decorated._nvim_rpc_spec['opts']['count'] == count_value
22
23 # Test presence with value of 1
24 count_value = 1
25 decorated = command('test', count=count_value)(function)
26 assert 'count' in decorated._nvim_rpc_spec['opts']
27 assert decorated._nvim_rpc_spec['opts']['count'] == count_value
00 # -*- coding: utf-8 -*-
1 from nose.tools import with_setup, eq_ as eq
2 from test_common import vim, cleanup
31
42
5 @with_setup(setup=cleanup)
6 def test_receiving_events():
3 def test_receiving_events(vim):
74 vim.command('call rpcnotify(%d, "test-event", 1, 2, 3)' % vim.channel_id)
85 event = vim.next_message()
9 eq(event[1], 'test-event')
10 eq(event[2], [1, 2, 3])
6 assert event[1] == 'test-event'
7 assert event[2] == [1, 2, 3]
118 vim.command('au FileType python call rpcnotify(%d, "py!", bufnr("$"))' %
129 vim.channel_id)
1310 vim.command('set filetype=python')
1411 event = vim.next_message()
15 eq(event[1], 'py!')
16 eq(event[2], [vim.current.buffer.number])
12 assert event[1] == 'py!'
13 assert event[2] == [vim.current.buffer.number]
1714
18 @with_setup(setup=cleanup)
19 def test_sending_notify():
15
16 def test_sending_notify(vim):
2017 # notify after notify
21 vim.command("let g:test = 3", async=True)
18 vim.command("let g:test = 3", async_=True)
2219 cmd = 'call rpcnotify(%d, "test-event", g:test)' % vim.channel_id
23 vim.command(cmd, async=True)
20 vim.command(cmd, async_=True)
2421 event = vim.next_message()
25 eq(event[1], 'test-event')
26 eq(event[2], [3])
22 assert event[1] == 'test-event'
23 assert event[2] == [3]
2724
2825 # request after notify
29 vim.command("let g:data = 'xyz'", async=True)
30 eq(vim.eval('g:data'), 'xyz')
26 vim.command("let g:data = 'xyz'", async_=True)
27 assert vim.eval('g:data') == 'xyz'
3128
3229
33 @with_setup(setup=cleanup)
34 def test_broadcast():
30 def test_broadcast(vim):
3531 vim.subscribe('event2')
3632 vim.command('call rpcnotify(0, "event1", 1, 2, 3)')
3733 vim.command('call rpcnotify(0, "event2", 4, 5, 6)')
3834 vim.command('call rpcnotify(0, "event2", 7, 8, 9)')
3935 event = vim.next_message()
40 eq(event[1], 'event2')
41 eq(event[2], [4, 5, 6])
36 assert event[1] == 'event2'
37 assert event[2] == [4, 5, 6]
4238 event = vim.next_message()
43 eq(event[1], 'event2')
44 eq(event[2], [7, 8, 9])
39 assert event[1] == 'event2'
40 assert event[2] == [7, 8, 9]
4541 vim.unsubscribe('event2')
4642 vim.subscribe('event1')
4743 vim.command('call rpcnotify(0, "event2", 10, 11, 12)')
4844 vim.command('call rpcnotify(0, "event1", 13, 14, 15)')
4945 msg = vim.next_message()
50 eq(msg[1], 'event1')
51 eq(msg[2], [13, 14, 15])
46 assert msg[1] == 'event1'
47 assert msg[2] == [13, 14, 15]
0 import os
1 from nose.tools import with_setup, eq_ as eq, ok_ as ok
2 from test_common import vim, cleanup
0 def test_windows(vim):
1 vim.command('tabnew')
2 vim.command('vsplit')
3 assert list(vim.tabpages[0].windows) == [vim.windows[0]]
4 assert list(vim.tabpages[1].windows) == [vim.windows[1], vim.windows[2]]
5 assert vim.tabpages[1].window == vim.windows[1]
6 vim.current.window = vim.windows[2]
7 assert vim.tabpages[1].window == vim.windows[2]
38
49
5 @with_setup(setup=cleanup)
6 def test_windows():
7 vim.command('tabnew')
8 vim.command('vsplit')
9 eq(list(vim.tabpages[0].windows), [vim.windows[0]])
10 eq(list(vim.tabpages[1].windows), [vim.windows[1], vim.windows[2]])
11 eq(vim.tabpages[1].window, vim.windows[1])
12 vim.current.window = vim.windows[2]
13 eq(vim.tabpages[1].window, vim.windows[2])
10 def test_vars(vim):
11 vim.current.tabpage.vars['python'] = [1, 2, {'3': 1}]
12 assert vim.current.tabpage.vars['python'] == [1, 2, {'3': 1}]
13 assert vim.eval('t:python') == [1, 2, {'3': 1}]
1414
1515
16 @with_setup(setup=cleanup)
17 def test_vars():
18 vim.current.tabpage.vars['python'] = [1, 2, {'3': 1}]
19 eq(vim.current.tabpage.vars['python'], [1, 2, {'3': 1}])
20 eq(vim.eval('t:python'), [1, 2, {'3': 1}])
16 def test_valid(vim):
17 vim.command('tabnew')
18 tabpage = vim.tabpages[1]
19 assert tabpage.valid
20 vim.command('tabclose')
21 assert not tabpage.valid
2122
2223
23 @with_setup(setup=cleanup)
24 def test_valid():
25 vim.command('tabnew')
26 tabpage = vim.tabpages[1]
27 ok(tabpage.valid)
28 vim.command('tabclose')
29 ok(not tabpage.valid)
30
31
32 @with_setup(setup=cleanup)
33 def test_number():
24 def test_number(vim):
3425 curnum = vim.current.tabpage.number
3526 vim.command('tabnew')
36 eq(vim.current.tabpage.number, curnum + 1)
27 assert vim.current.tabpage.number == curnum + 1
3728 vim.command('tabnew')
38 eq(vim.current.tabpage.number, curnum + 2)
29 assert vim.current.tabpage.number == curnum + 2
00 # -*- coding: utf-8 -*-
1 import os, tempfile
2 from nose.tools import with_setup, eq_ as eq, ok_ as ok
3 from test_common import vim, cleanup
1 import os
2 import sys
3 import tempfile
44
5 def source(code):
5
6 def source(vim, code):
67 fd, fname = tempfile.mkstemp()
7 with os.fdopen(fd,'w') as f:
8 with os.fdopen(fd, 'w') as f:
89 f.write(code)
9 vim.command('source '+fname)
10 vim.command('source ' + fname)
1011 os.unlink(fname)
1112
1213
13 @with_setup(setup=cleanup)
14 def test_command():
14 def test_command(vim):
1515 fname = tempfile.mkstemp()[1]
1616 vim.command('new')
17 vim.command('edit %s' % fname)
17 vim.command('edit {}'.format(fname))
1818 # skip the "press return" state, which does not handle deferred calls
1919 vim.input('\r')
2020 vim.command('normal itesting\npython\napi')
2121 vim.command('w')
22 ok(os.path.isfile(fname))
22 assert os.path.isfile(fname)
2323 with open(fname) as f:
24 eq(f.read(), 'testing\npython\napi\n')
24 assert f.read() == 'testing\npython\napi\n'
2525 os.unlink(fname)
2626
2727
28 @with_setup
29 def test_command_output():
30 eq(vim.command_output('echo test'), 'test')
28 def test_command_output(vim):
29 assert vim.command_output('echo "test"') == 'test'
3130
3231
33 @with_setup(setup=cleanup)
34 def test_eval():
32 def test_eval(vim):
3533 vim.command('let g:v1 = "a"')
3634 vim.command('let g:v2 = [1, 2, {"v3": 3}]')
37 eq(vim.eval('g:'), {'v1': 'a', 'v2': [1, 2, {'v3': 3}]})
35 assert vim.eval('g:'), {'v1': 'a', 'v2': [1, 2 == {'v3': 3}]}
3836
39 @with_setup(setup=cleanup)
40 def test_call():
41 eq(vim.funcs.join(['first', 'last'], ', '), 'first, last')
42 source("""
37
38 def test_call(vim):
39 assert vim.funcs.join(['first', 'last'], ', '), 'first == last'
40 source(vim, """
4341 function! Testfun(a,b)
4442 return string(a:a).":".a:b
4543 endfunction
4644 """)
47 eq(vim.funcs.Testfun(3, 'alpha'), '3:alpha')
45 assert vim.funcs.Testfun(3, 'alpha') == '3:alpha'
4846
4947
50 @with_setup(setup=cleanup)
51 def test_api():
48 def test_api(vim):
5249 vim.api.command('let g:var = 3')
53 eq(vim.api.eval('g:var'), 3)
50 assert vim.api.eval('g:var') == 3
5451
5552
56 @with_setup(setup=cleanup)
57 def test_strwidth():
58 eq(vim.strwidth('abc'), 3)
53 def test_strwidth(vim):
54 assert vim.strwidth('abc') == 3
5955 # 6 + (neovim)
6056 # 19 * 2 (each japanese character occupies two cells)
61 eq(vim.strwidth('neovimのデザインかなりまともなのになってる。'), 44)
57 assert vim.strwidth('neovimのデザインかなりまともなのになってる。') == 44
6258
63 @with_setup(setup=cleanup)
64 def test_chdir():
59
60 def test_chdir(vim):
6561 pwd = vim.eval('getcwd()')
6662 root = os.path.abspath(os.sep)
6763 # We can chdir to '/' on Windows, but then the pwd will be the root drive
6864 vim.chdir('/')
69 eq(vim.eval('getcwd()'), root)
65 assert vim.eval('getcwd()') == root
7066 vim.chdir(pwd)
71 eq(vim.eval('getcwd()'), pwd)
67 assert vim.eval('getcwd()') == pwd
7268
7369
74 @with_setup(setup=cleanup)
75 def test_current_line():
76 eq(vim.current.line, '')
70 def test_current_line(vim):
71 assert vim.current.line == ''
7772 vim.current.line = 'abc'
78 eq(vim.current.line, 'abc')
73 assert vim.current.line == 'abc'
7974
8075
81 @with_setup(setup=cleanup)
82 def test_vars():
76 def test_vars(vim):
8377 vim.vars['python'] = [1, 2, {'3': 1}]
84 eq(vim.vars['python'], [1, 2, {'3': 1}])
85 eq(vim.eval('g:python'), [1, 2, {'3': 1}])
78 assert vim.vars['python'], [1, 2 == {'3': 1}]
79 assert vim.eval('g:python'), [1, 2 == {'3': 1}]
8680
8781
88 @with_setup(setup=cleanup)
89 def test_options():
90 eq(vim.options['listchars'], 'tab:> ,trail:-,nbsp:+')
82 def test_options(vim):
83 assert vim.options['listchars'] == 'tab:> ,trail:-,nbsp:+'
9184 vim.options['listchars'] = 'tab:xy'
92 eq(vim.options['listchars'], 'tab:xy')
85 assert vim.options['listchars'] == 'tab:xy'
9386
9487
95 @with_setup(setup=cleanup)
96 def test_buffers():
88 def test_buffers(vim):
9789 buffers = []
9890
9991 # Number of elements
100 eq(len(vim.buffers), 1)
92 assert len(vim.buffers) == 1
10193
10294 # Indexing (by buffer number)
103 eq(vim.buffers[vim.current.buffer.number], vim.current.buffer)
95 assert vim.buffers[vim.current.buffer.number] == vim.current.buffer
10496
10597 buffers.append(vim.current.buffer)
10698 vim.command('new')
107 eq(len(vim.buffers), 2)
99 assert len(vim.buffers) == 2
108100 buffers.append(vim.current.buffer)
109 eq(vim.buffers[vim.current.buffer.number], vim.current.buffer)
101 assert vim.buffers[vim.current.buffer.number] == vim.current.buffer
110102 vim.current.buffer = buffers[0]
111 eq(vim.buffers[vim.current.buffer.number], buffers[0])
103 assert vim.buffers[vim.current.buffer.number] == buffers[0]
112104
113105 # Membership test
114 ok(buffers[0] in vim.buffers)
115 ok(buffers[1] in vim.buffers)
116 ok({} not in vim.buffers)
106 assert buffers[0] in vim.buffers
107 assert buffers[1] in vim.buffers
108 assert {} not in vim.buffers
117109
118110 # Iteration
119 eq(buffers, list(vim.buffers))
111 assert buffers == list(vim.buffers)
120112
121113
122 @with_setup(setup=cleanup)
123 def test_windows():
124 eq(len(vim.windows), 1)
125 eq(vim.windows[0], vim.current.window)
114 def test_windows(vim):
115 assert len(vim.windows) == 1
116 assert vim.windows[0] == vim.current.window
126117 vim.command('vsplit')
127118 vim.command('split')
128 eq(len(vim.windows), 3)
129 eq(vim.windows[0], vim.current.window)
119 assert len(vim.windows) == 3
120 assert vim.windows[0] == vim.current.window
130121 vim.current.window = vim.windows[1]
131 eq(vim.windows[1], vim.current.window)
122 assert vim.windows[1] == vim.current.window
132123
133124
134 @with_setup(setup=cleanup)
135 def test_tabpages():
136 eq(len(vim.tabpages), 1)
137 eq(vim.tabpages[0], vim.current.tabpage)
125 def test_tabpages(vim):
126 assert len(vim.tabpages) == 1
127 assert vim.tabpages[0] == vim.current.tabpage
138128 vim.command('tabnew')
139 eq(len(vim.tabpages), 2)
140 eq(len(vim.windows), 2)
141 eq(vim.windows[1], vim.current.window)
142 eq(vim.tabpages[1], vim.current.tabpage)
129 assert len(vim.tabpages) == 2
130 assert len(vim.windows) == 2
131 assert vim.windows[1] == vim.current.window
132 assert vim.tabpages[1] == vim.current.tabpage
143133 vim.current.window = vim.windows[0]
144134 # Switching window also switches tabpages if necessary(this probably
145135 # isn't the current behavior, but compatibility will be handled in the
146136 # python client with an optional parameter)
147 eq(vim.tabpages[0], vim.current.tabpage)
148 eq(vim.windows[0], vim.current.window)
137 assert vim.tabpages[0] == vim.current.tabpage
138 assert vim.windows[0] == vim.current.window
149139 vim.current.tabpage = vim.tabpages[1]
150 eq(vim.tabpages[1], vim.current.tabpage)
151 eq(vim.windows[1], vim.current.window)
140 assert vim.tabpages[1] == vim.current.tabpage
141 assert vim.windows[1] == vim.current.window
152142
153143
154 @with_setup(setup=cleanup)
155 def test_hash():
144 def test_hash(vim):
156145 d = {}
157146 d[vim.current.buffer] = "alpha"
158 eq(d[vim.current.buffer], "alpha")
147 assert d[vim.current.buffer] == 'alpha'
159148 vim.command('new')
160149 d[vim.current.buffer] = "beta"
161 eq(d[vim.current.buffer], "beta")
150 assert d[vim.current.buffer] == 'beta'
162151 vim.command('winc w')
163 eq(d[vim.current.buffer], "alpha")
152 assert d[vim.current.buffer] == 'alpha'
164153 vim.command('winc w')
165 eq(d[vim.current.buffer], "beta")
154 assert d[vim.current.buffer] == 'beta'
155
156
157 def test_cwd(vim, tmpdir):
158 pycmd = 'python'
159 if sys.version_info >= (3, 0):
160 pycmd = 'python3'
161
162 vim.command('{} import os'.format(pycmd))
163 cwd_before = vim.command_output('{} print(os.getcwd())'.format(pycmd))
164
165 vim.command('cd {}'.format(tmpdir.strpath))
166 cwd_vim = vim.command_output('pwd')
167 cwd_python = vim.command_output('{} print(os.getcwd())'.format(pycmd))
168 assert cwd_python == cwd_vim
169 assert cwd_python != cwd_before
170
171 lua_code = """
172 local a = vim.api
173 local y = ...
174 function pynvimtest_func(x)
175 return x+y
176 end
177
178 local function setbuf(buf,lines)
179 a.nvim_buf_set_lines(buf, 0, -1, true, lines)
180 end
181
182
183 local function getbuf(buf)
184 return a.nvim_buf_line_count(buf)
185 end
186
187 pynvimtest = {setbuf=setbuf,getbuf=getbuf}
188
189 return "eggspam"
190 """
191
192 def test_lua(vim):
193 assert vim.exec_lua(lua_code, 7) == "eggspam"
194 assert vim.lua.pynvimtest_func(3) == 10
195 testmod = vim.lua.pynvimtest
196 buf = vim.current.buffer
197 testmod.setbuf(buf, ["a", "b", "c", "d"], async_=True)
198 assert testmod.getbuf(buf) == 4
0 import os
1 from nose.tools import with_setup, eq_ as eq, ok_ as ok
2 from test_common import vim, cleanup
0 def test_buffer(vim):
1 assert vim.current.buffer == vim.windows[0].buffer
2 vim.command('new')
3 vim.current.window = vim.windows[1]
4 assert vim.current.buffer == vim.windows[1].buffer
5 assert vim.windows[0].buffer != vim.windows[1].buffer
36
47
5 @with_setup(setup=cleanup)
6 def test_buffer():
7 eq(vim.current.buffer, vim.windows[0].buffer)
8 vim.command('new')
9 vim.current.window = vim.windows[1]
10 eq(vim.current.buffer, vim.windows[1].buffer)
11 ok(vim.windows[0].buffer != vim.windows[1].buffer)
8 def test_cursor(vim):
9 assert vim.current.window.cursor == [1, 0]
10 vim.command('normal ityping\033o some text')
11 assert vim.current.buffer[:] == ['typing', ' some text']
12 assert vim.current.window.cursor == [2, 10]
13 vim.current.window.cursor = [2, 6]
14 vim.command('normal i dumb')
15 assert vim.current.buffer[:] == ['typing', ' some dumb text']
1216
1317
14 @with_setup(setup=cleanup)
15 def test_cursor():
16 eq(vim.current.window.cursor, [1, 0])
17 vim.command('normal ityping\033o some text')
18 eq(vim.current.buffer[:], ['typing', ' some text'])
19 eq(vim.current.window.cursor, [2, 10])
20 vim.current.window.cursor = [2, 6]
21 vim.command('normal i dumb')
22 eq(vim.current.buffer[:], ['typing', ' some dumb text'])
18 def test_height(vim):
19 vim.command('vsplit')
20 assert vim.windows[1].height == vim.windows[0].height
21 vim.current.window = vim.windows[1]
22 vim.command('split')
23 assert vim.windows[1].height == vim.windows[0].height // 2
24 vim.windows[1].height = 2
25 assert vim.windows[1].height == 2
2326
2427
25 @with_setup(setup=cleanup)
26 def test_height():
28 def test_width(vim):
29 vim.command('split')
30 assert vim.windows[1].width == vim.windows[0].width
31 vim.current.window = vim.windows[1]
2732 vim.command('vsplit')
28 eq(vim.windows[1].height, vim.windows[0].height)
29 vim.current.window = vim.windows[1]
30 vim.command('split')
31 eq(vim.windows[1].height, vim.windows[0].height // 2)
32 vim.windows[1].height = 2
33 eq(vim.windows[1].height, 2)
33 assert vim.windows[1].width == vim.windows[0].width // 2
34 vim.windows[1].width = 2
35 assert vim.windows[1].width == 2
3436
3537
36 @with_setup(setup=cleanup)
37 def test_width():
38 vim.command('split')
39 eq(vim.windows[1].width, vim.windows[0].width)
40 vim.current.window = vim.windows[1]
41 vim.command('vsplit')
42 eq(vim.windows[1].width, vim.windows[0].width // 2)
43 vim.windows[1].width = 2
44 eq(vim.windows[1].width, 2)
38 def test_vars(vim):
39 vim.current.window.vars['python'] = [1, 2, {'3': 1}]
40 assert vim.current.window.vars['python'] == [1, 2, {'3': 1}]
41 assert vim.eval('w:python') == [1, 2, {'3': 1}]
4542
4643
47 @with_setup(setup=cleanup)
48 def test_vars():
49 vim.current.window.vars['python'] = [1, 2, {'3': 1}]
50 eq(vim.current.window.vars['python'], [1, 2, {'3': 1}])
51 eq(vim.eval('w:python'), [1, 2, {'3': 1}])
44 def test_options(vim):
45 vim.current.window.options['colorcolumn'] = '4,3'
46 assert vim.current.window.options['colorcolumn'] == '4,3'
47 # global-local option
48 vim.current.window.options['statusline'] = 'window-status'
49 assert vim.current.window.options['statusline'] == 'window-status'
50 assert vim.options['statusline'] == ''
5251
5352
54 @with_setup(setup=cleanup)
55 def test_options():
56 vim.current.window.options['colorcolumn'] = '4,3'
57 eq(vim.current.window.options['colorcolumn'], '4,3')
58 # global-local option
59 vim.current.window.options['statusline'] = 'window-status'
60 eq(vim.current.window.options['statusline'], 'window-status')
61 eq(vim.options['statusline'], '')
62
63
64 @with_setup(setup=cleanup)
65 def test_position():
53 def test_position(vim):
6654 height = vim.windows[0].height
6755 width = vim.windows[0].width
6856 vim.command('split')
6957 vim.command('vsplit')
70 eq((vim.windows[0].row, vim.windows[0].col), (0, 0))
58 assert (vim.windows[0].row, vim.windows[0].col) == (0, 0)
7159 vsplit_pos = width / 2
7260 split_pos = height / 2
73 eq(vim.windows[1].row, 0)
74 ok(vsplit_pos - 1 <= vim.windows[1].col <= vsplit_pos + 1)
75 ok(split_pos - 1 <= vim.windows[2].row <= split_pos + 1)
76 eq(vim.windows[2].col, 0)
61 assert vim.windows[1].row == 0
62 assert vsplit_pos - 1 <= vim.windows[1].col <= vsplit_pos + 1
63 assert split_pos - 1 <= vim.windows[2].row <= split_pos + 1
64 assert vim.windows[2].col == 0
7765
7866
79 @with_setup(setup=cleanup)
80 def test_tabpage():
67 def test_tabpage(vim):
8168 vim.command('tabnew')
8269 vim.command('vsplit')
83 eq(vim.windows[0].tabpage, vim.tabpages[0])
84 eq(vim.windows[1].tabpage, vim.tabpages[1])
85 eq(vim.windows[2].tabpage, vim.tabpages[1])
70 assert vim.windows[0].tabpage == vim.tabpages[0]
71 assert vim.windows[1].tabpage == vim.tabpages[1]
72 assert vim.windows[2].tabpage == vim.tabpages[1]
8673
8774
88 @with_setup(setup=cleanup)
89 def test_valid():
75 def test_valid(vim):
9076 vim.command('split')
9177 window = vim.windows[1]
9278 vim.current.window = window
93 ok(window.valid)
79 assert window.valid
9480 vim.command('q')
95 ok(not window.valid)
81 assert not window.valid
9682
9783
98 @with_setup(setup=cleanup)
99 def test_number():
84 def test_number(vim):
10085 curnum = vim.current.window.number
10186 vim.command('bot split')
102 eq(vim.current.window.number, curnum + 1)
87 assert vim.current.window.number == curnum + 1
10388 vim.command('bot split')
104 eq(vim.current.window.number, curnum + 2)
89 assert vim.current.window.number == curnum + 2
10590
10691
107 @with_setup(setup=cleanup)
108 def test_handle():
92 def test_handle(vim):
10993 hnd1 = vim.current.window.handle
11094 vim.command('bot split')
11195 hnd2 = vim.current.window.handle
112 ok(hnd2 != hnd1)
96 assert hnd2 != hnd1
11397 vim.command('bot split')
11498 hnd3 = vim.current.window.handle
115 ok(hnd3 != hnd1)
116 ok(hnd3 != hnd2)
99 assert hnd1 != hnd2 != hnd3
117100 vim.command('wincmd w')
118 eq(vim.current.window.handle,hnd1)
101 assert vim.current.window.handle == hnd1