1
Fork 0
satellite/dotfiles/neovim/lua/my/plugins/abolish.lua
2022-12-26 20:07:10 +01:00

228 lines
6.3 KiB
Lua

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