Skip to content

Commit

Permalink
Merge pull request #325 from bfredl/lua
Browse files Browse the repository at this point in the history
Add lua integration (and update_highlights as an useful example)
  • Loading branch information
bfredl committed Apr 26, 2018
2 parents 13f7635 + 2c0880e commit d9aed96
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 1 deletion.
23 changes: 23 additions & 0 deletions neovim/api/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
75 changes: 75 additions & 0 deletions neovim/api/nvim.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down Expand Up @@ -92,6 +115,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
Expand All @@ -115,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.
Expand Down Expand Up @@ -253,6 +283,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.
Expand Down Expand Up @@ -467,5 +518,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
2 changes: 1 addition & 1 deletion neovim/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions test/test_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
29 changes: 29 additions & 0 deletions test/test_vim.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit d9aed96

Please sign in to comment.