From 5fa76a6522b0ac51d918e0576b3203c229b7f74d Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Mon, 30 Dec 2024 20:31:50 -0800 Subject: [PATCH] Rework for deep parsing --- lua/argonaut/builder.lua | 56 ++++ lua/argonaut/config.lua | 53 ---- lua/argonaut/cursor.lua | 94 ++++++ lua/argonaut/init.lua | 42 ++- lua/argonaut/options.lua | 61 ++++ lua/argonaut/pairing.lua | 71 +++++ lua/argonaut/param.lua | 95 ++++++ lua/argonaut/range.lua | 236 +++++++++++++++ lua/argonaut/types.lua | 620 --------------------------------------- plugin/argonaut.lua | 5 +- 10 files changed, 652 insertions(+), 681 deletions(-) create mode 100644 lua/argonaut/builder.lua delete mode 100644 lua/argonaut/config.lua create mode 100644 lua/argonaut/cursor.lua create mode 100644 lua/argonaut/options.lua create mode 100644 lua/argonaut/pairing.lua create mode 100644 lua/argonaut/param.lua create mode 100644 lua/argonaut/range.lua delete mode 100644 lua/argonaut/types.lua diff --git a/lua/argonaut/builder.lua b/lua/argonaut/builder.lua new file mode 100644 index 0000000..7e32c16 --- /dev/null +++ b/lua/argonaut/builder.lua @@ -0,0 +1,56 @@ +local Builder = {} +Builder.__index = Builder + +function Builder.new(indent_level, indent_block) + local builder = { + line = '', + buffer = {}, + indent_level = indent_level, + indent_block = indent_block, + } + + return setmetatable(builder, Builder) +end + +function Builder:indent() + self:write(self.indent_block) +end + +function Builder:push_indent() + self.indent_level = self.indent_level + 1 +end + +function Builder:pop_indent() + assert(self.indent_level > 0) + self.indent_level = self.indent_level - 1 +end + +function Builder:write(text) + self.line = self.line .. text +end + +function Builder:endline() + local indent = string.rep(self.indent_block, self.indent_level) + table.insert(self.buffer, indent .. self.line) + self.line = '' +end + +function Builder:output(row_begin, row_end) + local row = row_begin + local row_count = row_end - row_begin + 1 + + for i, line in ipairs(self.buffer) do + if i <= row_count then + vim.fn.setline(row, line) + else + vim.fn.append(row - 1, line) + end + row = row + 1 + end + + if row <= row_end then + vim.fn.execute(string.format('%d,%dd_', row, row_end)) + end +end + +return Builder diff --git a/lua/argonaut/config.lua b/lua/argonaut/config.lua deleted file mode 100644 index 20ea66a..0000000 --- a/lua/argonaut/config.lua +++ /dev/null @@ -1,53 +0,0 @@ -local opt_curr = { - default = { - brace_last_indent = false, - brace_last_wrap = true, - brace_pad = false, - comma_last = false, - comma_prefix = false, - comma_prefix_indent = false, - line_max = 32, - }, - go = { - comma_last = true, - }, - vim = { - line_prefix = '\\', - } -} - -local function setup(opt) - for file_type, file_opt in pairs(opt) do - local file_opt_curr = opt_curr[file_type] - if not file_opt_curr then - file_opt_curr = {} - opt_curr[file_type] = file_opt_curr - end - - for param_name, param_value in pairs(file_opt) do - file_opt_curr[param_name] = param_value - end - end -end - -local function get() - local file_opt_curr = opt_curr[vim.bo.filetype] - - local file_opt = {} - for param_name, param_value in pairs(opt_curr.default) do - file_opt[param_name] = param_value - if file_opt_curr then - local param_value_curr = file_opt_curr[param_name] - if param_value_curr ~= nil then - file_opt[param_name] = param_value_curr - end - end - end - - return file_opt -end - -return { - setup = setup, - get = get, -} diff --git a/lua/argonaut/cursor.lua b/lua/argonaut/cursor.lua new file mode 100644 index 0000000..954c5db --- /dev/null +++ b/lua/argonaut/cursor.lua @@ -0,0 +1,94 @@ +local Cursor = {} +Cursor.__index = Cursor + +function Cursor.new(row, col) + local cursor = { + row = row, + col = col, + } + + return setmetatable(cursor, Cursor) +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.setcursorcharpos({self.row, self.col}) +end + +function Cursor:get_value() + assert(self:is_valid()) + + local line = vim.fn.getline(self.row) + return line:sub(self.col, self.col) +end + +function Cursor:is_valid() + return self.row > 0 and self.col > 0 +end + +function Cursor:is_literal() + 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$') ~= nil or syn_attr:find('Comment$') ~= nil +end + +function Cursor:next() + assert(self:is_valid()) + + local current_line = vim.fn.getline(self.row) + local next_col = self.col + 1 + local next_row = self.row + + if next_col > #current_line then + next_row = next_row + 1 + next_col = 1 + end + + return Cursor.new(next_row, next_col) +end + +function Cursor.__lt(cursor_1, cursor_2) + if cursor_1 == cursor_2 then + return false + end + + if cursor_1.row > cursor_2.row then + return false + end + + if cursor_1.row == cursor_2.row and cursor_1.col > cursor_2.col then + return false + end + + return true +end + +function Cursor.__gt(cursor_1, cursor_2) + if cursor_1 == cursor_2 then + return false + end + + if cursor_1.row < cursor_2.row then + return false + end + + if cursor_1.row == cursor_2.row and cursor_1.col < cursor_2.col then + return false + end + + return true +end + +function Cursor.__eq(cursor_1, cursor_2) + return cursor_1.row == cursor_2.row and cursor_1.col == cursor_2.col +end + +return Cursor diff --git a/lua/argonaut/init.lua b/lua/argonaut/init.lua index cf1c62a..b15aa18 100644 --- a/lua/argonaut/init.lua +++ b/lua/argonaut/init.lua @@ -1,14 +1,44 @@ -local config = require('argonaut.config') -local types = require('argonaut.types') +local Builder = require('argonaut.builder') +local Cursor = require('argonaut.cursor') +local Options = require('argonaut.options') +local Range = require('argonaut.range') local function reflow() - local wrap_context = types.WrapContext.new(config.get()) - if wrap_context:parse() then - wrap_context:toggle() + local range = Range.find_closest() + if not range then + return false end + + local trace = range:hit_test(Cursor.get_current()) + assert(trace) + + local line_first = vim.fn.getline(range.start_cursor.row) + local line_last = vim.fn.getline(range.stop_cursor.row) + + local indent_level = #line_first:match('^(%s*)') + local indent_block = '\t' + if vim.o.expandtab then + indent_level = math.floor(indent_level / vim.o.shiftwidth) + indent_block = string.rep(' ', vim.o.shiftwidth) + end + + local range_prefix = line_first:sub(indent_level * #indent_block + 1, range.start_cursor.col - 1) + local range_suffix = line_last:sub(range.stop_cursor.col + 1) + + local builder = Builder.new(indent_level, indent_block) + builder:write(range_prefix) + range:write(builder, not range:is_wrapped()) + builder:write(range_suffix) + builder:endline() + builder:output(range.start_cursor.row, range.stop_cursor.row) + + local new_range = Range.find_at_cursor(range.start_cursor) + new_range:hit_search(trace, 1):set_current() + + return true end return { reflow = reflow, - setup = config.setup, + setup = Options.setup, } diff --git a/lua/argonaut/options.lua b/lua/argonaut/options.lua new file mode 100644 index 0000000..b89788d --- /dev/null +++ b/lua/argonaut/options.lua @@ -0,0 +1,61 @@ +local options_current = { + default = { + brace_last_indent = false, + brace_last_wrap = true, + brace_pad = false, + comma_last = false, + comma_prefix = false, + comma_prefix_indent = false, + line_max = 32, + }, + go = { + comma_last = true, + }, +} + +local function setup(opt) + for file_type, file_options in pairs(opt) do + local file_options_current = options_current[file_type] + if not file_options_current then + file_options_current = {} + options_current[file_type] = file_options_current + end + + for param_name, param_value in pairs(file_options) do + file_options_current[param_name] = param_value + end + end +end + +local function query(param_name, pairing) + assert(pairing) + + local default_param_value = options_current.default[param_name] + local param_value = default_param_value + + local file_options_current = options_current[vim.bo.filetype] + if file_options_current then + param_value = file_options_current[param_name] + if param_value == nil then + param_value = default_param_value + end + end + + if type(param_value) == 'table' then + param_value = param_value[pairing.open] + if param_value == nil then + param_value = default_param_value + end + end + + if type(param_value) == 'table' then + param_value = param_value[pairing.open] + end + + return param_value +end + +return { + setup = setup, + query = query, +} diff --git a/lua/argonaut/pairing.lua b/lua/argonaut/pairing.lua new file mode 100644 index 0000000..e2e0e32 --- /dev/null +++ b/lua/argonaut/pairing.lua @@ -0,0 +1,71 @@ +local Cursor = require('argonaut.cursor') + +local Pairing = {} +Pairing.__index = Pairing + +function Pairing.new(open, close) + local pair = { + open = open, + close = close + } + + return setmetatable(pair, Pairing) +end + +function Pairing.from_brace(brace) + local all_pairs = { + {'(', ')'}, + {'[', ']'}, + {'{', '}'}, + {'<', '>'}, + } + + for _, pair in ipairs(all_pairs) do + if pair[1] == brace or pair[2] == brace then + return Pairing.new(pair[1], pair[2]) + end + end +end + +function Pairing: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 Pairing.new( + escape_func(self.open), + escape_func(self.close) + ) +end + +function Pairing:find_closest(forward) + -- See flags: https://neovim.io/doc/user/builtin.html#search() + local flags = 'Wnbc' + if forward then + flags = 'Wn' + end + + local ignore_func = function() + local cursor = Cursor.get_current() + return cursor:is_literal() + end + + local escaped_pair = self:get_escaped() + local cursor = Cursor.new(unpack(vim.fn.searchpairpos( + escaped_pair.open, + '', + escaped_pair.close, + flags, + ignore_func + ))) + + if cursor:is_valid() then + return cursor + end +end + +return Pairing diff --git a/lua/argonaut/param.lua b/lua/argonaut/param.lua new file mode 100644 index 0000000..86c283d --- /dev/null +++ b/lua/argonaut/param.lua @@ -0,0 +1,95 @@ +local Param = {} +Param.__index = Param + +local function is_empty_cell(cell) + return cell.type == 'cursor' and not cell.value:get_value():match('%S') +end + +function Param.new(range, index, cells) + local param = { + range = range, + index = index, + cells = cells or {}, + } + + return setmetatable(param, Param) +end + +function Param:append(value, type) + table.insert(self.cells, { + value = value, + type = type, + }) +end + +function Param:stripped() + local cells = {} + for i = self:find_index_begin(), self:find_index_end() do + table.insert(cells, self.cells[i]) + end + return Param.new(self.range, self.index, cells) +end + +function Param:hit_test(cursor) + for i, cell in ipairs(self.cells) do + local trace = nil + if cell.type == 'range' then + local trace_param = cell.value:hit_test(cursor) + if trace_param then + trace = {i} + for _, trace_param_frame in ipairs(trace_param) do + table.insert(trace, trace_param_frame) + end + end + elseif cell.type == 'cursor' and cell.value == cursor then + trace = {i} + end + + if trace then + return trace + end + end +end + +function Param:hit_search(trace, depth) + local index = trace[depth] + local cell = self.cells[index] + if cell.type == 'range' then + return cell.value:hit_search(trace, depth + 1) + elseif cell.type == 'cursor' then + return cell.value + end +end + +function Param:write(builder, wrapped) + for i = self:find_index_begin(), self:find_index_end() do + local cell = self.cells[i] + if cell.type == 'range' then + cell.value:write(builder, wrapped) + elseif cell.type == 'cursor' then + builder:write(cell.value:get_value()) + end + end +end + +function Param:find_index_begin() + for i = 1, #self.cells do + if not is_empty_cell(self.cells[i]) then + return i + end + end +end + +function Param:find_index_end() + for i = #self.cells, 1, -1 do + if not is_empty_cell(self.cells[i]) then + return i + end + end +end + +function Param:is_empty() + return self:find_index_begin() == nil +end + +return Param diff --git a/lua/argonaut/range.lua b/lua/argonaut/range.lua new file mode 100644 index 0000000..20047be --- /dev/null +++ b/lua/argonaut/range.lua @@ -0,0 +1,236 @@ +local Cursor = require('argonaut.cursor') +local Options = require('argonaut.options') +local Pairing = require('argonaut.pairing') +local Param = require('argonaut.param') + +local Range = {} +Range.__index = Range + +local function find_closest_pair(pairing) + local stop_cursor = pairing:find_closest(true) + if stop_cursor then + local start_cursor = pairing:find_closest(false) + if start_cursor then + return Range.new(start_cursor, stop_cursor, pairing) + end + end +end + +local function find_closest() + local ranges = {} + for _, brace in pairs({'(', '[', '{', '<'}) do + local pairing = Pairing.from_brace(brace) + local range = find_closest_pair(pairing) + if range then + table.insert(ranges, range) + end + end + + if #ranges > 0 then + table.sort(ranges) + local range = ranges[1] + range:parse() + return range + end +end + +local function find_at_cursor(start_cursor) + local pairing = Pairing.from_brace(start_cursor:get_value()) + if pairing then + local cursor_current = Cursor:get_current() + start_cursor:set_current() + local stop_cursor = pairing:find_closest(true) + cursor_current:set_current() + if stop_cursor then + local range = Range.new(start_cursor, stop_cursor, pairing) + range:parse() + return range + end + end +end + +function Range.new(start_cursor, stop_cursor, pairing) + local range = { + start_cursor = start_cursor, + stop_cursor = stop_cursor, + pairing = pairing, + params = {}, + } + + return setmetatable(range, Range) +end + +function Range:parse() + local param = nil + local param_index = 1 + + local append_param_value = function(value, type) + if not param then + param = Param.new(self, param_index) + param_index = param_index + 1 + end + param:append(value, type) + end + + local append_param = function() + if param and not param:is_empty() then + table.insert(self.params, param:stripped()) + end + param = nil + end + + local cursor = self.start_cursor:next() + while cursor ~= self.stop_cursor do + local value = cursor:get_value() + + if cursor:is_literal() then + append_param_value(cursor, 'cursor') + else + local range = find_at_cursor(cursor) + if range then + append_param_value(range, 'range') + cursor = range.stop_cursor + elseif value == ',' then + append_param() + else + append_param_value(cursor, 'cursor') + end + end + + cursor = cursor:next() + end + + append_param() +end + +function Range:hit_test(cursor) + if self:contains(cursor) then + local trace = nil + for i, param in pairs(self.params) do + local trace_param = param:hit_test(cursor) + if trace_param then + trace = {i} + for _, trace_param_frame in ipairs(trace_param) do + table.insert(trace, trace_param_frame) + end + break + end + end + + if not trace then + trace = {0} + end + + return trace + end +end + +function Range:hit_search(trace, depth) + local index = trace[depth] + if index == 0 then + return self.start_cursor + else + return self.params[index]:hit_search(trace, depth + 1) + end +end + +function Range:write(builder, wrapped) + if wrapped == nil then + wrapped = self:is_wrapped() + end + + if wrapped then + builder:write(self.pairing.open) + + if #self.params > 0 then + builder:endline() + builder:push_indent() + + for i, param in ipairs(self.params) do + param:write(builder) + + local is_last_param = i == #self.params + if not is_last_param or self:query_option('comma_last') then + builder:write(',') + end + if not is_last_param then + builder:endline() + end + end + + if self:query_option('brace_last_wrap') then + builder:endline() + builder:pop_indent() + end + end + + if not self:query_option('brace_last_wrap') then + if self:query_option('brace_last_indent') then + builder:indent() + end + + if self:query_option('brace_pad') then + builder:write(' ') + end + end + + builder:write(self.pairing.close) + else + builder:write(self.pairing.open) + if self:query_option('brace_pad') then + builder:write(' ') + end + + for i, param in ipairs( self.params ) do + param:write(builder) + + local is_last_param = i == #self.params + if not is_last_param then + builder:write(', ') + end + end + + if self:query_option('brace_pad') then + builder:write(' ') + end + builder:write(self.pairing.close) + end +end + +function Range:contains(cursor) + return cursor >= self.start_cursor and cursor <= self.stop_cursor +end + +function Range:query_option(name) + return Options.query(name, self.pairing) +end + +function Range:is_wrapped() + return self.start_cursor.row ~= self.stop_cursor.row +end + +function Range.__lt(range_1, range_2) + local cursor = Cursor:get_current() + + local row_diff1 = range_1.start_cursor.row - cursor.row + local col_diff1 = range_1.start_cursor.col - cursor.col + local row_diff2 = range_2.start_cursor.row - cursor.row + local col_diff2 = range_2.start_cursor.col - cursor.col + + if row_diff1 < row_diff2 then + return false + elseif row_diff1 > row_diff2 then + return true + elseif col_diff1 < col_diff2 then + return false + elseif col_diff1 > col_diff2 then + return true + else + return true + end +end + +return { + find_closest = find_closest, + find_at_cursor = find_at_cursor, +} diff --git a/lua/argonaut/types.lua b/lua/argonaut/types.lua deleted file mode 100644 index bd2ab27..0000000 --- a/lua/argonaut/types.lua +++ /dev/null @@ -1,620 +0,0 @@ --- --- Cursor --- - -local Cursor = {} -Cursor.__index = Cursor - -function Cursor.new(row, col) - local cursor = {row = row, col = col} - return setmetatable(cursor, Cursor) -end - -function Cursor:is_valid() - return self.row > 0 and self.col > 0 -end - -function Cursor:is_literal() - 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$') ~= nil or syn_attr:find('Comment$') ~= nil -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.setcursorcharpos({self.row, self.col}) -end - -function Cursor.__eq(self, other) - return self.row == other.row and self.col == other.col -end - --- --- BracePair --- - -local BracePair = {} -BracePair.__index = BracePair - -function BracePair.new(open, close) - local pair = {open = open, close = close} - return setmetatable(pair, 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 = 'Wcnb' - end - - local ignore_func = function() - local cursor = Cursor.get_current() - return cursor:is_literal() - 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 - -function BracePair.__eq(self, other) - return self.open == other.open and self.close == other.close -end - --- --- BraceStack --- - -local BraceStack = {} -BraceStack.__index = BraceStack - -function BraceStack.new() - local stack = {stack = {}} - return setmetatable(stack, 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 = {} -BraceRange.__index = BraceRange - -function BraceRange.new(start, stop, pair) - local range = { - start = start, - stop = stop, - pair = pair, - } - - return setmetatable(range, 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 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 - table.sort(ranges) - return ranges[1] - end -end - -function BraceRange:get_rows() - return self.stop.row - self.start.row -end - -function BraceRange:is_wrapped() - return self.start.row < self.stop.row -end - -function BraceRange.__lt(range_1, range_2) - local cursor = Cursor:get_current() - - local row_diff1 = range_1.start.row - cursor.row - local col_diff1 = range_1.start.col - cursor.col - local row_diff2 = range_2.start.row - cursor.row - local col_diff2 = range_2.start.col - cursor.col - - if row_diff1 < row_diff2 then - return false - elseif row_diff1 > row_diff2 then - return true - elseif col_diff1 < col_diff2 then - return false - elseif col_diff1 > col_diff2 then - return true - else - return true - end -end - --- --- Param --- - -local Param = {} -Param.__index = Param - -function Param.new(pair, opt) - local param = { - pair = pair, - opt = opt, - text = '', - literals = {}, - offset = nil, - start = nil, - stop = nil, - terminator = nil, - } - - return setmetatable(param, Param) -end - -function Param:append(char, cursor) - assert(cursor:is_valid()) - - self.text = self.text .. char - table.insert(self.literals, cursor:is_literal()) - - if cursor == Cursor.get_current() then - self.offset = #self.text - end - - if not self.start then - self.start = cursor - end - - self.stop = cursor -end - -function Param:terminate(cursor) - self.terminator = cursor - if self.terminator == Cursor.get_current() then - self.offset = #self.text - end -end - -function Param:is_active() - return self.offset ~= nil -end - -function Param:slice(start, stop) - assert(#self.text == #self.literals) - - local text = '' - local literals = {} - - for i = start, stop do - text = text .. self.text:sub(i, i) - table.insert(literals, self.literals[i]) - end - - self.text = text - self.literals = literals - - if self.offset then - self.offset = math.min(self.offset, stop) - self.offset = math.max(self.offset - start + 1, 1) - end -end - -function Param:trim() - assert(#self.text == #self.literals) - - self:slice(1, #self.text - #self.text:match('%s*$')) - self:slice(1 + #self.text:match('^%s*'), #self.text) - - local text = '' - local literals = {} - local offset = self.offset - - for i = 1, #self.text do - local char = self.text:sub(i, i) - local literal = self.literals[i] - - if literal or not char:match('%s') or not text:match('%s$') then - text = text .. char - table.insert(literals, literal) - elseif offset and offset >= i then - self.offset = math.max(1, self.offset - 1) - end - end - - self.text = text - self.literals = literals - - return #self.text > 0 -end - --- --- ParamList --- - -local ParamList = {} -ParamList.__index = ParamList - -function ParamList.new(range, opt) - local params = { - range = range, - opt = opt, - current = nil, - parsed = {}, - } - - return setmetatable(params, ParamList) -end - -function ParamList:flush(cursor) - if self.current then - if cursor then - self.current:terminate(cursor) - end - - if self.current:trim() then - table.insert(self.parsed, self.current) - end - - self.current = nil - end -end - -function ParamList:update(char, brace_stack, cursor) - if not cursor:is_literal() then - brace_stack:update(char) - if brace_stack:empty() and char == ',' then - self:flush(cursor) - return - end - end - - if not self.current then - self.current = Param.new(self.range, self.opt) - end - - self.current:append(char, cursor) -end - -function ParamList:parse() - local brace_stack = BraceStack:new() - - for row = self.range.start.row, self.range.stop.row do - local line = vim.fn.getline(row) - - local start_col = 1 - if row == self.range.start.row then - start_col = self.range.start.col + 1 - end - - local stop_col = #line - if row == self.range.stop.row then - stop_col = self.range.stop.col - 1 - end - - for col = start_col, stop_col do - self:update(line:sub(col, col), brace_stack, Cursor.new(row, col)) - end - end - - self:flush() -end - --- --- Builder --- - -local Builder = {} -Builder.__index = Builder - -function Builder.new(indent_level, indent_block) - local builder = { - lines = {}, - line = '', - indent_level = indent_level, - indent_block = indent_block, - } - - return setmetatable(builder, Builder) -end - -function Builder:indent() - self.indent_level = self.indent_level + 1 -end - -function Builder:unindent() - assert(self.indent_level > 0) - self.indent_level = self.indent_level - 1 -end - -function Builder:update(text) - self.line = self.line .. text -end - -function Builder:flush() - local indent = string.rep(self.indent_block, self.indent_level) - table.insert(self.lines, indent .. self.line) - self.line = '' -end - -function Builder:get_offset() - local indent = string.rep(self.indent_block, self.indent_level) - return Cursor.new(#self.lines, #self.line + #indent) -end - --- --- WrapContext --- - -local WrapContext = {} -WrapContext.__index = WrapContext - -function WrapContext.new(opt) - local wrap_context = { - opt = nil, - base_opt = opt, - indent = '', - prefix = '', - suffix = '', - range = nil, - params = nil, - } - - return setmetatable(wrap_context, WrapContext) -end - -function WrapContext:config_opt() - self.opt = {} - for key, value in pairs(self.base_opt) do - if type(value) == 'table' then - self.opt[key] = false - for _, brace in ipairs(value) do - if self.range.pair == BracePair.from_brace(brace) then - self.opt[key] = true - break - end - end - else - self.opt[key] = value - end - end -end - -function WrapContext:config_indent(line) - local padding = #line:match('^(%s*)') - if vim.o.expandtab then - self.indent_level = math.floor(padding / vim.o.shiftwidth) - self.indent_block = string.rep(' ', vim.o.shiftwidth) - else - self.indent_level = padding - self.indent_block = '\t' - end -end - -function WrapContext:parse() - self.range = BraceRange.find_closest_any() - if not self.range then - return false - end - - self:config_opt() - - if self.range:get_rows() > self.opt.line_max then - return false - end - - local first_line = vim.fn.getline(self.range.start.row) - local last_line = vim.fn.getline(self.range.stop.row) - self:config_indent(first_line) - self.prefix = first_line:sub(self.indent_level * #self.indent_block + 1, self.range.start.col) - self.suffix = last_line:sub(self.range.stop.col) - - self.params = ParamList.new(self.range, self.opt) - self.params:parse() - - return true -end - -function WrapContext:update_builder_param(builder, param) - local cursor = nil - if param:is_active() then - cursor = builder:get_offset() - cursor.row = cursor.row + self.range.start.row - cursor.col = cursor.col + param.offset - end - - builder:update(param.text) - return cursor -end - -function WrapContext:wrap() - local builder = Builder.new(self.indent_level, self.indent_block) - builder:update(self.prefix) - builder:flush() - builder:indent() - - local cursor = nil - for i, param in ipairs(self.params.parsed) do - local is_first_param = i == 1 - local is_last_param = i == #self.params.parsed - - if self.opt.comma_prefix then - if not is_first_param then - builder:update(', ') - elseif self.opt.comma_prefix_indent and not is_last_param then - builder:update(' ') - end - cursor = self:update_builder_param(builder, param) or cursor - else - cursor = self:update_builder_param(builder, param) or cursor - if not is_last_param or self.opt.comma_last then - builder:update(',') - end - end - - if is_last_param and not self.opt.brace_last_wrap then - builder:update(self.suffix) - end - - builder:flush() - end - - if not self.opt.brace_last_indent then - builder:unindent() - end - - if self.opt.brace_last_wrap then - builder:update(self.suffix) - builder:flush() - end - - local row = self.range.start.row - for i, line in ipairs(builder.lines) do - if i == 1 then - vim.fn.setline(row, line) - else - vim.fn.append(row, line) - row = row + 1 - end - end - - if not cursor then - cursor = self.range.start - end - - cursor:set_current() -end - -function WrapContext:unwrap() - local padding = '' - if self.opt.brace_pad then - padding = ' ' - end - - local builder = Builder.new(self.indent_level, self.indent_block) - builder:update(self.prefix) - builder:update(padding) - - local cursor = nil - for i, param in ipairs(self.params.parsed) do - cursor = self:update_builder_param(builder, param) or cursor - if i < #self.params.parsed then - builder:update(', ') - end - end - - builder:update(padding) - builder:update(self.suffix) - builder:flush() - - vim.fn.setline(self.range.start.row, builder.lines[1]) - vim.fn.execute(string.format('%d,%dd_', self.range.start.row + 1, self.range.stop.row)) - - if not cursor then - cursor = self.range.start - end - - cursor:set_current() -end - -function WrapContext:toggle() - if self.range:is_wrapped() then - self:unwrap() - else - self:wrap() - end -end - -return { - WrapContext = WrapContext, -} diff --git a/plugin/argonaut.lua b/plugin/argonaut.lua index 7296da9..4518d6b 100644 --- a/plugin/argonaut.lua +++ b/plugin/argonaut.lua @@ -1,4 +1,4 @@ -if not vim.g.argonaut then +if not vim.g.argonaut_loaded then local function argonaut_reload() for name, _ in pairs(package.loaded) do if vim.startswith(name, 'argonaut') then @@ -8,11 +8,12 @@ if not vim.g.argonaut then end local function argonaut_reflow() + argonaut_reload() require('argonaut').reflow() end vim.api.nvim_create_user_command('ArgonautReload', argonaut_reload, {}) vim.api.nvim_create_user_command('ArgonautReflow', argonaut_reflow, {}) - vim.g.argonaut = true + vim.g.argonaut_loaded = true end