1
argonaut.nvim/lua/argonaut/types.lua
2024-04-24 20:48:40 -07:00

396 lines
8.2 KiB
Lua

--
-- Cursor
--
local Cursor = {}
function Cursor.new(row, col)
local cursor = {row = row, col = col}
return setmetatable(cursor, {__index = Cursor})
end
function Cursor:is_valid()
return self.row > 0 and self.col > 0
end
function Cursor:is_string()
assert(self:is_valid())
local syn_id = vim.fn.synID(self.row, self.col, false)
local syn_attr = vim.fn.synIDattr(syn_id, 'name')
return syn_attr:find('String$')
end
function Cursor.get_current()
local _, row, col, _ = unpack(vim.fn.getpos('.'))
return Cursor.new(row, col)
end
function Cursor:set_current()
assert(self:is_valid())
vim.fn.secursorcharpos({self.row, self.col})
end
--
-- BracePair
--
local BracePair = {}
function BracePair.new(open, close)
local pair = {open = open, close = close}
return setmetatable(pair, {__index = BracePair})
end
function BracePair.from_brace(brace)
local all_pairs = {
{'(', ')'},
{'[', ']'},
{'{', '}'},
{'<', '>'},
}
for _, pair in ipairs(all_pairs) do
if pair[1] == brace or pair[2] == brace then
return BracePair.new(pair[1], pair[2])
end
end
end
function BracePair:get_escaped()
local escape_func = function(brace_raw)
if brace_raw == '[' or brace_raw == ']' then
return '\\' .. brace_raw
else
return brace_raw
end
end
return BracePair.new(
escape_func(self.open),
escape_func(self.close)
)
end
function BracePair:find_closest(backward)
-- See flags: https://neovim.io/doc/user/builtin.html#search()
local flags = 'Wcn'
if backward then
flags = 'Wbn'
end
local ignore_func = function()
local cursor = Cursor.get_current()
return cursor:is_string()
end
local escaped_pair = self:get_escaped()
local position = vim.fn.searchpairpos(
escaped_pair.open,
'',
escaped_pair.close,
flags,
ignore_func
)
local brace_cursor = Cursor.new(unpack(position))
if brace_cursor:is_valid() then
return brace_cursor
end
end
--
-- BraceStack
--
local BraceStack = {}
function BraceStack.new()
local stack = {stack = {}}
return setmetatable(stack, {__index = BraceStack})
end
function BraceStack:update(brace)
local pair = BracePair.from_brace(brace)
if pair then
if brace == pair.close and self:top() == pair.open then
self:pop()
else
self:push(brace)
end
end
end
function BraceStack:push(brace)
table.insert(self.stack, brace)
end
function BraceStack:pop()
assert(not self:empty())
return table.remove(self.stack, #self.stack)
end
function BraceStack:empty()
return #self.stack == 0
end
function BraceStack:top()
return #self.stack[#self.stack]
end
--
-- BraceRange
--
local BraceRange = {}
function BraceRange.new(start, stop, pair)
local range = {
start = start,
stop = stop,
pair = pair,
}
return setmetatable(range, {__index = BraceRange})
end
function BraceRange.find_closest(pair)
local stop = pair:find_closest(false)
if stop then
local start = pair:find_closest(true)
if start then
return BraceRange.new(start, stop, pair, {})
end
end
end
function BraceRange.find_closest_any()
local range_compare = function(range_1, range_2)
local cursor = Cursor:get_current()
local row_diff1 = cursor.row - range_1.start.row
local row_diff2 = cursor.row - range_2.start.row
if row_diff1 < row_diff2 then
return -1
elseif row_diff1 > row_diff2 then
return 1
end
local col_diff1 = cursor.col - range_1.start.col
local col_diff2 = cursor.col - range_2.start.col
if col_diff1 < col_diff2 then
return -1
elseif col_diff1 > col_diff2 then
return 1
end
return 0
end
local ranges = {}
for _, brace in ipairs({'(', '[', '{', '<'}) do
local pair = BracePair.from_brace(brace)
local range = BraceRange.find_closest(pair)
if range then
table.insert(ranges, range)
end
end
if #ranges > 0 then
vim.fn.sort(ranges, range_compare)
return ranges[1]
end
end
function BraceRange:is_wrapped()
return self.start.row < self.stop.row
end
--
-- Param
--
local Param = {}
function Param.new(text, pair)
local param = {
text = text,
pair = pair,
}
return setmetatable(param, {__index = Param})
end
function Param:append(char)
self.text = self.text .. char
end
--
-- ParamList
--
local ParamList = {}
function ParamList.new()
local params = {
current = nil,
parsed = {},
}
return setmetatable(params, {__index = ParamList})
end
function ParamList:flush()
if self.current then
table.insert(self.parsed, self.current)
self.current = nil
end
end
function ParamList:update(char, brace_stack, range, cursor)
if not cursor:is_string() then
brace_stack:update(char)
if brace_stack:empty() and char == ',' then
self:flush()
return
end
end
if self.current then
self.current:append(char)
else
self.current = Param.new(char, range)
end
end
function ParamList:parse(range)
local brace_stack = BraceStack:new()
for row = range.start.row, range.stop.row do
local line = vim.fn.getline(row)
local start_col = 1
if row == range.start.row then
start_col = range.start.col + 1
end
local stop_col = #line
if row == range.stop.row then
stop_col = range.stop.col - 1
end
for col = start_col, stop_col do
self:update(line:sub(col, col), brace_stack, range, Cursor.new(row, col))
end
end
self:flush()
end
--
-- WrapContext
--
local WrapContext = {}
function WrapContext.new(opts)
local wrap_context = {
opts = opts,
indent = '',
prefix = '',
suffix = '',
range = nil,
params = nil,
}
return setmetatable(wrap_context, {__index = WrapContext})
end
function WrapContext:parse()
self.range = BraceRange.find_closest_any()
if not self.range then
return false
end
local first_line = vim.fn.getline(self.range.start.row)
self.indent = first_line:match('^(%s*)')
self.prefix = first_line:sub(#self.indent + 1, self.range.start.col)
local last_line = vim.fn.getline(self.range.stop.row)
self.suffix = last_line:sub(self.range.stop.col)
self.params = ParamList.new()
self.params:parse(self.range)
return true
end
function WrapContext:wrap()
local line = self.indent .. self.prefix
for i, param in ipairs(self.params.parsed) do
line = line .. param.text
if i < #self.params.parsed then
line = line .. ', '
end
end
line = line .. self.suffix
vim.fn.setline(self.range.start.row, line)
vim.fn.execute(string.format('%d,%dd_', self.range.start.row + 1, self.range.stop.row))
end
function WrapContext:unwrap()
vim.fn.setline(
self.range.start.row,
self.indent .. self.prefix
)
local cursor = nil
local row = self.range.start.row
for i, param in ipairs(self.params.parsed) do
local on_last_param = i == #self.params.parsed
local line = self.indent .. param.text
if self.opts.tail_comma or not on_last_param then
line = line .. ','
end
if on_last_param and not self.opts.wrap_closing_brace then
line = line .. self.suffix
end
vim.fn.append(row, line)
vim.fn.execute(string.format('%d>', row + 1))
-- if param.offset then
-- cursor = cursor
-- cursor.col = cursor.col + param.offset
-- cursor.row = row + 1
-- end
row = row + 1
end
if self.opts.wrap_closing_brace then
vim.fn.append(row, self.indent .. self.suffix)
end
if cursor then
cursor:set_current()
end
end
function WrapContext:toggle()
if self.range:is_wrapped() then
self:wrap()
else
self:unwrap()
end
end
return {
WrapContext = WrapContext,
}