merge!: cleanup the plugin
This was my first plugin. Looking back on it, it was full of bad design decisions (and I have elected to keep most of the user-facing ones for backwards-compatability). This merge tries to uncrustify the plugin by standardizing the documentation, removing much unecessary code, reorganizing the internal structures, and removing references to my own made-up terminology.pull/14/head
parent
822614e7e8
commit
49a02ad692
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
||||
" Register key combos for splitting windows and then closing windows
|
||||
let s:barModeCombos = {
|
||||
\ 'zf': 'split',
|
||||
\ 'zfo': 'vsplit',
|
||||
\ 'zfc': 'q'
|
||||
\}
|
||||
|
||||
" Enter the mode using the key combos.
|
||||
call libmodal#Enter('BAR', s:barModeCombos)
|
@ -0,0 +1,9 @@
|
||||
" Register keymaps for splitting windows and then closing windows
|
||||
let s:barModeKeymaps = {
|
||||
\ 'zf': 'split',
|
||||
\ 'zfo': 'vsplit',
|
||||
\ 'zfc': 'q'
|
||||
\}
|
||||
|
||||
" Enter the mode using the keymaps.
|
||||
call libmodal#Enter('BAR', s:barModeKeymaps)
|
@ -1,21 +1,21 @@
|
||||
-- Imports
|
||||
local libmodal = require('libmodal')
|
||||
local libmodal = require 'libmodal'
|
||||
|
||||
-- create a new layer.
|
||||
local exitFunc = libmodal.layer.enter({
|
||||
n = { -- normal mode mappings
|
||||
gg = { -- remap `gg`
|
||||
rhs = 'G', -- map it to `G`
|
||||
noremap = true, -- don't recursively map.
|
||||
},
|
||||
G = { -- remap `G`
|
||||
rhs = 'gg', -- map it to `gg`
|
||||
noremap = true -- don't recursively map.
|
||||
libmodal.layer.enter(
|
||||
{
|
||||
n = { -- normal mode mappings
|
||||
gg = { -- remap `gg`
|
||||
rhs = 'G', -- map it to `G`
|
||||
noremap = true, -- don't recursively map.
|
||||
},
|
||||
G = { -- remap `G`
|
||||
rhs = 'gg', -- map it to `gg`
|
||||
noremap = true -- don't recursively map.
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
'<Esc>'
|
||||
)
|
||||
|
||||
-- The layer will deactivate in 5 seconds for this demo.
|
||||
vim.loop.new_timer():start(5000, 0, vim.schedule_wrap(
|
||||
function() exitFunc(); print('EXITED.') end
|
||||
))
|
||||
-- The layer will deactivate when you press <Esc>
|
||||
|
@ -1,36 +1,63 @@
|
||||
--[[
|
||||
/*
|
||||
* MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
local libmodal = require('libmodal/src')
|
||||
|
||||
--[[
|
||||
/*
|
||||
* MIRRORS
|
||||
*/
|
||||
--]]
|
||||
|
||||
libmodal.layer = {enter = function(keymap)
|
||||
local layer = libmodal.Layer.new(keymap)
|
||||
layer:enter()
|
||||
return function() layer:exit() end
|
||||
end}
|
||||
|
||||
libmodal.mode = {enter = function(name, instruction, ...)
|
||||
libmodal.Mode.new(name, instruction, ...):enter()
|
||||
end}
|
||||
|
||||
libmodal.prompt = {enter = function(name, instruction, ...)
|
||||
libmodal.Prompt.new(name, instruction, ...):enter()
|
||||
end}
|
||||
|
||||
|
||||
--[[
|
||||
/*
|
||||
* PUBLICIZE MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
return libmodal
|
||||
-- TODO: remove the __index here after a period of time to let people remove `libmodal.Layer` from their configurations
|
||||
return setmetatable(
|
||||
{
|
||||
layer =
|
||||
{
|
||||
--- Enter a new layer.
|
||||
--- @param keymap table the keymaps (e.g. `{n = {gg = {rhs = 'G', silent = true}}}`)
|
||||
--- @param exit_char nil|string a character which can be used to exit the layer from normal mode.
|
||||
--- @return function|nil exit a function to exit the layer, or `nil` if `exit_char` is passed
|
||||
enter = function(keymap, exit_char)
|
||||
local layer = require('libmodal/src/Layer').new(keymap)
|
||||
layer:enter()
|
||||
|
||||
if exit_char then
|
||||
layer:map('n', exit_char, function() layer:exit() end, {})
|
||||
else
|
||||
return function() layer:exit() end
|
||||
end
|
||||
end,
|
||||
|
||||
--- Create a new layer.
|
||||
--- @param keymap table the keymaps (e.g. `{n = {gg = {rhs = 'G', silent = true}}}`)
|
||||
--- @return libmodal.Layer
|
||||
new = function(keymap)
|
||||
return require('libmodal/src/Layer').new(keymap)
|
||||
end,
|
||||
},
|
||||
|
||||
mode =
|
||||
{
|
||||
--- Enter a mode.
|
||||
--- @param name string the name of the mode.
|
||||
--- @param instruction function|string|table a Lua function, keymap dictionary, Vimscript command.
|
||||
enter = function(name, instruction, supress_exit)
|
||||
require('libmodal/src/Mode').new(name, instruction, supress_exit):enter()
|
||||
end
|
||||
},
|
||||
|
||||
prompt =
|
||||
{
|
||||
--- Enter a prompt.
|
||||
--- @param name string the name of the prompt
|
||||
--- @param instruction function|table<string, function|string> what to do with user input
|
||||
--- @param user_completions table<string>|nil a list of possible inputs, provided by the user
|
||||
enter = function(name, instruction, user_completions)
|
||||
require('libmodal/src/Prompt').new(name, instruction, user_completions):enter()
|
||||
end
|
||||
}
|
||||
},
|
||||
{
|
||||
__index = function(tbl, key)
|
||||
if key ~= 'Layer' then
|
||||
return rawget(tbl, key)
|
||||
else
|
||||
require('libmodal/src/utils/api').nvim_show_err(
|
||||
require('libmodal/src/globals').DEFAULT_ERROR_TITLE,
|
||||
'`libmodal.Layer` is deprecated in favor of `libmodal.layer`. It will work FOR NOW, but uncapitalize that `L` please :)'
|
||||
)
|
||||
return require 'libmodal/src/Layer'
|
||||
end
|
||||
end,
|
||||
}
|
||||
)
|
||||
|
@ -1,37 +0,0 @@
|
||||
--[[
|
||||
/*
|
||||
* MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
local HighlightSegment = {}
|
||||
|
||||
--[[
|
||||
/*
|
||||
* CLASS `HighlightSegment`
|
||||
*/
|
||||
--]]
|
||||
|
||||
--------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Create a new `Indicator.HighlightSegment`.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `hlgroup` => The `highlight-group` to be used for this `Indicator.HighlightSegment`.
|
||||
* `str` => The text for this `Indicator.HighlightSegment`.
|
||||
]]
|
||||
--------------------------------
|
||||
function HighlightSegment.new(hlgroup, str)
|
||||
return {
|
||||
hl = hlgroup,
|
||||
str = str
|
||||
}
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* PUBLICIZE MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
return HighlightSegment
|
@ -1,71 +0,0 @@
|
||||
--[[
|
||||
/*
|
||||
* MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
local Indicator = {
|
||||
HighlightSegment = require('libmodal/src/Indicator/HighlightSegment')
|
||||
}
|
||||
|
||||
-- highlight group names
|
||||
local _HL_GROUP_MODE = 'LibmodalPrompt'
|
||||
local _HL_GROUP_PROMPT = 'LibmodalStar'
|
||||
|
||||
-- predefined segments
|
||||
local _SEGMENT_MODE_BEGIN = Indicator.HighlightSegment.new(_HL_GROUP_MODE, '-- ')
|
||||
local _SEGMENT_MODE_END = Indicator.HighlightSegment.new(_HL_GROUP_MODE, ' --')
|
||||
local _PROMPT_TEMPLATE = {'* ', ' > '}
|
||||
|
||||
--[[
|
||||
/*
|
||||
* META `Indicator`
|
||||
*/
|
||||
--]]
|
||||
|
||||
--[[
|
||||
/*
|
||||
* CLASS `Indicator`
|
||||
*/
|
||||
--]]
|
||||
|
||||
---------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Create a new `Indicator` for a mode.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `modeName` => the name of the mode that this `Indicator` is for.
|
||||
]]
|
||||
---------------------------------
|
||||
function Indicator.mode(modeName)
|
||||
return {
|
||||
[1] = _SEGMENT_MODE_BEGIN,
|
||||
[2] = Indicator.HighlightSegment.new(
|
||||
_HL_GROUP_MODE, tostring(modeName)
|
||||
),
|
||||
[3] = _SEGMENT_MODE_END,
|
||||
}
|
||||
end
|
||||
|
||||
-----------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Create a new `Indicator` for a prompt.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `modeName` => the name of the mode that this `Indicator` is for.
|
||||
]]
|
||||
-----------------------------------
|
||||
function Indicator.prompt(modeName)
|
||||
return Indicator.HighlightSegment.new(
|
||||
_HL_GROUP_PROMPT,
|
||||
table.concat(_PROMPT_TEMPLATE, modeName)
|
||||
)
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* PUBLICIZE MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
return Indicator
|
@ -1,253 +1,135 @@
|
||||
--[[
|
||||
/*
|
||||
* IMPORTS
|
||||
*/
|
||||
--]]
|
||||
|
||||
local api = vim.api
|
||||
local libmodal_api = require('libmodal/src/utils/api')
|
||||
|
||||
--[[
|
||||
/*
|
||||
* MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
local Layer = {TYPE = 'libmodal-layer'}
|
||||
|
||||
local _BUFFER_CURRENT = 0
|
||||
local _ERR_NO_MAP = 'E5555: API call: E31: No such mapping'
|
||||
local _RESTORED = nil
|
||||
|
||||
local function convertKeymap(keymapEntry)
|
||||
local lhs = keymapEntry.lhs
|
||||
keymapEntry.lhs = nil
|
||||
|
||||
return {lhs, keymapEntry}
|
||||
--- Remove and return the right-hand side of a `keymap`.
|
||||
--- @param keymap table the keymap to unpack
|
||||
--- @return string lhs, table options
|
||||
local function unpack_keymap_lhs(keymap)
|
||||
local lhs = keymap.lhs
|
||||
keymap.lhs = nil
|
||||
|
||||
return lhs, keymap
|
||||
end
|
||||
|
||||
local function deconvertKeymap(convertedKeymap)
|
||||
local rhs = convertedKeymap.rhs
|
||||
convertedKeymap.rhs = nil
|
||||
--- Remove and return the right-hand side of a `keymap`.
|
||||
--- @param keymap table the keymap to unpack
|
||||
--- @return function|string rhs, table options
|
||||
local function unpack_keymap_rhs(keymap)
|
||||
local rhs = keymap.rhs
|
||||
keymap.rhs = nil
|
||||
|
||||
return {rhs, convertedKeymap}
|
||||
return rhs, keymap
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* META `Layer`
|
||||
*/
|
||||
--]]
|
||||
|
||||
local _metaLayer = require('libmodal/src/classes').new(Layer.TYPE)
|
||||
|
||||
---------------------------
|
||||
--[[ SUMMARY:
|
||||
* Enter the `Layer`.
|
||||
* Only activates for the current buffer.
|
||||
]]
|
||||
---------------------------
|
||||
function _metaLayer:enter()
|
||||
if self._priorKeymap then
|
||||
--- @class libmodal.Layer
|
||||
--- @field private existing_keymap table the keymaps to restore when exiting the mode; generated automatically
|
||||
--- @field private layer_keymap table the keymaps to apply when entering the mode; provided by user
|
||||
local Layer = require('libmodal/src/utils/classes').new()
|
||||
|
||||
--- Apply the `Layer`'s keymaps buffer.
|
||||
function Layer:enter()
|
||||
if self.existing_keymap then
|
||||
error('This layer has already been entered. `:exit()` before entering again.')
|
||||
end
|
||||
|
||||
-- add local aliases.
|
||||
local layerKeymap = self._keymap
|
||||
local priorKeymap = {}
|
||||
|
||||
--[[ iterate over the new mappings to both:
|
||||
1. Populate `priorKeymap`
|
||||
2. Map the `layerKeymap` to the buffer. ]]
|
||||
for mode, newMappings in pairs(layerKeymap) do
|
||||
-- if `mode` key has not yet been made for `priorKeymap`.
|
||||
if not priorKeymap[mode] then
|
||||
priorKeymap[mode] = {}
|
||||
self.existing_keymap = {}
|
||||
|
||||
--[[ iterate over the new keymaps to both:
|
||||
1. Populate a list of keymaps which will be overwritten to `existing_keymap`
|
||||
2. Apply the layer's keymappings. ]]
|
||||
for mode, new_keymaps in pairs(self.layer_keymap) do
|
||||
-- if `mode` key has not yet been made for `existing_keymap`.
|
||||
if not self.existing_keymap[mode] then
|
||||
self.existing_keymap[mode] = {}
|
||||
end
|
||||
|
||||
-- store the previously mapped keys
|
||||
for _, bufMap in ipairs(api.nvim_buf_get_keymap(_BUFFER_CURRENT, mode)) do
|
||||
-- if the new mappings would overwrite this one
|
||||
if newMappings[bufMap.lhs] then
|
||||
for _, existing_keymap in ipairs(vim.api.nvim_get_keymap(mode)) do
|
||||
-- if the new keymaps would overwrite this one
|
||||
if new_keymaps[existing_keymap.lhs] then
|
||||
-- remove values so that it is in line with `nvim_set_keymap`.
|
||||
local lhs, keymap = unpack(convertKeymap(bufMap))
|
||||
priorKeymap[mode][lhs] = keymap
|
||||
local lhs, keymap = unpack_keymap_lhs(existing_keymap)
|
||||
self.existing_keymap[mode][lhs] = keymap
|
||||
end
|
||||
end
|
||||
|
||||
-- add the new mappings
|
||||
for lhs, newMapping in pairs(newMappings) do
|
||||
local rhs, options = unpack(deconvertKeymap(newMapping))
|
||||
api.nvim_buf_set_keymap(_BUFFER_CURRENT, mode, lhs, rhs, options)
|
||||
-- add the new keymaps
|
||||
for lhs, new_keymap in pairs(new_keymaps) do
|
||||
local rhs, options = unpack_keymap_rhs(new_keymap)
|
||||
vim.keymap.set(mode, lhs, rhs, options)
|
||||
end
|
||||
end
|
||||
|
||||
self._priorKeymap = priorKeymap
|
||||
end
|
||||
|
||||
--------------------------------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Add a mapping to the mode.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `mode` => the mode that this mapping for.
|
||||
* `lhs` => the left hand side of the mapping.
|
||||
* `rhs` => the right hand side of the mapping.
|
||||
* `options` => options for the mapping.
|
||||
]]
|
||||
--[[ SEE ALSO:
|
||||
* `nvim_buf_set_keymap()`
|
||||
]]
|
||||
--------------------------------------------------------
|
||||
function _metaLayer:_mapToBuffer(mode, lhs, rhs, options)
|
||||
local priorKeymap = self._priorKeymap
|
||||
|
||||
if not priorKeymap then error(
|
||||
"You can't map to a buffer without activating the layer first."
|
||||
) end
|
||||
|
||||
if not priorKeymap[mode][lhs] then -- the mapping's state has not been saved.
|
||||
for _, bufMap in
|
||||
ipairs(api.nvim_buf_get_keymap(_BUFFER_CURRENT, mode))
|
||||
do -- check if it exists in the buffer
|
||||
if bufMap.lhs == lhs then -- add it to the undo list
|
||||
priorKeymap[mode][lhs] = unpack(convertKeymap(bufMap))
|
||||
break
|
||||
--- Add a keymap to the mode.
|
||||
--- @param mode string the mode that this keymap for.
|
||||
--- @param lhs string the left hand side of the keymap.
|
||||
--- @param rhs function|string the right hand side of the keymap.
|
||||
--- @param options table options for the keymap.
|
||||
--- @see `vim.keymap.set`
|
||||
function Layer:map(mode, lhs, rhs, options)
|
||||
if self.existing_keymap then -- the layer has been activated
|
||||
if not self.existing_keymap[mode][lhs] then -- the keymap's state has not been saved.
|
||||
for _, existing_keymap in ipairs(vim.api.nvim_get_keymap(mode)) do -- check if it has a keymap
|
||||
if existing_keymap.lhs == lhs then -- add it to the undo list
|
||||
existing_keymap.lhs = nil
|
||||
self.existing_keymap[mode][lhs] = existing_keymap
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- map the `lhs` to `rhs` in `mode` with `options` for the current buffer.
|
||||
vim.keymap.set(mode, lhs, rhs, options)
|
||||
end
|
||||
|
||||
-- map the `lhs` to `rhs` in `mode` with `options` for the current buffer.
|
||||
api.nvim_buf_set_keymap(_BUFFER_CURRENT, mode, lhs, rhs, options)
|
||||
-- add the new mapping to the layer's keymap
|
||||
options.rhs = rhs
|
||||
self.layer_keymap[mode][lhs] = options
|
||||
end
|
||||
|
||||
------------------------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Add a mapping to the mode.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `mode` => the mode that this mapping for.
|
||||
* `lhs` => the left hand side of the mapping.
|
||||
* `rhs` => the right hand side of the mapping.
|
||||
* `options` => options for the mapping.
|
||||
]]
|
||||
--[[ SEE ALSO:
|
||||
* `nvim_buf_set_keymap()`
|
||||
]]
|
||||
------------------------------------------------
|
||||
function _metaLayer:map(mode, lhs, rhs, options)
|
||||
if self._priorKeymap then -- the layer has been activated.
|
||||
self:_mapToBuffer(mode, lhs, rhs, options)
|
||||
--- Restore one keymapping to its original state.
|
||||
--- @param mode string the mode of the keymap.
|
||||
--- @param lhs string the keys which invoke the keymap.
|
||||
--- @see `vim.api.nvim_del_keymap`
|
||||
function Layer:unmap(mode, lhs)
|
||||
if not self.existing_keymap then
|
||||
error("Don't call this function before activating the layer; just remove from the keymap passed to `Layer.new` instead.")
|
||||
end
|
||||
|
||||
-- add the new mapping to the keymap
|
||||
self._keymap[mode][lhs] = vim.tbl_extend('force',
|
||||
options, {rhs = rhs}
|
||||
)
|
||||
end
|
||||
|
||||
----------------------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Undo a mapping after `enter()`.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `mode` => the mode to map (e.g. `n`, `i`).
|
||||
* `lhs` => the mapping to undo.
|
||||
]]
|
||||
----------------------------------------------
|
||||
function _metaLayer:_unmapFromBuffer(mode, lhs)
|
||||
local priorKeymap = self._priorKeymap
|
||||
local priorMapping = self._priorKeymap[mode][lhs]
|
||||
|
||||
if not priorKeymap then error(
|
||||
"You can't undo a map from a buffer without activating the layer first."
|
||||
) end
|
||||
|
||||
if priorMapping then -- there is an older mapping to go back to.
|
||||
-- undo the mapping
|
||||
local rhs, deconvertedKeymap = unpack(deconvertKeymap(priorMapping))
|
||||
api.nvim_buf_set_keymap(_BUFFER_CURRENT, mode, lhs, rhs, deconvertedKeymap)
|
||||
|
||||
-- set the prior mapping as restored.
|
||||
priorKeymap[mode][lhs] = _RESTORED
|
||||
if self.existing_keymap[mode][lhs] then -- there is an older keymap to go back to, so undo this layer_keymap
|
||||
local rhs, options = unpack_keymap_rhs(self.existing_keymap[mode][lhs])
|
||||
vim.keymap.set(mode, lhs, rhs, options)
|
||||
else
|
||||
-- just delete the buffer mapping.
|
||||
local noErrors, err = pcall(api.nvim_buf_del_keymap, _BUFFER_CURRENT, mode, lhs)
|
||||
-- just make the keymap go back to default
|
||||
local no_errors, err = pcall(vim.api.nvim_del_keymap, mode, lhs)
|
||||
|
||||
if not noErrors and err ~= _ERR_NO_MAP then
|
||||
if not no_errors and err ~= 'E31: No such mapping' then
|
||||
print(err)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Remove a mapping from the mode.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `mode` => the mode that this mapping for.
|
||||
* `lhs` => the left hand side of the mapping.
|
||||
]]
|
||||
--[[ SEE ALSO:
|
||||
* `nvim_buf_del_keymap()`
|
||||
]]
|
||||
------------------------------------
|
||||
function _metaLayer:unmap(mode, lhs)
|
||||
-- unmap for the buffer too, if the layer is activated.
|
||||
if self._priorKeymap then
|
||||
self:_unmapFromBuffer(mode, lhs)
|
||||
end
|
||||
|
||||
-- remove the mapping from the internal keymap
|
||||
self._keymap[mode][lhs] = _RESTORED
|
||||
-- remove this keymap from the list of ones to restore
|
||||
self.existing_keymap[mode][lhs] = nil
|
||||
end
|
||||
|
||||
--------------------------
|
||||
--[[ SUMMARY:
|
||||
* Exit the layer.
|
||||
]]
|
||||
--------------------------
|
||||
function _metaLayer:exit()
|
||||
if not self._priorKeymap then
|
||||
--- Exit the layer, restoring all previous keymaps.
|
||||
function Layer:exit()
|
||||
if not self.existing_keymap then
|
||||
error('This layer has not been entered yet.')
|
||||
end
|
||||
|
||||
for mode, mappings in pairs(self._keymap) do
|
||||
for lhs, _ in pairs(mappings) do
|
||||
self:_unmapFromBuffer(mode, lhs)
|
||||
for mode, keymaps in pairs(self.layer_keymap) do
|
||||
for lhs, _ in pairs(keymaps) do
|
||||
self:unmap(mode, lhs)
|
||||
end
|
||||
end
|
||||
self._priorKeymap = _RESTORED
|
||||
self.existing_keymap = nil
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* CLASS `Layer`
|
||||
*/
|
||||
--]]
|
||||
|
||||
-----------------------------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Create a new `Layer` for the buffer-local keymap.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `mappings` => the list of user mappings to replace.
|
||||
]]
|
||||
--[[ RETURNS:
|
||||
* A new `Layer`.
|
||||
]]
|
||||
-----------------------------------------------------
|
||||
function Layer.new(keymap)
|
||||
return setmetatable(
|
||||
{_keymap = keymap},
|
||||
_metaLayer
|
||||
)
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* PUBLICIZE `Layer`
|
||||
*/
|
||||
--]]
|
||||
|
||||
return Layer
|
||||
return
|
||||
{
|
||||
--- @param keymap table the keymaps (e.g. `{n = {gg = {rhs = 'G', silent = true}}}`)
|
||||
--- @return libmodal.Layer
|
||||
new = function(keymap)
|
||||
return setmetatable({layer_keymap = keymap}, Layer)
|
||||
end
|
||||
}
|
||||
|
@ -1,188 +1,153 @@
|
||||
--[[/* IMPORTS */]]
|
||||
|
||||
local globals = require('libmodal/src/globals')
|
||||
local utils = require('libmodal/src/utils')
|
||||
local Vars = require('libmodal/src/Vars')
|
||||
|
||||
--[[/* MODULE */]]
|
||||
|
||||
local Prompt = {TYPE = 'libmodal-prompt'}
|
||||
|
||||
local _HELP = 'help'
|
||||
local _REPLACEMENTS = {
|
||||
local globals = require 'libmodal/src/globals'
|
||||
local utils = require 'libmodal/src/utils'
|
||||
|
||||
--- @class libmodal.Prompt
|
||||
--- @field private completions table<string>|nil
|
||||
--- @field private exit libmodal.utils.Vars
|
||||
--- @field private help libmodal.utils.Help|nil
|
||||
--- @field private indicator libmodal.utils.Indicator
|
||||
--- @field private input libmodal.utils.Vars
|
||||
--- @field private instruction function|table<string, function|string>
|
||||
--- @field private name string
|
||||
local Prompt = utils.classes.new()
|
||||
|
||||
local HELP = 'help'
|
||||
local REPLACEMENTS =
|
||||
{
|
||||
'(', ')', '[', ']', '{', '}',
|
||||
'=', '+', '<', '>', '^',
|
||||
',', '/', ':', '?', '@', '!', '$', '*', '.', '%', '&', '\\',
|
||||
}
|
||||
for i, replacement in ipairs(_REPLACEMENTS) do
|
||||
_REPLACEMENTS[i], _ = vim.pesc(replacement)
|
||||
|
||||
for i, replacement in ipairs(REPLACEMENTS) do
|
||||
REPLACEMENTS[i], _ = vim.pesc(replacement)
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* META `Prompt`
|
||||
*/
|
||||
--]]
|
||||
|
||||
local _metaPrompt = require('libmodal/src/classes').new(Prompt.TYPE)
|
||||
|
||||
---------------------------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Execute the specified instruction based on user input.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `userInput` => the input from the user, used to determine how to execute the `self._instruction`.
|
||||
]]
|
||||
---------------------------------------------------
|
||||
function _metaPrompt:_executeInstruction(userInput)
|
||||
-- get the instruction for the mode.
|
||||
local instruction = self._instruction
|
||||
|
||||
if type(instruction) == globals.TYPE_TBL then -- The instruction is a command table.
|
||||
if instruction[userInput] then -- There is a defined command for the input.
|
||||
local to_execute = instruction[userInput]
|
||||
--- Execute the instruction specified by the `user_input`.
|
||||
--- @param user_input string
|
||||
function Prompt:execute_instruction(user_input)
|
||||
if type(self.instruction) == globals.TYPE_TBL then -- The self.instruction is a command table.
|
||||
if self.instruction[user_input] then -- There is a defined command for the input.
|
||||
local to_execute = self.instruction[user_input]
|
||||
if type(to_execute) == globals.TYPE_FUNC then
|
||||
to_execute()
|
||||
else
|
||||
vim.api.nvim_command(instruction[userInput])
|
||||
vim.api.nvim_command(to_execute)
|
||||
end
|
||||
elseif userInput == _HELP then -- The user did not define a 'help' command, so use the default.
|
||||
self._help:show()
|
||||
elseif user_input == HELP then -- The user did not define a 'help' command, so use the default.
|
||||
self.help:show()
|
||||
else -- show an error.
|
||||
utils.api.nvim_show_err(globals.DEFAULT_ERROR_TITLE, 'Unknown command')
|
||||
end
|
||||
elseif type(instruction) == globals.TYPE_STR then -- The instruction is a function.
|
||||
vim.fn[instruction]()
|
||||
else -- attempt to call the instruction.
|
||||
instruction()
|
||||
elseif type(self.instruction) == globals.TYPE_STR then -- The self.instruction is a function.
|
||||
vim.fn[self.instruction]()
|
||||
else -- attempt to call the self.instruction.
|
||||
self.instruction()
|
||||
end
|
||||
end
|
||||
|
||||
---------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Loop to get user input with `input()`.
|
||||
]]
|
||||
---------------------------------
|
||||
function _metaPrompt:_inputLoop()
|
||||
-- If the mode is not handling exit events automatically and the global exit var is true.
|
||||
if self.exit.supress and globals.is_true(self.exit:nvimGet()) then
|
||||
return false
|
||||
end
|
||||
|
||||
--- Get more input from the user.
|
||||
--- @return boolean more_input
|
||||
function Prompt:get_user_input()
|
||||
-- clear previous `echo`s.
|
||||
utils.api.nvim_redraw()
|
||||
|
||||
-- determine what to do with the input
|
||||
local function userInputCallback(userInput)
|
||||
if userInput and string.len(userInput) > 0 then -- The user actually entered something.
|
||||
self.input:nvimSet(userInput)
|
||||
self:_executeInstruction(userInput)
|
||||
else -- indicate we want to leave the prompt
|
||||
return false
|
||||
end
|
||||
local continue_prompt -- will set to true `true` if looping this prompt again
|
||||
|
||||
--- 1. Set `g:<mode_name>ModeInput` to `user_input`
|
||||
--- 2. Execute any commands indicated by `user_input`
|
||||
--- 3. Read `g:<mode_name>ModeExit` to see if we should `continue_prompt`
|
||||
--- @param user_input string
|
||||
local function user_input_callback(user_input)
|
||||
if user_input and string.len(user_input) > 0 then -- the user actually entered something.
|
||||
self.input:set(user_input)
|
||||
self:execute_instruction(user_input)
|
||||
|
||||
return true
|
||||
local should_exit = self.exit:get()
|
||||
if should_exit ~= nil then
|
||||
continue_prompt = not should_exit
|
||||
end
|
||||
else -- the user entered nothing.
|
||||
continue_prompt = false
|
||||
end
|
||||
end
|
||||
|
||||
-- echo the highlighting
|
||||
vim.api.nvim_command('echohl ' .. self.indicator.hl)
|
||||
|
||||
-- set the user input variable
|
||||
if self._completions then
|
||||
if self.completions then
|
||||
vim.api.nvim_command('echo "' .. self.indicator.str .. '"')
|
||||
return vim.ui.select(self._completions, {}, userInputCallback)
|
||||
vim.ui.select(self.completions, {}, user_input_callback)
|
||||
else
|
||||
return vim.ui.input({prompt = self.indicator.str}, userInputCallback)
|
||||
vim.ui.input({prompt = self.indicator.str}, user_input_callback)
|
||||
end
|
||||
|
||||
return continue_prompt == nil and true or continue_prompt
|
||||
end
|
||||
|
||||
----------------------------
|
||||
--[[ SUMMARY:
|
||||
* Enter a prompt 'mode'.
|
||||
]]
|
||||
----------------------------
|
||||
function _metaPrompt:enter()
|
||||
--- Enter the prompt.
|
||||
function Prompt:enter()
|
||||
-- enter the mode using a loop.
|
||||
local continueMode = true
|
||||
while continueMode do
|
||||
local noErrors, promptResult = pcall(self._inputLoop, self)
|
||||
local continue_mode = true
|
||||
while continue_mode do
|
||||
local no_errors, prompt_result = pcall(self.get_user_input, self)
|
||||
|
||||
-- if there were errors.
|
||||
if not noErrors then
|
||||
utils.show_error(promptResult)
|
||||
continueMode = true
|
||||
if not no_errors then
|
||||
utils.show_error(prompt_result)
|
||||
continue_mode = false
|
||||
else
|
||||
continueMode = promptResult
|
||||
continue_mode = prompt_result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* CLASS `Prompt`
|
||||
*/
|
||||
--]]
|
||||
|
||||
-------------------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Enter a prompt.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `name` => the prompt name.
|
||||
* `instruction` => the prompt callback, or mode command table.
|
||||
* `...` => a completions table.
|
||||
]]
|
||||
-------------------------------------------
|
||||
function Prompt.new(name, instruction, ...)
|
||||
name = vim.trim(name)
|
||||
|
||||
local self = setmetatable(
|
||||
{
|
||||
exit = Vars.new('exit', name),
|
||||
indicator = require('libmodal/src/Indicator').prompt(name),
|
||||
input = require('libmodal/src/Vars').new('input', name),
|
||||
_instruction = instruction,
|
||||
_name = name
|
||||
},
|
||||
_metaPrompt
|
||||
)
|
||||
|
||||
-- get the arguments
|
||||
local userCompletions, supressExit = unpack({...})
|
||||
|
||||
self.exit.supress = supressExit or false
|
||||
|
||||
-- get the completion list.
|
||||
if type(instruction) == globals.TYPE_TBL then -- unload the keys of the mode command table.
|
||||
-- Create one if the user specified a command table.
|
||||
local completions = {}
|
||||
local containedHelp = false
|
||||
|
||||
for command, _ in pairs(instruction) do
|
||||
completions[#completions + 1] = command
|
||||
if command == _HELP then containedHelp = true
|
||||
return
|
||||
{
|
||||
--- Enter a prompt.
|
||||
--- @param name string the name of the prompt
|
||||
--- @param instruction function|table<string, function|string> what to do with user input
|
||||
--- @param user_completions table<string>|nil a list of possible inputs, provided by the user
|
||||
--- @return libmodal.Prompt
|
||||
new = function(name, instruction, user_completions)
|
||||
name = vim.trim(name)
|
||||
|
||||
local self = setmetatable(
|
||||
{
|
||||
exit = utils.Vars.new('exit', name),
|
||||
indicator = utils.Indicator.prompt(name),
|
||||
input = utils.Vars.new('input', name),
|
||||
instruction = instruction,
|
||||
name = name
|
||||
},
|
||||
Prompt
|
||||
)
|
||||
|
||||
-- get the completion list.
|
||||
if type(instruction) == globals.TYPE_TBL then -- unload the keys of the mode command table.
|
||||
-- Create one if the user specified a command table.
|
||||
local completions = {}
|
||||
local contained_help = false
|
||||
|
||||
for command, _ in pairs(instruction) do
|
||||
completions[#completions + 1] = command
|
||||
if command == HELP then
|
||||
contained_help = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not containedHelp then -- assign it.
|
||||
completions[#completions + 1] = _HELP
|
||||
self._help = utils.Help.new(instruction, 'COMMAND')
|
||||
if not contained_help then -- assign it.
|
||||
completions[#completions + 1] = HELP
|
||||
self.help = utils.Help.new(instruction, 'COMMAND')
|
||||
end
|
||||
|
||||
self.completions = completions
|
||||
elseif user_completions then
|
||||
-- Use the table that the user gave.
|
||||
self.completions = user_completions
|
||||
end
|
||||
|
||||
self._completions = completions
|
||||
elseif userCompletions then
|
||||
-- Use the table that the user gave.
|
||||
self._completions = userCompletions
|
||||
return self
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
/*
|
||||
* PUBLICIZE MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
return Prompt
|
||||
}
|
||||
|
@ -1,111 +0,0 @@
|
||||
--[[/* IMPORTS */]]
|
||||
|
||||
local api = vim.api
|
||||
|
||||
--[[
|
||||
/*
|
||||
* MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
local Vars = {
|
||||
libmodalTimeouts = vim.g.libmodalTimeouts,
|
||||
TYPE = 'libmodal-vars'
|
||||
}
|
||||
|
||||
--[[
|
||||
/*
|
||||
* META `_metaVars`
|
||||
*/
|
||||
--]]
|
||||
|
||||
local _metaVars = require('libmodal/src/classes').new(Vars.TYPE)
|
||||
|
||||
---------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Get the name of `modeName`s global setting.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `modeName` => the name of the mode.
|
||||
]]
|
||||
---------------------------------
|
||||
function _metaVars:name()
|
||||
return self._modeName .. self._varName
|
||||
end
|
||||
|
||||
------------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Retrieve a variable value.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `modeName` => the mode name this value is being retrieved for.
|
||||
]]
|
||||
------------------------------------
|
||||
function _metaVars:nvimGet()
|
||||
return vim.g[self:name()]
|
||||
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(val)
|
||||
vim.g[self:name()] = val
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* 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, modeName)
|
||||
local self = setmetatable({}, _metaVars)
|
||||
|
||||
local function noSpaces(strWithSpaces, firstLetterModifier)
|
||||
local splitStr = vim.split(
|
||||
string.gsub(strWithSpaces, vim.pesc('_'), vim.pesc(' ')),
|
||||
' '
|
||||
)
|
||||
|
||||
local function camelCase(str, func)
|
||||
return func(string.sub(str, 0, 1) or '')
|
||||
.. string.lower(string.sub(str, 2) or '')
|
||||
end
|
||||
|
||||
splitStr[1] = camelCase(splitStr[1], firstLetterModifier)
|
||||
|
||||
for i = 2, #splitStr do splitStr[i] =
|
||||
camelCase(splitStr[i], string.upper)
|
||||
end
|
||||
|
||||
return table.concat(splitStr)
|
||||
end
|
||||
|
||||
self._modeName = noSpaces(modeName, string.lower)
|
||||
|
||||
self._varName = 'Mode' .. noSpaces(keyName, string.upper)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* PUBLICIZE MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
return Vars
|
@ -1,37 +0,0 @@
|
||||
return {
|
||||
-------------------------
|
||||
--[[ SUMMARY:
|
||||
* Define a class-metatable.
|
||||
]]
|
||||
--[[
|
||||
* `name` => the name of the class.
|
||||
* `base` => the base class to use (`{}` by default).
|
||||
]]
|
||||
-------------------------
|
||||
new = function(name, ...)
|
||||
-- set self to `base`, or `{}` if nil.
|
||||
local self = unpack({...}) or {}
|
||||
|
||||
-- set `__index`.
|
||||
if not self.__index then
|
||||
self.__index = self
|
||||
end
|
||||
|
||||
-- set `__type`.
|
||||
self.__type = name
|
||||
|
||||
return self
|
||||
end,
|
||||
|
||||
------------------
|
||||
--[[ SUMMARY:
|
||||
* Get the type of some value `v`, if it has one.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `v` => the value to get the type of.
|
||||
]]
|
||||
------------------
|
||||
type = function(v)
|
||||
return v.__type or type(v)
|
||||
end
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
--[[/* MODULE */]]
|
||||
|
||||
local Popup = require('libmodal/src/classes').new(
|
||||
'libmodal-popup',
|
||||
{config = {
|
||||
anchor = 'SW',
|
||||
col = vim.go.columns - 1,
|
||||
focusable = false,
|
||||
height = 1,
|
||||
relative = 'editor',
|
||||
row = vim.go.lines - vim.go.cmdheight - 1,
|
||||
style = 'minimal',
|
||||
width = 1
|
||||
}}
|
||||
)
|
||||
|
||||
----------------------------
|
||||
--[[ SUMMARY:
|
||||
* Check if `window` is non-`nil` and is valid.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `window` => the window number.
|
||||
]]
|
||||
--[[ RETURNS:
|
||||
* `true` => `window` is non-`nil` and is valid
|
||||
* `false` => otherwise
|
||||
]]
|
||||
----------------------------
|
||||
local function valid(window)
|
||||
return window and vim.api.nvim_win_is_valid(window)
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* META `Popup`
|
||||
*/
|
||||
--]]
|
||||
|
||||
local _metaPopup = require('libmodal/src/classes').new(Popup.TYPE)
|
||||
|
||||
-------------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Close `self.window`
|
||||
* The `self` is inert after calling this.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `keep_buffer` => whether or not to keep `self.buffer`.
|
||||
]]
|
||||
-------------------------------------
|
||||
function _metaPopup:close(keepBuffer)
|
||||
if valid(self.window) then
|
||||
vim.api.nvim_win_close(self.window, false)
|
||||
end
|
||||
|
||||
self.window = nil
|
||||
|
||||
if not keepBuffer then
|
||||
self.buffer = nil
|
||||
self._inputChars = nil
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------
|
||||
--[[ SUMMARY:
|
||||
* Open the popup.
|
||||
* If the popup was already open, close it and re-open it.
|
||||
]]
|
||||
--------------------------
|
||||
function _metaPopup:open(config)
|
||||
if not config then config = Popup.config end
|
||||
|
||||
if valid(self.window) then
|
||||
config = vim.tbl_extend('keep', config, vim.api.nvim_win_get_config(self.window))
|
||||
self:close(true)
|
||||
end
|
||||
|
||||
self.window = vim.api.nvim_open_win(self.buffer, false, config)
|
||||
end
|
||||
|
||||
---------------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Update `buffer` with the latest user `inputBytes`.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `inputBytes` => the charaters to fill the popup with.
|
||||
]]
|
||||
---------------------------------------
|
||||
function _metaPopup:refresh(inputBytes)
|
||||
local inputBytesLen = #inputBytes
|
||||
|
||||
-- The user simply typed one more character onto the last one.
|
||||
if inputBytesLen == #self._inputChars + 1 then
|
||||
self._inputChars[inputBytesLen] = string.char(inputBytes[inputBytesLen])
|
||||
elseif inputBytesLen == 1 then -- the user's typing was reset by a parser.
|
||||
self._inputChars = {string.char(inputBytes[1])}
|
||||
else -- other tries to optimize this procedure fell through, so do it the hard way.
|
||||
local chars = {}
|
||||
for i, byte in ipairs(inputBytes) do
|
||||
chars[i] = string.char(byte)
|
||||
end
|
||||
self._inputChars = chars
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_lines(self.buffer, 0, 1, true, {
|
||||
table.concat(self._inputChars)
|
||||
})
|
||||
|
||||
if not valid(self.window) or vim.api.nvim_win_get_tabpage(self.window) ~= vim.api.nvim_get_current_tabpage() then
|
||||
self:open()
|
||||
end
|
||||
|
||||
vim.api.nvim_win_set_width(self.window, #self._inputChars)
|
||||
end
|
||||
|
||||
--[[/* CLASS `Popup` */]]
|
||||
|
||||
--------------------
|
||||
--[[ SUMMARY:
|
||||
* Create a new popup window.
|
||||
]]
|
||||
--[[ RETURNS:
|
||||
* A new popup window.
|
||||
]]
|
||||
--------------------
|
||||
function Popup.new(config)
|
||||
local self = setmetatable(
|
||||
{
|
||||
buffer = vim.api.nvim_create_buf(false, true),
|
||||
_inputChars = {},
|
||||
},
|
||||
_metaPopup
|
||||
)
|
||||
|
||||
self:open(config)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--[[/* PUBLICIZE `Popup` */]]
|
||||
|
||||
return Popup
|
@ -1,103 +1,27 @@
|
||||
--[[
|
||||
/*
|
||||
* MODULE `Stack`
|
||||
*/
|
||||
--]]
|
||||
--- @class libmodal.collections.Stack
|
||||
local Stack = require('libmodal/src/utils/classes').new()
|
||||
|
||||
local Stack = {TYPE = 'libmodal-stack'}
|
||||
|
||||
--[[
|
||||
/*
|
||||
* META `Stack`
|
||||
*/
|
||||
--]]
|
||||
|
||||
local _metaStack = require('libmodal/src/classes').new(Stack.TYPE)
|
||||
|
||||
_metaStack._len = 0
|
||||
|
||||
--------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Get the foremost value in `self`.
|
||||
]]
|
||||
--[[
|
||||
* The foremost value in `self`.
|
||||
]]
|
||||
--------------------------------
|
||||
function _metaStack:peek()
|
||||
return self._top
|
||||
--- @return unknown top the foremost value of the stack
|
||||
function Stack:peek()
|
||||
return self[#self]
|
||||
end
|
||||
|
||||
-------------------------
|
||||
--[[ SUMMARY:
|
||||
* Remove the foremost value in `self` and return it.
|
||||
]]
|
||||
--[[ RETURNS:
|
||||
* The foremost value in `self`.
|
||||
]]
|
||||
-------------------------
|
||||
function _metaStack:pop()
|
||||
local previousLen = self._len
|
||||
|
||||
if previousLen < 1 then return nil
|
||||
end
|
||||
|
||||
-- Store the previous top of the stack.
|
||||
local previousTop = self._top
|
||||
|
||||
-- Remove the previous top of the stack.
|
||||
self[previousLen] = nil
|
||||
|
||||
-- Get the new length of the stack
|
||||
local newLen = previousLen - 1
|
||||
|
||||
-- Update the values of the stack.
|
||||
if newLen < 1 then -- the stack is empty
|
||||
self._len = nil
|
||||
self._top = nil
|
||||
else -- there is still something in the stack
|
||||
self._len = newLen
|
||||
self._top = self[newLen]
|
||||
end
|
||||
|
||||
-- Return the previous top of the stack.
|
||||
return previousTop
|
||||
--- Remove the foremost value from the stack and return it.
|
||||
--- @return unknown top the foremost value of the stack
|
||||
function Stack:pop()
|
||||
return table.remove(self)
|
||||
end
|
||||
|
||||
-------------------------------
|
||||
--[[ SUMMARY:
|
||||
* Push some `value` onto `self`.
|
||||
]]
|
||||
--[[ PARAMS:
|
||||
* `value` => the value to append to `self`.
|
||||
]]
|
||||
-------------------------------
|
||||
function _metaStack:push(value)
|
||||
-- create placeholder so new values are not put into the table until operations have succeeded.
|
||||
local newLen = self._len + 1
|
||||
|
||||
--- Push some `value` on to the stack.
|
||||
--- @param value unknown the value to push onto the stack.
|
||||
function Stack:push(value)
|
||||
-- Push to the stack
|
||||
self[newLen] = value
|
||||
|
||||
-- update stack values
|
||||
self._len = newLen
|
||||
self._top = value
|
||||
self[#self + 1] = value
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* CLASS `Stack`
|
||||
*/
|
||||
--]]
|
||||
|
||||
function Stack.new()
|
||||
return setmetatable({}, _metaStack)
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* PUBLICIZE `Stack`
|
||||
*/
|
||||
--]]
|
||||
|
||||
return Stack
|
||||
return
|
||||
{
|
||||
new = function()
|
||||
return setmetatable({}, Stack)
|
||||
end,
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
return {
|
||||
ParseTable = require('libmodal/src/collections/ParseTable'),
|
||||
Popup = require('libmodal/src/collections/Popup'),
|
||||
Stack = require('libmodal/src/collections/Stack')
|
||||
return
|
||||
{
|
||||
ParseTable = require 'libmodal/src/collections/ParseTable',
|
||||
Stack = require 'libmodal/src/collections/Stack'
|
||||
}
|
||||
|
@ -1,24 +1,42 @@
|
||||
local _VIM_FALSE = 0
|
||||
local _VIM_TRUE = 1
|
||||
local VIM_FALSE = 0
|
||||
local VIM_TRUE = 1
|
||||
|
||||
return {
|
||||
DEFAULT_ERROR_TITLE = 'vim-libmodal error',
|
||||
--- The default error title for `nvim-libmodal` errors.
|
||||
DEFAULT_ERROR_TITLE = 'nvim-libmodal error',
|
||||
|
||||
--- The key-code for the escape character.
|
||||
ESC_NR = 27,
|
||||
|
||||
TYPE_FUNC = 'function',
|
||||
TYPE_NUM = 'number',
|
||||
TYPE_STR = 'string',
|
||||
TYPE_TBL = 'table',
|
||||
--- The string which is returned by `type(function() end)` (or any function)
|
||||
TYPE_FUNC = type(function() end),
|
||||
|
||||
VIM_FALSE = _VIM_FALSE,
|
||||
VIM_TRUE = _VIM_TRUE,
|
||||
--- The string which is returned by `type(0)` (or any number)
|
||||
TYPE_NUM = type(0),
|
||||
|
||||
--- The string which is returned by `type ''` (or any string)
|
||||
TYPE_STR = type '',
|
||||
|
||||
--- The string which is returned by `type {}` (or any table)
|
||||
TYPE_TBL = type {},
|
||||
|
||||
--- The value of Vimscript's `v:false`
|
||||
VIM_FALSE = VIM_FALSE,
|
||||
|
||||
--- The value of Vimscript's `v:true`
|
||||
VIM_TRUE = VIM_TRUE,
|
||||
|
||||
--- Assert some value is either `false` or `v:false`.
|
||||
--- @param val boolean|number
|
||||
--- @return boolean
|
||||
is_false = function(val)
|
||||
return val == false or val == _VIM_FALSE
|
||||
return val == false or val == VIM_FALSE
|
||||
end,
|
||||
|
||||
--- Assert some value is either `true` or `v:true`.
|
||||
--- @param val boolean|number
|
||||
--- @return boolean
|
||||
is_true = function(val)
|
||||
return val == true or val == _VIM_TRUE
|
||||
return val == true or val == VIM_TRUE
|
||||
end
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
return {
|
||||
classes = require('libmodal/src/classes'),
|
||||
collections = require('libmodal/src/collections'),
|
||||
globals = require('libmodal/src/globals'),
|
||||
Indicator = require('libmodal/src/Indicator'),
|
||||
Layer = require('libmodal/src/Layer'),
|
||||
Mode = require('libmodal/src/Mode'),
|
||||
Prompt = require('libmodal/src/Prompt'),
|
||||
utils = require('libmodal/src/utils')
|
||||
return
|
||||
{
|
||||
collections = require 'libmodal/src/collections',
|
||||
globals = require 'libmodal/src/globals',
|
||||
Layer = require 'libmodal/src/Layer',
|
||||
Mode = require 'libmodal/src/Mode',
|
||||
Prompt = require 'libmodal/src/Prompt',
|
||||
utils = require 'libmodal/src/utils',
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
local MODE_HIGHLIGHT = 'LibmodalPrompt'
|
||||
local PROMPT_HIGHLIGHT = 'LibmodalStar'
|
||||
|
||||
--- @class libmodal.utils.Indicator
|
||||
--- @field public hl string the highlight group to use when printing `str`
|
||||
--- @field public str string the text to write
|
||||
local Indicator = {}
|
||||
|
||||
--- @param highlight_group string the highlight group to use when printing `str`
|
||||
--- @param str string what to print
|
||||
--- @return libmodal.utils.Indicator
|
||||
function Indicator.new(highlight_group, str)
|
||||
return
|
||||
{
|
||||
hl = highlight_group,
|
||||
str = str,
|
||||
}
|
||||
end
|
||||
|
||||
function Indicator.mode(mode_name)
|
||||
return Indicator.new(MODE_HIGHLIGHT, '-- ' .. mode_name .. ' --')
|
||||
end
|
||||
|
||||
function Indicator.prompt(prompt_name)
|
||||
return Indicator.new(PROMPT_HIGHLIGHT, '* ' .. prompt_name .. ' > ')
|
||||
end
|
||||
|
||||
return Indicator
|
@ -0,0 +1,85 @@
|
||||
--- @class libmodal.utils.Popup
|
||||
--- @field private buffer number the number of the window which this popup is rendered on.
|
||||
--- @field private input_chars table<string> the characters input by the user.
|
||||
--- @field private window number the number of the window which this popup is rendered on.
|
||||
local Popup = require('libmodal/src/utils/classes').new()
|
||||
|
||||
--- @param window number
|
||||
--- @return boolean `true` if the window is non-`nil` and `nvim_win_is_valid`
|
||||
local function valid(window)
|
||||
return window and vim.api.nvim_win_is_valid(window)
|
||||
end
|
||||
|
||||
--- Close `self.window`
|
||||
--- The `self` is inert after calling this.
|
||||
--- @param keep_buffer boolean `self.buffer` is passed to `nvim_buf_delete` unless `keep_buffer` is `false`
|
||||
function Popup:close(keep_buffer)
|
||||
if valid(self.window) then
|
||||
vim.api.nvim_win_close(self.window, false)
|
||||
end
|
||||
|
||||
self.window = nil
|
||||
|
||||
if not keep_buffer then
|
||||
vim.api.nvim_buf_delete(self.buffer, {force = true})
|
||||
self.buffer = nil
|
||||
self.input_chars = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Attempt to open this popup. If the popup was already open, close it and re-open it.
|
||||
function Popup:open(config)
|
||||
if not config then
|
||||
config =
|
||||
{
|
||||
anchor = 'SW',
|
||||
col = vim.go.columns - 1,
|
||||
focusable = false,
|
||||
height = 1,
|
||||
relative = 'editor',
|
||||
row = vim.go.lines - vim.go.cmdheight - 1,
|
||||
style = 'minimal',
|
||||
width = 1
|
||||
}
|
||||
end
|
||||
|
||||
if valid(self.window) then
|
||||
self:close(true)
|
||||
end
|
||||
|
||||
self.window = vim.api.nvim_open_win(self.buffer, false, config)
|
||||
end
|
||||
|
||||
--- Display `input_bytes` in `self.buffer`
|
||||
--- @param input_bytes table<number> a list of character codes to display
|
||||
function Popup:refresh(input_bytes)
|
||||
-- The user simply typed one more character onto the last one.
|
||||
if #input_bytes == #self.input_chars + 1 then
|
||||
self.input_chars[#input_bytes] = string.char(input_bytes[#input_bytes])
|
||||
elseif #input_bytes == 1 then -- the user's typing was reset by a parser.
|
||||
self.input_chars = {string.char(input_bytes[1])}
|
||||
else -- other tries to optimize this procedure fell through, so do it the hard way.
|
||||
self.input_chars = {}
|
||||
for i, byte in ipairs(input_bytes) do
|
||||
self.input_chars[i] = string.char(byte)
|
||||
end
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_lines(self.buffer, 0, 1, true, {table.concat(self.input_chars)})
|
||||
|
||||
-- Close and reopen the window if it was not already open.
|
||||
if not valid(self.window) or vim.api.nvim_win_get_tabpage(self.window) ~= vim.api.nvim_get_current_tabpage() then
|
||||
self:open()
|
||||
end
|
||||
|
||||
vim.api.nvim_win_set_width(self.window, #self.input_chars)
|
||||
end
|
||||
|
||||
return
|
||||
{
|
||||
new = function(config)
|
||||
local self = setmetatable({buffer = vim.api.nvim_create_buf(false, true), input_chars = {}}, Popup)
|
||||
self:open(config)
|
||||
return self
|
||||
end
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
--- @class libmodal.utils.Vars
|
||||
--- @field private mode_name string the highlight group to use when printing `str`
|
||||
--- @field private var_name string the highlight group to use when printing `str`
|
||||
local Vars = require('libmodal/src/utils/classes').new()
|
||||
|
||||
--- @return string name the global Neovim setting
|
||||
function Vars:name()
|
||||
return self.mode_name .. self.var_name
|
||||
end
|
||||
|
||||
--- @return unknown `vim.g[self:name()])`
|
||||
function Vars:get()
|
||||
return vim.g[self:name()]
|
||||
end
|
||||
|
||||
--- @param val unknown set `vim.g[self:name()])` equal to this value
|
||||
function Vars:set(val)
|
||||
vim.g[self:name()] = val
|
||||
end
|
||||
|
||||
return
|
||||
{
|
||||
--- Create a new set of variables
|
||||
--- @param var_name string the name of the key used to refer to this variable in `Vars`.
|
||||
--- @param mode_name string the name of the mode
|
||||
new = function(var_name, mode_name)
|
||||
local self = setmetatable({}, Vars)
|
||||
|
||||
local function no_spaces(str_with_spaces, first_letter_modifier)
|
||||
local split_str = vim.split(string.gsub(str_with_spaces, vim.pesc '_', vim.pesc ' '), ' ')
|
||||
|
||||
local function camel_case(str, func)
|
||||
return func(string.sub(str, 0, 1) or '') .. string.lower(string.sub(str, 2) or '')
|
||||
end
|
||||
|
||||
split_str[1] = camel_case(split_str[1], first_letter_modifier)
|
||||
|
||||
for i = 2, #split_str do split_str[i] =
|
||||
camel_case(split_str[i], string.upper)
|
||||
end
|
||||
|
||||
return table.concat(split_str)
|
||||
end
|
||||
|
||||
self.mode_name = no_spaces(mode_name, string.lower)
|
||||
|
||||
self.var_name = 'Mode' .. no_spaces(var_name, string.upper)
|
||||
|
||||
return self
|
||||
end
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
--[[/* IMPORTS */]]
|
||||
|
||||
local api = require('libmodal/src/utils/api')
|
||||
|
||||
--[[
|
||||
/*
|
||||
* MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
local WindowState = {TYPE = 'libmodal-window-state'}
|
||||
|
||||
--[[
|
||||
/*
|
||||
* META `WindowState`
|
||||
*/
|
||||
--]]
|
||||
|
||||
local _metaWindowState = require('libmodal/src/classes').new(WindowState.TYPE)
|
||||
|
||||
-----------------------------------
|
||||
--[[ SUMMARY
|
||||
* Restore the state of `self`.
|
||||
]]
|
||||
-----------------------------------
|
||||
function _metaWindowState:restore()
|
||||
vim.go.winheight = self.height
|
||||
vim.go.winwidth = self.width
|
||||
api.nvim_redraw()
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* CLASS `WindowState`
|
||||
*/
|
||||
--]]
|
||||
|
||||
--------------------------
|
||||
--[[ SUMMARY:
|
||||
* Create a table representing the size of the current window.
|
||||
]]
|
||||
--[[ RETURNS:
|
||||
* The new `WindowState`.
|
||||
]]
|
||||
--------------------------
|
||||
function WindowState.new()
|
||||
return setmetatable(
|
||||
{
|
||||
height = vim.go.winheight,
|
||||
width = vim.go.winwidth,
|
||||
},
|
||||
_metaWindowState
|
||||
)
|
||||
end
|
||||
|
||||
--[[
|
||||
/*
|
||||
* PUBLICIZE MODULE
|
||||
*/
|
||||
--]]
|
||||
|
||||
return WindowState
|
@ -0,0 +1,16 @@
|
||||
return
|
||||
{
|
||||
--- Define a metatable.
|
||||
--- @param template table the default value
|
||||
new = function(template)
|
||||
-- set self to `template`, or `{}` if nil.
|
||||
local self = template or {}
|
||||
|
||||
-- set `__index`.
|
||||
if not self.__index then
|
||||
self.__index = self
|
||||
end
|
||||
|
||||
return self
|
||||
end,
|
||||
}
|
Loading…
Reference in New Issue