From 2d8889d5b805f9bb1446ce0bdeffe960d6b85f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Linse?= Date: Sat, 25 Nov 2017 09:46:10 +0100 Subject: [PATCH 1/2] implement nvim.exec_lua and nvim.lua.function --- neovim/api/nvim.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ test/test_vim.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/neovim/api/nvim.py b/neovim/api/nvim.py index caddd182..e3f5521c 100644 --- a/neovim/api/nvim.py +++ b/neovim/api/nvim.py @@ -92,6 +92,7 @@ def __init__(self, session, channel_id, metadata, types, self.current = Current(self) self.session = CompatibilitySession(self) self.funcs = Funcs(self) + self.lua = LuaFuncs(self) self.error = NvimError self._decode = decode self._err_cb = err_cb @@ -253,6 +254,27 @@ def call(self, name, *args, **kwargs): """Call a vimscript function.""" return self.request('nvim_call_function', name, args, **kwargs) + def exec_lua(self, code, *args, **kwargs): + """Execute lua code. + + Additional parameters are available as `...` inside the lua chunk. + Only statements are executed. To evaluate an expression, prefix it + with `return`: `return my_function(...)` + + There is a shorthand syntax to call lua functions with arguments: + + nvim.lua.func(1,2) + nvim.lua.mymod.myfunction(data, async_=True) + + is equivalent to + + nvim.exec_lua("return func(...)", 1, 2) + nvim.exec_lua("mymod.myfunction(...)", data, async_=True) + + Note that with `async_=True` there is no return value. + """ + return self.request('nvim_execute_lua', code, args, **kwargs) + def strwidth(self, string): """Return the number of display cells `string` occupies. @@ -467,5 +489,29 @@ def __getattr__(self, name): return partial(self._nvim.call, name) +class LuaFuncs(object): + + """Wrapper to allow lua functions to be called like python methods.""" + + def __init__(self, nvim, name=""): + self._nvim = nvim + self.name = name + + def __getattr__(self, name): + """Return wrapper to named api method.""" + prefix = self.name + "." if self.name else "" + return LuaFuncs(self._nvim, prefix + name) + + def __call__(self, *args, **kwargs): + # first new function after keyword rename, be a bit noisy + if 'async' in kwargs: + raise ValueError('"async" argument is not allowed. ' + 'Use "async_" instead.') + async_ = kwargs.get('async_', False) + pattern = "return {}(...)" if not async_ else "{}(...)" + code = pattern.format(self.name) + return self._nvim.exec_lua(code, *args, **kwargs) + + class NvimError(Exception): pass diff --git a/test/test_vim.py b/test/test_vim.py index dadef5af..1b6af331 100644 --- a/test/test_vim.py +++ b/test/test_vim.py @@ -168,3 +168,32 @@ def test_cwd(vim, tmpdir): cwd_python = vim.command_output('{} print(os.getcwd())'.format(pycmd)) assert cwd_python == cwd_vim assert cwd_python != cwd_before + +lua_code = """ +local a = vim.api +local y = ... +function pynvimtest_func(x) + return x+y +end + +local function setbuf(buf,lines) + a.nvim_buf_set_lines(buf, 0, -1, true, lines) +end + + +local function getbuf(buf) + return a.nvim_buf_line_count(buf) +end + +pynvimtest = {setbuf=setbuf,getbuf=getbuf} + +return "eggspam" +""" + +def test_lua(vim): + assert vim.exec_lua(lua_code, 7) == "eggspam" + assert vim.lua.pynvimtest_func(3) == 10 + testmod = vim.lua.pynvimtest + buf = vim.current.buffer + testmod.setbuf(buf, ["a", "b", "c", "d"], async_=True) + assert testmod.getbuf(buf) == 4 From 2c0880ea9084df2c510c656f8ca4fdc874f01d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Linse?= Date: Tue, 17 Apr 2018 10:49:58 +0200 Subject: [PATCH 2/2] implement update_highlights using lua --- neovim/api/buffer.py | 23 +++++++++++++++++++++++ neovim/api/nvim.py | 29 +++++++++++++++++++++++++++++ neovim/compat.py | 2 +- test/test_buffer.py | 7 +++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/neovim/api/buffer.py b/neovim/api/buffer.py index 0bc9735e..786e322c 100644 --- a/neovim/api/buffer.py +++ b/neovim/api/buffer.py @@ -112,6 +112,29 @@ def clear_highlight(self, src_id, line_start=0, line_end=-1, async_=None, self.request('nvim_buf_clear_highlight', src_id, line_start, line_end, async_=async_) + def update_highlights(self, src_id, hls, clear_start=0, clear_end=-1, + clear=False, async_=True): + """Add or update highlights in batch to avoid unnecessary redraws. + + A `src_id` must have been allocated prior to use of this function. Use + for instance `nvim.new_highlight_source()` to get a src_id for your + plugin. + + `hls` should be a list of highlight items. Each item should be a list + or tuple on the form `("GroupName", linenr, col_start, col_end)` or + `("GroupName", linenr)` to highlight an entire line. + + By default existing highlights are preserved. Specify a line range with + clear_start and clear_end to replace highlights in this range. As a + shorthand, use clear=True to clear the entire buffer before adding the + new highlights. + """ + if clear and clear_start is None: + clear_start = 0 + lua = self._session._get_lua_private() + lua.update_highlights(self, src_id, hls, clear_start, clear_end, + async_=async_) + @property def name(self): """Get the buffer name.""" diff --git a/neovim/api/nvim.py b/neovim/api/nvim.py index e3f5521c..8afc235b 100644 --- a/neovim/api/nvim.py +++ b/neovim/api/nvim.py @@ -19,6 +19,29 @@ os_chdir = os.chdir +lua_module = """ +local a = vim.api +local function update_highlights(buf, src_id, hls, clear_first, clear_end) + if clear_first ~= nil then + a.nvim_buf_clear_highlight(buf, src_id, clear_first, clear_end) + end + for _,hl in pairs(hls) do + local group, line, col_start, col_end = unpack(hl) + if col_start == nil then + col_start = 0 + end + if col_end == nil then + col_end = -1 + end + a.nvim_buf_add_highlight(buf, src_id, group, line, col_start, col_end) + end +end + +local chid = ... +local mod = {update_highlights=update_highlights} +_G["_pynvim_"..chid] = mod +""" + class Nvim(object): @@ -116,6 +139,12 @@ def _to_nvim(self, obj): return ExtType(*obj.code_data) return obj + def _get_lua_private(self): + if not getattr(self._session, "_has_lua", False): + self.exec_lua(lua_module, self.channel_id) + self._session._has_lua = True + return getattr(self.lua, "_pynvim_{}".format(self.channel_id)) + def request(self, name, *args, **kwargs): r"""Send an API request or notification to nvim. diff --git a/neovim/compat.py b/neovim/compat.py index b480a3dc..778fc1c1 100644 --- a/neovim/compat.py +++ b/neovim/compat.py @@ -40,7 +40,7 @@ def find_module(fullname, path): def check_async(async_, kwargs, default): - """Return a value of 'async' in kwargs or default when async_ is None + """Return a value of 'async' in kwargs or default when async_ is None. This helper function exists for backward compatibility (See #274). It shows a warning message when 'async' in kwargs is used to note users. diff --git a/test/test_buffer.py b/test/test_buffer.py index b5ecfaa2..904ee6d1 100644 --- a/test/test_buffer.py +++ b/test/test_buffer.py @@ -161,3 +161,10 @@ def test_set_items_for_range(vim): r = vim.current.buffer.range(1, 3) r[1:3] = ['foo']*3 assert vim.current.buffer[:] == ['a', 'foo', 'foo', 'foo', 'd', 'e'] + +# NB: we can't easily test the effect of this. But at least run the lua +# function sync, so we know it runs without runtime error with simple args. +def test_update_highlights(vim): + vim.current.buffer[:] = ['a', 'b', 'c'] + src_id = vim.new_highlight_source() + vim.current.buffer.update_highlights(src_id, [["Comment", 0, 0, -1], ("String", 1, 0, 1)], clear=True, async_=False)