perf: general improvements

I forgot to break it up into individual commits, and I ain't going
backwards now.
pull/20/head
Iron-E 1 year ago
parent e535c17e84
commit 8c164b811d
No known key found for this signature in database
GPG Key ID: 83A6AEB40395D40D

@ -15,10 +15,10 @@ 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 foo_mode() local function foo_mode()
-- append to the input history, the latest button press -- append to the input history, the latest button press
input_history[#input_history + 1] = string.char( table.insert(input_history, 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

@ -8,7 +8,7 @@ return setmetatable(
--- @param exit_char? string a character which can be used to exit the layer from normal mode. --- @param exit_char? string a character which can be used to exit the layer from normal mode.
--- @return fun()|nil exit a function to exit the layer, or `nil` if `exit_char` is passed --- @return fun()|nil exit a function to exit the layer, or `nil` if `exit_char` is passed
enter = function(keymap, exit_char) enter = function(keymap, exit_char)
local layer = require('libmodal/src/Layer').new(keymap) local layer = require('libmodal.src.Layer').new(keymap)
layer:enter() layer:enter()
if exit_char then if exit_char then
@ -22,7 +22,7 @@ return setmetatable(
--- @param keymap table the keymaps (e.g. `{n = {gg = {rhs = 'G', silent = true}}}`) --- @param keymap table the keymaps (e.g. `{n = {gg = {rhs = 'G', silent = true}}}`)
--- @return libmodal.Layer --- @return libmodal.Layer
new = function(keymap) new = function(keymap)
return require('libmodal/src/Layer').new(keymap) return require('libmodal.src.Layer').new(keymap)
end, end,
}, },
@ -32,7 +32,7 @@ return setmetatable(
--- @param name string the name of the mode. --- @param name string the name of the mode.
--- @param instruction fun()|string|table a Lua function, keymap dictionary, Vimscript command. --- @param instruction fun()|string|table a Lua function, keymap dictionary, Vimscript command.
enter = function(name, instruction, supress_exit) enter = function(name, instruction, supress_exit)
require('libmodal/src/Mode').new(name, instruction, supress_exit):enter() require('libmodal.src.Mode').new(name, instruction, supress_exit):enter()
end end
}, },
@ -43,7 +43,7 @@ return setmetatable(
--- @param instruction fun()|{[string]: fun()|string} what to do with user input --- @param instruction fun()|{[string]: fun()|string} what to do with user input
--- @param user_completions? string[] a list of possible inputs, provided by the user --- @param user_completions? string[] a list of possible inputs, provided by the user
enter = function(name, instruction, user_completions) enter = function(name, instruction, user_completions)
require('libmodal/src/Prompt').new(name, instruction, user_completions):enter() require('libmodal.src.Prompt').new(name, instruction, user_completions):enter()
end end
} }
}, },
@ -52,12 +52,17 @@ return setmetatable(
if key ~= 'Layer' then if key ~= 'Layer' then
return rawget(tbl, key) return rawget(tbl, key)
else else
vim.notify_once( if vim.deprecate then
'`libmodal.Layer` is deprecated in favor of `libmodal.layer`. It will work FOR NOW, but uncapitalize that `L` please :)', vim.deprecate('`libmodal.Layer`', '`libmodal.layer`', '4.0.0', 'nvim-libmodal')
vim.log.levels.WARN, else
{title = 'nvim-libmodal'} vim.notify_once(
) '`libmodal.Layer` is deprecated in favor of `libmodal.layer`. It will work FOR NOW, but uncapitalize that `L` please :)',
return require 'libmodal/src/Layer' vim.log.levels.WARN,
{title = 'nvim-libmodal'}
)
end
return rawget(tbl, 'layer')
end end
end, end,
} }

@ -1,8 +1,8 @@
--- @type libmodal.globals --- @type libmodal.globals
local globals = require 'libmodal/src/globals' local globals = require 'libmodal.src.globals'
--- @type libmodal.utils --- @type libmodal.utils
local utils = require 'libmodal/src/utils' local utils = require 'libmodal.src.utils'
--- Normalizes a `buffer = true|false|0` argument into a number. --- Normalizes a `buffer = true|false|0` argument into a number.
--- @param buffer boolean|number the argument to normalize --- @param buffer boolean|number the argument to normalize
@ -52,9 +52,10 @@ end
--- @field private active boolean whether the layer is currently applied --- @field private active boolean whether the layer is currently applied
--- @field private existing_keymaps_by_mode table the keymaps to restore when exiting the mode; generated automatically --- @field private existing_keymaps_by_mode table the keymaps to restore when exiting the mode; generated automatically
--- @field private layer_keymaps_by_mode table the keymaps to apply when entering the mode; provided by user --- @field private layer_keymaps_by_mode table the keymaps to apply when entering the mode; provided by user
local Layer = require('libmodal/src/utils/classes').new(nil) local Layer = require('libmodal.src.utils.classes').new()
--- apply the `Layer`'s keymaps buffer. --- apply the `Layer`'s keymaps buffer.
--- @return nil
function Layer:enter() function Layer:enter()
if self:is_active() then if self:is_active() then
vim.notify( vim.notify(
@ -76,6 +77,7 @@ function Layer:enter()
end end
--- exit the layer, restoring all previous keymaps. --- exit the layer, restoring all previous keymaps.
--- @return nil
function Layer:exit() function Layer:exit()
if not self.active then if not self.active then
vim.notify( vim.notify(
@ -106,6 +108,7 @@ end
--- @param lhs string the left hand side of the keymap. --- @param lhs string the left hand side of the keymap.
--- @param rhs fun()|string the right hand side of the keymap. --- @param rhs fun()|string the right hand side of the keymap.
--- @param options table options for the keymap. --- @param options table options for the keymap.
--- @return nil
--- @see vim.keymap.set --- @see vim.keymap.set
function Layer:map(mode, lhs, rhs, options) function Layer:map(mode, lhs, rhs, options)
lhs = utils.api.replace_termcodes(lhs) lhs = utils.api.replace_termcodes(lhs)
@ -151,6 +154,7 @@ end
--- @param buffer? number the buffer to unmap from (`nil` if it is not buffer-local) --- @param buffer? number the buffer to unmap from (`nil` if it is not buffer-local)
--- @param mode string the mode of the keymap. --- @param mode string the mode of the keymap.
--- @param lhs string the keys which invoke the keymap. --- @param lhs string the keys which invoke the keymap.
--- @return nil
--- @see vim.api.nvim_del_keymap --- @see vim.api.nvim_del_keymap
function Layer:unmap(buffer, mode, lhs) function Layer:unmap(buffer, mode, lhs)
lhs = utils.api.replace_termcodes(lhs) lhs = utils.api.replace_termcodes(lhs)
@ -170,7 +174,7 @@ function Layer:unmap(buffer, mode, lhs)
end) end)
if not ok and err:match 'E31: No such mapping' then if not ok and err:match 'E31: No such mapping' then
require('libmodal/src/utils').notify_error('nvim-libmodal encountered an error while unmapping from layer', err) require('libmodal.src.utils').notify_error('nvim-libmodal encountered an error while unmapping from layer', err)
return return
end end
end end

@ -1,17 +1,11 @@
--- @type libmodal.globals local globals = require 'libmodal.src.globals' --- @type libmodal.globals
local globals = require 'libmodal/src/globals' local ParseTable = require 'libmodal.src.collections.ParseTable' --- @type libmodal.collections.ParseTable
local utils = require 'libmodal.src.utils' --- @type libmodal.utils
--- @type libmodal.collections.ParseTable
local ParseTable = require 'libmodal/src/collections/ParseTable'
--- @type libmodal.utils
local utils = require 'libmodal/src/utils'
--- @class libmodal.Mode --- @class libmodal.Mode
--- @field private exit libmodal.utils.Vars --- @field private exit libmodal.utils.Vars
--- @field private flush_input_timer unknown --- @field private flush_input_timer unknown
--- @field private help? libmodal.utils.Help --- @field private help? libmodal.utils.Help
--- @field private indicator libmodal.utils.Indicator
--- @field private input libmodal.utils.Vars --- @field private input libmodal.utils.Vars
--- @field private input_bytes? number[] --- @field private input_bytes? number[]
--- @field private instruction fun()|{[string]: fun()|string} --- @field private instruction fun()|{[string]: fun()|string}
@ -23,19 +17,20 @@ local utils = require 'libmodal/src/utils'
--- @field private timeouts_enabled boolean --- @field private timeouts_enabled boolean
local Mode = utils.classes.new() local Mode = utils.classes.new()
local HELP = '?' local HELP_CHAR = '?'
local TIMEOUT = local TIMEOUT =
{ {
CHAR = 'ø', CHAR = ' ',
LEN = vim.go.timeoutlen, LEN = vim.go.timeoutlen,
SEND = function(self) vim.api.nvim_feedkeys(self.CHAR, 'nt', false) end SEND = function(self) vim.api.nvim_feedkeys(self.CHAR, 'nt', false) end
} }
TIMEOUT.CHAR_NUMBER = string.byte(TIMEOUT.CHAR) TIMEOUT.CHAR_NUMBER = TIMEOUT.CHAR:byte()
--- execute the `instruction`. --- execute the `instruction`.
--- @param instruction fun()|string a Lua function or Vimscript command. --- @param instruction fun()|string a Lua function or Vimscript command.
--- @return nil
function Mode.execute_instruction(instruction) function Mode.execute_instruction(instruction)
if type(instruction) == globals.TYPE_FUNC then if type(instruction) == 'function' then
instruction() instruction()
else else
vim.api.nvim_command(instruction) vim.api.nvim_command(instruction)
@ -44,6 +39,7 @@ end
--- check the user's input against the `self.instruction` mappings to see if there is anything to execute. --- check the user's input against the `self.instruction` mappings to see if there is anything to execute.
--- if there is nothing to execute, the user's input is rendered on the screen (as does Vim by default). --- if there is nothing to execute, the user's input is rendered on the screen (as does Vim by default).
--- @return nil
function Mode:check_input_for_mapping() function Mode:check_input_for_mapping()
-- stop any running timers -- stop any running timers
self.flush_input_timer:stop() self.flush_input_timer:stop()
@ -59,12 +55,12 @@ function Mode:check_input_for_mapping()
-- if there was no matching command -- if there was no matching command
if not cmd then if not cmd then
if #self.input_bytes < 2 and self.input_bytes[1] == string.byte(HELP) then if #self.input_bytes < 2 and self.input_bytes[1] == HELP_CHAR:byte() then
self.help:show() self.help:show()
end end
self.input_bytes = {} self.input_bytes = {}
elseif command_type == globals.TYPE_TBL and globals.is_true(self.timeouts_enabled) then -- the command was a table, meaning that it MIGHT match. elseif command_type == 'table' and globals.is_true(self.timeouts_enabled) then -- the command was a table, meaning that it MIGHT match.
self.flush_input_timer:start( -- start the timer self.flush_input_timer:start( -- start the timer
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`
@ -88,9 +84,10 @@ function Mode:check_input_for_mapping()
end end
--- enter this mode. --- enter this mode.
--- @return nil
function Mode:enter() function Mode: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) == 'table' then
-- initialize the input history variable. -- initialize the input history variable.
self.popups:push(utils.Popup.new()) self.popups:push(utils.Popup.new())
end end
@ -151,9 +148,9 @@ function Mode:get_user_input()
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 instruction_type = type(self.instruction) local instruction_type = type(self.instruction)
if instruction_type == globals.TYPE_TBL then -- the instruction was provided as a was a set of mappings. if instruction_type == 'table' then -- the instruction was provided as a was a set of mappings.
self:check_input_for_mapping() self:check_input_for_mapping()
elseif instruction_type == globals.TYPE_STR then -- the instruction is the name of a Vimscript function. elseif instruction_type == 'string' then -- the instruction is the name of a Vimscript function.
vim.fn[self.instruction]() vim.fn[self.instruction]()
else -- the instruction is a function. else -- the instruction is a function.
self.instruction() self.instruction()
@ -164,8 +161,9 @@ function Mode:get_user_input()
end end
--- uninitialize variables from after exiting the mode. --- uninitialize variables from after exiting the mode.
--- @return nil
function Mode:tear_down() function Mode:tear_down()
if type(self.instruction) == globals.TYPE_TBL then if type(self.instruction) == 'table' then
self.flush_input_timer:stop() self.flush_input_timer:stop()
self.input_bytes = nil self.input_bytes = nil
@ -192,7 +190,6 @@ function Mode.new(name, instruction, supress_exit)
local self = setmetatable( local self = setmetatable(
{ {
exit = utils.Vars.new('exit', name), exit = utils.Vars.new('exit', name),
indicator = utils.Indicator.new('LibmodalPrompt', '-- ' .. name .. ' --'),
input = utils.Vars.new('input', name), input = utils.Vars.new('input', name),
instruction = instruction, instruction = instruction,
name = name, name = name,
@ -200,23 +197,23 @@ function Mode.new(name, instruction, supress_exit)
Mode Mode
) )
self.show_name = (not vim.o.showmode) and utils.api.redraw or function() self.show_name = vim.o.showmode and
utils.api.redraw() function()
utils.api.redraw()
vim.api.nvim_command('echohl ' .. self.indicator.hl .. " | echon '" .. self.indicator.str .. "'") vim.api.nvim_echo({{'-- ' .. name .. ' --', 'LibmodalPrompt'}}, false, {})
vim.api.nvim_command 'echohl None' end or
end utils.api.redraw
-- define the exit flag -- define the exit flag
self.supress_exit = supress_exit or false self.supress_exit = supress_exit or false
-- if the user provided keymaps -- if the user provided keymaps
if type(instruction) == globals.TYPE_TBL then if type(instruction) == 'table' then
-- create a timer to perform actions with. -- create a timer to perform actions with.
self.flush_input_timer = vim.loop.new_timer() self.flush_input_timer = vim.loop.new_timer()
-- determine if a default `Help` should be created. -- determine if a default `Help` should be created.
if not self.instruction[HELP] then if not self.instruction[HELP_CHAR] then
--- @diagnostic disable-next-line:param-type-mismatch we checked that `instruction` is a table above --- @diagnostic disable-next-line:param-type-mismatch we checked that `instruction` is a table above
self.help = utils.Help.new(self.instruction, 'KEY MAP') self.help = utils.Help.new(self.instruction, 'KEY MAP')
end end
@ -228,7 +225,7 @@ function Mode.new(name, instruction, supress_exit)
self.mappings = ParseTable.new(self.instruction) self.mappings = ParseTable.new(self.instruction)
-- create a table for mode-specific data. -- create a table for mode-specific data.
self.popups = require('libmodal/src/collections/Stack').new() self.popups = require('libmodal.src.collections.Stack').new()
-- create a variable for whether or not timeouts are enabled. -- create a variable for whether or not timeouts are enabled.
self.timeouts = utils.Vars.new('timeouts', self.name) self.timeouts = utils.Vars.new('timeouts', self.name)

@ -1,18 +1,14 @@
--- @type libmodal.globals local utils = require 'libmodal.src.utils' --- @type libmodal.utils
local globals = require 'libmodal/src/globals'
--- @type libmodal.utils
local utils = require 'libmodal/src/utils'
--- @class libmodal.Prompt --- @class libmodal.Prompt
--- @field private completions? string[] --- @field private completions? string[]
--- @field private indicator {hl: string, text: string}
--- @field private exit libmodal.utils.Vars --- @field private exit libmodal.utils.Vars
--- @field private help? libmodal.utils.Help --- @field private help? libmodal.utils.Help
--- @field private indicator libmodal.utils.Indicator
--- @field private input libmodal.utils.Vars --- @field private input libmodal.utils.Vars
--- @field private instruction fun()|{[string]: fun()|string} --- @field private instruction fun()|{[string]: fun()|string}
--- @field private name string --- @field private name string
local Prompt = utils.classes.new(nil) local Prompt = utils.classes.new()
local HELP = 'help' local HELP = 'help'
local REPLACEMENTS = local REPLACEMENTS =
@ -28,11 +24,12 @@ end
--- execute the instruction specified by the `user_input`. --- execute the instruction specified by the `user_input`.
--- @param user_input string --- @param user_input string
--- @return nil
function Prompt:execute_instruction(user_input) function Prompt:execute_instruction(user_input)
if type(self.instruction) == globals.TYPE_TBL then -- the self.instruction is a command table. if type(self.instruction) == 'table' then -- the self.instruction is a command table.
if self.instruction[user_input] then -- there is a defined command for the input. if self.instruction[user_input] then -- there is a defined command for the input.
local to_execute = self.instruction[user_input] local to_execute = self.instruction[user_input]
if type(to_execute) == globals.TYPE_FUNC then if type(to_execute) == 'function' then
to_execute() to_execute()
else else
vim.api.nvim_command(to_execute) vim.api.nvim_command(to_execute)
@ -42,7 +39,7 @@ function Prompt:execute_instruction(user_input)
else -- show an error. else -- show an error.
vim.notify('nvim-libmodal prompt: unkown command', vim.log.levels.ERROR, {title = 'nvim-libmodal'}) vim.notify('nvim-libmodal prompt: unkown command', vim.log.levels.ERROR, {title = 'nvim-libmodal'})
end end
elseif type(self.instruction) == globals.TYPE_STR then -- the self.instruction is a function. elseif type(self.instruction) == 'string' then -- the self.instruction is a function.
vim.fn[self.instruction]() vim.fn[self.instruction]()
else -- attempt to call the self.instruction. else -- attempt to call the self.instruction.
self.instruction() self.instruction()
@ -62,7 +59,7 @@ function Prompt:get_user_input()
--- 3. Read `g:<mode_name>ModeExit` to see if we should `continue_prompt` --- 3. Read `g:<mode_name>ModeExit` to see if we should `continue_prompt`
--- @param user_input string --- @param user_input string
local function user_input_callback(user_input) local function user_input_callback(user_input)
if user_input and string.len(user_input) > 0 then -- the user actually entered something. if user_input and user_input:len() > 0 then -- the user actually entered something.
self.input:set(user_input) self.input:set(user_input)
self:execute_instruction(user_input) self:execute_instruction(user_input)
@ -75,21 +72,20 @@ function Prompt:get_user_input()
end end
end end
-- echo the highlighting
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_echo({{self.indicator.text, self.indicator.hl}}, false, {})
vim.ui.select(self.completions, {}, user_input_callback) vim.ui.select(self.completions, {}, user_input_callback)
else else
vim.ui.input({prompt = self.indicator.str}, user_input_callback) vim.api.nvim_command('echohl ' .. self.indicator.hl)
vim.ui.input({prompt = self.indicator.text}, user_input_callback)
end end
return continue_prompt == nil and true or continue_prompt return continue_prompt == nil and true or continue_prompt
end end
--- enter the prompt. --- enter the prompt.
--- @return nil
function Prompt:enter() function Prompt:enter()
-- enter the mode using a loop. -- enter the mode using a loop.
local continue_mode = true local continue_mode = true
@ -107,53 +103,52 @@ function Prompt:enter()
end end
end end
return --- enter a prompt.
{ --- @param name string the name of the prompt
--- enter a prompt. --- @param instruction fun()|{[string]: fun()|string} what to do with user input
--- @param name string the name of the prompt --- @param user_completions? string[] a list of possible inputs, provided by the user
--- @param instruction fun()|{[string]: fun()|string} what to do with user input --- @return libmodal.Prompt
--- @param user_completions? string[] a list of possible inputs, provided by the user function Prompt.new(name, instruction, user_completions)
--- @return libmodal.Prompt name = vim.trim(name)
new = function(name, instruction, user_completions)
name = vim.trim(name) local self = setmetatable(
{
local self = setmetatable( exit = utils.Vars.new('exit', name),
{ indicator = {hl = 'LibmodalStar', text = '* ' .. name .. ' > '},
exit = utils.Vars.new('exit', name), input = utils.Vars.new('input', name),
indicator = utils.Indicator.new('LibmodalStar', '* ' .. name .. ' > '), instruction = instruction,
input = utils.Vars.new('input', name), name = name
instruction = instruction, },
name = name Prompt
}, )
Prompt
) -- get the completion list.
if type(instruction) == 'table' then -- unload the keys of the mode command table.
-- get the completion list. -- create one if the user specified a command table.
if type(instruction) == globals.TYPE_TBL then -- unload the keys of the mode command table. local completions = {}
-- create one if the user specified a command table. local contained_help = false
local completions = {}
local contained_help = false --- @diagnostic disable-next-line:param-type-mismatch we check `instruction` is `table`
for command, _ in pairs(instruction) do
--- @diagnostic disable-next-line:param-type-mismatch we check `instruction` is `table` table.insert(completions, command)
for command, _ in pairs(instruction) do if command == HELP then
completions[#completions + 1] = command contained_help = true
if command == HELP then
contained_help = true
end
end
if not contained_help then -- assign it.
completions[#completions + 1] = HELP
--- @diagnostic disable-next-line:param-type-mismatch we checked that `instruction` is a table above
self.help = utils.Help.new(instruction, 'COMMAND')
end end
end
self.completions = completions if not contained_help then -- assign it.
elseif user_completions then table.insert(completions, HELP)
-- use the table that the user gave. --- @diagnostic disable-next-line:param-type-mismatch we checked that `instruction` is a table above
self.completions = user_completions self.help = utils.Help.new(instruction, 'COMMAND')
end end
return self self.completions = completions
elseif user_completions then
-- use the table that the user gave.
self.completions = user_completions
end end
}
return self
end
return Prompt

@ -1,34 +1,33 @@
--- @type libmodal.globals local utils = require('libmodal.src.utils') --- @type libmodal.utils
local globals = require 'libmodal/src/globals'
--- the number corresponding to <CR> in vim. --- the number corresponding to `<CR>` in vim.
local CR = string.byte(vim.api.nvim_replace_termcodes('<CR>', true, true, true)) local CR = utils.api.replace_termcodes('<CR>'):byte()
--- @class libmodal.collections.ParseTable --- @class libmodal.collections.ParseTable
--- @field CR number the byte representation of `<CR>` --- @field CR number the byte representation of `<CR>`
local ParseTable = require('libmodal/src/utils/classes').new {CR = CR} local ParseTable = utils.classes.new {CR = CR}
--- reverse the order of elements in some `tbl` --- reverse the order of elements in some `list`.
--- @param tbl table the table to reverse --- @generic T
--- @return table tbl_reversed --- @param list T[]
local function table_reverse(tbl) --- @return T[] reversed
local function table_reverse(list)
local reversed = {} local reversed = {}
while #reversed < #tbl do for i = #list, 1, -1 do
-- look, no variables! table.insert(reversed, list[i])
reversed[#reversed + 1] = tbl[#tbl - #reversed]
end end
return reversed return reversed
end end
--- @param str string --- @param s string
--- @return string[] chars of `str` --- @return string[] chars of `str`
local function chars(str) local function chars(s)
return vim.split(str, '') return vim.split(s, '', {plain = true, trimempty = true})
end end
--- retrieve the mapping of `lhs_reversed_bytes` --- retrieve the mapping of `lhs_reversed_bytes`
--- @param parse_table libmodal.collections.ParseTable the table to fetch `lhs_reversed_bytes` from. --- @param parse_table libmodal.collections.ParseTable the table to fetch `lhs_reversed_bytes` from.
--- @param lhs_reversed_bytes string[] the characters of the left-hand side of the mapping reversed passed to `string.byte` --- @param lhs_reversed_bytes string[]|integer[] the characters of the left-hand side of the mapping reversed passed to `string.byte`
--- @return false|fun()|nil|string|table match a string/func when fully matched; a table when partially matched; false when no match. --- @return false|fun()|nil|string|table match a string/func when fully matched; a table when partially matched; false when no match.
local function get(parse_table, lhs_reversed_bytes) local function get(parse_table, lhs_reversed_bytes)
--[[ Get the next character in the keymap string. ]] --[[ Get the next character in the keymap string. ]]
@ -47,13 +46,13 @@ local function get(parse_table, lhs_reversed_bytes)
local val = parse_table[k] local val = parse_table[k]
local val_type = type(val) local val_type = type(val)
if val_type == globals.TYPE_TBL then if val_type == 'table' then
if val[CR] and #lhs_reversed_bytes < 1 then if val[CR] and #lhs_reversed_bytes < 1 then
return val return val
else else
return get(val, lhs_reversed_bytes) return get(val, lhs_reversed_bytes)
end end
elseif val_type == globals.TYPE_STR or val_type == globals.TYPE_FUNC and #lhs_reversed_bytes < 1 then elseif val_type == 'string' or val_type == 'function' and #lhs_reversed_bytes < 1 then
return val return val
end end
end end
@ -64,16 +63,17 @@ end
--- insert a `value` into `parse_table` at the position indicated by `lhs_reversed_bytes` --- insert a `value` into `parse_table` at the position indicated by `lhs_reversed_bytes`
--- @param lhs_reversed_bytes string[] the characters of the left-hand side of the mapping reversed passed to `string.byte` --- @param lhs_reversed_bytes string[] the characters of the left-hand side of the mapping reversed passed to `string.byte`
--- @param value fun()|string the right-hand-side of the mapping --- @param value fun()|string the right-hand-side of the mapping
--- @return nil
local function put(parse_table, lhs_reversed_bytes, value) local function put(parse_table, lhs_reversed_bytes, value)
--[[ Get the next character in the table. ]] --[[ Get the next character in the table. ]]
local byte = string.byte(table.remove(lhs_reversed_bytes)) local byte = table.remove(lhs_reversed_bytes):byte()
if #lhs_reversed_bytes > 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 parse_table[byte] then -- this is a new mapping if not parse_table[byte] then -- this is a new mapping
parse_table[byte] = {} parse_table[byte] = {}
else -- if there is a previous command mapping in place else -- if there is a previous command mapping in place
local value_type = type(parse_table[byte]) local value_type = type(parse_table[byte])
if value_type == globals.TYPE_STR or value_type == globals.TYPE_FUNC then -- if this is not a tree of inputs already if value_type == 'string' or value_type == 'function' then -- if this is not a tree of inputs already
-- make the mapping require hitting enter before executing -- make the mapping require hitting enter before executing
parse_table[byte] = {[CR] = parse_table[byte]} parse_table[byte] = {[CR] = parse_table[byte]}
end end
@ -82,7 +82,7 @@ local function put(parse_table, lhs_reversed_bytes, value)
-- run put() again -- run put() again
put(parse_table[byte], lhs_reversed_bytes, value) put(parse_table[byte], lhs_reversed_bytes, value)
-- if parse_Table[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(parse_table[byte]) == globals.TYPE_TBL then elseif type(parse_table[byte]) == 'table' then
parse_table[byte][CR] = value parse_table[byte][CR] = value
else else
parse_table[byte] = value -- parse_table[k] is not a table, go ahead and clobber the value. parse_table[byte] = value -- parse_table[k] is not a table, go ahead and clobber the value.
@ -114,11 +114,11 @@ end
--- @return false|fun()|nil|string|table match a string/func when fully found; a table when partially found; false when not found. --- @return false|fun()|nil|string|table match a string/func when fully found; a table when partially found; false when not found.
function ParseTable:parse_get(key) function ParseTable:parse_get(key)
--- @type table<number|string> --- @type table<number|string>
local parsed_table = chars(string.reverse(key)) local parsed_table = chars(key:reverse())
-- convert all of the strings to bytes. -- convert all of the strings to bytes.
for i, v in ipairs(parsed_table) do for i, v in ipairs(parsed_table) do
parsed_table[i] = string.byte(v) parsed_table[i] = v:byte()
end end
return get(self, parsed_table) return get(self, parsed_table)
@ -127,12 +127,14 @@ end
--- parse `key` and assign it to `value`. --- parse `key` and assign it to `value`.
--- @param key string the left-hand-side of the mapping --- @param key string the left-hand-side of the mapping
--- @param value fun()|string the right-hand-side of the mapping --- @param value fun()|string the right-hand-side of the mapping
--- @return nil
function ParseTable:parse_put(key, value) function ParseTable:parse_put(key, value)
put(self, chars(string.reverse(key)), value) put(self, chars(key:reverse()), value)
end end
--- `:parse_put` all `{key, value}` pairs in `keys_and_values`. --- `:parse_put` all `{key, value}` pairs in `keys_and_values`.
--- @param keys_and_values {[string]: fun()|string} --- @param keys_and_values {[string]: fun()|string}
--- @return nil
function ParseTable:parse_put_all(keys_and_values) function ParseTable:parse_put_all(keys_and_values)
for k, v in pairs(keys_and_values) do for k, v in pairs(keys_and_values) do
self:parse_put(k, v) self:parse_put(k, v)

@ -1,27 +1,30 @@
--- @class libmodal.collections.Stack --- @class libmodal.collections.Stack
local Stack = require('libmodal/src/utils/classes').new(nil) local Stack = require('libmodal.src.utils.classes').new()
--- @return libmodal.collections.Stack --- @return libmodal.collections.Stack
function Stack.new() function Stack.new()
return setmetatable({}, Stack) return setmetatable({}, Stack)
end end
--- @return unknown top the foremost value of the stack --- @generic T
--- @return T top the foremost value of the stack
function Stack:peek() function Stack:peek()
return self[#self] return self[#self]
end end
--- remove the foremost value from the stack and return it. --- remove the foremost value from the stack and return it.
--- @return unknown top the foremost value of the stack --- @generic T
--- @return T top the foremost value of the stack
function Stack:pop() function Stack:pop()
return table.remove(self) return table.remove(self)
end end
--- push some `value` on to the stack. --- push some `value` on to the stack.
--- @param value unknown the value to push onto the stack. --- @generic T
--- @param value T the value to push onto the stack.
function Stack:push(value) function Stack:push(value)
-- push to the stack -- push to the stack
self[#self + 1] = value table.insert(self, value)
end end
return Stack return Stack

@ -1,8 +1,10 @@
--- @class libmodal.collections --- @class libmodal.collections
--- @field private ParseTable libmodal.collections.ParseTable --- @field ParseTable libmodal.collections.ParseTable
--- @field private Stack libmodal.collections.Stack --- @field Stack libmodal.collections.Stack
return local collections =
{ {
ParseTable = require 'libmodal/src/collections/ParseTable', ParseTable = require 'libmodal.src.collections.ParseTable',
Stack = require 'libmodal/src/collections/Stack' Stack = require 'libmodal.src.collections.Stack'
} }
return collections

@ -1,31 +1,23 @@
--- @class libmodal.globals --- @class libmodal.globals
--- @field ESC_NR number the key-code for the escape character. --- @field ESC_NR integer the key-code for the escape character.
--- @field TYPE_FUNC string the string which is returned by `type(function() end)` (or any function) --- @field VIM_FALSE integer the value of Vimscript's `v:false`
--- @field TYPE_NUM string the string which is returned by `type(0)` (or any number) --- @field VIM_TRUE integer the value of Vimscript's `v:true`
--- @field TYPE_STR string the string which is returned by `type ''` (or any string)
--- @field TYPE_TBL string the string which is returned by `type {}` (or any table)
--- @field VIM_FALSE number the value of Vimscript's `v:false`
--- @field VIM_TRUE number the value of Vimscript's `v:true`
local globals = local globals =
{ {
ESC_NR = 27, ESC_NR = vim.api.nvim_replace_termcodes('<Esc>', true, true, true):byte(),
TYPE_FUNC = type(function() end),
TYPE_NUM = type(0),
TYPE_STR = type '',
TYPE_TBL = type {},
VIM_FALSE = 0, VIM_FALSE = 0,
VIM_TRUE = 1, VIM_TRUE = 1,
} }
--- assert some value is either `false` or `v:false`. --- assert some value is either `false` or `v:false`.
--- @param val boolean|number --- @param val boolean|integer
--- @return boolean --- @return boolean
function globals.is_false(val) function globals.is_false(val)
return val == false or val == globals.VIM_FALSE return val == false or val == globals.VIM_FALSE
end end
--- assert some value is either `true` or `v:true`. --- assert some value is either `true` or `v:true`.
--- @param val boolean|number --- @param val boolean|integer
--- @return boolean --- @return boolean
function globals.is_true(val) function globals.is_true(val)
return val == true or val == globals.VIM_TRUE return val == true or val == globals.VIM_TRUE

@ -7,12 +7,12 @@
--- @field utils libmodal.utils --- @field utils libmodal.utils
local libmodal = local libmodal =
{ {
collections = require 'libmodal/src/collections', collections = require 'libmodal.src.collections',
globals = require 'libmodal/src/globals', globals = require 'libmodal.src.globals',
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',
} }
return libmodal return libmodal

@ -1,31 +1,31 @@
--- @type libmodal.globals
local globals = require 'libmodal/src/globals'
--- @class libmodal.utils.Help --- @class libmodal.utils.Help
local Help = require('libmodal/src/utils/classes').new(nil) local Help = require('libmodal.src.utils.classes').new()
--- align `tbl` according to the `longest_key_len`. --- align `tbl` according to the `longest_key_len`.
--- @param tbl table what to align. --- @param tbl {[string]: string|fun()} what to align.
--- @param longest_key_len number how long the longest key is. --- @param longest_key_len number how long the longest key is.
--- @return table aligned --- @return string aligned
local function align_columns(tbl, longest_key_len) local function align_columns(tbl, longest_key_len)
local to_print = {} local to_print = {}
for key, value in pairs(tbl) do for key, value in pairs(tbl) do
to_print[#to_print + 1] = key table.insert(to_print, key)
local len = string.len(key) local len = key:len()
local byte = string.byte(key) local byte = key:byte()
-- 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 if byte <= 32 or byte == 127 then
len = len + 1 len = len + 1
end end
for _ = len, longest_key_len do for _ = len, longest_key_len do
to_print[#to_print + 1] = ' ' table.insert(to_print, ' ')
end end
to_print[#to_print + 1] = '' .. (type(value) == globals.TYPE_STR and value or vim.inspect(value)) .. '\n' table.insert(to_print, '' .. (type(value) == 'string' and value or vim.inspect(value)) .. '\n')
end end
return to_print
return table.concat(to_print)
end end
--- create a default help table with `commands_or_maps` and vim expressions. --- create a default help table with `commands_or_maps` and vim expressions.
@ -33,12 +33,13 @@ end
--- @param title string --- @param title string
--- @return libmodal.utils.Help --- @return libmodal.utils.Help
function Help.new(commands_or_maps, title) function Help.new(commands_or_maps, title)
-- find the longest key in the table, or at least the length of the title --- the longest key in the table
local longest_key_maps = string.len(title) local longest_key = title:len()
for key, _ in pairs(commands_or_maps) do for key, _ in pairs(commands_or_maps) do
local key_len = string.len(key) local key_len = key:len()
if key_len > longest_key_maps then if key_len > longest_key then
longest_key_maps = key_len longest_key = key_len
end end
end end
@ -46,19 +47,18 @@ function Help.new(commands_or_maps, title)
return setmetatable( return setmetatable(
{ {
[1] = ' ', [1] = ' ',
[2] = table.concat(align_columns({[title] = 'VIM EXPRESSION'}, longest_key_maps)), [2] = align_columns({[title] = 'VIM EXPRESSION'}, longest_key),
[3] = table.concat(align_columns({[string.rep('-', string.len(title))] = '--------------'}, longest_key_maps)), [3] = align_columns({[('-'):rep(title:len())] = '--------------'}, longest_key),
[4] = table.concat(align_columns(commands_or_maps, longest_key_maps)), [4] = align_columns(commands_or_maps, longest_key),
}, },
Help Help
) )
end end
--- show the contents of this `Help`. --- show the contents of this `Help`.
--- @return nil
function Help:show() function Help:show()
for _, help_text in ipairs(self) do vim.api.nvim_echo(self, false, {})
print(help_text)
end
vim.fn.getchar() vim.fn.getchar()
end end

@ -1,13 +0,0 @@
--- @class libmodal.utils.Indicator
--- @field hl string the highlight group to use when printing `str`
--- @field 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
return Indicator

@ -2,7 +2,7 @@
--- @field private buffer number the number of the window which this popup is rendered on. --- @field private buffer number the number of the window which this popup is rendered on.
--- @field private input_chars string[] the characters input by the user. --- @field private input_chars string[] the characters input by the user.
--- @field private window number the number of the window which this popup is rendered on. --- @field private window number the number of the window which this popup is rendered on.
local Popup = require('libmodal/src/utils/classes').new(nil) local Popup = require('libmodal.src.utils.classes').new()
--- @param window number --- @param window number
--- @return boolean `true` if the window is non-`nil` and `nvim_win_is_valid` --- @return boolean `true` if the window is non-`nil` and `nvim_win_is_valid`
@ -13,6 +13,7 @@ end
--- Close `self.window` --- Close `self.window`
--- The `self` is inert after calling this. --- The `self` is inert after calling this.
--- @param keep_buffer boolean `self.buffer` is passed to `nvim_buf_delete` unless `keep_buffer` is `false` --- @param keep_buffer boolean `self.buffer` is passed to `nvim_buf_delete` unless `keep_buffer` is `false`
--- @return nil
function Popup:close(keep_buffer) function Popup:close(keep_buffer)
if valid(self.window) then if valid(self.window) then
vim.api.nvim_win_close(self.window, false) vim.api.nvim_win_close(self.window, false)
@ -35,6 +36,7 @@ function Popup.new(config)
end end
--- attempt to open this popup. If the popup was already open, close it and re-open it. --- attempt to open this popup. If the popup was already open, close it and re-open it.
--- @return nil
function Popup:open(config) function Popup:open(config)
if not config then if not config then
config = config =
@ -62,6 +64,7 @@ end
--- display `input_bytes` in `self.buffer` --- display `input_bytes` in `self.buffer`
--- @param input_bytes number[] a list of character codes to display --- @param input_bytes number[] a list of character codes to display
--- @return nil
function Popup:refresh(input_bytes) function Popup:refresh(input_bytes)
-- the user simply typed one more character onto the last one. -- the user simply typed one more character onto the last one.
if #input_bytes == #self.input_chars + 1 then if #input_bytes == #self.input_chars + 1 then

@ -1,7 +1,7 @@
--- @class libmodal.utils.Vars --- @class libmodal.utils.Vars
--- @field private mode_name string the highlight group to use when printing `str` --- @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` --- @field private var_name string the highlight group to use when printing `str`
local Vars = require('libmodal/src/utils/classes').new(nil) local Vars = require('libmodal.src.utils.classes').new()
--- @return unknown `vim.g[self:name()])` --- @return unknown `vim.g[self:name()])`
function Vars:get() function Vars:get()
@ -20,11 +20,15 @@ end
function Vars.new(var_name, mode_name) function Vars.new(var_name, mode_name)
local self = setmetatable({}, Vars) local self = setmetatable({}, Vars)
--- @param str_with_spaces string
--- @param first_letter_modifier fun(s: string): string
local function no_spaces(str_with_spaces, first_letter_modifier) 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 split_str = vim.split(str_with_spaces:gsub(vim.pesc '_', vim.pesc ' '), ' ')
--- @param str string
--- @param func fun(s: string): string
local function camel_case(str, func) local function camel_case(str, func)
return func(string.sub(str, 0, 1) or '') .. string.lower(string.sub(str, 2) or '') return func(str:sub(0, 1) or '') .. (str:sub(2) or ''):lower()
end end
split_str[1] = camel_case(split_str[1], first_letter_modifier) split_str[1] = camel_case(split_str[1], first_letter_modifier)
@ -42,9 +46,11 @@ function Vars.new(var_name, mode_name)
return self return self
end end
--- @param val unknown set `vim.g[self:name()])` equal to this value --- @generic T
--- @param val T set `g:{self:name()})` equal to this value
--- @return nil
function Vars:set(val) function Vars:set(val)
vim.g[self:name()] = val vim.api.nvim_set_var(self:name(), val)
end end
return Vars return Vars

@ -1,14 +1,14 @@
--- @type libmodal.globals local globals = require 'libmodal.src.globals' --- @type libmodal.globals
local globals = require 'libmodal/src/globals'
--- @class libmodal.utils.api --- @class libmodal.utils.api
local api = {} local api = {}
--- send a character to exit a mode. --- send a character to exit a mode.
--- @param exit_char? number|string the character used to exit the mode, or ESCAPE if none was provided. --- @param exit_char? number|string the character used to exit the mode, or ESCAPE if none was provided.
--- @return nil
function api.mode_exit(exit_char) function api.mode_exit(exit_char)
-- if there was no provided `exit_char`, or it is a character code. -- if there was no provided `exit_char`, or it is a character code.
if type(exit_char) == globals.TYPE_NUM then if type(exit_char) == 'number' then
-- translate the character code or default to escape. -- translate the character code or default to escape.
--- @diagnostic disable-next-line:param-type-mismatch we just checked `exit_char` == `number` --- @diagnostic disable-next-line:param-type-mismatch we just checked `exit_char` == `number`
exit_char = string.char(exit_char) exit_char = string.char(exit_char)
@ -22,6 +22,7 @@ function api.mode_exit(exit_char)
end end
--- run the `mode` command to refresh the screen. --- run the `mode` command to refresh the screen.
--- @return nil
function api.redraw() function api.redraw()
vim.api.nvim_command 'mode' vim.api.nvim_command 'mode'
end end

@ -1,18 +1,18 @@
--- @class libmodal.utils.classes --- @class libmodal.utils.classes
local classes = {} local classes =
{
--- define a metatable.
--- @param template? table the default value
--- @return table class
new = function(template)
-- set self to `template`, or `{}` if nil.
local self = template or {}
--- define a metatable. -- set `__index`.
--- @param template? table the default value
function classes.new(template)
-- set self to `template`, or `{}` if nil.
local self = template or {}
-- set `__index`.
if not self.__index then
self.__index = self self.__index = self
end
return self return self
end end,
}
return classes return classes

@ -1,23 +1,22 @@
--- @class libmodal.utils --- @class libmodal.utils
--- @field api libmodal.utils.api --- @field api libmodal.utils.api
--- @field classes libmodal.utils.classes --- @field classes libmodal.utils.classes
--- @field Indicator libmodal.utils.Indicator
--- @field Help libmodal.utils.Help --- @field Help libmodal.utils.Help
--- @field Popup libmodal.utils.Popup --- @field Popup libmodal.utils.Popup
--- @field Vars libmodal.utils.Vars --- @field Vars libmodal.utils.Vars
local utils = local utils =
{ {
api = require 'libmodal/src/utils/api', api = require 'libmodal.src.utils.api',
classes = require 'libmodal/src/utils/classes', classes = require 'libmodal.src.utils.classes',
Indicator = require 'libmodal/src/utils/Indicator', Help = require 'libmodal.src.utils.Help',
Help = require 'libmodal/src/utils/Help', Popup = require 'libmodal.src.utils.Popup',
Popup = require 'libmodal/src/utils/Popup', Vars = require 'libmodal.src.utils.Vars',
Vars = require 'libmodal/src/utils/Vars',
} }
--- `vim.notify` with a `msg` some `error` which has a `vim.v.throwpoint` and `vim.v.exception`. --- `vim.notify` with a `msg` some `error` which has a `vim.v.throwpoint` and `vim.v.exception`.
--- @param msg string --- @param msg string
--- @param err string --- @param err string
--- @return nil
function utils.notify_error(msg, err) function utils.notify_error(msg, err)
vim.notify( vim.notify(
msg .. ': ' .. vim.v.throwpoint .. '\n' .. vim.v.exception .. '\n' .. err, msg .. ': ' .. vim.v.throwpoint .. '\n' .. vim.v.exception .. '\n' .. err,

@ -1,6 +1,3 @@
if vim.g.loaded_libmodal then return end
vim.g.loaded_libmodal = true
vim.g.libmodalTimeouts = vim.g.libmodalTimeouts or vim.go.timeout 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.

Loading…
Cancel
Save