diff --git a/autoload/SpaceVim/layers/core.vim b/autoload/SpaceVim/layers/core.vim index 0e72c24de..b73b70cb1 100644 --- a/autoload/SpaceVim/layers/core.vim +++ b/autoload/SpaceVim/layers/core.vim @@ -98,6 +98,7 @@ function! SpaceVim#layers#core#plugins() abort endif call add(plugins, [g:_spacevim_root_dir . 'bundle/gruvbox', {'loadconf' : 1, 'merged' : 0}]) call add(plugins, [g:_spacevim_root_dir . 'bundle/vim-clipboard', {'merged' : 0}]) + call add(plugins, [g:_spacevim_root_dir . 'bundle/nvim-if-lua-compat', {'merged' : 0}]) call add(plugins, [g:_spacevim_root_dir . 'bundle/open-browser.vim', { \ 'merged' : 0, 'loadconf' : 1, \}]) diff --git a/bundle/nvim-if-lua-compat/README.md b/bundle/nvim-if-lua-compat/README.md new file mode 100644 index 000000000..9d8da20af --- /dev/null +++ b/bundle/nvim-if-lua-compat/README.md @@ -0,0 +1,67 @@ +# nvim-if-lua-compat + +An `if_lua` compatibility layer for Neovim (WIP, needs testing) + +## Goals + +Maintain some amount of compatibility with the existing Vim interface for Lua (see also: [neovim/neovim#12537](https://github.com/neovim/neovim/issues/12537)). + +## Progress + +Items annotated with an asterisk `*` are only partially supported. + +- [x] * `vim.list()` (actually a Lua table, `luaeval()` just makes a copy and transforms it into a Vim list...) + - [x] `#l` + - [x] `l[k]` + - [x] `l()` + - [x] `table.insert(l, newitem)` + - [x] `table.insert(l, position, newitem)` + - [x] `table.remove(l, position)` + - [x] `l:add(item)` + - [x] `l:insert(item[, pos])` +- [x] * `vim.dict()` (actually a Lua table, `luaeval()` just makes a copy and transforms it into a Vim dict...) + - [x] * `#d` (Only works with Lua 5.2+ or LuaJIT built with 5.2 extensions) + - [x] `d.key` or `d['key']` + - [x] `d()` +- [x] * `vim.blob()` (actually a Lua table) + - [x] * `#b` (Only works with Lua 5.2+ or LuaJIT built with 5.2 extensions) + - [x] `b[k]` + - [x] `b:add(bytes)` +- [x] `vim.funcref()` (exists in Neovim core, but the implementation is different here) + - [x] * `#f` (Only works with Lua 5.2+ or LuaJIT built with 5.2 extensions) + - [x] `f(...)` +- [x] `vim.buffer()` + - [x] `b()` + - [x] * `#b` (Only works with Lua 5.2+ or LuaJIT built with 5.2 extensions) + - [x] `b[k]` + - [x] `b.name` + - [x] `b.fname` + - [x] `b.number` + - [x] `b:insert(newline[, pos])` + - [x] `b:next()` + - [x] `b:previous()` + - [x] `b:isvalid()` +- [x] `vim.window()` + - [x] `w()` + - [x] `w.buffer` + - [x] `w.line` (get and set) + - [x] `w.col` (get and set) + - [x] `w.width` (get and set) + - [x] `w.height` (get and set) + - [x] `w:next()` + - [x] `w:previous()` + - [x] `w:isvalid()` +- [x] `vim.type()` + - [x] `list` + - [x] `dict` + - [x] `blob` + - [x] `funcref` + - [x] `buffer` + - [x] `window` +- [x] `vim.command()` (alias to `vim.api.nvim_command()`) +- [x] * `vim.eval()` (alias to `vim.api.nvim_eval()`, doesn't actually return a `vim.list/dict/blob`) +- [x] `vim.line()` (alias to `vim.api.nvim_get_current_line()`) +- [x] `vim.beep()` +- [x] `vim.open()` +- [x] `vim.call()` (in Neovim core) +- [x] `vim.fn()` (in Neovim core) diff --git a/bundle/nvim-if-lua-compat/lua/if_lua_compat/blob.lua b/bundle/nvim-if-lua-compat/lua/if_lua_compat/blob.lua new file mode 100644 index 000000000..54ade9bcb --- /dev/null +++ b/bundle/nvim-if-lua-compat/lua/if_lua_compat/blob.lua @@ -0,0 +1,61 @@ +--- Wrapper class to interact with blobs +--- @class Blob + +local Blob + +--- @param str string +--- @param index number +--- @return number, number +local function str_iter(str, index) + if index == #str then return end + index = index + 1 + return index, str:sub(index, index):byte() +end + +--- @param bytes_str string|number +--- @return function, string, number +local function str_to_bytes(bytes_str) + if type(bytes_str) == 'number' then bytes_str = tostring(bytes_str) end + if type(bytes_str) ~= 'string' then error('string expected, got ' .. type(bytes_str)) end + return str_iter, bytes_str, 0 +end + +local blob_methods = { + --- @param self Blob + --- @param bytes_str string|number + add = function(self, bytes_str) + local is_empty = not self[0] + for _, byte in str_to_bytes(bytes_str) do + if is_empty then + table.insert(self, 0, byte) + is_empty = false + else + table.insert(self, byte) + end + end + end, +} + +local blob_mt = { + _vim_type = 'blob', + __index = blob_methods, + __newindex = function(blob, key, value) + if type(key) == 'number' then rawset(blob, key, value) end + return + end, + __len = function(blob) return rawlen(blob) + 1 end, +} + +--- @param bytes_str string|number +--- @return Blob +function Blob(bytes_str) + local blob = {} + if bytes_str ~= nil then + for idx, byte in str_to_bytes(bytes_str) do + blob[idx - 1] = byte + end + end + return setmetatable(blob, blob_mt) +end + +return Blob diff --git a/bundle/nvim-if-lua-compat/lua/if_lua_compat/buffer.lua b/bundle/nvim-if-lua-compat/lua/if_lua_compat/buffer.lua new file mode 100644 index 000000000..6ef3d464f --- /dev/null +++ b/bundle/nvim-if-lua-compat/lua/if_lua_compat/buffer.lua @@ -0,0 +1,115 @@ +local api = vim.api +local fn = vim.fn + +--- Wrapper to interact with buffers +--- @class Buffer + +local Buffer + +local buf_methods = { + --- @param self Buffer + --- @param newline string + --- @param pos number + insert = function(self, newline, pos) + api.nvim_buf_set_lines(self._bufnr, pos or -1, pos or -1, false, {newline}) + end, + + --- @param self Buffer + --- @return boolean + isvalid = function(self) + return api.nvim_buf_is_valid(self._bufnr) + end, + + --- @param self Buffer + --- @return Buffer|nil + next = function(self) + local bufnr = self._bufnr + local buffers = api.nvim_list_bufs() + local next_buf + for k, v in ipairs(buffers) do + if v == bufnr then + next_buf = k + 1 + break + end + end + if next_buf and buffers[next_buf] then + return Buffer(buffers[next_buf]) + end + return nil + end, + + --- @param self Buffer + --- @return Buffer|nil + previous = function(self) + local bufnr = self._bufnr + local buffers = api.nvim_list_bufs() + local prev_buf + for k, v in ipairs(buffers) do + if v == bufnr then + prev_buf = k - 1 + break + end + end + if prev_buf and buffers[prev_buf] then + return Buffer(buffers[prev_buf]) + end + return nil + end, +} + +local buf_getters = { + --- @param bufnr number + --- @return number + number = function(bufnr) + return bufnr + end, + + --- @param bufnr number + --- @return string + fname = api.nvim_buf_get_name, + + --- @param bufnr number + --- @return string + name = fn.bufname, +} + +local buf_mt = { + _vim_type = 'buffer', + __index = function(tbl, key) + if type(key) == 'number' then + return api.nvim_buf_get_lines(tbl._bufnr, key - 1, key, false)[1] + end + if buf_methods[key] then return buf_methods[key] end + if buf_getters[key] then return buf_getters[key](tbl._bufnr) end + end, + __newindex = function() return end, + __call = function(tbl) + api.nvim_set_current_buf(tbl._bufnr) + end, + -- Only works with Lua 5.2+ or LuaJIT built with 5.2 extensions + __len = function(tbl) + return api.nvim_buf_line_count(tbl._bufnr) + end, +} + +--- @param arg ?any +--- @return Buffer|nil +function Buffer(arg) + local bufnr + + if not arg then + bufnr = api.nvim_get_current_buf() + elseif type(arg) == 'string' then + bufnr = fn.bufnr(arg) + if bufnr == -1 then return nil end + elseif type(arg) == 'number' then + if not api.nvim_buf_is_valid(arg) then return nil end + bufnr = arg + else + bufnr = api.nvim_list_bufs()[1] + end + + return setmetatable({_bufnr = bufnr}, buf_mt) +end + +return Buffer diff --git a/bundle/nvim-if-lua-compat/lua/if_lua_compat/dict.lua b/bundle/nvim-if-lua-compat/lua/if_lua_compat/dict.lua new file mode 100644 index 000000000..0159cf8dd --- /dev/null +++ b/bundle/nvim-if-lua-compat/lua/if_lua_compat/dict.lua @@ -0,0 +1,46 @@ +local validate = vim.validate + +--- Wrapper class to interact with vim dictionaries +--- @class Dict + +local valid_key_types = { + string = true, + number = true, +} + +local dict_mt = { + __index = function(dict, key) + if not valid_key_types[type(key)] then + return error(("bad argument #2 to '__index' (string expected, got %s)"):format(type(key))) + end + return rawget(dict, tostring(key)) + end, + __newindex = function(dict, key, value) + if not valid_key_types[type(key)] then + return error(("bad argument #2 to '__newindex' (string expected, got %s)"):format(type(key))) + end + rawset(dict, tostring(key), value) + end, + __call = pairs, + __len = vim.tbl_count, + _vim_type = 'dict', +} + +--- @param tbl table +--- @return Dict +function Dict(tbl) + validate { + tbl = {tbl, 'table', true} + } + + local dict = vim.empty_dict() + if tbl then + for k, v in pairs(tbl) do + if not valid_key_types[type(k)] then return nil end + dict[tostring(k)] = v + end + end + return setmetatable(dict, dict_mt) +end + +return Dict diff --git a/bundle/nvim-if-lua-compat/lua/if_lua_compat/init.lua b/bundle/nvim-if-lua-compat/lua/if_lua_compat/init.lua new file mode 100644 index 000000000..58d6726ac --- /dev/null +++ b/bundle/nvim-if-lua-compat/lua/if_lua_compat/init.lua @@ -0,0 +1,24 @@ +local fn = vim.fn +if fn.has('nvim') == 0 then return end + +local api = vim.api + +local Buffer = require('if_lua_compat.buffer') +local Window = require('if_lua_compat.window') +local List = require('if_lua_compat.list') +local Dict = require('if_lua_compat.dict') +local Blob = require('if_lua_compat.blob') +local misc = require('if_lua_compat.misc') + +vim.command = api.nvim_command +vim.eval = api.nvim_eval +vim.line = api.nvim_get_current_line +vim.buffer = Buffer +vim.window = Window +vim.list = List +vim.dict = Dict +vim.blob = Blob +vim.open = misc.open +vim.type = misc.type +vim.beep = misc.beep +vim.funcref = misc.funcref diff --git a/bundle/nvim-if-lua-compat/lua/if_lua_compat/list.lua b/bundle/nvim-if-lua-compat/lua/if_lua_compat/list.lua new file mode 100644 index 000000000..0fd472752 --- /dev/null +++ b/bundle/nvim-if-lua-compat/lua/if_lua_compat/list.lua @@ -0,0 +1,59 @@ +local validate = vim.validate + +--- Wrapper class to interact with vim lists +--- @class List + +local list_methods = { + --- @param self List + --- @param item any + add = table.insert, + + --- @param self List + --- @param item any + --- @param position number + insert = function(self, item, position) + if position then + if position > #self then position = #self end + if position < 0 then position = 0 end + table.insert(self, position + 1, item) + else + table.insert(self, 1, item) + end + end, +} + +local list_mt = { + __index = list_methods, + __newindex = function(list, key, value) + if type(key) == 'number' then rawset(list, key, value) end + end, + __call = function(list) + local index = 0 + local length = #list + local function list_iter() + if index == length then return end + index = index + 1 + return list[index] + end + return list_iter, list, nil + end, + _vim_type = 'list', +} + +--- @param tbl table +--- @return List +function List(tbl) + validate { + tbl = {tbl, 'table', true} + } + + local list = {} + if tbl then + for _, v in ipairs(tbl) do + table.insert(list, v) + end + end + return setmetatable(list, list_mt) +end + +return List diff --git a/bundle/nvim-if-lua-compat/lua/if_lua_compat/misc.lua b/bundle/nvim-if-lua-compat/lua/if_lua_compat/misc.lua new file mode 100644 index 000000000..2184d6afa --- /dev/null +++ b/bundle/nvim-if-lua-compat/lua/if_lua_compat/misc.lua @@ -0,0 +1,58 @@ +local api = vim.api +local fn = vim.fn +local Buffer = require('if_lua_compat.buffer') + +local valid_fname_types = { + number = true, + string = true, +} + +--- @param fname ?any +--- @return Buffer +local function vim_open(fname) + fname = valid_fname_types[type(fname)] and tostring(fname) or nil + + local bufnr = fn.bufadd(fname or '') + api.nvim_buf_set_option(bufnr, 'buflisted', true) + + return Buffer(bufnr) +end + +--- @param object any +--- @return string +local function vim_type(object) + local mt = getmetatable(object) or {} + return mt._vim_type or type(object) +end + +local function vim_beep() + local belloff = api.nvim_get_option('belloff') + if belloff:match('all') or belloff:match('lang') then return end + io.stdout:write('\a') +end + +--- Wrapper class to interact with vim funcrefs +--- @class Funcref + +local funcref_mt = { + _vim_type = 'funcref', + __call = function(tbl, ...) return vim.call(tbl._funcname, ...) end, + -- Only works with Lua 5.2+ or LuaJIT built with 5.2 extensions + __len = function(tbl) return tbl._funcname end, +} + +--- @param funcname string +--- @return Funcref +local function vim_funcref(funcname) + if type(funcname) ~= 'string' then + return error(("Bad argument #1 to 'funcref' (string expected, got %s)"):format(type(funcname))) + end + return setmetatable({_funcname = funcname}, funcref_mt) +end + +return { + open = vim_open, + type = vim_type, + beep = vim_beep, + funcref = vim_funcref, +} diff --git a/bundle/nvim-if-lua-compat/lua/if_lua_compat/test123.lua b/bundle/nvim-if-lua-compat/lua/if_lua_compat/test123.lua new file mode 100644 index 000000000..09c04b46f --- /dev/null +++ b/bundle/nvim-if-lua-compat/lua/if_lua_compat/test123.lua @@ -0,0 +1,110 @@ +require('if_lua_compat') +local b = vim.buffer() +-- b() +-- print(#b) +-- print(b[154]) +-- print(b.name) +-- print(b.fname) +-- print(b.number) +-- b:insert('hello', 127) +-- print(b:isvalid()) +-- print(b:next()) +-- print(b:previous()) +-- print(vim.type(b)) +-- print(b:next().name) +-- print(b:previous().name) + +local w = vim.window() +-- w() +-- print(w.buffer.name) +-- print(w.line) +-- w.line = 20 +-- print(w.col) +-- w.col = 3 +-- print(w.width) +-- w.width = 60 +-- print(w.height) +-- w.height = 20 +-- w:next() +-- w:previous() +-- print(w:isvalid()) +-- print(vim.type(w)) +-- print(w:next().height) +-- print(w:previous().line) + + +-- print(vim.type(3)) +-- print(vim.type({})) +-- print(vim.type('str')) + + +local l = vim.list({1, test = 2, 3}) +-- print(l[2]) +-- print(l.test) +-- print(vim.type(l)) +-- print(#l) +-- for v in l() do +-- print(v) +-- end +-- table.insert(l, 4) +-- table.insert(l, 2, 2) +-- table.insert(l, 2, 2) +-- print(l[2]) +-- print(l[4]) +-- print(vim.inspect(l)) +-- table.remove(l, 4) +-- print(vim.inspect(l)) +-- l.test = 'hello' +-- print(l.test) +-- l:add(4) +-- l:insert('test', 1) +-- l:insert('test2') +-- for k, v in l() do +-- print(k, v) +-- end + +local d = vim.dict({1, test = 2, 3}) +-- print(d.test) +-- print(d['test']) +-- print(d[1]) +-- print(d[2]) +-- for k, v in d() do +-- print(k, v) +-- end +-- print(#d) +-- print(vim.type(d)) +-- d[4] = 4 +-- print(d['4'], d[4]) +-- d[true] = 1 + +vim.beep() + +-- vim.open('init.lua') +-- vim.open('not_a_file') +-- vim.open(1) +-- vim.open() +-- vim.open(true) +-- vim.open({}) + +local f = vim.funcref('printf') +-- local invalid_f = vim.funcref('ffffff') +-- local invalid_type = vim.funcref({}) + +-- f() + +local bl = vim.blob('test') +local bl2 = vim.blob('😀') +local bl3 = vim.blob() +-- print(vim.inspect(bl)) +-- print(vim.inspect(bl2)) +-- local tbl = {} +-- bl2:add(1.1) +-- +-- print(vim.inspect(bl2)) +-- +-- for i = 0, #bl2 do +-- table.insert(tbl, string.char(bl2[i])) +-- end +-- +-- print(table.concat(tbl)) +-- bl3:add('test') diff --git a/bundle/nvim-if-lua-compat/lua/if_lua_compat/window.lua b/bundle/nvim-if-lua-compat/lua/if_lua_compat/window.lua new file mode 100644 index 000000000..c0cc8b679 --- /dev/null +++ b/bundle/nvim-if-lua-compat/lua/if_lua_compat/window.lua @@ -0,0 +1,137 @@ +local api = vim.api +local Buffer = require('if_lua_compat.buffer') + +--- Wrapper to interact with windows +--- @class Window + +local Window + +local win_methods = { + --- @param self Window + --- @return boolean + isvalid = function(self) + return api.nvim_win_is_valid(self._winnr) + end, + + --- @param self Window + --- @return Window|nil + next = function(self) + local winnr = self._winnr + local windows = api.nvim_tabpage_list_wins(api.nvim_win_get_tabpage(winnr)) + local next_win + for k, v in ipairs(windows) do + if v == winnr then + next_win = k + 1 + break + end + end + if next_win and windows[next_win] then + return Window(next_win) + end + return nil + end, + + --- @param self Window + --- @return Window|nil + previous = function(self) + local winnr = self._winnr + local windows = api.nvim_tabpage_list_wins(api.nvim_win_get_tabpage(winnr)) + local prev_win + for k, v in ipairs(windows) do + if v == winnr then + prev_win = k - 1 + break + end + end + if prev_win and windows[prev_win] then + return Window(prev_win) + end + return nil + end, +} + +local win_getters = { + --- @param winnr number + --- @return Buffer + buffer = function(winnr) + return Buffer(api.nvim_win_get_buf(winnr)) + end, + + --- @param winnr number + --- @return number + line = function(winnr) + return api.nvim_win_get_cursor(winnr)[1] + end, + + --- @param winnr number + --- @return number + col = function(winnr) + return api.nvim_win_get_cursor(winnr)[2] + 1 + end, + + --- @param winnr number + --- @return number + width = api.nvim_win_get_width, + + --- @param winnr number + --- @return number + height = api.nvim_win_get_height, +} + +local win_setters = { + --- @param winnr number + --- @param line number + line = function(winnr, line) + api.nvim_win_set_cursor(winnr, {line, 0}) + end, + + --- @param winnr number + --- @param col number + col = function(winnr, col) + api.nvim_win_set_cursor(winnr, {api.nvim_win_get_cursor(winnr)[1], col - 1}) + end, + + --- @param winnr number + --- @param width number + width = api.nvim_win_set_width, + + --- @param winnr number + --- @param height number + height = api.nvim_win_set_height, +} + +local win_mt = { + _vim_type = 'window', + __index = function(tbl, key) + if win_methods[key] then return win_methods[key] end + if win_getters[key] then return win_getters[key](tbl._winnr) end + end, + __newindex = function(tbl, key, value) + if win_setters[key] then return win_setters[key](tbl._winnr, value) + else error(('Invalid window property: %s'):format(key)) + end + end, + __call = function(tbl) + api.nvim_set_current_win(tbl._winnr) + end, +} + +--- @param arg ?any +--- @return Window|nil +function Window(arg) + local windows = api.nvim_tabpage_list_wins(0) + local winnr + + if not arg then + winnr = api.nvim_get_current_win() + elseif type(arg) == 'number' then + if not windows[arg] then return nil end + winnr = windows[arg] + else + winnr = windows[1] + end + + return setmetatable({_winnr = winnr}, win_mt) +end + +return Window