diff --git a/README.md b/README.md index 1812d8f..efd2ede 100644 --- a/README.md +++ b/README.md @@ -9,5 +9,6 @@ The experiments are currently organized based on the language they use: - [Purescript](./purescript/) - [Typescript](./typescript/) - [Lean](./lean/) +- [Lua](./lua/) - [Idris](./idris/) - [Typst](./typst/) diff --git a/lua/README.md b/lua/README.md new file mode 100644 index 0000000..3828ad2 --- /dev/null +++ b/lua/README.md @@ -0,0 +1,5 @@ +# Lua + +| Name | Description | +| ------------------------ | ------------------------------------------------ | +| [slam-nvim](./slam-nvim) | Dropped attempt at rewriting vim-arpeggio in lua | diff --git a/lua/slam-nvim/lua/slam/init.lua b/lua/slam-nvim/lua/slam/init.lua new file mode 100644 index 0000000..3f19bfd --- /dev/null +++ b/lua/slam-nvim/lua/slam/init.lua @@ -0,0 +1,230 @@ +local M = {} + +local keymaps = {} +local nextKeymapId = 0 +local processedKeys = {} +local pressedKeys = {} +local currentTimer = nil +local timeout = 40 + +local function clearTimer() + print("Clearing a timer") + if currentTimer ~= nil then + currentTimer.cancel() + currentTimer = nil + end +end + +local function normalExec(command) + print("Executing:", command) + print("Replaced:", vim.api.nvim_replace_termcodes(command, true, false, true), "mt", true) + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(command, true, false, true), "mt", true) +end + +local function contains(table, key) + for _, v in ipairs(table) do + if v == key then + return true + end + end + return false +end + +-- TODO: optimize to use the sorted nature of the lists +local function containsAll(big, small) + for _, v in ipairs(small) do + if not contains(big, v) then + return false + end + end + return true +end + +local function listsEq(l, r) + if #l ~= #r then return false end + + for i = 1, #l do + if l[i] ~= r[i] then + return false + end + end + + return true +end + +local function getMatchingChords(keys) + local results = { + exact = {}, + longer = {} + } + + local mode = vim.fn.mode() + + for _, keymap in ipairs(keymaps) do + if contains(keymap.modes, mode) then + if listsEq(keys, keymap.keys) then + table.insert(results.exact, keymap) + else if containsAll(keymap.keys, keys) then + table.insert(results.longer, keymap) + end + end + end + end + + if #results.longer == 0 and #results.exact == 0 then + return nil + end + + return { exact = results.exact[#results.exact], longer = results.longer } +end + +local execResults = { + halt = 0, + wait = 1, + done = 2 +} + +local function attemptExec(canWait) + local matching = getMatchingChords(pressedKeys) + + if matching == nil then + return execResults.halt + end + + if matching.exact == nil then + if canWait and #matching.longer ~= 0 then + return execResults.wait + end + + return execResults.halt + end + + + normalExec("(slam-exec:" .. matching.exact.id .. ")") + + return execResults.done +end + +local function handleExecResult(result) + if result == execResults.wait then + print("Setting up timer") + + if currentTimer ~= nil then + print("WHY TF IS THERE A TIMER HERE") + end + + local timerControls = { active = true } + + currentTimer = { + cancel = function() + timerControls.active = false + end + } + + vim.defer_fn(function() + if not timerControls.active then + print("Timer got cancelled!") + return + end + + print("Timer run!") + + clearTimer() + handleExecResult(attemptExec(false)) + end, timeout) + end + + if result == execResults.done then + print("Execution finished!") + pressedKeys = {} + end + + if result == execResults.halt then + print("Halting execution") + local defaultActions = "" + for _, key in pairs(pressedKeys) do + defaultActions = defaultActions .. "" .. "(slam-cancel:" .. key .. ")" + end + + normalExec(defaultActions) + + pressedKeys = {} + end + + -- TODO: fail +end + +local function processKey(key) + print("Processing: " .. key) + clearTimer() + + if contains(pressedKeys, key) then + table.insert(pressedKeys, key) + handleExecResult(execResults.halt) + return + end + + + table.insert(pressedKeys, key) + + local result = attemptExec(true) + handleExecResult(result) +end + +-- TODO: rework this to be more efficient by using counts instead of whatever this is +local function addTrackedKey(key, data) + local existing = processedKeys[key] + if existing == nil then + processedKeys[key] = { data } + local allModes = { "i", "n", "v", "o" } + + vim.keymap.set(allModes, "(slam-cancel:" .. key .. ")", key) + vim.keymap.set(allModes, key, function() + local mode = vim.fn.mode() + + for _, entry in ipairs(processedKeys[key]) do + if contains(entry.modes, mode) then + processKey(key) + return + end + end + + print("(slam-cancel:" .. key .. ")") + normalExec("(slam-cancel:" .. key .. ")") + end) + else + table.insert(processedKeys[key], data) + end +end + +local function splitKeys(keys) + return vim.fn["split"](keys, [[\(<[^<>]\+>\|.\)\zs]]) +end + +function M.set(modes, lhs, rhs, opts) + local id = nextKeymapId + + if type(modes) == "string" then + modes = { modes } + end + + nextKeymapId = nextKeymapId + 1 + + local keyList = splitKeys(lhs) + + -- TODO: error out on no keys + for _, key in ipairs(keyList) do + addTrackedKey(key, { + id = id, + modes = modes + }) + end + + table.insert(keymaps, { + id = id, keys = keyList, modes = modes + }) + + vim.keymap.set(modes, "(slam-exec:" .. id .. ")", rhs, opts) +end + +return M