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
Iron-E 2 years ago
parent 822614e7e8
commit 49a02ad692
No known key found for this signature in database
GPG Key ID: 19B71B7B7B021D22

@ -3,7 +3,7 @@
" argument specifying how many times to run (runs until exiting by default). " argument specifying how many times to run (runs until exiting by default).
" PARAMS: " PARAMS:
" * `a:1` => `modeName` " * `a:1` => `modeName`
" * `a:2` => `modeCallback` OR `modeCombos` " * `a:2` => `modeCallback` OR `modeKeymaps`
" * `a:3` => `supressExit` " * `a:3` => `supressExit`
function! libmodal#Enter(...) abort function! libmodal#Enter(...) abort
call libmodal#_lua('mode', a:000) call libmodal#_lua('mode', a:000)

File diff suppressed because it is too large Load Diff

@ -112,8 +112,8 @@ VARIABLES *libmodal-usage-variable
FUNCTIONS *libmodal-usage-functions* FUNCTIONS *libmodal-usage-functions*
*libmodal-mode* *libmodal#Enter()* *libmodal.mode.enter()* *libmodal-mode* *libmodal#Enter()* *libmodal.mode.enter()*
`libmodal.mode`.enter({name}, {instruction} [, {supressExit}]) `libmodal.mode`.enter({name}, {instruction} [, {supress_exit}])
`libmodal`#Enter({name}, {instruction} [, {supressExit}]) `libmodal`#Enter({name}, {instruction} [, {supress_exit}])
Enter a new |vim-mode| using {instruction} to determine what actions will Enter a new |vim-mode| using {instruction} to determine what actions will
be taken upon specific user inputs. be taken upon specific user inputs.
@ -182,8 +182,8 @@ FUNCTIONS *libmodal-usage-function
- If `g:libmodalTimeouts` is enabled, then user input will be - If `g:libmodalTimeouts` is enabled, then user input will be
subjected to the |timeoutlen|. subjected to the |timeoutlen|.
{supressExit} Whether or not to automatically exit the mode upon an {supress_exit} Whether or not to automatically exit the mode upon an
<Esc> press. <Esc> press.
- If |v:false|/`false`, then <Esc> is automatically mapped to - If |v:false|/`false`, then <Esc> is automatically mapped to
exiting. exiting.
@ -198,7 +198,8 @@ FUNCTIONS *libmodal-usage-function
|libmodal-examples-mode| For examples of this function. |libmodal-examples-mode| For examples of this function.
`libmodal.layer`.enter({keymap}) *libmodal-layer* *libmodal.layer.enter()* *libmodal-layer* *libmodal.layer.enter()*
`libmodal.layer`.enter({keymap} [, {exit_char}])
While a |libmodal-mode| ignores behavior that has not been explicitly While a |libmodal-mode| ignores behavior that has not been explicitly
defined, a |libmodal-layer| allows unrecognized |input| to be passed back defined, a |libmodal-layer| allows unrecognized |input| to be passed back
@ -221,19 +222,23 @@ FUNCTIONS *libmodal-usage-function
} }
< Where {mode}, {lhs}, {rhs}, and {opts} are the same as in < Where {mode}, {lhs}, {rhs}, and {opts} are the same as in
|nvim_set_keymap()| |vim.keymap.set()| (except you should not use multiple `<mode>` at
one time, despite |vim.keymap.set()| supporting it).
{exit_char} The character used to exit the layer.
Return: ~ Return: ~
- The `function` used to undo changes made by the layer. - The `function` used to undo changes made by the layer, or `nil` if
{exit_char} is provided.
See also: ~ See also: ~
|libmodal-examples-layers| For an example. |libmodal-examples-layers| For an example.
|nvim_set_keymap()| For more information about {keymap}. |nvim_set_keymap()| For more information about {keymap}.
*libmodal-prompt* *libmodal#Prompt()* *libmodal.prompt.enter()* *libmodal-prompt* *libmodal#Prompt()* *libmodal.prompt.enter()*
`libmodal.prompt`.enter({name}, {instruction} [, {completions}, {supressExit}]) `libmodal.prompt`.enter({name}, {instruction} [, {completions}])
`libmodal`#Prompt({name}, {instruction} [, {completions}, {supressExit}]) `libmodal`#Prompt({name}, {instruction} [, {completions}])
Besides accepting user input like keys in |Normal-mode|, |libmodal| is Besides accepting user input like keys in |Normal-mode|, |libmodal| is
also capable of prompting the user for |input| like |Cmdline-mode|. To also capable of prompting the user for |input| like |Cmdline-mode|. To
@ -296,15 +301,8 @@ FUNCTIONS *libmodal-usage-function
Note: If no `help` command is defined, one will be created Note: If no `help` command is defined, one will be created
automatically. automatically.
{supressExit} Whether or not to automatically exit the mode upon an Note: The user may set the `g:`{name}`ModeExit` variable to
<Esc> press. `true` at any time to prematurely exit.
- If |v:false|/`false`, then <Esc> is automatically mapped to
exiting.
- If |v:true|/`true`, then <Esc> is ignored unless specified by
the user. In such cases, the user should set the
`g:`{name}`ModeExit` variable to `true` when exiting is
desired. See |libmodal-examples-supress-exit|.
See also: ~ See also: ~
@ -314,238 +312,9 @@ FUNCTIONS *libmodal-usage-function
================================================================================ ================================================================================
3. Examples *libmodal-examples* 3. Examples *libmodal-examples*
Below are examples written in |Lua| to help show how specific features of See the official examples at the link below:
|libmodal| may be implemented. In each example, the name of the mode is
defined as "FOO". Additionally, each category of example has one example for
both `function` and `table` {instruction}s.
The source code can be either copied from here or downloaded from the
repository's `examples/lua` folder. Assuming |libmodal| is installed, they can
all be tested using the |luafile| |command|.
See: |libmodal-usage|, |libmodal-use-case|, |lua-require-example|.
--------------------------------------------------------------------------------
MODES *libmodal-examples-modes*
Using a callback `function`: >
local libmodal = require('libmodal')
local fooModeInputHistory = {}
local function clearHistory(indexToCheck)
if #fooModeInputHistory >= indexToCheck then
fooModeInputHistory = {}
end
end
function fooMode()
fooModeInputHistory[#fooModeInputHistory + 1] = string.char(
vim.g.fooModeInput
)
local index = 1
if fooModeInputHistory[1] == 'z' then
if fooModeInputHistory[2] == 'f' then
if fooModeInputHistory[3] == 'o' then
vim.api.nvim_command "echom 'It works!'"
else index = 3 end
else index = 2 end
end
clearHistory(index)
end
libmodal.mode.enter('FOO', fooMode)
<
using a |key-mapping| `dict`: >
let s:barModeCombos = {
\ 'zf': 'split',
\ 'zfo': 'vsplit',
\ 'zfc': 'tabnew'
\}
call libmodal#Enter('BAR', s:barModeCombos)
<
Using a |key-mapping| `table`: >
local libmodal = require('libmodal')
local fooModeCombos = {
zf = 'split',
zfo = 'vsplit',
zfc = 'tabnew'
}
libmodal.mode.enter('FOO', fooModeCombos)
<
Exit Supression ~
*libmodal-examples-supress-exit*
Using a callback `function`: >
local libmodal = require('libmodal')
function fooMode()
local userInput = string.char(
vim.g.fooModeInput
)
if userInput == '' then
vim.api.nvim_command "echom 'You cant leave using <Esc>.'"
elseif userInput == 'q' then
vim.g.fooModeExit = true
end
end
vim.g.fooModeExit = false
libmodal.mode.enter('FOO', fooMode, true)
<
Using a |key-mapping| `dict`: >
let s:barModeCombos = {
\ '': 'echom "You cant exit using escape."',
\ 'q': 'let g:barModeExit = 1'
\}
let g:barModeExit = 0
call libmodal#Enter('BAR', s:barModeCombos, 1)
<
Using a |key-mapping| `table`: >
local libmodal = require('libmodal')
local fooModeCombos = {
[''] = 'echom "You cant exit using escape."',
q = 'let g:fooModeExit = 1'
}
vim.g.fooModeExit = false
libmodal.mode.enter('FOO', fooModeCombos, true)
<
Submodes ~
*libmodal-examples-submodes*
Using a callback `function`: >
local libmodal = require('libmodal')
local fooModeRecurse = 0
function fooMode()
local userInput = string.char(vim.g[
'foo'..tostring(fooModeRecurse)..'ModeInput'
])
if userInput == 'z' then
fooModeRecurse = fooModeRecurse + 1
enter()
fooModeRecurse = fooModeRecurse - 1
end
end
function enter()
libmodal.mode.enter('FOO' .. fooModeRecurse, fooMode)
end
enter()
<
Using a |key-mapping| `table`: >
let s:barModeRecurse = 0
let s:barModeCombos = {
\ 'z': 'BarModeEnter',
\}
function! s:BarMode()
let s:barModeRecurse += 1
call libmodal#Enter('BAR' . s:barModeRecurse, s:barModeCombos)
let s:barModeRecurse -= 1
endfunction
command! BarModeEnter call s:BarMode()
execute 'BarModeEnter'
<
Using a |key-mapping| `table`: >
local libmodal = require('libmodal')
local fooModeRecurse = 0
local fooModeCombos = {
z = 'lua fooMode()'
}
function fooMode()
fooModeRecurse = fooModeRecurse + 1
libmodal.mode.enter('FOO' .. fooModeRecurse, fooModeCombos)
fooModeRecurse = fooModeRecurse - 1
end
fooMode() https://github.com/Iron-E/nvim-libmodal/tree/master/examples
<
--------------------------------------------------------------------------------
LAYERS *libmodal-examples-layers*
>
local libmodal = require('libmodal')
-- save the exit function
local exitFunc = libmodal.layer.enter({
n = { -- normal mode
gg = { -- remap `gg`
rhs = 'G', -- map it to `G`
noremap = true -- don't remap
},
G = { -- remap `G`
rhs = 'gg', -- map it to `gg`
noremap = true -- don't remap
}
}
})
-- exit the mode in five seconds
vim.loop.new_timer():start(5000, 0,
vim.schedule_wrap(exitFunc)
)
<
--------------------------------------------------------------------------------
PROMPTS *libmodal-examples-prompts*
Using a callback `function`: >
local libmodal = require('libmodal')
local commandList = {'new', 'close', 'last'}
function fooMode()
local userInput = vim.g.fooModeInput
if userInput == 'new' then
vim.api.nvim_command 'tabnew'
elseif userInput == 'close' then
vim.api.nvim_command 'tabclose'
elseif userInput == 'last' then
vim.api.nvim_command 'tablast'
end
end
libmodal.prompt.enter('FOO', fooMode, commandList)
<
Using a |command| `dict`: >
let s:commands = {
\ 'new': 'tabnew',
\ 'close': 'tabclose',
\ 'last': 'tablast'
\}
call libmodal#Prompt('TAB', s:commands)
<
Using a |command| `table`: >
local libmodal = require('libmodal')
local commands = {
new = 'tabnew',
close = 'tabclose',
last = 'tablast'
}
libmodal.prompt.enter('BAR', commands)
<
================================================================================ ================================================================================
4. Configuration *libmodal-configuration* 4. Configuration *libmodal-configuration*

@ -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)

@ -2,14 +2,14 @@
let s:barModeRecurse = 0 let s:barModeRecurse = 0
" Register 'z' as the map for recursing further (by calling the BarMode function again). " Register 'z' as the map for recursing further (by calling the BarMode function again).
let s:barModeCombos = { let s:barModeKeymaps = {
\ 'z': 'BarModeEnter', \ 'z': 'BarModeEnter',
\} \}
" define the BarMode() function which is called whenever the user presses 'z' " define the BarMode() function which is called whenever the user presses 'z'
function! s:BarMode() function! s:BarMode()
let s:barModeRecurse += 1 let s:barModeRecurse += 1
call libmodal#Enter('BAR' . s:barModeRecurse, s:barModeCombos) call libmodal#Enter('BAR' . s:barModeRecurse, s:barModeKeymaps)
let s:barModeRecurse -= 1 let s:barModeRecurse -= 1
endfunction endfunction

@ -1,5 +1,5 @@
" Register key commands and what they do. " Register key commands and what they do.
let s:barModeCombos = { let s:barModeKeymaps = {
\ '': 'echom "You cant exit using escape."', \ '': 'echom "You cant exit using escape."',
\ 'q': 'let g:barModeExit = 1' \ 'q': 'let g:barModeExit = 1'
\} \}
@ -7,5 +7,5 @@ let s:barModeCombos = {
" Tell the mode not to exit automatically. " Tell the mode not to exit automatically.
let g:barModeExit = 0 let g:barModeExit = 0
" Enter the mode using the key combos created before. " Enter the mode using the keymaps created before.
call libmodal#Enter('BAR', s:barModeCombos, 1) call libmodal#Enter('BAR', s:barModeKeymaps, 1)

@ -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)

@ -13,7 +13,4 @@ let s:layer = {
\} \}
" Capture the exit function " Capture the exit function
let s:exitFunc = luaeval("require('libmodal').layer.enter(_A)", s:layer) let s:exitFunc = luaeval("require('libmodal').layer.enter(_A, '<Esc>')", s:layer)
" Call the exit function in 5 seconds.
call timer_start(5000, s:exitFunc)

@ -2,11 +2,11 @@
local libmodal = require 'libmodal' local libmodal = require 'libmodal'
-- Keep track of the user's input history manually. -- Keep track of the user's input history manually.
local _inputHistory = {} local input_history = {}
-- Clear the input history if it grows too long for our usage. -- Clear the input history if it grows too long for our usage.
function _inputHistory:clear(indexToCheck) function input_history:clear(index_to_check)
if #self >= indexToCheck then if #self >= index_to_check then
for i, _ in ipairs(self) do for i, _ in ipairs(self) do
self[i] = nil self[i] = nil
end end
@ -14,18 +14,18 @@ function _inputHistory:clear(indexToCheck)
end end
-- This is the function that will be called whenever the user presses a button. -- This is the function that will be called whenever the user presses a button.
local function fooMode() local function foo_mode()
-- Append to the input history, the latest button press. -- Append to the input history, the latest button press.
_inputHistory[#_inputHistory + 1] = string.char( input_history[#input_history + 1] = string.char(
-- The input is a character number. -- The input is a character number.
vim.g.fooModeInput vim.g.fooModeInput
) )
-- Custom logic to test for each character index to see if it matches the 'zfo' mapping. -- Custom logic to test for each character index to see if it matches the 'zfo' mapping.
local index = 1 local index = 1
if _inputHistory[1] == 'z' then if input_history[1] == 'z' then
if _inputHistory[2] == 'f' then if input_history[2] == 'f' then
if _inputHistory[3] == 'o' then if input_history[3] == 'o' then
vim.api.nvim_command "echom 'It works!'" vim.api.nvim_command "echom 'It works!'"
else index = 3 else index = 3
end end
@ -33,8 +33,8 @@ local function fooMode()
end end
end end
_inputHistory:clear(index) input_history:clear(index)
end end
-- Enter the mode to begin the demo. -- Enter the mode to begin the demo.
libmodal.mode.enter('FOO', fooMode) libmodal.mode.enter('FOO', foo_mode)

@ -2,21 +2,22 @@
local libmodal = require('libmodal') local libmodal = require('libmodal')
-- Recurse counter. -- Recurse counter.
local fooModeRecurse = 0 local foo_mode_recurse = 0
-- Register 'z' as the map for recursing further (by calling the FooMode function again). -- Register 'z' as the map for recursing further (by calling the FooMode function again).
local fooModeCombos = { local foo_mode_keymaps =
{
z = 'lua FooMode()' z = 'lua FooMode()'
} }
-- define the FooMode() function which is called whenever the user presses 'z' -- define the FooMode() function which is called whenever the user presses 'z'
function FooMode() function FooMode()
fooModeRecurse = fooModeRecurse + 1 foo_mode_recurse = foo_mode_recurse + 1
libmodal.mode.enter('FOO' .. fooModeRecurse, fooModeCombos) libmodal.mode.enter('FOO' .. foo_mode_recurse, foo_mode_keymaps)
fooModeRecurse = fooModeRecurse - 1 foo_mode_recurse = foo_mode_recurse - 1
end end
-- Define the character 'f' as the function we defined— but directly through lua, instead of vimL. -- Define the character 'f' as the function we defined— but directly through lua, instead of vimL.
fooModeCombos['f'] = FooMode foo_mode_keymaps['f'] = FooMode
-- Call FooMode() initially to begin the demo. -- Call FooMode() initially to begin the demo.
FooMode() FooMode()

@ -2,7 +2,8 @@
local libmodal = require 'libmodal' local libmodal = require 'libmodal'
-- Register key commands and what they do. -- Register key commands and what they do.
local fooModeCombos = { local fooModeKeymaps =
{
[''] = 'echom "You cant exit using escape."', [''] = 'echom "You cant exit using escape."',
q = 'let g:fooModeExit = 1' q = 'let g:fooModeExit = 1'
} }
@ -10,5 +11,5 @@ local fooModeCombos = {
-- Tell the mode not to exit automatically. -- Tell the mode not to exit automatically.
vim.g.fooModeExit = false vim.g.fooModeExit = false
-- Enter the mode using the key combos created before. -- Enter the mode using the keymaps created before.
libmodal.mode.enter('FOO', fooModeCombos, true) libmodal.mode.enter('FOO', fooModeKeymaps, true)

@ -2,18 +2,19 @@
local libmodal = require 'libmodal' local libmodal = require 'libmodal'
-- A function which will split the window both horizontally and vertically. -- A function which will split the window both horizontally and vertically.
local function _split_twice() local function split_twice()
vim.api.nvim_command 'split' vim.api.nvim_command 'split'
vim.api.nvim_command 'vsplit' vim.api.nvim_command 'vsplit'
end end
-- Register key combos for splitting windows and then closing windows -- Register keymaps for splitting windows and then closing windows
local fooModeCombos = { local fooModeKeymaps =
{
zf = 'split', zf = 'split',
zfo = 'vsplit', zfo = 'vsplit',
zfc = 'q', zfc = 'q',
zff = _split_twice zff = split_twice
} }
-- Enter the mode using the key combos. -- Enter the mode using the keymaps.
libmodal.mode.enter('FOO', fooModeCombos) libmodal.mode.enter('FOO', fooModeKeymaps)

@ -1,21 +1,21 @@
-- Imports -- Imports
local libmodal = require('libmodal') local libmodal = require 'libmodal'
-- create a new layer. -- create a new layer.
local exitFunc = libmodal.layer.enter({ libmodal.layer.enter(
n = { -- normal mode mappings {
gg = { -- remap `gg` n = { -- normal mode mappings
rhs = 'G', -- map it to `G` gg = { -- remap `gg`
noremap = true, -- don't recursively map. rhs = 'G', -- map it to `G`
}, noremap = true, -- don't recursively map.
G = { -- remap `G` },
rhs = 'gg', -- map it to `gg` G = { -- remap `G`
noremap = true -- don't recursively map. rhs = 'gg', -- map it to `gg`
noremap = true -- don't recursively map.
}
} }
} },
}) '<Esc>'
)
-- The layer will deactivate in 5 seconds for this demo. -- The layer will deactivate when you press <Esc>
vim.loop.new_timer():start(5000, 0, vim.schedule_wrap(
function() exitFunc(); print('EXITED.') end
))

@ -1,40 +1,28 @@
-- Imports -- Imports
local libmodal = require('libmodal') local libmodal = require 'libmodal'
-- create a new layer. -- create a new layer.
local layer = libmodal.Layer.new({ local layer = libmodal.layer.new(
n = { -- normal mode mappings {
gg = { -- remap `gg` n =
{ -- normal mode mappings
gg = -- remap `gg`
{
rhs = 'G', -- map it to `G` rhs = 'G', -- map it to `G`
noremap = true, -- don't recursively map. -- other options such as `noremap` and `silent` can be set to `true` here
}, },
G = { -- remap `G` G = -- remap `G`
{
rhs = 'gg', -- map it to `gg` rhs = 'gg', -- map it to `gg`
noremap = true -- don't recursively map. -- other options such as `noremap` and `silent` can be set to `true` here
} },
} }
}) })
-- enter the `layer`. -- Add an additional mapping for `<Esc>` to exit the mode
layer:enter() layer:map('n', '<Esc>', function() layer:exit() end, {})
-- add a global function for exiting the mode.
function LibmodalLayerExampleExit()
layer:exit()
end
-- Add an additional mapping for `z`. layer:enter()
layer:map('n', 'z', 'gg', {noremap = true})
-- add an additional mapping for `q`.
layer:map(
'n', 'q', ':lua LibmodalLayerExampleExit()<CR>',
{noremap = true, silent = true}
)
--[[ unmap `gg` and `G`. Notice they both return to their defaults, --[[ unmap `gg`. Notice that now both `gg` and `G` return the cursor to the top. ]]
rather than just not doing anything anymore. ]]
layer:unmap('n', 'gg') layer:unmap('n', 'gg')
layer:unmap('n', 'G')
-- If you wish to only change the mappings of a layer temporarily, you should use another layer. `map` and `unmap` permanently add and remove from the layer's keymap.

@ -11,6 +11,7 @@ function FooMode()
vim.api.nvim_command 'tabnew' vim.api.nvim_command 'tabnew'
elseif userInput == 'close' then elseif userInput == 'close' then
vim.api.nvim_command 'tabclose' vim.api.nvim_command 'tabclose'
vim.g.fooModeExit = true
elseif userInput == 'last' then elseif userInput == 'last' then
vim.api.nvim_command 'tablast' vim.api.nvim_command 'tablast'
end end

@ -2,11 +2,12 @@
local libmodal = require 'libmodal' local libmodal = require 'libmodal'
-- Define commands through a dictionary. -- Define commands through a dictionary.
local commands = { local commands =
{
new = 'tabnew', new = 'tabnew',
close = 'tabclose', close = 'tabclose',
last = 'tablast', last = 'tablast',
exit = libmodal.utils.api.mode_exit exit = 'let g:fooModeExit = v:true',
} }
-- Begin the prompt. -- Begin the prompt.

@ -2,27 +2,27 @@
local libmodal = require 'libmodal' local libmodal = require 'libmodal'
-- Recurse counter -- Recurse counter
local fooModeRecurse = 1 local foo_mode_recurse = 1
-- Function which is called whenever the user presses a button -- Function which is called whenever the user presses a button
function FooMode() function FooMode()
-- Append to the input history, the latest button press. -- Append to the input history, the latest button press.
local userInput = string.char(vim.g[ local userInput = string.char(vim.g[
-- The input is a character number. -- The input is a character number.
'foo' .. tostring(fooModeRecurse) .. 'ModeInput' 'foo' .. tostring(foo_mode_recurse) .. 'ModeInput'
]) ])
-- If the user pressed 'z', then increase the counter and recurse. -- If the user pressed 'z', then increase the counter and recurse.
if userInput == 'z' then if userInput == 'z' then
fooModeRecurse = fooModeRecurse + 1 foo_mode_recurse = foo_mode_recurse + 1
Enter() Enter()
fooModeRecurse = fooModeRecurse - 1 foo_mode_recurse = foo_mode_recurse - 1
end end
end end
-- Function to wrap around entering the mode so it can be recursively called. -- Function to wrap around entering the mode so it can be recursively called.
function Enter() function Enter()
libmodal.mode.enter('FOO' .. fooModeRecurse, FooMode) libmodal.mode.enter('FOO' .. foo_mode_recurse, FooMode)
end end
-- Initially call the function to begin the demo. -- Initially call the function to begin the demo.

@ -8,6 +8,7 @@ function! s:fooMode() abort
tabnew tabnew
elseif userInput == 'close' elseif userInput == 'close'
tabclose tabclose
let g:fooModeExit = v:true
elseif userInput == 'last' elseif userInput == 'last'
tablast tablast
endif endif

@ -1,36 +1,63 @@
--[[ -- TODO: remove the __index here after a period of time to let people remove `libmodal.Layer` from their configurations
/* return setmetatable(
* MODULE {
*/ layer =
--]] {
--- Enter a new layer.
local libmodal = require('libmodal/src') --- @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)
* MIRRORS local layer = require('libmodal/src/Layer').new(keymap)
*/ layer:enter()
--]]
if exit_char then
libmodal.layer = {enter = function(keymap) layer:map('n', exit_char, function() layer:exit() end, {})
local layer = libmodal.Layer.new(keymap) else
layer:enter() return function() layer:exit() end
return function() layer:exit() end end
end} end,
libmodal.mode = {enter = function(name, instruction, ...) --- Create a new layer.
libmodal.Mode.new(name, instruction, ...):enter() --- @param keymap table the keymaps (e.g. `{n = {gg = {rhs = 'G', silent = true}}}`)
end} --- @return libmodal.Layer
new = function(keymap)
libmodal.prompt = {enter = function(name, instruction, ...) return require('libmodal/src/Layer').new(keymap)
libmodal.Prompt.new(name, instruction, ...):enter() end,
end} },
mode =
--[[ {
/* --- Enter a mode.
* PUBLICIZE MODULE --- @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()
return libmodal 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 @@
--[[ --- Remove and return the right-hand side of a `keymap`.
/* --- @param keymap table the keymap to unpack
* IMPORTS --- @return string lhs, table options
*/ local function unpack_keymap_lhs(keymap)
--]] local lhs = keymap.lhs
keymap.lhs = nil
local api = vim.api
local libmodal_api = require('libmodal/src/utils/api') return lhs, keymap
--[[
/*
* 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}
end end
local function deconvertKeymap(convertedKeymap) --- Remove and return the right-hand side of a `keymap`.
local rhs = convertedKeymap.rhs --- @param keymap table the keymap to unpack
convertedKeymap.rhs = nil --- @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 end
--[[ --- @class libmodal.Layer
/* --- @field private existing_keymap table the keymaps to restore when exiting the mode; generated automatically
* META `Layer` --- @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.
local _metaLayer = require('libmodal/src/classes').new(Layer.TYPE) function Layer:enter()
if self.existing_keymap then
---------------------------
--[[ SUMMARY:
* Enter the `Layer`.
* Only activates for the current buffer.
]]
---------------------------
function _metaLayer:enter()
if self._priorKeymap then
error('This layer has already been entered. `:exit()` before entering again.') error('This layer has already been entered. `:exit()` before entering again.')
end end
-- add local aliases. -- add local aliases.
local layerKeymap = self._keymap self.existing_keymap = {}
local priorKeymap = {}
--[[ iterate over the new keymaps to both:
--[[ iterate over the new mappings to both: 1. Populate a list of keymaps which will be overwritten to `existing_keymap`
1. Populate `priorKeymap` 2. Apply the layer's keymappings. ]]
2. Map the `layerKeymap` to the buffer. ]] for mode, new_keymaps in pairs(self.layer_keymap) do
for mode, newMappings in pairs(layerKeymap) do -- if `mode` key has not yet been made for `existing_keymap`.
-- if `mode` key has not yet been made for `priorKeymap`. if not self.existing_keymap[mode] then
if not priorKeymap[mode] then self.existing_keymap[mode] = {}
priorKeymap[mode] = {}
end end
-- store the previously mapped keys -- store the previously mapped keys
for _, bufMap in ipairs(api.nvim_buf_get_keymap(_BUFFER_CURRENT, mode)) do for _, existing_keymap in ipairs(vim.api.nvim_get_keymap(mode)) do
-- if the new mappings would overwrite this one -- if the new keymaps would overwrite this one
if newMappings[bufMap.lhs] then if new_keymaps[existing_keymap.lhs] then
-- remove values so that it is in line with `nvim_set_keymap`. -- remove values so that it is in line with `nvim_set_keymap`.
local lhs, keymap = unpack(convertKeymap(bufMap)) local lhs, keymap = unpack_keymap_lhs(existing_keymap)
priorKeymap[mode][lhs] = keymap self.existing_keymap[mode][lhs] = keymap
end end
end end
-- add the new mappings -- add the new keymaps
for lhs, newMapping in pairs(newMappings) do for lhs, new_keymap in pairs(new_keymaps) do
local rhs, options = unpack(deconvertKeymap(newMapping)) local rhs, options = unpack_keymap_rhs(new_keymap)
api.nvim_buf_set_keymap(_BUFFER_CURRENT, mode, lhs, rhs, options) vim.keymap.set(mode, lhs, rhs, options)
end end
end end
self._priorKeymap = priorKeymap
end end
-------------------------------------------------------- --- Add a keymap to the mode.
--[[ SUMMARY: --- @param mode string the mode that this keymap for.
* Add a mapping to the mode. --- @param lhs string the left hand side of the keymap.
]] --- @param rhs function|string the right hand side of the keymap.
--[[ PARAMS: --- @param options table options for the keymap.
* `mode` => the mode that this mapping for. --- @see `vim.keymap.set`
* `lhs` => the left hand side of the mapping. function Layer:map(mode, lhs, rhs, options)
* `rhs` => the right hand side of the mapping. if self.existing_keymap then -- the layer has been activated
* `options` => options for the mapping. 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
--[[ SEE ALSO: if existing_keymap.lhs == lhs then -- add it to the undo list
* `nvim_buf_set_keymap()` existing_keymap.lhs = nil
]] self.existing_keymap[mode][lhs] = existing_keymap
-------------------------------------------------------- break
function _metaLayer:_mapToBuffer(mode, lhs, rhs, options) end
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
end end
end end
-- map the `lhs` to `rhs` in `mode` with `options` for the current buffer.
vim.keymap.set(mode, lhs, rhs, options)
end end
-- map the `lhs` to `rhs` in `mode` with `options` for the current buffer. -- add the new mapping to the layer's keymap
api.nvim_buf_set_keymap(_BUFFER_CURRENT, mode, lhs, rhs, options) options.rhs = rhs
self.layer_keymap[mode][lhs] = options
end end
------------------------------------------------ --- Restore one keymapping to its original state.
--[[ SUMMARY: --- @param mode string the mode of the keymap.
* Add a mapping to the mode. --- @param lhs string the keys which invoke the keymap.
]] --- @see `vim.api.nvim_del_keymap`
--[[ PARAMS: function Layer:unmap(mode, lhs)
* `mode` => the mode that this mapping for. if not self.existing_keymap then
* `lhs` => the left hand side of the mapping. error("Don't call this function before activating the layer; just remove from the keymap passed to `Layer.new` instead.")
* `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)
end end
-- add the new mapping to the keymap if self.existing_keymap[mode][lhs] then -- there is an older keymap to go back to, so undo this layer_keymap
self._keymap[mode][lhs] = vim.tbl_extend('force', local rhs, options = unpack_keymap_rhs(self.existing_keymap[mode][lhs])
options, {rhs = rhs} vim.keymap.set(mode, lhs, rhs, options)
)
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
else else
-- just delete the buffer mapping. -- just make the keymap go back to default
local noErrors, err = pcall(api.nvim_buf_del_keymap, _BUFFER_CURRENT, mode, lhs) 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) print(err)
end end
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 -- remove this keymap from the list of ones to restore
self._keymap[mode][lhs] = _RESTORED self.existing_keymap[mode][lhs] = nil
end end
-------------------------- --- Exit the layer, restoring all previous keymaps.
--[[ SUMMARY: function Layer:exit()
* Exit the layer. if not self.existing_keymap then
]]
--------------------------
function _metaLayer:exit()
if not self._priorKeymap then
error('This layer has not been entered yet.') error('This layer has not been entered yet.')
end end
for mode, mappings in pairs(self._keymap) do for mode, keymaps in pairs(self.layer_keymap) do
for lhs, _ in pairs(mappings) do for lhs, _ in pairs(keymaps) do
self:_unmapFromBuffer(mode, lhs) self:unmap(mode, lhs)
end end
end end
self._priorKeymap = _RESTORED self.existing_keymap = nil
end end
--[[ return
/* {
* CLASS `Layer` --- @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
--[[ 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

@ -1,32 +1,23 @@
--[[/* IMPORTS */]] local globals = require 'libmodal/src/globals'
local ParseTable = require 'libmodal/src/collections/ParseTable'
local classes = require('libmodal/src/classes') local utils = require 'libmodal/src/utils'
local globals = require('libmodal/src/globals')
local ParseTable = require('libmodal/src/collections/ParseTable') --- @class libmodal.Mode
local utils = require('libmodal/src/utils') --- @field private exit libmodal.utils.Vars
local Vars = require('libmodal/src/Vars') --- @field private flush_input_timer unknown
--- @field private help libmodal.utils.Help|nil
--[[/* MODULE */]] --- @field private indicator libmodal.utils.Indicator
--- @field private input libmodal.utils.Vars
local Mode = {TYPE = 'libmodal-mode'} --- @field private instruction function|table<string, function|string>
--- @field private mappings libmodal.collections.ParseTable
local _HELP = '?' --- @field private name string
local _TIMEOUT = { --- @field private popups libmodal.collections.Stack
CHAR = 'ø', --- @field private supress_exit boolean
LEN = vim.go.timeoutlen, --- @field private timeouts_enabled boolean
SEND = function(self) vim.api.nvim_feedkeys(self.CHAR, 'nt', false) end local Mode = utils.classes.new()
}
_TIMEOUT.CHAR_NUMBER = string.byte(_TIMEOUT.CHAR) local InputBytes = utils.classes.new(
{
--[[
/*
* META `_metaMode`
*/
--]]
local _metaMode = classes.new(Mode.TYPE)
local _metaInputBytes = classes.new(nil, {
clear = function(self) clear = function(self)
for i, _ in ipairs(self) do for i, _ in ipairs(self) do
self[i] = nil self[i] = nil
@ -34,158 +25,108 @@ local _metaInputBytes = classes.new(nil, {
end end
}) })
classes = nil local HELP = '?'
local TIMEOUT =
----------------------------------------------------------- {
--[[ SUMMARY: CHAR = 'ø',
* Execute some `selection` according to a set of determined logic. LEN = vim.go.timeoutlen,
]] SEND = function(self) vim.api.nvim_feedkeys(self.CHAR, 'nt', false) end
--[[ REMARKS: }
* Only provides logic for when `self._instruction` is a table of commands. TIMEOUT.CHAR_NUMBER = string.byte(TIMEOUT.CHAR)
]]
--[[ PARAMS: --- Execute the `instruction`.
* `selection` => The instruction that is desired to be executed. --- @param instruction function|string a Lua function or Vimscript command.
]] function Mode.execute_instruction(instruction)
----------------------------------------------------------- if type(instruction) == globals.TYPE_FUNC then
function _metaMode._commandTableExecute(instruction) instruction()
if type(instruction) == globals.TYPE_FUNC then instruction() else
else vim.api.nvim_command(instruction) end vim.api.nvim_command(instruction)
end
end end
----------------------------------------------- --- Check the user's input against the `self.instruction` mappings to see if there is anything to execute.
--[[ SUMMARY: --- If there is nothing to execute, the user's input is rendered on the screen (as does Vim by default).
* Parse `self.mappings` and see if there is any command to execute. function Mode:check_input_for_mapping()
]]
-----------------------------------------------
function _metaMode:_checkInputForMapping()
-- Stop any running timers -- Stop any running timers
self._flushInputTimer:stop() self.flush_input_timer:stop()
-- Append the latest input to the locally stored input history. -- Append the latest input to the locally stored input history.
local inputBytes = self.inputBytes self.input_bytes[#self.input_bytes + 1] = self.input:get()
inputBytes[#inputBytes + 1] = self.input:nvimGet()
-- Get the command based on the users input. -- Get the command based on the users input.
local cmd = self.mappings:get(inputBytes) local cmd = self.mappings:get(self.input_bytes)
-- Get the type of the command. -- Get the type of the command.
local commandType = type(cmd) local command_type = type(cmd)
-- if there was no matching command -- if there was no matching command
if not cmd then if not cmd then
if #inputBytes < 2 and inputBytes[1] == string.byte(_HELP) then if #self.input_bytes < 2 and self.input_bytes[1] == string.byte(HELP) then
self._help:show() self.help:show()
end end
inputBytes:clear() self.input_bytes:clear()
-- 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 elseif command_type == globals.TYPE_TBL
and globals.is_true(self._timeouts.enabled) and globals.is_true(self.timeouts_enabled)
then then
-- start the timer -- start the timer
self._flushInputTimer:start( self.flush_input_timer:start(
_TIMEOUT.LEN, 0, 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`
_TIMEOUT:SEND() TIMEOUT:SEND()
-- if there is a command, execute it. -- if there is a command, execute it.
if cmd[ParseTable.CR] then if cmd[ParseTable.CR] then
self._commandTableExecute(cmd[ParseTable.CR]) self.execute_instruction(cmd[ParseTable.CR])
end end
-- clear input -- clear input
inputBytes:clear() self.input_bytes:clear()
self._popups:peek():refresh(inputBytes) self.popups:peek():refresh(self.input_bytes)
end) end)
) )
-- The command was an actual vim command. -- The command was an actual vim command.
else else
self._commandTableExecute(cmd) self.execute_instruction(cmd)
inputBytes:clear() self.input_bytes:clear()
end end
self._popups:peek():refresh(inputBytes) self.popups:peek():refresh(self.input_bytes)
end end
-------------------------- --- Enter this mode.
--[[ SUMMARY: function Mode:enter()
* Enter `self`'s mode.
]]
--------------------------
function _metaMode:enter()
-- intialize variables that are needed for each recurse of a function -- intialize variables that are needed for each recurse of a function
if type(self._instruction) == globals.TYPE_TBL then if type(self.instruction) == globals.TYPE_TBL then
-- Initialize the input history variable. -- Initialize the input history variable.
self._popups:push(require('libmodal/src/collections/Popup').new()) self.popups:push(utils.Popup.new())
end end
self._previousModeName = vim.g.libmodalActiveModeName self.previous_mode_name = vim.g.libmodalActiveModeName
vim.g.libmodalActiveModeName = self._name vim.g.libmodalActiveModeName = self.name
--[[ MODE LOOP. ]] --[[ MODE LOOP. ]]
local continueMode = true local continue_mode = true
while continueMode do while continue_mode do
-- Try (using pcall) to use the mode. -- Try (using pcall) to use the mode.
local noErrors, modeResult = pcall(self._inputLoop, self) local no_errors, mode_result = pcall(self.get_user_input, self)
-- If there were errors, handle them. -- If there were errors, handle them.
if not noErrors then if not no_errors then
utils.show_error(modeResult) utils.show_error(mode_result)
continueMode = true continue_mode = false
else else
continueMode = modeResult continue_mode = mode_result
end end
end end
self:_tearDown() self:tear_down()
end
----------------------------------
--[[ SUMMARY:
* Set the initial values used for parsing user input as combos.
]]
----------------------------------
function _metaMode:_initMappings()
-- Create a timer to perform actions with.
self._flushInputTimer = vim.loop.new_timer()
-- Determine if a default `Help` should be created.
if not self._instruction[_HELP] then
self._help = utils.Help.new(self._instruction, 'KEY MAP')
end
self.inputBytes = setmetatable({}, _metaInputBytes)
-- Build the parse tree.
self.mappings = ParseTable.new(self._instruction)
-- Create a table for mode-specific data.
self._popups = require('libmodal/src/collections/Stack').new()
-- Create a variable for whether or not timeouts are enabled.
self._timeouts = Vars.new('timeouts', self._name)
-- Read the correct timeout variable.
if vim.g[self._timeouts:name()] ~= nil
then self._timeouts.enabled =
self._timeouts:nvimGet()
else self._timeouts.enabled =
Vars.libmodalTimeouts
end
end end
------------------------------- --- Get input from the user.
--[[ SUMMARY: --- @return boolean more_input
* Loop an initialized `mode`. function Mode:get_user_input()
]]
--[[ RETURNS:
* `boolean` => whether or not the mode should continue
]]
-------------------------------
function _metaMode:_inputLoop()
-- If the mode is not handling exit events automatically and the global exit var is true. -- If the mode is not handling exit events automatically and the global exit var is true.
if self.exit.supress if self.supress_exit and globals.is_true(self.exit:get()) then
and globals.is_true(self.exit:nvimGet())
then
return false return false
end end
@ -193,114 +134,103 @@ function _metaMode:_inputLoop()
utils.api.nvim_lecho(self.indicator) utils.api.nvim_lecho(self.indicator)
-- Capture input. -- Capture input.
local userInput = utils.api.nvim_input() local user_input = vim.fn.getchar()
-- Return if there was a timeout event. -- Return if there was a timeout event.
if userInput == _TIMEOUT.CHAR_NUMBER then if user_input == TIMEOUT.CHAR_NUMBER then
return true return true
end end
-- Set the global input variable to the new input. -- Set the global input variable to the new input.
self.input:nvimSet(userInput) self.input:set(user_input)
if not self.exit.supress and userInput == globals.ESC_NR then -- The user wants to exit. if not self.supress_exit and user_input == globals.ESC_NR then -- The user wants to exit.
return false -- As in, "I don't want to continue." return false -- As in, "I don't want to continue."
else -- The user wants to continue. else -- The user wants to continue.
--[[ The instruction type is determined every cycle, because the user may be assuming a more direct control --[[ The instruction type is determined every cycle, because the user may be assuming a more direct control
over the instruction and it may change over the course of execution. ]] over the instruction and it may change over the course of execution. ]]
local instructionType = type(self._instruction) local instruction_type = type(self.instruction)
if instructionType == globals.TYPE_TBL then -- The second argument was a dict. Parse it. if instruction_type == globals.TYPE_TBL then -- The instruction was provided as a was a set of mappings.
self:_checkInputForMapping() self:check_input_for_mapping()
elseif instructionType == globals.TYPE_STR then -- It is the name of a VimL function. elseif instruction_type == globals.TYPE_STR then -- The instruction is the name of a Vimscript function.
vim.fn[self._instruction]() vim.fn[self.instruction]()
else -- the second argument was a function; execute it. else -- The instruction is a function.
self._instruction() self.instruction()
end end
end end
return true return true
end end
------------------------------ --- Uninitialize variables from after exiting the mode.
--[[ SUMMARY: function Mode:tear_down()
* Remove variables used for a mode. if type(self.instruction) == globals.TYPE_TBL then
]] self.flush_input_timer:stop()
------------------------------ self.input_bytes = nil
function _metaMode:_tearDown()
if type(self._instruction) == globals.TYPE_TBL then self.popups:pop():close()
self._flushInputTimer:stop()
self.inputBytes = nil
self._popups:pop():close()
end end
if self._previousModeName and #vim.trim(self._previousModeName) < 1 then if self.previous_mode_name and #vim.trim(self.previous_mode_name) < 1 then
vim.g.libmodalActiveModeName = nil vim.g.libmodalActiveModeName = nil
else else
vim.g.libmodalActiveModeName = self._previousModeName vim.g.libmodalActiveModeName = self.previous_mode_name
end end
self._winState:restore()
utils.api.nvim_redraw() utils.api.nvim_redraw()
end end
--[[ return
/* {
* CLASS `Mode` --- Create a new mode.
*/ --- @param name string the name of the mode.
--]] --- @param instruction function|string|table a Lua function, keymap dictionary, Vimscript command.
--- @return libmodal.Mode
----------------------------------------- new = function(name, instruction, supress_exit)
--[[ SUMMARY: name = vim.trim(name)
* Enter a mode.
]] -- Inherit the metatable.
--[[ PARAMS: local self = setmetatable(
* `name` => the mode name. {
* `instruction` => the mode callback, or mode combo table. exit = utils.Vars.new('exit', name),
* `...` => optional exit supresion flag. indicator = utils.Indicator.mode(name),
]] input = utils.Vars.new('input', name),
----------------------------------------- instruction = instruction,
function Mode.new(name, instruction, ...) name = name,
name = vim.trim(name) },
Mode
-- Inherit the metatable. )
local self = setmetatable(
{
exit = Vars.new('exit', name),
indicator = require('libmodal/src/Indicator').mode(name),
input = Vars.new('input', name),
_instruction = instruction,
_name = name,
_winState = utils.WindowState.new(),
},
_metaMode
)
-- Define the exit flag
self.exit.supress = (function(optionalValue)
if optionalValue then
return globals.is_true(optionalValue)
else
return false
end
end)(unpack({...}))
-- Define other "session" variables. -- Define the exit flag
self.supress_exit = supress_exit or false
-- Determine whether a callback was specified, or a combo table. -- If the user provided keymaps
if type(instruction) == globals.TYPE_TBL then if type(instruction) == globals.TYPE_TBL then
self:_initMappings() -- Create a timer to perform actions with.
end self.flush_input_timer = vim.loop.new_timer()
return self -- Determine if a default `Help` should be created.
end if not self.instruction[HELP] then
self.help = utils.Help.new(self.instruction, 'KEY MAP')
end
self.input_bytes = setmetatable({}, InputBytes)
-- Build the parse tree.
self.mappings = ParseTable.new(self.instruction)
--[[ -- Create a table for mode-specific data.
/ self.popups = require('libmodal/src/collections/Stack').new()
* PUBLICIZE MODULE
*/
--]]
return Mode -- Create a variable for whether or not timeouts are enabled.
self.timeouts = utils.Vars.new('timeouts', self.name)
-- Read the correct timeout variable.
self.timeouts_enabled = self.timeouts:get() or vim.g.libmodalTimeouts
end
return self
end
}

@ -1,188 +1,153 @@
--[[/* IMPORTS */]] local globals = require 'libmodal/src/globals'
local utils = require 'libmodal/src/utils'
local globals = require('libmodal/src/globals')
local utils = require('libmodal/src/utils') --- @class libmodal.Prompt
local Vars = require('libmodal/src/Vars') --- @field private completions table<string>|nil
--- @field private exit libmodal.utils.Vars
--[[/* MODULE */]] --- @field private help libmodal.utils.Help|nil
--- @field private indicator libmodal.utils.Indicator
local Prompt = {TYPE = 'libmodal-prompt'} --- @field private input libmodal.utils.Vars
--- @field private instruction function|table<string, function|string>
local _HELP = 'help' --- @field private name string
local _REPLACEMENTS = { 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 end
--[[ --- Execute the instruction specified by the `user_input`.
/* --- @param user_input string
* META `Prompt` 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]
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]
if type(to_execute) == globals.TYPE_FUNC then if type(to_execute) == globals.TYPE_FUNC then
to_execute() to_execute()
else else
vim.api.nvim_command(instruction[userInput]) vim.api.nvim_command(to_execute)
end end
elseif userInput == _HELP then -- The user did not define a 'help' command, so use the default. elseif user_input == HELP then -- The user did not define a 'help' command, so use the default.
self._help:show() self.help:show()
else -- show an error. else -- show an error.
utils.api.nvim_show_err(globals.DEFAULT_ERROR_TITLE, 'Unknown command') utils.api.nvim_show_err(globals.DEFAULT_ERROR_TITLE, 'Unknown command')
end end
elseif type(instruction) == globals.TYPE_STR then -- The instruction is a function. elseif type(self.instruction) == globals.TYPE_STR then -- The self.instruction is a function.
vim.fn[instruction]() vim.fn[self.instruction]()
else -- attempt to call the instruction. else -- attempt to call the self.instruction.
instruction() self.instruction()
end end
end end
--------------------------------- --- Get more input from the user.
--[[ SUMMARY: --- @return boolean more_input
* Loop to get user input with `input()`. function Prompt:get_user_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
-- clear previous `echo`s. -- clear previous `echo`s.
utils.api.nvim_redraw() utils.api.nvim_redraw()
-- determine what to do with the input local continue_prompt -- will set to true `true` if looping this prompt again
local function userInputCallback(userInput)
if userInput and string.len(userInput) > 0 then -- The user actually entered something. --- 1. Set `g:<mode_name>ModeInput` to `user_input`
self.input:nvimSet(userInput) --- 2. Execute any commands indicated by `user_input`
self:_executeInstruction(userInput) --- 3. Read `g:<mode_name>ModeExit` to see if we should `continue_prompt`
else -- indicate we want to leave the prompt --- @param user_input string
return false local function user_input_callback(user_input)
end 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 end
-- echo the highlighting -- echo the highlighting
vim.api.nvim_command('echohl ' .. self.indicator.hl) vim.api.nvim_command('echohl ' .. self.indicator.hl)
-- set the user input variable -- set the user input variable
if self._completions then if self.completions then
vim.api.nvim_command('echo "' .. self.indicator.str .. '"') vim.api.nvim_command('echo "' .. self.indicator.str .. '"')
return vim.ui.select(self._completions, {}, userInputCallback) vim.ui.select(self.completions, {}, user_input_callback)
else else
return vim.ui.input({prompt = self.indicator.str}, userInputCallback) vim.ui.input({prompt = self.indicator.str}, user_input_callback)
end end
return continue_prompt == nil and true or continue_prompt
end end
---------------------------- --- Enter the prompt.
--[[ SUMMARY: function Prompt:enter()
* Enter a prompt 'mode'.
]]
----------------------------
function _metaPrompt:enter()
-- enter the mode using a loop. -- enter the mode using a loop.
local continueMode = true local continue_mode = true
while continueMode do while continue_mode do
local noErrors, promptResult = pcall(self._inputLoop, self) local no_errors, prompt_result = pcall(self.get_user_input, self)
-- if there were errors. -- if there were errors.
if not noErrors then if not no_errors then
utils.show_error(promptResult) utils.show_error(prompt_result)
continueMode = true continue_mode = false
else else
continueMode = promptResult continue_mode = prompt_result
end end
end end
end end
--[[ return
/* {
* CLASS `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
------------------------------------------- --- @return libmodal.Prompt
--[[ SUMMARY: new = function(name, instruction, user_completions)
* Enter a prompt. name = vim.trim(name)
]]
--[[ PARAMS: local self = setmetatable(
* `name` => the prompt name. {
* `instruction` => the prompt callback, or mode command table. exit = utils.Vars.new('exit', name),
* `...` => a completions table. indicator = utils.Indicator.prompt(name),
]] input = utils.Vars.new('input', name),
------------------------------------------- instruction = instruction,
function Prompt.new(name, instruction, ...) name = name
name = vim.trim(name) },
Prompt
local self = setmetatable( )
{
exit = Vars.new('exit', name), -- get the completion list.
indicator = require('libmodal/src/Indicator').prompt(name), if type(instruction) == globals.TYPE_TBL then -- unload the keys of the mode command table.
input = require('libmodal/src/Vars').new('input', name), -- Create one if the user specified a command table.
_instruction = instruction, local completions = {}
_name = name local contained_help = false
},
_metaPrompt for command, _ in pairs(instruction) do
) completions[#completions + 1] = command
if command == HELP then
-- get the arguments contained_help = true
local userCompletions, supressExit = unpack({...}) end
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
end end
end
if not containedHelp then -- assign it. if not contained_help then -- assign it.
completions[#completions + 1] = _HELP completions[#completions + 1] = HELP
self._help = utils.Help.new(instruction, 'COMMAND') 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 end
self._completions = completions return self
elseif userCompletions then
-- Use the table that the user gave.
self._completions = userCompletions
end 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,56 +1,14 @@
--[[ --- The number corresponding to <CR> in vim.
/* local CR = 13
* IMPORTS local globals = require 'libmodal/src/globals'
*/
--]] --- @class libmodal.collections.ParseTable
local ParseTable = require('libmodal/src/utils/classes').new()
local globals = require('libmodal/src/globals')
--[[
/*
* MODULE
*/
--]]
local _REGEX_ALL = '.'
local ParseTable = {
CR = 13, -- The number corresponding to <CR> in vim.
TYPE = 'libmodal-parse-table',
--------------------------------------
--[[ SUMMARY:
* Split some `str` over a `regex`.
]]
--[[ PARAMS:
* `str` => the string to split.
* `regex` => the regex to split `str` with.
]]
--[[ RETURNS:
* The split `str`.
]]
--------------------------------------
stringSplit = function(str, regex)
local split = {}
for char in string.gmatch(str, regex) do
split[#split + 1] = char
end
return split
end
}
---------------------------------- --- Reverse the order of elements in some `tbl`
--[[ SUMMARY: --- @param tbl table the table to reverse
* Reverse the elements of some table. --- @return table tbl_reversed
]] local function table_reverse(tbl)
--[[ PARAMS:
* `tbl` => the table to reverse.
]]
--[[ RETURNS:
* The reversed `tbl`.
]]
----------------------------------
local function _table_reverse(tbl)
local reversed = {} local reversed = {}
while #reversed < #tbl do while #reversed < #tbl do
-- look, no variables! -- look, no variables!
@ -59,196 +17,124 @@ local function _table_reverse(tbl)
return reversed return reversed
end end
------------------------------ --- @param str string
--[[ SUMMARY: --- @return table<string> chars of `str`
* Parse a `key`. local function chars(str)
]] return vim.split(str, '')
--[[ PARAMS:
* `key` => the key to parse.
]]
--[[ RETURNS:
* The parsed `key`.
]]
------------------------------
function ParseTable.parse(key)
return ParseTable.stringSplit(key, _REGEX_ALL)
end end
----------------------------------------- --- Retrieve the mapping of `lhs_reversed_bytes`
--[[ SUMMARY --- @param parse_table libmodal.collections.ParseTable the table to fetch `lhs_reversed_bytes` from.
* Get `splitKey` from some `parseTable`. --- @param lhs_reversed_bytes table<string> the characters of the left-hand side of the mapping reversed passed to `string.byte`
]] --- @return false|function|string|table match a string/func when fully matched; a table when partially matched; false when no match.
--[[ PARAMS: local function get(parse_table, lhs_reversed_bytes)
* `parseTable` => the table to fetch `splitKey` from. --[[ Get the next character in the keymap string. ]]
* `splitKey` => the key split into groups.
]]
-----------------------------------------
local function _get(parseTable, splitKey)
--[[ Get the next character in the combo string. ]]
local k = '' local k = ''
if #splitKey > 0 then -- There is more input to parse if #lhs_reversed_bytes > 0 then -- There is more input to parse
k = table.remove(splitKey) -- the table should already be `char2nr()`'d k = table.remove(lhs_reversed_bytes) -- the table should already be `string.byte`'d
else -- the user input has run out, but there is more in the `parseTable`. else -- the user input has run out, but there is more in the `parse_table`.
return parseTable return parse_table
end end
--[[ Parse the `k`. ]] --[[ Parse the `k`. ]]
-- Make sure the dicitonary has a key for that value. -- Make sure the dicitonary has a key for that value.
if parseTable[k] then if parse_table[k] then
local val = parseTable[k] local val = parse_table[k]
local valType = type(val) local val_type = type(val)
if valType == globals.TYPE_TBL then if val_type == globals.TYPE_TBL then
if val[ParseTable.CR] and #splitKey < 1 then if val[CR] and #lhs_reversed_bytes < 1 then
return val return val
else else
return _get(val, splitKey) return get(val, lhs_reversed_bytes)
end end
elseif valType == globals.TYPE_STR or valType == globals.TYPE_FUNC and #splitKey < 1 then elseif val_type == globals.TYPE_STR or val_type == globals.TYPE_FUNC and #lhs_reversed_bytes < 1 then
return val return val
end end
end end
return nil return nil
end end
------------------------------------------------ --- Insert a `value` into `parse_table` at the position indicated by `lhs_reversed_bytes`
--[[ SUMMARY: --- @param lhs_reversed_bytes table<string> the characters of the left-hand side of the mapping reversed passed to `string.byte`
* Update the values of some `dict` using a `splitKey`. --- @param value function|string the right-hand-side of the mapping
]] local function put(parse_table, lhs_reversed_bytes, value)
--[[ PARAMS:
* `parseTable` => the parseTable to update.
* `splitKey` => the key split into groups.
]]
------------------------------------------------
local function _put(parseTable, splitKey, value)
--[[ Get the next character in the table. ]] --[[ Get the next character in the table. ]]
local k = string.byte(table.remove(splitKey)) local byte = string.byte(table.remove(lhs_reversed_bytes))
if #splitKey > 0 then -- there are still characters left in the key. if #lhs_reversed_bytes > 0 then -- there are still characters left in the key.
if not parseTable[k] then parseTable[k] = {} if not parse_table[byte] then -- this is a new mapping
-- If there is a previous command mapping in place parse_table[byte] = {}
else local valueType = type(parseTable[k]) else -- if there is a previous command mapping in place
if valueType == globals.TYPE_STR or valueType == globals.TYPE_FUNC then local value_type = type(parse_table[byte])
-- Swap the mapping to a `CR` if value_type == globals.TYPE_STR or value_type == globals.TYPE_FUNC then -- if this is not a tree of inputs already
parseTable[k] = {[ParseTable.CR] = parseTable[k]} -- Make the mapping require hitting enter before executing
parse_table[byte] = {[CR] = parse_table[byte]}
end end
end end
-- run _update() again -- run put() again
_put(parseTable[k], splitKey, value) put(parse_table[byte], lhs_reversed_bytes, value)
-- If parseTable[k] is a pre-existing table, don't clobber the table— clobber the `CR` value. -- If parse_Table[k] is a pre-existing table, don't clobber the table— clobber the `CR` value.
elseif type(parseTable[k]) == globals.TYPE_TBL then elseif type(parse_table[byte]) == globals.TYPE_TBL then
parseTable[k][ParseTable.CR] = value parse_table[byte][CR] = value
else parseTable[k] = value -- parseTable[k] is not a table, go ahead and clobber the value. else
parse_table[byte] = value -- parse_table[k] is not a table, go ahead and clobber the value.
end end
end end
--[[ --- Retrieve the mapping of `lhs_reversed_bytes`
/* --- @param key_dict table a list of characters (most recent input first)
* META `ParseTable` --- @return false|function|string|table match a string/func when fully matched; a table when partially matched; false when no match.
*/ function ParseTable:get(key_dict)
--]] return get(self, table_reverse(key_dict))
local _metaParseTable = require('libmodal/src/classes').new(ParseTable.TYPE)
-------------------------------------
--[[ SUMMARY:
* Get a value from this `ParseTable`.
]]
--[[ PARAMS:
* `key` => the PARSED key to get.
]]
--[[ RETURNS:
* `function` => when `key` is a full match.
* `table` => when the `key` partially mathes.
* `false` => when `key` is not ANYWHERE.
]]
-------------------------------------
function _metaParseTable:get(keyDict)
return _get(self, _table_reverse(keyDict))
end end
-------------------------------------- --- Parse `key` and retrieve its value
--[[ SUMMARY: --- @param key string the left-hand-side of the mapping to retrieve
* Get a value from this `ParseTable`. --- @return false|function|string|table match a string/func when fully found; a table when partially found; false when not found.
]] function ParseTable:parse_get(key)
--[[ PARAMS: local parsed_table = chars(string.reverse(key))
* `key` => the key to get.
]]
--[[ RETURNS:
* `function` => when `key` is a full match.
* `table` => when the `key` partially mathes.
* `false` => when `key` is not ANYWHERE.
]]
--------------------------------------
function _metaParseTable:parseGet(key)
local parsedTable = ParseTable.parse(string.reverse(key))
-- convert all of the strings to bytes. -- convert all of the strings to bytes.
for i, v in ipairs(parsedTable) do for i, v in ipairs(parsed_table) do
parsedTable[i] = string.byte(v) parsed_table[i] = string.byte(v)
end end
return _get(self, parsedTable) return get(self, parsed_table)
end end
--------------------------------------------- --- Parse `key` and assign it to `value`.
--[[ SUMMARY: --- @param key string the left-hand-side of the mapping
* Put `value` into the parse tree as `key`. --- @param value function|string the right-hand-side of the mapping
]] function ParseTable:parse_put(key, value)
--[[ PARAMS: put(self, chars(string.reverse(key)), value)
* `key` => the key that `value` is reffered to by.
* `value` => the value to store as `key`.
]]
---------------------------------------------
function _metaParseTable:parsePut(key, value)
_put(self, ParseTable.parse(string.reverse(key)), value)
end end
-------------------------------------------------- --- `:parse_put` all `{key, value}` pairs in `keys_and_values`.
--[[ SUMMARY: --- @param keys_and_values table<string, function|string>
* Create the union of `self` and `tableToUnite` function ParseTable:parse_put_all(keys_and_values)
]] for k, v in pairs(keys_and_values) do
--[[ PARAMS: self:parse_put(k, v)
* `tableToUnite` => the table to unite with `self.`
]]
--------------------------------------------------
function _metaParseTable:parsePutAll(tableToUnite)
for k, v in pairs(tableToUnite) do
self:parsePut(k, v)
end end
end end
--[[ return
/* {
* CLASS `ParseTable` CR = CR,
*/
--]] --- Create a new `libmodal.collections.ParseTable` from a user-provided table.
--- @param user_table table keymaps (e.g. `{zfo = 'tabnew'}`)
---------------------------------- --- @return libmodal.collections.ParseTable
--[[ SUMMARY: new = function(user_table)
* Create a new parse table from a user-defined table. local self = setmetatable({}, ParseTable)
]]
--[[ PARAMS:
* `userTable` => the table of combos defined by the user.
]]
----------------------------------
function ParseTable.new(userTable)
local self = setmetatable({}, _metaParseTable)
-- Parse the passed in table.
self:parsePutAll(userTable)
-- Return the new `ParseTable`.
return self
end
--[[ -- Parse the passed in table.
/* self:parse_put_all(user_table)
* PUBLICIZE MODULE
*/
--]]
return ParseTable -- Return the new `ParseTable`.
return self
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 @@
--[[ --- @class libmodal.collections.Stack
/* local Stack = require('libmodal/src/utils/classes').new()
* MODULE `Stack`
*/
--]]
local Stack = {TYPE = 'libmodal-stack'} --- @return unknown top the foremost value of the stack
function Stack:peek()
--[[ return self[#self]
/*
* 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
end end
------------------------- --- Remove the foremost value from the stack and return it.
--[[ SUMMARY: --- @return unknown top the foremost value of the stack
* Remove the foremost value in `self` and return it. function Stack:pop()
]] return table.remove(self)
--[[ 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
end end
------------------------------- --- Push some `value` on to the stack.
--[[ SUMMARY: --- @param value unknown the value to push onto the stack.
* Push some `value` onto `self`. function Stack:push(value)
]]
--[[ 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 to the stack -- Push to the stack
self[newLen] = value self[#self + 1] = value
-- update stack values
self._len = newLen
self._top = value
end end
--[[ return
/* {
* CLASS `Stack` new = function()
*/ return setmetatable({}, Stack)
--]] end,
}
function Stack.new()
return setmetatable({}, _metaStack)
end
--[[
/*
* PUBLICIZE `Stack`
*/
--]]
return Stack

@ -1,5 +1,5 @@
return { return
ParseTable = require('libmodal/src/collections/ParseTable'), {
Popup = require('libmodal/src/collections/Popup'), ParseTable = require 'libmodal/src/collections/ParseTable',
Stack = require('libmodal/src/collections/Stack') Stack = require 'libmodal/src/collections/Stack'
} }

@ -1,24 +1,42 @@
local _VIM_FALSE = 0 local VIM_FALSE = 0
local _VIM_TRUE = 1 local VIM_TRUE = 1
return { 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, ESC_NR = 27,
TYPE_FUNC = 'function', --- The string which is returned by `type(function() end)` (or any function)
TYPE_NUM = 'number', TYPE_FUNC = type(function() end),
TYPE_STR = 'string',
TYPE_TBL = 'table',
VIM_FALSE = _VIM_FALSE, --- The string which is returned by `type(0)` (or any number)
VIM_TRUE = _VIM_TRUE, 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) is_false = function(val)
return val == false or val == _VIM_FALSE return val == false or val == VIM_FALSE
end, end,
--- Assert some value is either `true` or `v:true`.
--- @param val boolean|number
--- @return boolean
is_true = function(val) is_true = function(val)
return val == true or val == _VIM_TRUE return val == true or val == VIM_TRUE
end end
} }

@ -1,10 +1,9 @@
return { return
classes = require('libmodal/src/classes'), {
collections = require('libmodal/src/collections'), collections = require 'libmodal/src/collections',
globals = require('libmodal/src/globals'), globals = require 'libmodal/src/globals',
Indicator = require('libmodal/src/Indicator'), Layer = require 'libmodal/src/Layer',
Layer = require('libmodal/src/Layer'), Mode = require 'libmodal/src/Mode',
Mode = require('libmodal/src/Mode'), Prompt = require 'libmodal/src/Prompt',
Prompt = require('libmodal/src/Prompt'), utils = require 'libmodal/src/utils',
utils = require('libmodal/src/utils')
} }

@ -1,85 +1,74 @@
--[[/* IMPORTS */]] local globals = require 'libmodal/src/globals'
local globals = require('libmodal/src/globals') --- @class libmodal.utils.Help
local Help = require('libmodal/src/utils/classes').new()
--[[/* Utilities */]] --- Align `tbl` according to the `longest_key_len`.
--- Align `tbl` according to the `longestKeyLen`.
--- @param tbl table what to align. --- @param tbl table what to align.
--- @param longestKeyLen number how long the longest key is. --- @param longest_key_len number how long the longest key is.
--- @return table aligned --- @return table aligned
local function tabAlign(tbl, longestKeyLen) local function align_columns(tbl, longest_key_len)
local toPrint = {} local to_print = {}
for key, value in pairs(tbl) do for key, value in pairs(tbl) do
toPrint[#toPrint + 1] = key to_print[#to_print + 1] = key
local len = string.len(key) local len = string.len(key)
local byte = string.byte(key) local byte = string.byte(key)
-- account for ASCII chars that take up more space. -- account for ASCII chars that take up more space.
if byte <= 32 or byte == 127 then len = len + 1 end if byte <= 32 or byte == 127 then
len = len + 1
end
for _ = len, longestKeyLen do for _ = len, longest_key_len do
toPrint[#toPrint + 1] = ' ' to_print[#to_print + 1] = ' '
end end
toPrint[#toPrint + 1] = table.concat( to_print[#to_print + 1] = '' .. (type(value) == globals.TYPE_STR and value or vim.inspect(value)) .. '\n'
{'', '\n'},
(type(value) == globals.TYPE_STR) and value or '<lua function>'
)
end end
return toPrint return to_print
end end
--[[/* MODULE */]]
local Help = {TYPE = 'libmodal-help'}
--[[/* META `Help` */]]
local _metaHelp = require('libmodal/src/classes').new(Help.TYPE)
--- Show the contents of this `Help`. --- Show the contents of this `Help`.
function _metaHelp:show() function Help:show()
for _, helpText in ipairs(self) do for _, help_text in ipairs(self) do
print(helpText) print(help_text)
end end
vim.fn.getchar() vim.fn.getchar()
end end
--[[/* CLASS `Help` */]] --[[/* CLASS `Help` */]]
--- Create a default help table with `commandsOrMaps` and vim expressions. return
--- @param commandsOrMaps table commands or mappings to vim expressions. {
--- @return table Help --- Create a default help table with `commands_or_maps` and vim expressions.
function Help.new(commandsOrMaps, title) --- @param commands_or_maps table<string, function|string> commands or mappings to vim expressions.
-- find the longest key in the table. --- @param title string
local longestKeyLen = 0 --- @return libmodal.utils.Help
for key, _ in pairs(commandsOrMaps) do new = function(commands_or_maps, title)
local keyLen = string.len(key) -- find the longest key in the table, or at least the length of the title
if keyLen > longestKeyLen then local longest_key_maps = string.len(title)
longestKeyLen = keyLen for key, _ in pairs(commands_or_maps) do
local key_len = string.len(key)
if key_len > longest_key_maps then
longest_key_maps = key_len
end
end end
end
-- adjust the longest key length if the table header is longer. -- define the separator for the help table.
if longestKeyLen < string.len(title) then local help_separator = {}
longestKeyLen = string.len(title) for i = 1, string.len(title) do
help_separator[i] = '-'
end
help_separator = table.concat(help_separator)
-- Create a new `Help`.
return setmetatable(
{
[1] = ' ',
[2] = table.concat(align_columns({[title] = 'VIM EXPRESSION'}, longest_key_maps)),
[3] = table.concat(align_columns({[help_separator] = '--------------'}, longest_key_maps)),
[4] = table.concat(align_columns(commands_or_maps, longest_key_maps)),
},
Help
)
end end
}
-- define the separator for the help table.
local helpSeparator = {}
for i = 1, string.len(title) do helpSeparator[i] = '-' end
helpSeparator = table.concat(helpSeparator)
-- Create a new `Help`.
return setmetatable(
{
[1] = ' ',
[2] = table.concat(tabAlign({[title] = 'VIM EXPRESSION'}, longestKeyLen)),
[3] = table.concat(tabAlign({[helpSeparator] = '--------------'}, longestKeyLen)),
[4] = table.concat(tabAlign(commandsOrMaps, longestKeyLen)),
},
_metaHelp
)
end
return Help

@ -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

@ -1,7 +1,5 @@
--[[/* IMPORTS */]] local globals = require 'libmodal/src/globals'
local Indicator = require 'libmodal/src/utils/Indicator'
local globals = require('libmodal/src/globals')
local HighlightSegment = require('libmodal/src/Indicator/HighlightSegment')
--[[/* MODULE */]] --[[/* MODULE */]]
@ -25,63 +23,32 @@ function api.nvim_bell()
vim.api.nvim_command('normal '..string.char(27)) -- escape char vim.api.nvim_command('normal '..string.char(27)) -- escape char
end end
--- Gets one character of user input, as a number. --- Run the `mode` command to refresh the screen.
function api.nvim_input()
return vim.fn.getchar()
end
--------------------------
--[[ SUMMARY:
* Run `mode` to refresh the screen.
* The function was not named `nvim_mode` because that would be really confusing given the name of this plugin.
]]
--------------------------
function api.nvim_redraw() function api.nvim_redraw()
vim.api.nvim_command 'mode' vim.api.nvim_command 'mode'
end end
--------------------------------- --- Echo a list of `Indicator`s with their associated highlighting.
--[[ SUMMARY: --- @param indicators libmodal.utils.Indicator|table<libmodal.utils.Indicator> the indicators to echo
* Echo a table of {`hlgroup`, `str`} tables. function api.nvim_lecho(indicators)
* Meant to be read as "nvim list echo". if indicators.hl then -- wrap the single indicator in a table to form a list of indicators
]] indicators = {indicators}
--[[ PARAMS: end
* `hlTables` => the tables to echo with highlights.
]]
---------------------------------
local lecho_template = {
[1] = "echohl ",
[2] = nil,
[3] = " | echon '",
[4] = nil,
[5] = "'"
}
function api.nvim_lecho(hlTables)
api.nvim_redraw() api.nvim_redraw()
for _, hlTable in ipairs(hlTables) do
-- `:echohl` the hlgroup and then `:echon` the string
lecho_template[2] = tostring(hlTable.hl)
lecho_template[4] = tostring(hlTable.str)
vim.api.nvim_command(table.concat(lecho_template)) for _, indicator in ipairs(indicators) do
vim.api.nvim_command('echohl ' .. indicator.hl .. " | echon '" .. indicator.str .. "'")
end end
vim.api.nvim_command 'echohl None' vim.api.nvim_command 'echohl None'
end end
-------------------------------------- --- Show an error.
--[[ SUMMARY: --- @param title string a succint category of error
* Show a `title` error. --- @param msg string a descriptive reason for the error
]]
--[[ PARAMS:
* `title` => the title of the error.
* `msg` => the message of the error.
]]
--------------------------------------
function api.nvim_show_err(title, msg) function api.nvim_show_err(title, msg)
api.nvim_lecho({ api.nvim_lecho {Indicator.new('Title', tostring(title)..'\n'), Indicator.new('Error', tostring(msg))}
HighlightSegment.new('Title', tostring(title)..'\n'),
HighlightSegment.new('Error', tostring(msg)),
})
vim.fn.getchar() vim.fn.getchar()
end end

@ -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,
}

@ -1,11 +1,14 @@
--[[/* MODULE */]] --[[/* MODULE */]]
local utils = {} local utils =
utils.api = require('libmodal/src/utils/api') {
utils.Help = require('libmodal/src/utils/Help') api = require 'libmodal/src/utils/api',
utils.WindowState = require('libmodal/src/utils/WindowState') classes = require 'libmodal/src/utils/classes',
Indicator = require 'libmodal/src/utils/Indicator',
--[[/* FUNCTIONS */]] Help = require 'libmodal/src/utils/Help',
Popup = require 'libmodal/src/utils/Popup',
Vars = require 'libmodal/src/utils/Vars',
}
--- Show an error from `pcall()`. --- Show an error from `pcall()`.
--- @param pcall_err string the error generated by `pcall()`. --- @param pcall_err string the error generated by `pcall()`.
@ -18,6 +21,4 @@ function utils.show_error(pcall_err)
) )
end end
--[[/* PUBLICIZE MODULE */]]
return utils return utils

@ -5,8 +5,5 @@ vim.g.libmodalTimeouts = vim.g.libmodalTimeouts or vim.go.timeout
-- The default highlight groups (for colors) are specified below. -- The default highlight groups (for colors) are specified below.
-- Change these default colors by defining or linking the corresponding highlight group. -- Change these default colors by defining or linking the corresponding highlight group.
vim.cmd vim.api.nvim_set_hl(0, 'LibmodalPrompt', {default = true, link = 'ModeMsg'})
[[ vim.api.nvim_set_hl(0, 'LibmodalStar', {default = true, link = 'StatusLine'})
highlight default link LibmodalPrompt ModeMsg
highlight default link LibmodalStar StatusLine
]]

Loading…
Cancel
Save