local A = require("my.abbreviations") local M = {} -- function M.abolish(from, to) -- vim.cmd(":Abolish -buffer " .. from .. " " .. to) -- end -- -- function M.abolishMany(many) -- for _, entry in pairs(many) do M.abolish(entry[1], entry[2]) end -- end local function concatTables(t1, t2) assert(type(t1) == "table") assert(type(t2) == "table") local t3 = {} for i = 1, #t1 do t3[#t3 + 1] = t1[i] end for i = 1, #t2 do t3[#t3 + 1] = t2[i] end return t3 end local function betterAbolish(unprocessed, context, out) local from = unprocessed[1] or {} local to = unprocessed[2] or {} assert(type(from) == "table" and type(to) == "table", "Both arguments should be tables. Found " .. vim.inspect(from) .. " and " .. vim.inspect(to) .. " instead.") -- print(vim.inspect({ context = context, unprocessed = unprocessed })) if #from == 0 and #to == 0 then table.insert(out, context) return end for i = 1, 2, 1 do local head = unprocessed[i][1] if type(head) == "string" then local context_clone = { context[1], context[2] } context_clone[i] = context_clone[i] .. head local unprocessed_clone = { unprocessed[1], unprocessed[2] } unprocessed_clone[i] = { unpack(unprocessed[i], 2) } return betterAbolish(unprocessed_clone, context_clone, out) end end -- print(vim.inspect({ from, to, context })) assert(type(from[1]) == "table", vim.inspect(from) .. " starts with neither a table nor a string") assert(type(to[1]) == "table", vim.inspect(to) .. " does not start with a table") for i = 1, #from[1], 1 do local when = from[1][i] local replacement = when if #to[1] > 0 then replacement = to[1][((i - 1) % #to[1]) + 1] end assert(type(when) == "table") assert(type(replacement) == "table") local unprocessed_clone = { concatTables(when, { unpack(from, 2) }), concatTables(replacement, { unpack(to, 2) }) } -- print(vim.inspect({ -- unprocessed_clone = unprocessed_clone, -- when = when, -- replacement = replacement, -- from = from, -- to = to, -- i = i -- })) betterAbolish(unprocessed_clone, context, out) end end function M.betterAbolish(from, to) local result = {} betterAbolish({ from, to }, { "", "" }, result) return result end local function withCasing(input) local out = {} for i = 1, #input, 1 do local from = input[i][1] local to = input[i][2] table.insert(out, { from, to }) table.insert(out, { string.upper(from), string.upper(to) }) if #from and #to then table.insert(out, { string.upper(string.sub(from, 1, 1)) .. string.sub(from, 2), string.upper(string.sub(to, 1, 1)) .. string.sub(to, 2) }) end end return out end ---Parses the input for this plugin ---@param input string ---@param context {delimiters:{left:string,right:string},separator:string} ---@return nil|{positio:number,message:string} ---@return (string|table)[]|nil local function parse(input, context) local position = 1 ---@type { start:number, contents:(string|table)[] }[] local stack = { { start = position, contents = {} } } local function error(message, pos) return { position = pos or position, message = message } end local escaped = false local escapedChars = { ["\\"] = true, [context.delimiters.left] = true, [context.delimiters.right] = true } -- When encountering {}, instead of treating it like a single -- choice containing an empty string, we must treat it as an empty choice -- -- The specialEmpty arg tells us whether to consider such cases. local function saveUp(specialEmpty) local prev = stack[#stack - 1].contents local current = stack[#stack] assert(type(prev[#prev]) == "table") ---@cast prev table -- If specialEmpty is true (so we are processing a '}'), -- we only want to keep empty strings (so those where #current.contents == 0) -- if we've had a , before (so #prev[#prev] > 0) if not specialEmpty or #current.contents > 0 or #prev[#prev] > 0 then table.insert(prev[#prev], current.contents) end end while position <= string.len(input) do local first = string.sub(input, position, position) local next = string.sub(input, position + 1, position + 1) local current = stack[#stack] if not escaped and first == "\\" and escapedChars[next] then escaped = true elseif not escaped and first == context.delimiters.left then table.insert(current.contents, {}) stack[#stack + 1] = { start = position, contents = {} } elseif not escaped and first == context.delimiters.right then if #stack == 1 then return nil, error("Delimiter " .. context.delimiters.right .. " never opened") end -- we want special treatment for {} saveUp(true) stack[#stack] = nil elseif not escaped and first == context.separator and #stack > 1 then -- we want to treat empty strings before , as empty strings saveUp(false) current.contents = {} else local last = current.contents[#current.contents] if type(last) == "string" then current.contents[#current.contents] = last .. first else table.insert(current.contents, first) end escaped = false end position = position + 1 end if #stack > 1 then return nil, error("Delimiter " .. context.delimiters.left .. " never closed", stack[2].start) end return stack[1].contents, nil end local context = { delimiters = { left = "{", right = "}" }, separator = "," } function M.abolishMany(many) local total = 0 for _, entry in pairs(many) do local left = parse(entry[1], context) local right = parse(entry[2], context) local abbreviations = withCasing(M.betterAbolish(left, right)) total = total + #abbreviations A.manyLocalAbbr(abbreviations) end print("Added " .. total .. " abbreviations") end -- function M.setup() -- local context = { delimiters = { left = "{", right = "}" }, separator = "," } -- print(vim.inspect({ parse("abc\\d{a, d,e}dsdf\\{sdf\\}", context) })) -- local parsed, _ = parse("ab{e,{f0,1e,d2},f,{3,4,5},\\{{000,111}\\}}cd", context) -- -- local parsed, _ = parse("abc", context) -- print(vim.inspect(parsed)) -- local processed = M.betterAbolish(parsed, parsed) -- print(vim.inspect(processed)) -- end return M