From 8c164b811d962f9c4b15e25fa7ff5048859c50b3 Mon Sep 17 00:00:00 2001 From: Iron-E Date: Sun, 12 Mar 2023 19:26:38 -0400 Subject: [PATCH] perf: general improvements I forgot to break it up into individual commits, and I ain't going backwards now. --- examples/lua/keymaps-manually.lua | 4 +- lua/libmodal/init.lua | 25 +++-- lua/libmodal/src/Layer.lua | 12 +- lua/libmodal/src/Mode.lua | 55 +++++---- lua/libmodal/src/Prompt.lua | 117 ++++++++++---------- lua/libmodal/src/collections/ParseTable.lua | 50 +++++---- lua/libmodal/src/collections/Stack.lua | 13 ++- lua/libmodal/src/collections/init.lua | 12 +- lua/libmodal/src/globals.lua | 20 +--- lua/libmodal/src/init.lua | 12 +- lua/libmodal/src/utils/Help.lua | 46 ++++---- lua/libmodal/src/utils/Indicator.lua | 13 --- lua/libmodal/src/utils/Popup.lua | 5 +- lua/libmodal/src/utils/Vars.lua | 16 ++- lua/libmodal/src/utils/api.lua | 7 +- lua/libmodal/src/utils/classes.lua | 24 ++-- lua/libmodal/src/utils/init.lua | 13 +-- plugin/libmodal.lua | 3 - 18 files changed, 220 insertions(+), 227 deletions(-) delete mode 100644 lua/libmodal/src/utils/Indicator.lua diff --git a/examples/lua/keymaps-manually.lua b/examples/lua/keymaps-manually.lua index 9b23eb1..e141c19 100644 --- a/examples/lua/keymaps-manually.lua +++ b/examples/lua/keymaps-manually.lua @@ -15,10 +15,10 @@ end -- this is the function that will be called whenever the user presses a button local function foo_mode() -- 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 vim.g.fooModeInput - ) + )) -- custom logic to test for each character index to see if it matches the 'zfo' mapping local index = 1 diff --git a/lua/libmodal/init.lua b/lua/libmodal/init.lua index d482aeb..358ae53 100644 --- a/lua/libmodal/init.lua +++ b/lua/libmodal/init.lua @@ -8,7 +8,7 @@ return setmetatable( --- @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 enter = function(keymap, exit_char) - local layer = require('libmodal/src/Layer').new(keymap) + local layer = require('libmodal.src.Layer').new(keymap) layer:enter() if exit_char then @@ -22,7 +22,7 @@ return setmetatable( --- @param keymap table the keymaps (e.g. `{n = {gg = {rhs = 'G', silent = true}}}`) --- @return libmodal.Layer new = function(keymap) - return require('libmodal/src/Layer').new(keymap) + return require('libmodal.src.Layer').new(keymap) end, }, @@ -32,7 +32,7 @@ return setmetatable( --- @param name string the name of the mode. --- @param instruction fun()|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() + require('libmodal.src.Mode').new(name, instruction, supress_exit):enter() end }, @@ -43,7 +43,7 @@ return setmetatable( --- @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 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 } }, @@ -52,12 +52,17 @@ return setmetatable( if key ~= 'Layer' then return rawget(tbl, key) else - vim.notify_once( - '`libmodal.Layer` is deprecated in favor of `libmodal.layer`. It will work FOR NOW, but uncapitalize that `L` please :)', - vim.log.levels.WARN, - {title = 'nvim-libmodal'} - ) - return require 'libmodal/src/Layer' + if vim.deprecate then + vim.deprecate('`libmodal.Layer`', '`libmodal.layer`', '4.0.0', 'nvim-libmodal') + else + vim.notify_once( + '`libmodal.Layer` is deprecated in favor of `libmodal.layer`. It will work FOR NOW, but uncapitalize that `L` please :)', + vim.log.levels.WARN, + {title = 'nvim-libmodal'} + ) + end + + return rawget(tbl, 'layer') end end, } diff --git a/lua/libmodal/src/Layer.lua b/lua/libmodal/src/Layer.lua index 6509f76..8f0d7b6 100644 --- a/lua/libmodal/src/Layer.lua +++ b/lua/libmodal/src/Layer.lua @@ -1,8 +1,8 @@ --- @type libmodal.globals -local globals = require 'libmodal/src/globals' +local globals = require 'libmodal.src.globals' --- @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. --- @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 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 -local Layer = require('libmodal/src/utils/classes').new(nil) +local Layer = require('libmodal.src.utils.classes').new() --- apply the `Layer`'s keymaps buffer. +--- @return nil function Layer:enter() if self:is_active() then vim.notify( @@ -76,6 +77,7 @@ function Layer:enter() end --- exit the layer, restoring all previous keymaps. +--- @return nil function Layer:exit() if not self.active then vim.notify( @@ -106,6 +108,7 @@ end --- @param lhs string the left hand side of the keymap. --- @param rhs fun()|string the right hand side of the keymap. --- @param options table options for the keymap. +--- @return nil --- @see vim.keymap.set function Layer:map(mode, lhs, rhs, options) 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 mode string the mode of the keymap. --- @param lhs string the keys which invoke the keymap. +--- @return nil --- @see vim.api.nvim_del_keymap function Layer:unmap(buffer, mode, lhs) lhs = utils.api.replace_termcodes(lhs) @@ -170,7 +174,7 @@ function Layer:unmap(buffer, mode, lhs) end) 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 end end diff --git a/lua/libmodal/src/Mode.lua b/lua/libmodal/src/Mode.lua index 9cc2882..8842b49 100644 --- a/lua/libmodal/src/Mode.lua +++ b/lua/libmodal/src/Mode.lua @@ -1,17 +1,11 @@ ---- @type libmodal.globals -local globals = require 'libmodal/src/globals' - ---- @type libmodal.collections.ParseTable -local ParseTable = require 'libmodal/src/collections/ParseTable' - ---- @type libmodal.utils -local utils = require 'libmodal/src/utils' +local globals = require 'libmodal.src.globals' --- @type libmodal.globals +local ParseTable = require 'libmodal.src.collections.ParseTable' --- @type libmodal.collections.ParseTable +local utils = require 'libmodal.src.utils' --- @type libmodal.utils --- @class libmodal.Mode --- @field private exit libmodal.utils.Vars --- @field private flush_input_timer unknown --- @field private help? libmodal.utils.Help ---- @field private indicator libmodal.utils.Indicator --- @field private input libmodal.utils.Vars --- @field private input_bytes? number[] --- @field private instruction fun()|{[string]: fun()|string} @@ -23,19 +17,20 @@ local utils = require 'libmodal/src/utils' --- @field private timeouts_enabled boolean local Mode = utils.classes.new() -local HELP = '?' +local HELP_CHAR = '?' local TIMEOUT = { - CHAR = 'ø', + CHAR = ' ', LEN = vim.go.timeoutlen, 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`. --- @param instruction fun()|string a Lua function or Vimscript command. +--- @return nil function Mode.execute_instruction(instruction) - if type(instruction) == globals.TYPE_FUNC then + if type(instruction) == 'function' then instruction() else 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. --- 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() -- stop any running timers self.flush_input_timer:stop() @@ -59,12 +55,12 @@ function Mode:check_input_for_mapping() -- if there was no matching command 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() end 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 TIMEOUT.LEN, 0, vim.schedule_wrap(function() -- send input to interrupt a blocking `getchar` @@ -88,9 +84,10 @@ function Mode:check_input_for_mapping() end --- enter this mode. +--- @return nil function Mode:enter() -- 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. self.popups:push(utils.Popup.new()) end @@ -151,9 +148,9 @@ function Mode:get_user_input() over the instruction and it may change over the course of execution. ]] 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() - 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]() else -- the instruction is a function. self.instruction() @@ -164,8 +161,9 @@ function Mode:get_user_input() end --- uninitialize variables from after exiting the mode. +--- @return nil function Mode:tear_down() - if type(self.instruction) == globals.TYPE_TBL then + if type(self.instruction) == 'table' then self.flush_input_timer:stop() self.input_bytes = nil @@ -192,7 +190,6 @@ function Mode.new(name, instruction, supress_exit) local self = setmetatable( { exit = utils.Vars.new('exit', name), - indicator = utils.Indicator.new('LibmodalPrompt', '-- ' .. name .. ' --'), input = utils.Vars.new('input', name), instruction = instruction, name = name, @@ -200,23 +197,23 @@ function Mode.new(name, instruction, supress_exit) Mode ) - self.show_name = (not vim.o.showmode) and utils.api.redraw or function() - utils.api.redraw() - - vim.api.nvim_command('echohl ' .. self.indicator.hl .. " | echon '" .. self.indicator.str .. "'") - vim.api.nvim_command 'echohl None' - end + self.show_name = vim.o.showmode and + function() + utils.api.redraw() + vim.api.nvim_echo({{'-- ' .. name .. ' --', 'LibmodalPrompt'}}, false, {}) + end or + utils.api.redraw -- define the exit flag self.supress_exit = supress_exit or false -- if the user provided keymaps - if type(instruction) == globals.TYPE_TBL then + if type(instruction) == 'table' then -- create a timer to perform actions with. self.flush_input_timer = vim.loop.new_timer() -- 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 self.help = utils.Help.new(self.instruction, 'KEY MAP') end @@ -228,7 +225,7 @@ function Mode.new(name, instruction, supress_exit) self.mappings = ParseTable.new(self.instruction) -- 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. self.timeouts = utils.Vars.new('timeouts', self.name) diff --git a/lua/libmodal/src/Prompt.lua b/lua/libmodal/src/Prompt.lua index d22309b..9e77e16 100644 --- a/lua/libmodal/src/Prompt.lua +++ b/lua/libmodal/src/Prompt.lua @@ -1,18 +1,14 @@ ---- @type libmodal.globals -local globals = require 'libmodal/src/globals' - ---- @type libmodal.utils -local utils = require 'libmodal/src/utils' +local utils = require 'libmodal.src.utils' --- @type libmodal.utils --- @class libmodal.Prompt --- @field private completions? string[] +--- @field private indicator {hl: string, text: string} --- @field private exit libmodal.utils.Vars --- @field private help? libmodal.utils.Help ---- @field private indicator libmodal.utils.Indicator --- @field private input libmodal.utils.Vars --- @field private instruction fun()|{[string]: fun()|string} --- @field private name string -local Prompt = utils.classes.new(nil) +local Prompt = utils.classes.new() local HELP = 'help' local REPLACEMENTS = @@ -28,11 +24,12 @@ end --- execute the instruction specified by the `user_input`. --- @param user_input string +--- @return nil 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. local to_execute = self.instruction[user_input] - if type(to_execute) == globals.TYPE_FUNC then + if type(to_execute) == 'function' then to_execute() else vim.api.nvim_command(to_execute) @@ -42,7 +39,7 @@ function Prompt:execute_instruction(user_input) else -- show an error. vim.notify('nvim-libmodal prompt: unkown command', vim.log.levels.ERROR, {title = 'nvim-libmodal'}) 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]() else -- attempt to call the self.instruction. self.instruction() @@ -62,7 +59,7 @@ function Prompt:get_user_input() --- 3. Read `g:ModeExit` to see if we should `continue_prompt` --- @param user_input string local function user_input_callback(user_input) - if user_input and string.len(user_input) > 0 then -- the user actually entered something. + if user_input and user_input:len() > 0 then -- the user actually entered something. self.input:set(user_input) self:execute_instruction(user_input) @@ -75,21 +72,20 @@ function Prompt:get_user_input() end end - -- echo the highlighting - vim.api.nvim_command('echohl ' .. self.indicator.hl) - -- set the user input variable 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) 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 return continue_prompt == nil and true or continue_prompt end --- enter the prompt. +--- @return nil function Prompt:enter() -- enter the mode using a loop. local continue_mode = true @@ -107,53 +103,52 @@ function Prompt:enter() end end -return -{ - --- enter a prompt. - --- @param name string the name of the prompt - --- @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 - --- @return libmodal.Prompt - new = function(name, instruction, user_completions) - name = vim.trim(name) - - local self = setmetatable( - { - exit = utils.Vars.new('exit', name), - indicator = utils.Indicator.new('LibmodalStar', '* ' .. name .. ' > '), - input = utils.Vars.new('input', name), - instruction = instruction, - name = name - }, - Prompt - ) - - -- get the completion list. - if type(instruction) == globals.TYPE_TBL then -- unload the keys of the mode command table. - -- create one if the user specified a command table. - local completions = {} - local contained_help = false - - --- @diagnostic disable-next-line:param-type-mismatch we check `instruction` is `table` - for command, _ in pairs(instruction) do - completions[#completions + 1] = command - 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') +--- enter a prompt. +--- @param name string the name of the prompt +--- @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 +--- @return libmodal.Prompt +function Prompt.new(name, instruction, user_completions) + name = vim.trim(name) + + local self = setmetatable( + { + exit = utils.Vars.new('exit', name), + indicator = {hl = 'LibmodalStar', text = '* ' .. name .. ' > '}, + input = utils.Vars.new('input', name), + instruction = instruction, + name = name + }, + Prompt + ) + + -- get the completion list. + if type(instruction) == 'table' then -- unload the keys of the mode command table. + -- create one if the user specified a command table. + local completions = {} + local contained_help = false + + --- @diagnostic disable-next-line:param-type-mismatch we check `instruction` is `table` + for command, _ in pairs(instruction) do + table.insert(completions, command) + if command == HELP then + contained_help = true end + end - self.completions = completions - elseif user_completions then - -- use the table that the user gave. - self.completions = user_completions + if not contained_help then -- assign it. + table.insert(completions, HELP) + --- @diagnostic disable-next-line:param-type-mismatch we checked that `instruction` is a table above + self.help = utils.Help.new(instruction, 'COMMAND') end - return self + self.completions = completions + elseif user_completions then + -- use the table that the user gave. + self.completions = user_completions end -} + + return self +end + +return Prompt diff --git a/lua/libmodal/src/collections/ParseTable.lua b/lua/libmodal/src/collections/ParseTable.lua index 8dcde56..ec24fe7 100644 --- a/lua/libmodal/src/collections/ParseTable.lua +++ b/lua/libmodal/src/collections/ParseTable.lua @@ -1,34 +1,33 @@ ---- @type libmodal.globals -local globals = require 'libmodal/src/globals' +local utils = require('libmodal.src.utils') --- @type libmodal.utils ---- the number corresponding to in vim. -local CR = string.byte(vim.api.nvim_replace_termcodes('', true, true, true)) +--- the number corresponding to `` in vim. +local CR = utils.api.replace_termcodes(''):byte() --- @class libmodal.collections.ParseTable --- @field CR number the byte representation of `` -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` ---- @param tbl table the table to reverse ---- @return table tbl_reversed -local function table_reverse(tbl) +--- reverse the order of elements in some `list`. +--- @generic T +--- @param list T[] +--- @return T[] reversed +local function table_reverse(list) local reversed = {} - while #reversed < #tbl do - -- look, no variables! - reversed[#reversed + 1] = tbl[#tbl - #reversed] + for i = #list, 1, -1 do + table.insert(reversed, list[i]) end return reversed end ---- @param str string +--- @param s string --- @return string[] chars of `str` -local function chars(str) - return vim.split(str, '') +local function chars(s) + return vim.split(s, '', {plain = true, trimempty = true}) end --- retrieve the mapping of `lhs_reversed_bytes` --- @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. local function get(parse_table, lhs_reversed_bytes) --[[ 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_type = type(val) - if val_type == globals.TYPE_TBL then + if val_type == 'table' then if val[CR] and #lhs_reversed_bytes < 1 then return val else return get(val, lhs_reversed_bytes) 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 end end @@ -64,16 +63,17 @@ end --- 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 value fun()|string the right-hand-side of the mapping +--- @return nil local function put(parse_table, lhs_reversed_bytes, value) --[[ 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 not parse_table[byte] then -- this is a new mapping parse_table[byte] = {} else -- if there is a previous command mapping in place 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 parse_table[byte] = {[CR] = parse_table[byte]} end @@ -82,7 +82,7 @@ local function put(parse_table, lhs_reversed_bytes, value) -- run put() again 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. - elseif type(parse_table[byte]) == globals.TYPE_TBL then + elseif type(parse_table[byte]) == 'table' then parse_table[byte][CR] = value else 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. function ParseTable:parse_get(key) --- @type table - local parsed_table = chars(string.reverse(key)) + local parsed_table = chars(key:reverse()) -- convert all of the strings to bytes. for i, v in ipairs(parsed_table) do - parsed_table[i] = string.byte(v) + parsed_table[i] = v:byte() end return get(self, parsed_table) @@ -127,12 +127,14 @@ end --- parse `key` and assign it to `value`. --- @param key string the left-hand-side of the mapping --- @param value fun()|string the right-hand-side of the mapping +--- @return nil function ParseTable:parse_put(key, value) - put(self, chars(string.reverse(key)), value) + put(self, chars(key:reverse()), value) end --- `:parse_put` all `{key, value}` pairs in `keys_and_values`. --- @param keys_and_values {[string]: fun()|string} +--- @return nil function ParseTable:parse_put_all(keys_and_values) for k, v in pairs(keys_and_values) do self:parse_put(k, v) diff --git a/lua/libmodal/src/collections/Stack.lua b/lua/libmodal/src/collections/Stack.lua index 342fb83..0211f4b 100644 --- a/lua/libmodal/src/collections/Stack.lua +++ b/lua/libmodal/src/collections/Stack.lua @@ -1,27 +1,30 @@ --- @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 function Stack.new() return setmetatable({}, Stack) end ---- @return unknown top the foremost value of the stack +--- @generic T +--- @return T top the foremost value of the stack function Stack:peek() return self[#self] end --- 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() return table.remove(self) end --- 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) -- push to the stack - self[#self + 1] = value + table.insert(self, value) end return Stack diff --git a/lua/libmodal/src/collections/init.lua b/lua/libmodal/src/collections/init.lua index e31af42..6ec29eb 100644 --- a/lua/libmodal/src/collections/init.lua +++ b/lua/libmodal/src/collections/init.lua @@ -1,8 +1,10 @@ --- @class libmodal.collections ---- @field private ParseTable libmodal.collections.ParseTable ---- @field private Stack libmodal.collections.Stack -return +--- @field ParseTable libmodal.collections.ParseTable +--- @field Stack libmodal.collections.Stack +local collections = { - ParseTable = require 'libmodal/src/collections/ParseTable', - Stack = require 'libmodal/src/collections/Stack' + ParseTable = require 'libmodal.src.collections.ParseTable', + Stack = require 'libmodal.src.collections.Stack' } + +return collections diff --git a/lua/libmodal/src/globals.lua b/lua/libmodal/src/globals.lua index 81014cb..210ce95 100644 --- a/lua/libmodal/src/globals.lua +++ b/lua/libmodal/src/globals.lua @@ -1,31 +1,23 @@ --- @class libmodal.globals ---- @field ESC_NR number the key-code for the escape character. ---- @field TYPE_FUNC string the string which is returned by `type(function() end)` (or any function) ---- @field TYPE_NUM string the string which is returned by `type(0)` (or any number) ---- @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` +--- @field ESC_NR integer the key-code for the escape character. +--- @field VIM_FALSE integer the value of Vimscript's `v:false` +--- @field VIM_TRUE integer the value of Vimscript's `v:true` local globals = { - ESC_NR = 27, - TYPE_FUNC = type(function() end), - TYPE_NUM = type(0), - TYPE_STR = type '', - TYPE_TBL = type {}, + ESC_NR = vim.api.nvim_replace_termcodes('', true, true, true):byte(), VIM_FALSE = 0, VIM_TRUE = 1, } --- assert some value is either `false` or `v:false`. ---- @param val boolean|number +--- @param val boolean|integer --- @return boolean function globals.is_false(val) return val == false or val == globals.VIM_FALSE end --- assert some value is either `true` or `v:true`. ---- @param val boolean|number +--- @param val boolean|integer --- @return boolean function globals.is_true(val) return val == true or val == globals.VIM_TRUE diff --git a/lua/libmodal/src/init.lua b/lua/libmodal/src/init.lua index c7b5436..a6a9058 100644 --- a/lua/libmodal/src/init.lua +++ b/lua/libmodal/src/init.lua @@ -7,12 +7,12 @@ --- @field utils libmodal.utils local libmodal = { - collections = require 'libmodal/src/collections', - globals = require 'libmodal/src/globals', - Layer = require 'libmodal/src/Layer', - Mode = require 'libmodal/src/Mode', - Prompt = require 'libmodal/src/Prompt', - utils = require 'libmodal/src/utils', + collections = require 'libmodal.src.collections', + globals = require 'libmodal.src.globals', + Layer = require 'libmodal.src.Layer', + Mode = require 'libmodal.src.Mode', + Prompt = require 'libmodal.src.Prompt', + utils = require 'libmodal.src.utils', } return libmodal diff --git a/lua/libmodal/src/utils/Help.lua b/lua/libmodal/src/utils/Help.lua index 2820dfb..e16d1ef 100644 --- a/lua/libmodal/src/utils/Help.lua +++ b/lua/libmodal/src/utils/Help.lua @@ -1,31 +1,31 @@ ---- @type libmodal.globals -local globals = require 'libmodal/src/globals' - --- @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`. ---- @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. ---- @return table aligned +--- @return string aligned local function align_columns(tbl, longest_key_len) local to_print = {} + for key, value in pairs(tbl) do - to_print[#to_print + 1] = key - local len = string.len(key) - local byte = string.byte(key) + table.insert(to_print, key) + local len = key:len() + local byte = key:byte() + -- account for ASCII chars that take up more space. if byte <= 32 or byte == 127 then len = len + 1 end for _ = len, longest_key_len do - to_print[#to_print + 1] = ' ' + table.insert(to_print, ' ') 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 - return to_print + + return table.concat(to_print) end --- create a default help table with `commands_or_maps` and vim expressions. @@ -33,12 +33,13 @@ end --- @param title string --- @return libmodal.utils.Help function Help.new(commands_or_maps, title) - -- find the longest key in the table, or at least the length of the title - local longest_key_maps = string.len(title) + --- the longest key in the table + local longest_key = title:len() + 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 + local key_len = key:len() + if key_len > longest_key then + longest_key = key_len end end @@ -46,19 +47,18 @@ function Help.new(commands_or_maps, title) return setmetatable( { [1] = ' ', - [2] = table.concat(align_columns({[title] = 'VIM EXPRESSION'}, longest_key_maps)), - [3] = table.concat(align_columns({[string.rep('-', string.len(title))] = '--------------'}, longest_key_maps)), - [4] = table.concat(align_columns(commands_or_maps, longest_key_maps)), + [2] = align_columns({[title] = 'VIM EXPRESSION'}, longest_key), + [3] = align_columns({[('-'):rep(title:len())] = '--------------'}, longest_key), + [4] = align_columns(commands_or_maps, longest_key), }, Help ) end --- show the contents of this `Help`. +--- @return nil function Help:show() - for _, help_text in ipairs(self) do - print(help_text) - end + vim.api.nvim_echo(self, false, {}) vim.fn.getchar() end diff --git a/lua/libmodal/src/utils/Indicator.lua b/lua/libmodal/src/utils/Indicator.lua deleted file mode 100644 index 5995caa..0000000 --- a/lua/libmodal/src/utils/Indicator.lua +++ /dev/null @@ -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 diff --git a/lua/libmodal/src/utils/Popup.lua b/lua/libmodal/src/utils/Popup.lua index 8c42fc0..6cd36df 100644 --- a/lua/libmodal/src/utils/Popup.lua +++ b/lua/libmodal/src/utils/Popup.lua @@ -2,7 +2,7 @@ --- @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 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 --- @return boolean `true` if the window is non-`nil` and `nvim_win_is_valid` @@ -13,6 +13,7 @@ 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` +--- @return nil function Popup:close(keep_buffer) if valid(self.window) then vim.api.nvim_win_close(self.window, false) @@ -35,6 +36,7 @@ function Popup.new(config) end --- attempt to open this popup. If the popup was already open, close it and re-open it. +--- @return nil function Popup:open(config) if not config then config = @@ -62,6 +64,7 @@ end --- display `input_bytes` in `self.buffer` --- @param input_bytes number[] a list of character codes to display +--- @return nil function Popup:refresh(input_bytes) -- the user simply typed one more character onto the last one. if #input_bytes == #self.input_chars + 1 then diff --git a/lua/libmodal/src/utils/Vars.lua b/lua/libmodal/src/utils/Vars.lua index 9fb8fac..384031f 100644 --- a/lua/libmodal/src/utils/Vars.lua +++ b/lua/libmodal/src/utils/Vars.lua @@ -1,7 +1,7 @@ --- @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(nil) +local Vars = require('libmodal.src.utils.classes').new() --- @return unknown `vim.g[self:name()])` function Vars:get() @@ -20,11 +20,15 @@ end function Vars.new(var_name, mode_name) 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 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) - 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 split_str[1] = camel_case(split_str[1], first_letter_modifier) @@ -42,9 +46,11 @@ function Vars.new(var_name, mode_name) return self 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) - vim.g[self:name()] = val + vim.api.nvim_set_var(self:name(), val) end return Vars diff --git a/lua/libmodal/src/utils/api.lua b/lua/libmodal/src/utils/api.lua index b4fc563..3e504ce 100644 --- a/lua/libmodal/src/utils/api.lua +++ b/lua/libmodal/src/utils/api.lua @@ -1,14 +1,14 @@ ---- @type libmodal.globals -local globals = require 'libmodal/src/globals' +local globals = require 'libmodal.src.globals' --- @type libmodal.globals --- @class libmodal.utils.api local api = {} --- 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. +--- @return nil function api.mode_exit(exit_char) -- 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. --- @diagnostic disable-next-line:param-type-mismatch we just checked `exit_char` == `number` exit_char = string.char(exit_char) @@ -22,6 +22,7 @@ function api.mode_exit(exit_char) end --- run the `mode` command to refresh the screen. +--- @return nil function api.redraw() vim.api.nvim_command 'mode' end diff --git a/lua/libmodal/src/utils/classes.lua b/lua/libmodal/src/utils/classes.lua index 989b48b..9452fc0 100644 --- a/lua/libmodal/src/utils/classes.lua +++ b/lua/libmodal/src/utils/classes.lua @@ -1,18 +1,18 @@ --- @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. ---- @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 + -- set `__index`. self.__index = self - end - return self -end + return self + end, +} return classes diff --git a/lua/libmodal/src/utils/init.lua b/lua/libmodal/src/utils/init.lua index e50df7a..433259a 100644 --- a/lua/libmodal/src/utils/init.lua +++ b/lua/libmodal/src/utils/init.lua @@ -1,23 +1,22 @@ --- @class libmodal.utils --- @field api libmodal.utils.api --- @field classes libmodal.utils.classes ---- @field Indicator libmodal.utils.Indicator --- @field Help libmodal.utils.Help --- @field Popup libmodal.utils.Popup --- @field Vars libmodal.utils.Vars local utils = { - api = require 'libmodal/src/utils/api', - classes = require 'libmodal/src/utils/classes', - Indicator = require 'libmodal/src/utils/Indicator', - Help = require 'libmodal/src/utils/Help', - Popup = require 'libmodal/src/utils/Popup', - Vars = require 'libmodal/src/utils/Vars', + api = require 'libmodal.src.utils.api', + classes = require 'libmodal.src.utils.classes', + Help = require 'libmodal.src.utils.Help', + Popup = require 'libmodal.src.utils.Popup', + Vars = require 'libmodal.src.utils.Vars', } --- `vim.notify` with a `msg` some `error` which has a `vim.v.throwpoint` and `vim.v.exception`. --- @param msg string --- @param err string +--- @return nil function utils.notify_error(msg, err) vim.notify( msg .. ': ' .. vim.v.throwpoint .. '\n' .. vim.v.exception .. '\n' .. err, diff --git a/plugin/libmodal.lua b/plugin/libmodal.lua index d54b74a..c24b8f1 100644 --- a/plugin/libmodal.lua +++ b/plugin/libmodal.lua @@ -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 -- The default highlight groups (for colors) are specified below.