diff --git a/examples/splitjoin.lua b/examples/splitjoin.lua index 963e799..f326d00 100644 --- a/examples/splitjoin.lua +++ b/examples/splitjoin.lua @@ -1,4 +1,3 @@ -local CodeWriter = require 'u.codewriter' local Range = require 'u.range' local vim_repeat = require 'u.repeat' @@ -8,8 +7,27 @@ local M = {} --- @param left string --- @param right string local function split(bracket_range, left, right) - local code = CodeWriter.from_pos(bracket_range.start) - code:write_raw(left) + local bufnr = bracket_range.start.bufnr + local first_line = Range.from_line(bufnr, bracket_range.start.lnum):text() + local ws = first_line:match '^%s*' + local expandtab = vim.bo[bufnr].expandtab + local shiftwidth = vim.bo[bufnr].shiftwidth + + local indent_str, base_indent + if expandtab then + indent_str = string.rep(' ', shiftwidth) + base_indent = math.floor(#ws / shiftwidth) + else + indent_str = '\t' + base_indent = #ws + end + + local lines = {} + local function write(line, indent_offset) + table.insert(lines, indent_str:rep(base_indent + (indent_offset or 0)) .. line) + end + + table.insert(lines, left) local curr = bracket_range.start:next() if curr == nil then return end @@ -27,7 +45,7 @@ local function split(bracket_range, left, right) if vim.tbl_contains({ ',', ';' }, curr:char()) then -- accumulate item: local item = vim.trim(Range.new(last_start, curr):text()) - if item ~= '' then code:indent():write(item) end + if item ~= '' then write(item, 1) end local next_last_start = curr:next() if next_last_start == nil then break end @@ -45,11 +63,11 @@ local function split(bracket_range, left, right) local pos_before_right = bracket_range.stop:must_next(-1) if last_start < pos_before_right then local item = vim.trim(Range.new(last_start, pos_before_right):text()) - if item ~= '' then code:indent():write(item) end + if item ~= '' then write(item, 1) end end - code:write(right) - bracket_range:replace(code.lines) + write(right) + bracket_range:replace(lines) end --- @param bracket_range u.Range diff --git a/examples/surround.lua b/examples/surround.lua index f825dd8..e73b952 100644 --- a/examples/surround.lua +++ b/examples/surround.lua @@ -1,4 +1,3 @@ -local CodeWriter = require 'u.codewriter' local Range = require 'u.range' local vim_repeat = require 'u.repeat' @@ -20,47 +19,37 @@ local surrounds = { ['`'] = { left = '`', right = '`' }, } ---- @type { left: string, right: string } | nil +--- @type { left: string; right: string } | nil local CACHED_BOUNDS = nil ---- @return { left: string, right: string }|nil +--- @return { left: string; right: string }|nil local function prompt_for_bounds() - if vim_repeat.is_repeating() then - -- If we are repeating, we don't want to prompt for bounds, because - -- we want to reuse the last bounds: - return CACHED_BOUNDS - end + if vim_repeat.is_repeating() then return CACHED_BOUNDS end local cn = vim.fn.getchar() - -- Check for non-printable characters: if type(cn) ~= 'number' or cn < 32 or cn > 126 then return end local c = vim.fn.nr2char(cn) if c == '<' then - -- Surround with a tag: vim.keymap.set('c', '>', '>') local tag = '<' .. vim.fn.input '<' if tag == '<' then return end vim.keymap.del('c', '>') local endtag = ']*' .. '>' - -- selene: allow(global_usage) CACHED_BOUNDS = { left = tag, right = endtag } return CACHED_BOUNDS else - -- Default surround: - CACHED_BOUNDS = (surrounds)[c] or { left = c, right = c } + CACHED_BOUNDS = surrounds[c] or { left = c, right = c } return CACHED_BOUNDS end end --- @param range u.Range ---- @param bounds { left: string, right: string } +--- @param bounds { left: string; right: string } local function do_surround(range, bounds) local left = bounds.left local right = bounds.right if range.mode == 'V' then - -- If we are surrounding multiple lines, we don't care about - -- space-padding: left = vim.trim(left) right = vim.trim(right) end @@ -69,28 +58,34 @@ local function do_surround(range, bounds) range:replace(left .. range:text() .. right) elseif range.mode == 'V' then local bufnr = vim.api.nvim_get_current_buf() - local cw = CodeWriter.from_line(range.start:line(), bufnr) + local first_line = Range.from_line(bufnr, range.start.lnum):text() + local ws = first_line:match '^%s*' + local expandtab = vim.bo[bufnr].expandtab + local shiftwidth = vim.bo[bufnr].shiftwidth - -- write the left bound at the current indent level: - cw:write(left) + local indent_str, base_indent + if expandtab then + indent_str = string.rep(' ', shiftwidth) + base_indent = math.floor(#ws / shiftwidth) + else + indent_str = '\t' + base_indent = #ws + end - local curr_ident_prefix = cw.indent_str:rep(cw.indent_level) - cw:indent(function(cw2) - for _, line in ipairs(range:lines()) do - -- trim the current indent prefix from the line: - if line:sub(1, #curr_ident_prefix) == curr_ident_prefix then - -- - line = line:sub(#curr_ident_prefix + 1) - end + local lines = {} + local function write(line, indent_offset) + table.insert(lines, indent_str:rep(base_indent + (indent_offset or 0)) .. line) + end - cw2:write(line) - end - end) + write(left) + local indent_prefix = indent_str:rep(base_indent) + for _, line in ipairs(range:lines()) do + if line:sub(1, #indent_prefix) == indent_prefix then line = line:sub(#indent_prefix + 1) end + write(line, 1) + end + write(right) - -- write the right bound at the current indent level: - cw:write(right) - - range:replace(cw.lines) + range:replace(lines) end range.start:save_to_pos '.' @@ -193,6 +188,7 @@ function M.setup() local txt_obj = vim_repeat.is_repeating() and CACHED_DELETE_FROM or vim.fn.getcharstr() CACHED_DELETE_FROM = txt_obj + local bufnr = vim.api.nvim_get_current_buf() local irange = Range.from_motion('i' .. txt_obj) local arange = Range.from_motion('a' .. txt_obj) if arange == nil or irange == nil then return end diff --git a/lua/u/codewriter.lua b/lua/u/codewriter.lua deleted file mode 100644 index d3362cb..0000000 --- a/lua/u/codewriter.lua +++ /dev/null @@ -1,77 +0,0 @@ ---- @class u.CodeWriter ---- @field lines string[] ---- @field indent_level number ---- @field indent_str string -local CodeWriter = {} -CodeWriter.__index = CodeWriter - ---- @param indent_level? number ---- @param indent_str? string ---- @return u.CodeWriter -function CodeWriter.new(indent_level, indent_str) - if indent_level == nil then indent_level = 0 end - if indent_str == nil then indent_str = ' ' end - - local cw = { - lines = {}, - indent_level = indent_level, - indent_str = indent_str, - } - setmetatable(cw, CodeWriter) - return cw -end - ---- @param p u.Pos -function CodeWriter.from_pos(p) - local Range = require 'u.range' - local line = Range.from_line(p.bufnr, p.lnum):text() - return CodeWriter.from_line(line, p.bufnr) -end - ---- @param line string ---- @param bufnr? number -function CodeWriter.from_line(line, bufnr) - if bufnr == nil or bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end - - local ws = line:match '^%s*' - local expandtab = vim.api.nvim_get_option_value('expandtab', { buf = bufnr }) - local shiftwidth = vim.api.nvim_get_option_value('shiftwidth', { buf = bufnr }) - - --- @type number - local indent_level - local indent_str = '' - if expandtab then - while #indent_str < shiftwidth do - indent_str = indent_str .. ' ' - end - indent_level = #ws / shiftwidth - else - indent_str = '\t' - indent_level = #ws - end - - return CodeWriter.new(indent_level, indent_str) -end - ---- @param line string -function CodeWriter:write_raw(line) - if line:find '\n' then error 'line contains newline character' end - table.insert(self.lines, line) -end - ---- @param line string -function CodeWriter:write(line) self:write_raw(self.indent_str:rep(self.indent_level) .. line) end - ---- @param f? fun(cw: u.CodeWriter):any -function CodeWriter:indent(f) - local cw = { - lines = self.lines, - indent_level = self.indent_level + 1, - indent_str = self.indent_str, - } - setmetatable(cw, { __index = CodeWriter }) - if f ~= nil then f(cw) end - return cw -end - -return CodeWriter diff --git a/spec/codewriter_spec.lua b/spec/codewriter_spec.lua deleted file mode 100644 index ddfc63a..0000000 --- a/spec/codewriter_spec.lua +++ /dev/null @@ -1,30 +0,0 @@ ---- @diagnostic disable: undefined-field, need-check-nil, need-check-nil -local CodeWriter = require 'u.codewriter' - -describe('CodeWriter', function() - it('should write with indentation', function() - local cw = CodeWriter.new() - cw:write '{' - cw:indent(function(cw2) cw2:write 'x: 123' end) - cw:write '}' - - assert.are.same(cw.lines, { '{', ' x: 123', '}' }) - end) - - it('should keep relative indentation', function() - local cw = CodeWriter.new() - cw:write '{' - cw:indent(function(cw2) - cw2:write 'x: 123' - cw2:write ' y: 123' - end) - cw:write '}' - - assert.are.same(cw.lines, { - '{', - ' x: 123', - ' y: 123', - '}', - }) - end) -end)