|
|
@ -25,19 +25,38 @@ mode.ParseTable = require('libmodal/src/mode/ParseTable')
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
--]]
|
|
|
|
--]]
|
|
|
|
|
|
|
|
|
|
|
|
local TIMEOUT_CHAR = 'ø'
|
|
|
|
local _TIMEOUT_CHAR = 'ø'
|
|
|
|
local TIMEOUT_NR = api.nvim_eval("char2nr('" .. TIMEOUT_CHAR .. "')")
|
|
|
|
local _TIMEOUT_NR = string.byte(_TIMEOUT_CHAR)
|
|
|
|
local TIMEOUT_LEN = api.nvim_get_option('TIMEOUT_LEN')
|
|
|
|
local _TIMEOUT_LEN = api.nvim_get_option('timeoutlen')
|
|
|
|
|
|
|
|
|
|
|
|
----------------------------------------
|
|
|
|
----------------------------------------
|
|
|
|
--[[ SUMMARY:
|
|
|
|
--[[ SUMMARY:
|
|
|
|
* Reset libmodal's internal counter of user input to default.
|
|
|
|
* Reset libmodal's internal counter of user input to default.
|
|
|
|
]]
|
|
|
|
]]
|
|
|
|
----------------------------------------
|
|
|
|
----------------------------------------
|
|
|
|
local function clearLocalInput(modeName)
|
|
|
|
local function _clearLocalInput(modeName)
|
|
|
|
vars.input.instances[modeName] = {}
|
|
|
|
vars.input.instances[modeName] = {}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
----------------------------------------------
|
|
|
|
|
|
|
|
--[[ SUMMARY:
|
|
|
|
|
|
|
|
* Update the floating window with the latest user input.
|
|
|
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
--[[ PARAMS:
|
|
|
|
|
|
|
|
* `modeName` => the name of the mode.
|
|
|
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
----------------------------------------------
|
|
|
|
|
|
|
|
local function _updateFloatingWindow(modeName)
|
|
|
|
|
|
|
|
local uinput = {}
|
|
|
|
|
|
|
|
for _, v in ipairs(vars.input.instances[modeName]) do
|
|
|
|
|
|
|
|
table.insert(uinput, string.char(v))
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
api.nvim_buf_set_lines(
|
|
|
|
|
|
|
|
vars.buffers.instances[modeName],
|
|
|
|
|
|
|
|
0, 1, true, {table.concat(uinput)}
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
------------------------------------
|
|
|
|
------------------------------------
|
|
|
|
--[[ SUMMARY:
|
|
|
|
--[[ SUMMARY:
|
|
|
|
* Parse the `comboDict` and see if there is any command to execute.
|
|
|
|
* Parse the `comboDict` and see if there is any command to execute.
|
|
|
@ -46,7 +65,7 @@ end
|
|
|
|
* `modeName` => the name of the mode that is currently active.
|
|
|
|
* `modeName` => the name of the mode that is currently active.
|
|
|
|
]]
|
|
|
|
]]
|
|
|
|
------------------------------------
|
|
|
|
------------------------------------
|
|
|
|
local function comboSelect(modeName)
|
|
|
|
local function _comboSelect(modeName)
|
|
|
|
-- Stop any running timers
|
|
|
|
-- Stop any running timers
|
|
|
|
if vars.timer.instances[modeName] then
|
|
|
|
if vars.timer.instances[modeName] then
|
|
|
|
vars.timer.instances[modeName]:stop()
|
|
|
|
vars.timer.instances[modeName]:stop()
|
|
|
@ -72,23 +91,24 @@ local function comboSelect(modeName)
|
|
|
|
local clearInput = false
|
|
|
|
local clearInput = false
|
|
|
|
|
|
|
|
|
|
|
|
-- if there was no matching command
|
|
|
|
-- if there was no matching command
|
|
|
|
if commandType == false then clearInput = true
|
|
|
|
if cmd == false then clearInput = true
|
|
|
|
-- The command was a table, meaning that it MIGHT match.
|
|
|
|
-- The command was a table, meaning that it MIGHT match.
|
|
|
|
elseif commandType == globals.TYPE_TBL then
|
|
|
|
elseif commandType == globals.TYPE_TBL then
|
|
|
|
-- Create a new timer
|
|
|
|
-- Create a new timer
|
|
|
|
vars.timer.instances[modeName] = vim.loop.new_timer()
|
|
|
|
vars.timer.instances[modeName] = vim.loop.new_timer()
|
|
|
|
|
|
|
|
|
|
|
|
-- start the timer
|
|
|
|
-- start the timer
|
|
|
|
vars.timer.instances[modeName]:start(TIMEOUT_LEN, 0,
|
|
|
|
vars.timer.instances[modeName]:start(
|
|
|
|
vim.schedule_wrap(function()
|
|
|
|
_TIMEOUT_LEN, 0, vim.schedule_wrap(function()
|
|
|
|
-- Send input to interrupt a blocking `getchar`
|
|
|
|
-- Send input to interrupt a blocking `getchar`
|
|
|
|
api.nvim_feedkeys(TIMEOUT_CHAR, '', false)
|
|
|
|
api.nvim_feedkeys(_TIMEOUT_CHAR, '', false)
|
|
|
|
-- if there is a command, execute it.
|
|
|
|
-- if there is a command, execute it.
|
|
|
|
if cmd[mode.ParseTable.CR] then
|
|
|
|
if cmd[mode.ParseTable.CR] then
|
|
|
|
api.nvim_command(cmd[mode.ParseTable.CR])
|
|
|
|
api.nvim_command(cmd[mode.ParseTable.CR])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- clear input
|
|
|
|
-- clear input
|
|
|
|
clearLocalInput(modeName)
|
|
|
|
_clearLocalInput(modeName)
|
|
|
|
|
|
|
|
_updateFloatingWindow(modeName)
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
-- The command was an actual vim command.
|
|
|
|
-- The command was an actual vim command.
|
|
|
@ -98,11 +118,12 @@ local function comboSelect(modeName)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
if clearInput then
|
|
|
|
if clearInput then
|
|
|
|
clearLocalInput(modeName)
|
|
|
|
_clearLocalInput(modeName)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
_updateFloatingWindow(modeName)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
-------------------------------------------------
|
|
|
|
------------------------------------------------
|
|
|
|
--[[ SUMMARY:
|
|
|
|
--[[ SUMMARY:
|
|
|
|
* Set the initial values used for parsing user input as combos.
|
|
|
|
* Set the initial values used for parsing user input as combos.
|
|
|
|
]]
|
|
|
|
]]
|
|
|
@ -110,24 +131,103 @@ end
|
|
|
|
* `modeName` => the name of the mode being initialized.
|
|
|
|
* `modeName` => the name of the mode being initialized.
|
|
|
|
* `comboTable` => the table of combos being initialized.
|
|
|
|
* `comboTable` => the table of combos being initialized.
|
|
|
|
]]
|
|
|
|
]]
|
|
|
|
-------------------------------------------------
|
|
|
|
------------------------------------------------
|
|
|
|
local function initCombos(modeName, comboTable)
|
|
|
|
local function _initCombos(modeName, comboTable)
|
|
|
|
-- Placeholder for timeout value.
|
|
|
|
-- Placeholder for timeout value.
|
|
|
|
local doTimeout = nil
|
|
|
|
local doTimeout = nil
|
|
|
|
|
|
|
|
|
|
|
|
-- Read the correct timeout variable.
|
|
|
|
-- Read the correct timeout variable.
|
|
|
|
if api.nvim_exists('g', vars.timeout.name(modeName)) then
|
|
|
|
if api.nvim_exists('g', vars.timeout:name(modeName)) then
|
|
|
|
doTimeout = vars.nvim_get(vars.timeout, modeName)
|
|
|
|
doTimeout = vars.nvim_get(vars.timeout, modeName)
|
|
|
|
else
|
|
|
|
else doTimeout = vars.libmodalTimeout end
|
|
|
|
doTimeout = vars.libmodalTimeout
|
|
|
|
|
|
|
|
end
|
|
|
|
-- Assign the timeout variable according to `doTimeout`
|
|
|
|
vars.timeout.instances[modeName] = doTimeout
|
|
|
|
vars.timeout.instances[modeName] = doTimeout
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- create a floating window
|
|
|
|
|
|
|
|
local buf = api.nvim_create_buf(false, true)
|
|
|
|
|
|
|
|
vars.buffers.instances[modeName] = buf
|
|
|
|
|
|
|
|
vars.windows.instances[modeName] = api.nvim_eval(
|
|
|
|
|
|
|
|
'libmodal#WinOpen(' .. buf .. ')'
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
-- Build the parse tree.
|
|
|
|
-- Build the parse tree.
|
|
|
|
vars.combos.instances[modeName] = mode.ParseTable.new(comboTable)
|
|
|
|
vars.combos.instances[modeName] = mode.ParseTable.new(comboTable)
|
|
|
|
|
|
|
|
|
|
|
|
-- Initialize the input history variable.
|
|
|
|
-- Initialize the input history variable.
|
|
|
|
clearLocalInput(modeName)
|
|
|
|
_clearLocalInput(modeName)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-----------------------------------------------------
|
|
|
|
|
|
|
|
--[[ SUMMARY:
|
|
|
|
|
|
|
|
* Remove variables used for a mode.
|
|
|
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
--[[ PARAMS:
|
|
|
|
|
|
|
|
* `modeName` => the name of the mode.
|
|
|
|
|
|
|
|
* `winState` => the window state prior to mode activation.
|
|
|
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
-----------------------------------------------------
|
|
|
|
|
|
|
|
local function _modeEnterTeardown(modeName, winState)
|
|
|
|
|
|
|
|
if vars.windows.instances[modeName] then
|
|
|
|
|
|
|
|
api.nvim_win_close(
|
|
|
|
|
|
|
|
vars.windows.instances[modeName], false
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, v in pairs(vars) do
|
|
|
|
|
|
|
|
if type(v) == globals.TYPE_TBL and v.instances[modeName] then
|
|
|
|
|
|
|
|
v.instances[modeName] = nil
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
api.nvim_command("mode | echo '' | call garbagecollect()")
|
|
|
|
|
|
|
|
winState:restore()
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
--[[ SUMMARY:
|
|
|
|
|
|
|
|
* Loop an initialized `mode`.
|
|
|
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
--[[ PARAMS:
|
|
|
|
|
|
|
|
* `handleExitEvents` => whether or not to automatically exit on `<Esc>` press.
|
|
|
|
|
|
|
|
* `indicator` => the indicator for the mode.
|
|
|
|
|
|
|
|
* `modeInstruction` => the instructions for the mode.
|
|
|
|
|
|
|
|
* `modeName` => the name of the `mode`.
|
|
|
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
--[[ RETURNS:
|
|
|
|
|
|
|
|
* `boolean` => whether or not the mode should continue
|
|
|
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
local function _modeLoop(handleExitEvents, indicator, modeInstruction, modeName)
|
|
|
|
|
|
|
|
-- If the mode is not handling exit events automatically and the global exit var is true.
|
|
|
|
|
|
|
|
if not handleExitEvents and globals.isTrue(
|
|
|
|
|
|
|
|
vars.nvim_get(vars.exit, modeName)
|
|
|
|
|
|
|
|
) then return false end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Echo the indicator.
|
|
|
|
|
|
|
|
api.nvim_lecho(indicator)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Capture input.
|
|
|
|
|
|
|
|
local uinput = api.nvim_input()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Return if there was a timeout event.
|
|
|
|
|
|
|
|
if uinput == _TIMEOUT_NR then
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Set the global input variable to the new input.
|
|
|
|
|
|
|
|
vars.nvim_set(vars.input, modeName, uinput)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Make sure that the user doesn't want to exit.
|
|
|
|
|
|
|
|
if handleExitEvents and uinput == globals.ESC_NR then
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
-- If the second argument was a dict, parse it.
|
|
|
|
|
|
|
|
elseif type(modeInstruction) == globals.TYPE_TBL then
|
|
|
|
|
|
|
|
_comboSelect(modeName)
|
|
|
|
|
|
|
|
-- If the second argument was a function, execute it.
|
|
|
|
|
|
|
|
else modeInstruction() end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
------------------------
|
|
|
|
------------------------
|
|
|
@ -157,61 +257,32 @@ function mode.enter(...)
|
|
|
|
-- Determine whether or not this function should handle exiting automatically.
|
|
|
|
-- Determine whether or not this function should handle exiting automatically.
|
|
|
|
local handleExitEvents = true
|
|
|
|
local handleExitEvents = true
|
|
|
|
if #args > 2 then
|
|
|
|
if #args > 2 then
|
|
|
|
handleExitEvents = args[3] == true
|
|
|
|
handleExitEvents = globals.isFalse(args[3])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
-- Determine whether a callback was specified, or a combo table.
|
|
|
|
-- Determine whether a callback was specified, or a combo table.
|
|
|
|
if type(args[2]) == globals.TYPE_TBL then
|
|
|
|
if type(args[2]) == globals.TYPE_TBL then
|
|
|
|
initCombos(modeName, args[2])
|
|
|
|
_initCombos(modeName, args[2])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--[[ MODE LOOP. ]]
|
|
|
|
--[[ MODE LOOP. ]]
|
|
|
|
|
|
|
|
|
|
|
|
local continueMode = true
|
|
|
|
local continueMode = true
|
|
|
|
while continueMode do
|
|
|
|
while continueMode == true do
|
|
|
|
-- Try (using pcall) to use the mode.
|
|
|
|
-- Try (using pcall) to use the mode.
|
|
|
|
local noErrors = pcall(function()
|
|
|
|
local noErrors = true
|
|
|
|
-- If the mode is not handling exit events automatically and the global exit var is true.
|
|
|
|
noErrors, continueMode = pcall(_modeLoop,
|
|
|
|
if not handleExitEvents and vars.nvim_get(vars.exit, modeName) then
|
|
|
|
handleExitEvents, indicator, args[2], modeName
|
|
|
|
continueMode = false
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Echo the indicator.
|
|
|
|
|
|
|
|
api.nvim_lecho(indicator)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Capture input.
|
|
|
|
|
|
|
|
local uinput = api.nvim_input()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Return if there was a timeout event.
|
|
|
|
|
|
|
|
if uinput == TIMEOUT_NR then return end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Set the global input variable to the new input.
|
|
|
|
|
|
|
|
vars.nvim_set(vars.input, modeName, uinput)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- Make sure that the user doesn't want to exit.
|
|
|
|
|
|
|
|
if handleExitEvents and uinput == globals.ESC_NR then
|
|
|
|
|
|
|
|
continueMode = false
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
-- If the second argument was a dict, parse it.
|
|
|
|
|
|
|
|
elseif type(args[2]) == globals.TYPE_TBL then
|
|
|
|
|
|
|
|
comboSelect(modeName)
|
|
|
|
|
|
|
|
-- If the second argument was a function, execute it.
|
|
|
|
|
|
|
|
else args[2]() end
|
|
|
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- If there were errors, handle them.
|
|
|
|
-- If there were errors, handle them.
|
|
|
|
if not noErrors then
|
|
|
|
if noErrors == false then
|
|
|
|
utils.showError()
|
|
|
|
utils.showError(continueMode)
|
|
|
|
continueMode = false
|
|
|
|
continueMode = false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--[[ TEARDOWN. ]]
|
|
|
|
_modeEnterTeardown(modeName, winState)
|
|
|
|
api.nvim_redraw()
|
|
|
|
|
|
|
|
api.nvim_echo('')
|
|
|
|
|
|
|
|
api.nvim_command('call garbagecollect()')
|
|
|
|
|
|
|
|
winState:restore()
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
--[[
|
|
|
|
--[[
|
|
|
|