Finish initial attempt to rewrite Mode

pull/3/head
Iron-E 4 years ago
parent 41564fa9f3
commit 7c2f9694d8
No known key found for this signature in database
GPG Key ID: 19B71B7B7B021D22

@ -0,0 +1,81 @@
--[[
/*
* IMPORTS
*/
--]]
local api = vim.api
--[[
/*
* MODULE
*/
--]]
local Popup = {}
--[[
/*
* CLASS `Popup`
*/
--]]
function Popup.new()
local self = {}
local buffer = api.nvim_create_buf(false, true)
local inputChars = {}
self.window = api.nvim_call_function('libmodal#_winOpen', {buf})
---------------------------
--[[ SUMMARY:
* Close `self.window`
* The `self` is inert after calling this.
]]
---------------------------
function self.close()
api.nvim_win_close(__self.window, false)
buffer = nil
inputChars = nil
self.window = nil
end
---------------------------------
--[[ SUMMARY:
* Update `buffer` with the latest user `inputBytes`.
]]
---------------------------------
function self.refresh(inputBytes)
-- The user simply typed one more character onto the last one.
if #inputBytes == #inputChars + 1 then
local len = #inputBytes
inputChars[len] = string.char(inputBytes[len])
elseif #inputBytes == 1 then -- the input was cleared.
inputChars = {
[1] = string.char(inputBytes[1])
}
else -- other tries to optimize this procedure fellthrough,
-- so do it the hard way.
inputChars = {}
for i, byte in ipairs(inputBytes) do
inputChars[i] = string.char(byte)
end
end
api.nvim_buf_set_lines(
buffer, 0, 1, true,
{table.concat(inputChars)}
)
end
return self
end
--[[
/*
* PUBLICIZE `Popup`
*/
--]]
return Popup

@ -16,79 +16,45 @@ local api = utils.api
*/
--]]
-- Public interface for this module.
local Mode = {}
-- Private class.
local _modeMetaTable = {}
Mode.ParseTable = require('libmodal/src/mode/ParseTable')
Mode.Popup = require('libmodal/src/Mode/Popup')
_modeMetaTable.ParseTable = require('libmodal/src/mode/ParseTable')
local _metaMode = {}
local _HELP = '?'
local _TIMEOUT = {
CHAR = 'ø',
NR = string.byte(_TIMEOUT.CHAR),
LEN = api.nvim_get_option('timeoutlen'),
SEND = function(__self)
['CHAR'] = 'ø',
['LEN'] = api.nvim_get_option('timeoutlen'),
['SEND'] = function(__self)
api.nvim_feedkeys(__self.CHAR, '', false)
end
}
_TIMEOUT.NR = string.byte(_TIMEOUT.CHAR)
--[[
/*
* META `_modeMetaTable`
* META `_metaMode`
*/
--]]
----------------------------------------
--[[ SUMMARY:
* Reset libmodal's internal counter of user input to default.
]]
----------------------------------------
function _modeMetaTable:clearInputBytes()
self._inputBytes = {}
end
-----------------------------------------------
--[[ SUMMARY:
* Update the floating window with the latest user input.
]]
--[[ PARAMS:
* `modeName` => the name of the mode.
* Parse `self._mappings` and see if there is any command to execute.
]]
-----------------------------------------------
function _modeMetaTable:_updateFloatingWindow()
local inputChars = {}
for _, byte in ipairs(self._inputBytes) do
inputChars[#inputChars + 1] = string.char(byte)
end
api.nvim_buf_set_lines(
self._popupBuffer,
0, 1, true, {table.concat(inputChars)}
)
end
--------------------------------------
--[[ SUMMARY:
* Parse the `comboDict` and see if there is any command to execute.
]]
--[[ PARAMS:
* `modeName` => the name of the mode that is currently active.
]]
--------------------------------------
function _modeMetaTable:_comboSelect()
function _metaMode:_checkInputForMapping()
-- Stop any running timers
if self._flushInputTimer then
self._flushInputTimer:stop()
end
-- Append the latest input to the locally stored input history.
self._inputBytes[#self._inputBytes + 1] = Vars.nvim_get(
self._inputBytes, self._modeName
)
self._input.bytes[#self._input.bytes + 1] = self._input:nvimGet(self._name)
-- Get the command based on the users input.
local cmd = self._keybindings:get(self._inputBytes)
local cmd = self._mappings:get(self._input.bytes)
-- Get the type of the command.
local commandType = type(cmd)
@ -96,13 +62,13 @@ function _modeMetaTable:_comboSelect()
-- if there was no matching command
if cmd == false then
if #self._inputBytes < 2 and self._inputBytes[1] == string.byte(_HELP) then
if #self._input.bytes < 2 and self._input.bytes[1] == string.byte(_HELP) then
self._help:show()
end
clearInputBytes = true
self._input:clear()
-- The command was a table, meaning that it MIGHT match.
elseif commandType == globals.TYPE_TBL
and globals.isTrue(self._timeoutsEnabled)
and globals.isTrue(self._timeouts.enabled)
then
-- Create a new timer
@ -112,120 +78,111 @@ function _modeMetaTable:_comboSelect()
-- Send input to interrupt a blocking `getchar`
_TIMEOUT:SEND()
-- if there is a command, execute it.
if cmd[self.ParseTable.CR] then
api.nvim_command(cmd[self.ParseTable.CR])
if cmd[Mode.ParseTable.CR] then
api.nvim_command(cmd[Mode.ParseTable.CR])
end
-- clear input
_clearInputBytes(modeName)
_updateFloatingWindow(modeName)
self._input:clear()
self._popup:refresh(self._input.bytes)
end)
)
-- The command was an actual vim command.
else
api.nvim_command(cmd)
clearInputBytes = true
self._input:clear()
end
if clearInputBytes then
self:_clearInputBytes()
end
self:_updateFloatingWindow()
self._popup:refresh(self._input.bytes)
end
------------------------------------------------
--------------------------
--[[ SUMMARY:
* Set the initial values used for parsing user input as combos.
]]
--[[ PARAMS:
* `modeName` => the name of the mode being initialized.
* `comboTable` => the table of combos being initialized.
* Enter `self`'s mode.
]]
------------------------------------------------
-- TODO
local function _initCombos(modeName, comboTable)
-- Placeholder for timeout value.
local timeoutsEnabled = nil
-- Read the correct timeout variable.
if api.nvim_exists('g', vars.timeouts:name(modeName)) then timeoutsEnabled =
vars.nvim_get(vars.timeouts, modeName)
else timeoutsEnabled =
vars.libmodalTimeouts
--------------------------
function _metaMode:enter()
if self._instruction == globals.TYPE_TBL then
-- Create a timer
self._flushInputTimer = vim.loop.new_timer()
-- Initialize the input history variable.
self._input = {
['bytes'] = {},
----------------------------
--[[ SUMMARY:
* Clear the self.bytes table.
]]
----------------------------
['clear'] = function(__self)
__self.bytes = {}
end
}
-- create a floating window
self._popup = Mode.Popup.new()
end
-- Assign the timeout variable according to `timeoutsEnabled`
self._timeoutsEnabled = timeoutsEnabled
-- create a floating window
local buf = api.nvim_create_buf(false, true)
vars.buffers.instances[modeName] = buf
self._popupWindow = api.nvim_call_function('libmodal#_winOpen', {buf})
--[[ MODE LOOP. ]]
local continueMode = true
while continueMode == true do
-- Try (using pcall) to use the mode.
local noErrors, modeResult = pcall(self._inputLoop, self)
-- Determine if a default `Help` should be created.
if not comboTable[_HELP] then
vars.help.instances[modeName] = utils.Help.new(comboTable, 'KEY MAP')
-- If there were errors, handle them.
if noErrors == false then
utils.showError(modeResult)
continueMode = false
else
continueMode = modeResult
end
end
-- Build the parse tree.
vars.combos.instances[modeName] = mode.ParseTable.new(comboTable)
-- Create a timer
self._flushInputTimer = vim.loop.new_timer()
-- Initialize the input history variable.
_clearInputBytes(modeName)
self:_tearDown()
end
-----------------------------------------------------
----------------------------------
--[[ SUMMARY:
* Remove variables used for a mode.
]]
--[[ PARAMS:
* `modeName` => the name of the mode.
* `self._winState` => the window state prior to mode activation.
* Set the initial values used for parsing user input as combos.
]]
-----------------------------------------------------
function _modeMetaTable:_deconstruct()
if self._flushInputTimer:info()['repeat'] ~= 0 then
self._flushInputTimer:stop()
end
----------------------------------
function _metaMode:_initMappings()
-- Create a variable for whether or not timeouts are enabled.
self._timeouts = Vars.new('timeouts')
if self._popupWindow then
api.nvim_win_close(self._popupWindow, false)
-- Read the correct timeout variable.
if api.nvim_exists('g', self._timeouts:name(modeName)) then self._timeouts.enabled =
self._timeouts:nvimGet(self._name)
else self._timeouts.enabled =
Vars.libmodalTimeouts
end
self._winState:restore()
for k, _ in pairs(self) do
self[k] = nil
-- Determine if a default `Help` should be created.
if not self._instruction[_HELP] then
self._help = utils.Help.new(self._instruction, 'KEY MAP')
end
api.nvim_command("mode | echo '' | call garbagecollect()")
-- Build the parse tree.
self._mappings = Mode.ParseTable.new(self._instruction)
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
]]
--------------------------------------------------------------------------------
-- TODO
local function _modeLoop(handleExitEvents, indicator, modeInstruction, modeName)
------------------------------------
function _metaMode:_inputLoop()
-- 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
if self._exit.supress
and globals.isTrue(self._exit:nvimGet(self._name))
then
return false
end
-- Echo the indicator.
api.nvim_lecho(indicator)
api.nvim_lecho(self._indicator)
-- Capture input.
local userInput = api.nvim_input()
@ -236,72 +193,88 @@ local function _modeLoop(handleExitEvents, indicator, modeInstruction, modeName)
end
-- Set the global input variable to the new input.
vars.nvim_set(vars.input, modeName, userInput)
self._input:nvimSet(self._name, userInput)
-- Make sure that the user doesn't want to exit.
if handleExitEvents and userInput == globals.ESC_NR then
return false
if not self._exit.supress
and userInput == 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
elseif type(self._instruction) == globals.TYPE_TBL then
self:_checkInputForMapping()
else -- the second argument was a function; execute it.
self._instruction()
end
return true
end
---------------------------------
--[[ SUMMARY:
* Remove variables used for a mode.
]]
---------------------------------
function _metaMode:_tearDown()
if self._instruction == globals.TYPE_TBL then
self._flushInputTimer:stop()
self._flushInputTimer = nil
for k, _ in pairs(self._input) do
self._input[k] = nil
end
self._input = nil
self._popup:close()
self._popup = nil
end
self._winState:restore()
end
--[[
/*
* CLASS `Mode`
*/
--]]
------------------------
--[[ SUMMARY:
* Enter a mode.
]]
--[[ PARAMS:
* `args[1]` => the mode name.
* `args[2]` => the mode callback, or mode combo table.
* `args[3]` => optional exit supresion flag.
* `name` => the mode name.
* `instruction` => the mode callback, or mode combo table.
* `...` => optional exit supresion flag.
]]
------------------------
-- TODO
function _modeMetaTable:enter(...)
local args = {...}
--[[ SETUP. ]]
-- Create the indicator for the mode.
local indicator = utils.Indicator.mode(args[1])
-- Grab the state of the window.
self._winState = utils.WindowState.new()
-- Convert the name into one that can be used for variables.
local modeName = string.lower(args[1])
function Mode.new(name, instruction, ...)
-- Inherit the metatable.
self = {}
setmetatable(self, _metaMode)
self.__index = self
-- Define the exit flag
self._exit = Vars.new('exit')
self._exit.supress = (function(optionalValue)
if #optionalValue > 0 then
return globals.isTrue(optionalValue)
else
return false
end
end)(unpack({...}))
-- Determine whether or not this function should handle exiting automatically.
local handleExitEvents = true
if #args > 2 then
handleExitEvents = globals.isFalse(args[3])
end
-- Define other "session" variables.
self._indicator = utils.Indicator.mode(name)
self._instruction = instruction
self._name = name
self._winState = utils.WindowState.new()
-- Determine whether a callback was specified, or a combo table.
if type(args[2]) == globals.TYPE_TBL then
_initCombos(modeName, args[2])
if type(instruction) == globals.TYPE_TBL then
self:_initMappings()
end
--[[ MODE LOOP. ]]
local continueMode = true
while continueMode == true do
-- Try (using pcall) to use the mode.
local noErrors = true
noErrors, continueMode = pcall(_modeLoop,
handleExitEvents, indicator, args[2], modeName
)
-- If there were errors, handle them.
if noErrors == false then
utils.showError(continueMode)
continueMode = false
end
end
return self
end
--[[

@ -17,42 +17,28 @@ local Vars = {}
Vars.libmodalTimeouts = api.nvim_get_var('libmodalTimeouts')
--[[
/*
* HELPERS
* META `_metaVars`
*/
--]]
---------------------------
local _metaVars = {}
-- Instances of variables pertaining to a certain mode.
_metaVars.varName = nil
-------------------------
--[[ SUMMARY:
* Create a new entry in `Vars`
* Get the name of `modeName`s global setting.
]]
--[[ PARAMS:
* `keyName` => the name of the key used to refer to this variable in `Vars`.
* `varName` => the name of the variable as it is stored in vim.
* `modeName` => the name of the mode.
]]
---------------------------
local function new(keyName)
self = {}
-- Instances of variables pertaining to a certain mode.
local varName = 'Mode' .. string.upper(
string.sub(keyName, 0, 1)
) .. string.sub(keyName, 2)
-------------------------
--[[ SUMMARY:
* Get the name of `modeName`s global setting.
]]
--[[ PARAMS:
* `modeName` => the name of the mode.
]]
-------------------------
name = function(modeName)
return string.lower(modeName) .. self._varName
end
return self
-------------------------
function _metaVars:name(modeName)
return string.lower(modeName) .. self._varName
end
------------------------------------
@ -60,16 +46,50 @@ end
* Retrieve a variable value.
]]
--[[ PARAMS:
* `var` => the `Vars.*` table to retrieve the value of.
* `modeName` => the mode name this value is being retrieved for.
]]
------------------------------------
function Vars.nvimGet(var, modeName)
return api.nvim_get_var(var.name(modeName))
function _metaVars:nvimGet(modeName)
return api.nvim_get_var(self:name(modeName))
end
-----------------------------------------
--[[ SUMMARY:
* Set a variable value.
]]
--[[ PARAMS:
* `modeName` => the mode name this value is being retrieved for.
* `val` => the value to set `self`'s Vimscript var to.
]]
-----------------------------------------
function _metaVars:nvimSet(modeName, val)
api.nvim_set_var(self:name(modeName), val)
end
function Vars.nvimSet(var, modeName, val)
api.nvim_set_var(var.name(modeName), val)
--[[
/*
* CLASS `VARS`
*/
--]]
--------------------------
--[[ SUMMARY:
* Create a new entry in `Vars`
]]
--[[ PARAMS:
* `keyName` => the name of the key used to refer to this variable in `Vars`.
]]
--------------------------
function Vars.new(keyName)
self = {}
setmetatable(self, _metaVars)
self.__index = self
self._varName = 'Mode' .. string.upper(
string.sub(keyName, 0, 1)
) .. string.sub(keyName, 2)
return self
end
--[[

Loading…
Cancel
Save