Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lua integration (and update_highlights as an useful example) #325

Merged
merged 2 commits into from
Apr 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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