diff --git a/autoload/libmodal.vim b/autoload/libmodal.vim index 3ca8f84..68c8c70 100644 --- a/autoload/libmodal.vim +++ b/autoload/libmodal.vim @@ -3,7 +3,7 @@ " argument specifying how many times to run (runs until exiting by default). " PARAMS: " * `a:1` => `modeName` -" * `a:2` => `modeCallback` OR `modeCombos` +" * `a:2` => `modeCallback` OR `modeKeymaps` " * `a:3` => `supressExit` function! libmodal#Enter(...) abort call libmodal#_lua('mode', a:000) diff --git a/doc/libmodal-lua.txt b/doc/libmodal-lua.txt deleted file mode 100644 index 8dd38fb..0000000 --- a/doc/libmodal-lua.txt +++ /dev/null @@ -1,1515 +0,0 @@ -*libmodal-lua.txt* Create modes for Neovim - Lua Reference -*libmodal-lua* -*libmodal-dev* - -This is a document specifying the lower-level implementation of |libmodal|. If -one wishes to make use of it to more finely control their |libmodal-mode| or -|libmodal-prompt|, this document should be the primary reference. - -Any material not covered here is covered in |libmodal-usage|. - -See: |libmodal-usage|, |lua|, |lua-require-example|. - -================================================================================ -0. Table of Contents *libmodal-lua-toc* - - 1. `libmodal` ............................. |libmodal-lua-libmodal| - 2. `libmodal.classes` ..................... |libmodal-lua-classes| - 3. `libmodal.collections` ................. |libmodal-lua-collections| - 3.1. `libmodal.collections.ParseTable` ...... |libmodal-lua-ParseTable| - 3.2. `libmodal.collections.Popup` ........... |libmodal-lua-Popup| - 3.3. `libmodal.collections.Stack` ........... |libmodal-lua-Stack| - 4. `libmodal.globals` ..................... |libmodal-lua-globals| - 5. `libmodal.Indicator` ................... |libmodal-lua-Indicator| - 5.1. `libmodal.Indicator.HighlightSegment` .. |libmodal-lua-HighlightSegment| - 6. `libmodal.Layer` ....................... |libmodal-lua-Layer| - 7. `libmodal.Mode` ........................ |libmodal-lua-Mode| - 8. `libmodal.Prompt` ...................... |libmodal-lua-Prompt| - 9. `libmodal.utils` ....................... |libmodal-lua-utils| - 9.1. `libmodal.utils.api` ................... |libmodal-lua-api| - 9.2. `libmodal.utils.Help` .................. |libmodal-lua-Help| - 9.3. `libmodal.utils.WindowState` ........... |libmodal-lua-Windowstate| -10. `libmodal.Vars` ........................ |libmodal-lua-Vars| - -================================================================================ -1. `libmodal` *libmodal-lua-libmodal* - -This is the base of |libmodal|. It can be imported using: > - local libmodal = require 'libmodal' -. - - Type: ~ - `number` - - Value: ~ - 13 - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-ParseTable-functions* - -`ParseTable`.stringSplit({str}, {regex}) *libmodal-lua-ParseTable.stringSplit()* - - Split some {str} into a `table` with a {regex} expression. - - Parameters: ~ - {str} The `string` to split. - {regex} The regex expression to split {str} with. - - Return: ~ - * The split {str} as a `table`. - - Example: ~ -> - local ParseTable = require('libmodal').collections.ParseTable - - print(vim.inspect(ParseTable.stringSplit('testing split', ' '))) -< - -`ParseTable`.parse({key}) *libmodal-lua-ParseTable.parse()* - - Parse some {key} so that it can be stored by a `ParseTable` instance. - - Note: this method can be locally overridden in order to change the - `ParseTable`'s behavior. Example: -> - local ParseTable = require('libmodal').collections.ParseTable - - ParseTable.parse = function(key) - return ParseTable.splitString(key, ' ') - end -< - - Parameters: ~ - {key} The key of the `table` to parse. - - Return: ~ - * The parsed {key}. Should be a `table`. - - Example: ~ -> - local ParseTable = require('libmodal').collections.ParseTable - - print(vim.inspect(ParseTable.parse('testkey'))) -< - -`ParseTable`.new({userTable}) *libmodal-lua-ParseTable.new()* - - Create a new `ParseTable` from a user-defined `table` of combos. - - All keys of a `ParseTable` are numbers of characters. - - Parameters: ~ - {userTable} the table of combos defined by the user (see - `libmodal.mode.enter()`) - - Return: ~ - A new `ParseTable`. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Create a mock set of user mappings. - local userTable = { - zf = "echo 'Hello!'", - zfo = "tabnew" - } - - -- Create a new `ParseTable` - local parseTable = libmodal.mode.ParseTable.new(userTable) - - -- Print the `parseTable`. - print(vim.inspect(parseTable)) -< - - See Also: ~ - |char2nr|, |nr2char| For character to number conversion and vice - versa. - - |libmodal-mode| For information about {userTable}. - -`self`:get({keyDict}) *libmodal-lua-ParseTable.get()* - - Get a value from an instance of `ParseTable`. - - Parameters: ~ - {keyDict} a string of key characters as bytes. - - Return: ~ - A `function` {keyDict} is a full match. - A `table` the {keyDict} partially matches. - * `nil` {keyDict} is not ANYWHERE. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Simulate user input. - local userInput = {122, 102} -- {'z', 'f'} - - -- Create a dummy `ParseTable`. - local parseTable = libmodal.mode.ParseTable.new({ - zfo = 'echo "Hello!"' - }) - -- Inspect it. - print(vim.inspect(parseTable)) - - -- this will return a `table` - local tbl = parseTable:get(userInput) - -- Inspect it to show the difference. - print(vim.inspect(tbl)) -< - -`self`:parsePut({key}, {value}) *libmodal-lua-ParseTable.parsePut()* - - Put `value` into the parse tree as `key`. - - Parameters: ~ - {key} the key that {value} is reffered to by. A `char`, not a - `byte`. - - {value} the value to store as {key}. A `string` to |execute|. - - Example: ~ -> - -- Create a dummy `ParseTable`. - local parseTable = libmodal.mode.ParseTable.new({ - zfo = 'echo "Hello!"' - }) - -- Inspect it. - print(vim.inspect(parseTable)) - - -- this will return a `table` - parseTable:parsePut('zfc', 'split') - -- Inspect it to show the difference. - print(vim.inspect(parseTable)) -< - - See also: ~ - |libmodal-lua-parsetable-parseputall| for how to put multiple {key}s - and {value}s at a time. - -`self`:parsePutAll({tableToUnite}) *libmodal-lua-ParseTable.parsePutAll()* - - Create the union of `self` and `tableToUnite` - - Interally calls |libmodal-lua-parsetable-parseput| on every key:value pair - in {tableToUnite}. - - Parameters: ~ - {tableToUnite} the table to unite with `self.` - - Example: ~ -> - -- Create an empty `ParseTable`. - local parseTable = libmodal.mode.ParseTable.new({}) - -- Create some dummy keybindings. - local unparsedUserKeybinds = { - zfo = 'echo "Hello!"', - zfc = 'split' - } - - -- Add the dummy keybindings. - parseTable:parsePutAll(unparsedUserKeybinds) - - -- Inspect it to show the difference. - print(vim.inspect(parseTable)) -< - See also: ~ - |libmodal-lua-parsetable-parseput| For more information on - {tableToUnite}. - -================================================================================ -3.2. `libmodal.collections.Popup` *libmodal-lua-Popup* - -When `:enter()`ing a `Mode`, an |api-floatwin| is displayed at the bottom -right-hand corner of the screen. `libmodal.Mode.Popup` is responsible for -opening it and keeping updated. - -Whenever a `Popup` is created, it is immediately opened. Additionally, it is -opened with the following options: > - local _winOpenOpts = { - anchor = 'SW', - col = vim.go.columns - 1, - focusable = false, - height = 1, - relative = 'editor', - row = vim.go.lines - vim.go.cmdheight - 1, - style = 'minimal', - width = 25, - } -< - --------------------------------------------------------------------------------- -VARIABLES *libmodal-lua-Popup-variables* - -`Popup`.config *libmodal-lua-Popup.config* - - The options used when opening a `Popup`. - - Note: this can be overwritten to change the default behavior of `Popup`. - - Type: ~ - |nvim_open_win| {config} `table`. - - Value: ~ -> - { - 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 - } -< - -`self`.buffer *libmodal-lua-Popup.buffer* - - The scratch buffer used by `Popup` to display text. - - Type: ~ - |nvim_create_buf| handle. - - Value: ~ - `vim.api.nvim_create_buf(false, true)` - -`self`.window *libmodal-lua-Popup.window* - - The window used to display the contents of `Popup.buffer`. - - Type: ~ - |nvim_open_win| handle. - - Value: ~ - `vim.api.nvim_open_win(self.buffer, false, Popup.config)` - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-Popup-functions* - -`self`:close({keepBuffer}) *libmodal-lua-Popup.close()* - - Close the |Popup.window|. - - Note: Variables of |Popup| are de-initialized after this method. - - Parameters: ~ - {keepBuffer} A `boolean` that (when `true`) indicates the underlying -               |scratch-buffer| should be kept - -`self`:open({config}) *libmodal-lua-Popup.open()* - - Open the |Popup.window|. - - Note: if the |Popup.window| is already open, then it will be reopened in -       case the |current_tabpage| has changed. - -       In this case, it will also override the default options with the -       options in the window so there is no need to pass in {config} a -       second time. - - Parameters: ~ - {config} Same as |nvim_open_win| {config}. - -`self`:refresh({inputBytes}) *libmodal-lua-Popup.refresh()* - - Update the content of the `Popup` using an array-like `table` of - |char2nr|s. - - Note: This is normally used to add what the user is typing. If you have a - `function` `Mode` {instruction}, then you can use `Popup` and this - function to manually show user input as it is typed. - - Parameters: ~ - {inputBytes} An array-like `table` of |char2nr|s to write to the - `Popup`. - -`Popup`.new({config}) *libmodal-lua-Popup.new()* - - Create a new `Popup` and immediately open it. - - Parameters: ~ - {config} Same as |nvim_open_win| {config}. -           Note: defaults to |Popup.config|. - - Return: ~ - * A new `Popup`. - - See also: ~ - |libmodal-lua-Popup| For the options used to spawn the window. - - -================================================================================ -4.2. `libmodal.collections.Stack` *libmodal-lua-Stack* - -The `libmodal.collections.Stack` is a simple implementation of a Stack data -structure. It is designed with reuse of resources in mind, as -commonly-accessed pieces of data are locally stored for faster reference -times. - -The `#` operator in |Lua| will work on this structure. - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-Stack-functions* - -`self`:peek() *libmodal-lua-Stack.peek()* - - Access the value at the top of the stack without removing it. - - Return: ~ - * The value at the top of the stack. - - Example: ~ -> - local Stack = require('libmodal').collections.Stack - - local stk = Stack.new() - stk:push('foo') - stk:push('bar') - print(stk:peek()) -< - -`self`:pop() *libmodal-lua-Stack.pop()* - - Access the value at the top of the stack by removing it. - - Return: ~ - * The value at the top of the stack. - - Example: ~ -> - local Stack = require('libmodal').collections.Stack - - local stk = Stack.new() - stk:push('foo') - stk:push('bar') - print(stk:peek()) - local _ = stk:pop() - print(stk:peek()) -< - -`self`:push({value}) *libmodal-lua-Stack.push()* - - Push some {value} onto the top of the stack. - - Parameters: ~ - {value} The value to put on top of the stack. - - Example: ~ -> - local Stack = require('libmodal').collections.Stack - - local stk = Stack.new() - stk:push('foo') - stk:push('bar') - print(vim.inspect(stk)) -< - -`Stack`.new() *libmodal-lua-Stack.new()* - - Create a new `libmodal.collections.Stack` and return it. - - Return: ~ - * A new `libmodal.collections.Stack`. - - Example: ~ -> - local Stack = require('libmodal').collections.Stack - - local stk = Stack.new() - print(vim.inspect(stk)) -< - -================================================================================ -4. `libmodal.globals` *libmodal-lua-globals* - -These are global functions used throughout the project. They are never -modified and never meant TO be modified. - --------------------------------------------------------------------------------- -VARIABLES *libmodal-lua-globals-variables* - -`globals`.DEFAULT_ERROR_TITLE *libmodal-lua-globals.DEFAULT_ERROR_TITLE* - - The default error message header for |libmodal| errors. - - Type: ~ - `string` - - Value: ~ - "vim-libmodal error" - -`globals`.ESC_NR *libmodal-lua-globals.ESC_NR* - - The byte of the character. - - Type: ~ - `number` - - Value: ~ - 27 - -`globals`.TYPE_FUNC *libmodal-lua-globals.TYPE_FUNC* - - The `string` yielded by `lua type(x)` when `x` is a `function`. - - Type: ~ - `string` - - Value: ~ - "function" - -`globals`.TYPE_NUM *libmodal-lua-globals.TYPE_NUM* - - The `string` yielded by `lua type(x)` when `x` is a `number`. - - Type: ~ - `string` - - Value: ~ - "number" - -`globals`.TYPE_STR *libmodal-lua-globals.TYPE_STR* - - The `string` yielded by `lua type(x)` when `x` is a `string`. - - Type: ~ - `string` - - Value: ~ - "string" - -`globals`.TYPE_TBL *libmodal-lua-globals.TYPE_TBL* - - The `string` yielded by `lua type(x)` when `x` is a `table`. - - Type: ~ - `string` - - Value: ~ - "table" - -`globals`.VIM_FALSE *libmodal-lua-globals.VIM_FALSE* - - The value Vimscript uses to return `false` (besides |v:false|). - - Type: ~ - `number` - - Value: ~ - 0 - -`globals`.VIM_TRUE *libmodal-lua-globals.VIM_TRUE* - - Type: ~ - `number` - - Value: ~ - 1 - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-globals-functions* - -`globals`.is_false({val}) *libmodal-lua-globals.is_false()* - - Determine whether or not some {val} is equal to `globals.VIM_FALSE`, - |v:false|, or `false`. - - Parameters: ~ - {val} The value to check. Should be a `boolean` or a `boolean` - expression. - - Return: ~ - - `true` if {val} is equal to `globals.VIM_FALSE`, |v:false|, or - `false`. - - `false` otherwise. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Get Lua's truth values. - local falseValue = false - local trueValue = true - - -- Get Vimscript's truth values. - local vim_falseValue = libmodal.globals.VIM_FALSE - local vim_trueValue = libmodal.globals.VIM_TRUE - - --[[ Test the function. ]] - - print(libmodal.globals.is_false(falseValue)) - print(libmodal.globals.is_false(v_falseValue)) - print(libmodal.globals.is_false(vim_falseValue)) - - print(libmodal.globals.is_false(trueValue)) - print(libmodal.globals.is_false(v_trueValue)) - print(libmodal.globals.is_false(vim_trueValue)) -< - -`globals`.is_true({val}) *libmodal-lua-globals.is_true()* - - Determine whether or not some {val} is equal to `globals.VIM_TRUE`, - |v:true|, or `true`. - - Parameters: ~ - {val} The value to check. Should be a `boolean` or a `boolean` - expression. - - Return: ~ - - `true` if {val} is equal to `globals.VIM_TRUE`, |v:true|, or `true`. - - `false` otherwise. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Get Lua's truth values. - local falseValue = false - local trueValue = true - - -- Get Vimscript's truth values. - local vim_falseValue = libmodal.globals.VIM_FALSE - local vim_trueValue = libmodal.globals.VIM_TRUE - - --[[ Test the function. ]] - - print(libmodal.globals.is_true(falseValue)) - print(libmodal.globals.is_true(v_falseValue)) - print(libmodal.globals.is_true(vim_falseValue)) - - print(libmodal.globals.is_true(trueValue)) - print(libmodal.globals.is_true(v_trueValue)) - print(libmodal.globals.is_true(vim_trueValue)) -< - -================================================================================ -5. `libmodal.utils.Indicator` *libmodal-lua-Indicator* - -Provides creation sources for mode and prompt |echo| / |echohl| `string`s. - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-indicator-functions* - -`Indicator`.mode({modeName}) *libmodal-lua-Indicator.mode()* - - Create a new `Indicator` for a mode. - - Parameters: ~ - {modeName} The name of the mode that this `Indicator` is for. - - Example: ~ -> - local libmodal = require 'libmodal' - local indicator = libmodal.utils.Indicator.mode('FOO') - libmodal.utils.api.nvim_lecho(indicator) -< - - See also: ~ - |libmodal-mode| For this function's use. - |libmodal-lua-api.nvim_lecho()| For effective |echo|ing of this - function. - -`Indicator`.prompt({modeName}) *libmodal-lua-Indicator.prompt()* - - Create a new `Indicator` for a prompt. - - Parameters: ~ - {modeName} The name of the mode that this `Indicator` is for. - - Example: ~ -> - local libmodal = require 'libmodal' - local indicator = libmodal.utils.Indicator.prompt('FOO') - print(indicator) -- you can't use `nvim_lecho` with this one. -< - - See also: ~ - |libmodal-prompt| For this function's use. - -================================================================================ -5.1. `libmodal.Indicator.HighlightSegment` *libmodal-lua-HighlightSegment* - -The `HighlightSegment` is a map describing how a particular string should be -highlighted by Vim. - -These `HighlightSegment`s can be put into a list and interpreted by -`libmodal.utils.api.nvim_lecho()`. - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-HighlightSegment-functions* - -`self`.hl *libmodal-lua-HighlightSegment.hl* - - Which |highlight-group| to use when |highlight|ing `HighlightSegment.str`. - - Type: ~ - |highlight-group| `string`. - -`self`.str *libmodal-lua-HighlightSegment.str* - - What this `HighlightSegment`'s string value is. - - Type: ~ - `string` - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-HighlightSegment-Functions* - -`HighlightSegment`.new({hlgroup}, {str}) *libmodal-lua-HighlightSegment.new()* --- - Create a new `HighlightSegment`. - - Parameters: ~ - {hlgroup} The |highlight-group| to use when |highlight|ing {str}. - {str} The {str} to |highlight|. - - Return: ~ - - A new `HighlightSegment`. - - Example: ~ -> - local libmodal = require 'libmodal' - local api = libmodal.utils.api - local HighlightSegment = libmodal.Indicator.HighlightSegment - - api.nvim_lecho({ - HighlightSegment.new('ModeMsg', '-- '), - HighlightSegment.new('None', 'FOO') - HighlightSegment.new('ModeMsg', ' --'), - }) -< - -================================================================================ -6. `libmodal.Layer` *libmodal-lua-Layer* - -The libmodal `Layer` class provides many additional features to the base -`libmodal.layer.enter()`. - -As there is no default mapping to leave a `Layer`, `libmodal.layer.enter()` -returns an anonymous `Layer`'s `:exit()` `function`. By directly having a -reference to a `Layer`, one can use the other `function`s that are provided, -and extend was is possible. - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-Layer-functions* - -`self`:enter() *libmodal-lua-Layer.enter()* - - Enter the `Layer`. - - Note: mappings only get replaced for the current buffer. - - *Error when the Layer has been entered previously but hasn't been exited. - - Example: ~ -> - local libmodal = require 'libmodal' - local layer = libmodal.Layer.new({ - n = { - gg = { - rhs = G - } - } - }) - - -- use `layer:exit()` or close the buffer to exit. - layer:enter() -< - - See also: ~ - |libmodal-lua-Layer.exit()| How to enter the `Layer`. - -`self`:map({mode}, {lhs}, {rhs}, {options}) *libmodal-lua-Layer.map()* - - Add a mapping to the `Layer`. - - Note: if the layer has been `:enter()`ed, then the current buffer's - mappings will be updated. - - Parameters: ~ - {mode} The |mode| that this mapping for. - - Note: |libmodal-mode| support is coming. - - {lhs} The left hand side of the mapping. - {rhs} The right hand side of the mapping. - {options} Options for the mapping. - - Example: ~ -> - local libmodal = require 'libmodal' - local layer = libmodal.Layer.new({ - n = { - gg = { - rhs = G - } - } - }) - - -- this adds a binding for `G` to `gg` in normal mode. - layer:map('n', 'G', 'gg', {noremap = true}) - - -- use `layer:exit()` or close the buffer to exit. - layer:enter() - - -- you can also call `:map()` after entering. - -- this adds a binding for `o` to `gg` in visual mode. - layer:map('v', 'o', 'gg', {noremap = true}) -< - - See also: ~ - |nvim_buf_set_keymap()| For more information about the parameters. - -`self`:unmap({mode}, {lhs}) *libmodal-lua-Layer.unmap()* - - Remove a mapping from the `Layer`. - - Parameters: ~ - {mode} The |mode| that this mapping for. - - Note: |libmodal-mode| support is coming. - - {lhs} The left hand side of the mapping. - - Example: ~ -> - local libmodal = require 'libmodal' - local layer = libmodal.Layer.new({ - n = { - gg = { - rhs = G - } - } - }) - - -- This adds a binding for `G` to `gg` in normal mode. - layer:map('n', 'G', 'gg', {noremap = true}) - - -- Unmap the initial `gg`. - layer:unmap('n', 'gg') - - -- Use `layer:exit()` or close the buffer to exit. - layer:enter() - - -- Start a timer for five seconds. - vim.loop.new_timer():start(5000, 0, vim.schedule_wrap( - -- You can also call `:unmap()` after entering. - function() layer:unmap('n', 'G') end - -- No bindings should be active now. - )) - -< - - See also: ~ - |nvim_buf_del_keymap()| For more information about the parameters. - -`self`:exit() *libmodal-lua-Layer.exit()* - - Exit the `Layer`. - - *Error when the Layer has not been entered yet. - - Example: ~ -> - local libmodal = require 'libmodal' - local layer = libmodal.Layer.new({ - n = { - gg = { - rhs = G - } - } - }) - - -- Enter the layer. - layer:enter() - - -- Start a timer for five seconds. - vim.loop.new_timer():start(5000, 0, - -- Exit the layer. `gg` should return to normal. - vim.schedule_wrap(layer:exit()) - ) - -< - - See also: ~ - |libmodal-lua-Layer.enter()| How to enter the `Layer`. - -`Layer`.new({keymap}) *libmodal-lua-Layer.new()* - - Create a new `Layer` for `mappings`. - - Parameters: ~ - {keymap} The list of |map|pings to replace. - - Returns: ~ - - A new `Layer`. - - See also: ~ - |libmodal-layer| For more information about the parameters. - -================================================================================ -7. `libmodal.Mode` *libmodal-lua-Mode* - -While `libmodal.mode.enter()` may enter a mode, it silently creates a `Mode` -underneath: > - function mode.enter(...) - Mode.new(...):enter() - end -< - -If you wish to have more control over a mode, such as the text of an -`Indicator`, one may manually create a `Mode` with `Mode.new()` and then -change some of the properties in order to make the mode do what you want. - -Additionally, one may alter the actual `function`s that are used by a `Mode` -without forking |libmodal|. By using `setmetatable`, and `vim.inspect()`, one -can grab code from the |nvim-libmodal| repository and change whatever they -want to for their mode specifically. > - local libmodal = require 'libmodal' - local Mode = libmodal.Mode - local HighlightSegment = libmodal.Indicator.HighlightSegment - - local fooMode = Mode.new(…) - fooMode.indicator = { - HighlightSegment.new('Error', '*'), - HighlightSegment.new('None', ' Something a duck walks on '), - HighlightSegment.new('Error', '>'), - HighlightSegment.new('None', ' ') - } - fooMode - fooMode:enter() -< (The specifications for these functions can be found at other places in this - document.) - --------------------------------------------------------------------------------- -VARIABLES *libmodal-lua-Mode-variables* - -`self`.exit *libmodal-lua-Mode.exit* - - A liason to the `g:`{name}`ModeExit` variable. - - Type: ~ - `libmodal.Vars` - - Value: ~ - `libmodal.Vars.new('exit', `{name}`)` - -`self`.indicator *libmodal-lua-Mode.indicator* - - The message that is shown in the bottom-left corner of the screen while the - mode is active. - - Note: this value may be replaced with any `table` of `HighlightSegment`s - (as shown in |libmodal-lua-Mode|). That will allow you to change the - message that a mode has from the vim-default "-- {name} --" to - something else. - - Type: ~ - `libmodal.Indicator`/array-like `table` of `HighlightSegment`s. - - Value: ~ - `libmodal.Indicator.mode(`{name}`)` - -`self`.input *libmodal-lua-Mode.input* - - A liason to `g:`{name}`ModeInput`. - - Note: you may use {ModeInstance}`.input:nvimGet()` and `:nvimSet()` to - consistently read and write to the mode's unique variable. - - Type: ~ - `libmodal.Vars` - - Value: ~ - `libmodal.Vars.new('input', `{name}`)` - -`self`.inputBytes *libmodal-lua-Mode.inputBytes* - - The history of user input, stored as |char2nr|s. - - Note: This variable is inert when `libmodal.Mode.new()`'s {instruction} - parameter is a `function`. Feel free to use it rather than creating - your own, to make your code more familiar to others. - - Type: ~ - array-like `table` of |char2nr|s. - - Value: ~ - - `nil` => {instruction} is a `function`. - - `{}` => {instruction} is a `table`. - -`self`.mappings *libmodal-lua-Mode.mappings* - - The mappings that have been processed by a `Mode` when - `libmodal.Mode.new()`'s {instruction} parameter is a `table`. - - Users may add or remove mappings at any time, and they will be reflected - in the next keypress. - - Type: ~ - `libmodal.collections.ParseTable` - - Value: ~ - `libmodal.collections.ParseTable.new(`{instruction}`)` - - See also: ~ - |libmodal-lua-ParseTable| For information about how to use this - variable. - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-Mode-functions* - -`self`:enter() *libmodal-lua-Mode.enter()* - - Enter the `Mode` that was created. - - Example: ~ -> - local Mode = require('libmodal').Mode - - local fooMode = Mode.new('FOO', {zz = 'tabnew', gg = 'tabclose'}) - fooMode:enter() -< - -`Mode`.new({name}, {instruction} [, {supressExit}]) *libmodal-lua-Mode.new()* - - Create a new `Mode` with a given {name}, using an {instruction} to perform - whenever a key is pressed, and whether or not to {supressExit} handling. - - Return: ~ - A new `Mode`. - - See also: ~ - |libmodal-mode| For more information, as all of the parameters are - the same. - -================================================================================ -8. `libmodal.Prompt` *libmodal-lua-Prompt* - -While `libmodal.prompt.enter()` may enter a |libmodal-prompt|, itilently -creates a `Mode` underneath: > - function prompt.enter(...) - Prompt.new(...):enter() - end -< - -See |libmodal-lua-Mode| for more information about the possibilities that are -enabled by using such a "class". - --------------------------------------------------------------------------------- -VARIABLES *libmodal-lua-Prompt-variables* - -`self`.indicator *libmodal-lua-Prompt.indicator* - - The message that is shown in the bottom-left corner of the screen while the - mode is active. - - Note: this value may be replaced with any `HighlightSegment`. That will - allow you to change the message to something else. - - Type: ~ - `HighlightSegment` - - Value: ~ - `libmodal.Indicator.prompt(`{name}`)` - -`self`.input *libmodal-lua-Prompt.input* - - A liason to `g:`{name}`ModeInput`. - - Note: you may use {ModeInstance}`.input:nvimGet()` and `:nvimSet()` to - consistently read and write to the mode's unique variable. - - Type: ~ - `libmodal.Vars` - - Value: ~ - `libmodal.Vars.new('input', `{name}`)` - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-Prompt-functions* - -`self`:enter() *libmodal-lua-Prompt.enter()* - - Enter the `Prompt` that was created. - - Example: ~ -> - local Prompt = require('libmodal').Prompt - - local fooMode = Prompt.new('FOO', {new = 'tabnew', close = 'tabclose'}) - fooMode:enter() -< - - *libmodal-lua-Prompt.new()* -`Prompt`.new({name}, {instruction} [, {completions}, {supressExit}]) - - User input is taken using |input()|. It is passed through a |g:var| - determined by the {name} of the mode. For example, if {name} is "FOO" - then the |g:var| is `g:fooModeInput`. - - Return: ~ - A new `Mode`. - - See also: ~ - |libmodal-prompt| For more information, as all of the parameters are - the same. - -================================================================================ -9. `libmodal.utils` *libmodal-lua-utils* - -Provides extra utilities to the |libmodal| library. - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-utils-functions* - -`utils`.show_error({pcall_err}) *libmodal-lua-utils.show_error()* - - Show an error from `pcall()`. - - Parameters: ~ - {pcall_err} the error generated by `pcall()`. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Run `pcall` on an anonymous function. - local noErrors, pcall_err = pcall(function() - -- This will always produce an error. - return "" .. true - end) - - -- If there were errors… - if not noErrors then - -- Show the error. - libmodal.utils.show_error(pcall_err) - end -< - -============================================================================= -9.1. `libmodal.utils.api` *libmodal-lua-api* - -Provides extensions to the `vim.api` |Lua| library. - -See: |API|. - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-api-functions* - -`api`.mode_exit({exit_char}) *libmodal-lua-api.mode_exit()* - - Use |feedkeys()| to send an {exit_char} which signals a |libmodal-mode| or - |libmodal-prompt| to stop listening for input. - - It is not usually necessary to specify {exit_char}. It is only necessary - to do so when |libmodal-mode|'s {supressExit} flag is active, because - it is possible that the user has set up some non-default character which - should be used to exit the mode (see |libmodal-examples-supress-exit|). - - Parameters: ~ - {exit_char} (Optional) The character used to exit the mode, or - |libmodal-lua-globals.ESC_NR| by default. - - Can be a character number (see |char2nr()|) or a string. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- This function is called every time the user presses a key. - local function _instruction() - -- Exit the mode after pressing any key. - libmodal.utils.api.mode_exit() - end - - libmodal.mode.enter('Press any key to exit.', _instruction) -< - - See also: ~ - |char2nr()| For more information about character numbers. - |libmodal-mode| For more information about {supressExit}. - |feedkeys()| For information about how this method is implemented. - -`api`.nvim_bell() *libmodal-lua-api.nvim_bell()* - - Make vim ring the visual/audio bell, if it is enabled. - - Example: ~ -> - local libmodal = require 'libmodal' - - libmodal.utils.api.nvim_bell() -< - - See also: ~ - 'belloff' For bell settings. - 'errorbells' For bell settings. - 'visualbell' For bell settings. - -`api`.nvim_input() *libmodal-lua-api.nvim_input()* - - Gets one character of user input, as a number. - - Uses |getchar()|. - - Return: ~ - One character of user input, as a number (byte). - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Get one character of user input. - local char = string.char(libmodal.utils.api.nvim_input()) - - -- … wait for user to press a character … - - -- Print the character. - print(char) -< - -`api`.nvim_lecho({hlTables}) *libmodal-lua-api.nvim_lecho()* - - Echo an `Indicator`. - Meant to be read as "nvim list echo". - - Parameters: ~ - {hlTables} The `tables` to |echo| with |highlights|. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Create an indicator. - local indicator = libmodal.utils.Indicator.mode('FOO') - - -- Show the indicator. - libmodal.utils.api.nvim_lecho(indicator) -< - -`api`.nvim_redraw() *libmodal-lua-api.nvim_redraw()* - - 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. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Echo hello and prompt for input before continuing. - vim.api.nvim_command "echo 'Hello!'" - vim.fn.getchar() - - -- Clear the screen. - libmodal.utils.api.nvim_redraw() -< - -`api`.nvim_show_err({title}, {msg}) *libmodal-lua-api.nvim_show_err()* - - Show a {title}-error with a given {msg}. - - Parameters: ~ - {title} The title of the error. - {msg} The message of the error. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Show an error. - libmodal.utils.api.nvim_show_err( - -- Use the default error title. - libmodal.globals.DEFAULT_ERROR_TITLE, - -- Use a custom error message. - 'This is a test error!' - ) -< - -================================================================================ -9.2. `libmodal.utils.Help` *libmodal-lua-Help* - -Allows for the creation of a default "Help" table. - -By default, this "Help" is shown by pressing `?` in a |libmodal-mode| or by -entering "help" into a |libmodal-prompt|. - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-Help-functions* - -`Help`.new({commandsOrMaps}, {title}) *libmodal-lua-Help.new()* - - Create a default help table with `commandsOrMaps` and vim expressions. - - Parameters: ~ - {commandsOrMaps} The `table` of |command|s or |map|pings to Vim |expression|s. - {title} The leftmost table column's title. - - Return: ~ - A new `Help`. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Create a table of mock user commands. - local commands = { - close = 'tabclose', - new = 'tabnew' - } - - -- Create a table of mock user maps. - local maps = { - zf = 'split', - zfo = 'echo "Hello!"' - } - - local commandHelp = libmodal.utils.Help.new(commands, 'COMMANDS') - local mapsHelp = libmodal.utils.Help.new(maps, 'MAPS') -< - -`self`:show() *libmodal-lua-Help.show()* - - Show the contents of this `Help`. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Create a table of mock user commands. - local commands = { - close = 'tabclose', - new = 'tabnew' - } - - -- Create a table of mock user maps. - local maps = { - zf = 'split', - zfo = 'echo "Hello!"' - } - - local commandHelp = libmodal.utils.Help.new(commands, 'COMMANDS') - local mapsHelp = libmodal.utils.Help.new(maps, 'MAPS') - - commandHelp:show() - mapsHelp:show() -< - -================================================================================ -9.3. `libmodal.utils.WindowState` *libmodal-lua-WindowState* - -Tracks 'winheight' and 'winwidth' when created, so that it can be modified -freely and then restored later. - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-WindowState-functions* - -`WindowState`.new() *libmodal-lua-WindowState.new()* - - Create a table representing the size of the current window. - - Return: ~ - The new `WindowState`. - - Example: ~ -> - local libmodal = require 'libmodal' - local windowState = libmodal.utils.WindowState.new() - print(vim.inspect(windowState)) -< - - See also: ~ - 'winheight' The `height` property of a `WindowState`. - 'winwidth' The `width` property of a `WindowState`. - -`self`:restore() *libmodal-lua-WindowState.restore()* - - Restore the state of this `WindowState`. - - Example: ~ -> - local libmodal = require 'libmodal' - - -- Create a new `WindowState`. - local windowState = libmodal.utils.WindowState.new() - - -- Set the 'winheight' to a new value. - vim.go.winheight = 100 - - -- Define a function to print the 'winheight' value. - local print_height = function() - vim.api.nvim_command 'echo &winheight' - end - - -- Print the 'winheight' prior to `restore()`. - print_height() - -- Restore the `windowState`. - windowState:restore() - -- Print the 'winheight' after `restore()`ing. - print_height() -< - -================================================================================ -10. `libmodal.Vars` *libmodal-lua-Vars* - -A `Var`'s purpose is to act as an intermediary between |global-variables| and -the modes that use them. - --------------------------------------------------------------------------------- -FUNCTIONS *libmodal-lua-Vars.functions* - -`self`:name() *libmodal-lua-Vars.name()* - - Get the name of `modeName`s global setting. - - Parameters: ~ - {modeName} The name of the mode. - - Return: ~ - - The name of the vimscript variable that this `Var` corresponds to. - - Example: ~ -> - local libmodal = require 'libmodal' - - local input = libmodal.Vars('input', 'FOO') - print(input:name()) -- 'fooModeInput' -< - -`self`:nvimGet() *libmodal-lua-Vars.nvimGet()* - - Retrieve a variable value. - - Parameters: ~ - {modeName} The mode name this value is being retrieved for. - - Return: ~ - * The |global-variable| value that this `Var` represents - - Example: ~ -> - local libmodal = require 'libmodal' - - local input = libmodal.Vars('input', 'FOO') - vim.g[input:name()] = 'test' - print(input:nvimGet()) -< - -`self`:nvimSet({val}) *libmodal-lua-Vars.nvimSet()* - - Set a |variable| value. - - Parameters: ~ - {modeName} The mode name this value is being retrieved for. - {val} The value to set `self`'s Vimscript var to. - - Example: ~ -> - local libmodal = require 'libmodal' - - local input = libmodal.Vars('input', 'FOO') - input:nvimSet('test') - print(input:nvimGet()) -< - -`Vars`.new(keyName, modeName) *libmodal-lua-Vars.new()* - - Create a new `Var`. - - Parameters: ~ - {keyName} The name of the key used to refer to this variable in `Vars`. - {modeName} The name of the mode that this `Variable` is for. - - Return: ~ - * A new `Var`. - -================================================================================ - vim:tw=80:ts=4:ft=help:norl: diff --git a/doc/libmodal.txt b/doc/libmodal.txt index c49b5a0..8d95c24 100644 --- a/doc/libmodal.txt +++ b/doc/libmodal.txt @@ -112,8 +112,8 @@ VARIABLES *libmodal-usage-variable FUNCTIONS *libmodal-usage-functions* *libmodal-mode* *libmodal#Enter()* *libmodal.mode.enter()* -`libmodal.mode`.enter({name}, {instruction} [, {supressExit}]) -`libmodal`#Enter({name}, {instruction} [, {supressExit}]) +`libmodal.mode`.enter({name}, {instruction} [, {supress_exit}]) +`libmodal`#Enter({name}, {instruction} [, {supress_exit}]) Enter a new |vim-mode| using {instruction} to determine what actions will be taken upon specific user inputs. @@ -182,8 +182,8 @@ FUNCTIONS *libmodal-usage-function - If `g:libmodalTimeouts` is enabled, then user input will be subjected to the |timeoutlen|. - {supressExit} Whether or not to automatically exit the mode upon an - press. + {supress_exit} Whether or not to automatically exit the mode upon an + press. - If |v:false|/`false`, then is automatically mapped to exiting. @@ -198,7 +198,8 @@ FUNCTIONS *libmodal-usage-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 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 - |nvim_set_keymap()| + |vim.keymap.set()| (except you should not use multiple `` at + one time, despite |vim.keymap.set()| supporting it). + + {exit_char} The character used to exit the layer. 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: ~ |libmodal-examples-layers| For an example. |nvim_set_keymap()| For more information about {keymap}. - *libmodal-prompt* *libmodal#Prompt()* *libmodal.prompt.enter()* -`libmodal.prompt`.enter({name}, {instruction} [, {completions}, {supressExit}]) -`libmodal`#Prompt({name}, {instruction} [, {completions}, {supressExit}]) + *libmodal-prompt* *libmodal#Prompt()* *libmodal.prompt.enter()* +`libmodal.prompt`.enter({name}, {instruction} [, {completions}]) +`libmodal`#Prompt({name}, {instruction} [, {completions}]) Besides accepting user input like keys in |Normal-mode|, |libmodal| is 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 automatically. - {supressExit} Whether or not to automatically exit the mode upon an - press. - - - If |v:false|/`false`, then is automatically mapped to - exiting. - - If |v:true|/`true`, then 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|. + Note: The user may set the `g:`{name}`ModeExit` variable to + `true` at any time to prematurely exit. See also: ~ @@ -314,238 +312,9 @@ FUNCTIONS *libmodal-usage-function ================================================================================ 3. Examples *libmodal-examples* -Below are examples written in |Lua| to help show how specific features of -|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 .'" - 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 +See the official examples at the link below: - fooMode() -< - --------------------------------------------------------------------------------- -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) -< +https://github.com/Iron-E/nvim-libmodal/tree/master/examples ================================================================================ 4. Configuration *libmodal-configuration* diff --git a/examples/key-combos.vim b/examples/key-combos.vim deleted file mode 100644 index 8fff30d..0000000 --- a/examples/key-combos.vim +++ /dev/null @@ -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) diff --git a/examples/key-combos-manually.vim b/examples/keymaps-manually.vim similarity index 100% rename from examples/key-combos-manually.vim rename to examples/keymaps-manually.vim diff --git a/examples/key-combos-submode.vim b/examples/keymaps-submode.vim similarity index 82% rename from examples/key-combos-submode.vim rename to examples/keymaps-submode.vim index 7eb0474..6b54dc8 100644 --- a/examples/key-combos-submode.vim +++ b/examples/keymaps-submode.vim @@ -2,14 +2,14 @@ let s:barModeRecurse = 0 " Register 'z' as the map for recursing further (by calling the BarMode function again). -let s:barModeCombos = { +let s:barModeKeymaps = { \ 'z': 'BarModeEnter', \} " define the BarMode() function which is called whenever the user presses 'z' function! s:BarMode() let s:barModeRecurse += 1 - call libmodal#Enter('BAR' . s:barModeRecurse, s:barModeCombos) + call libmodal#Enter('BAR' . s:barModeRecurse, s:barModeKeymaps) let s:barModeRecurse -= 1 endfunction diff --git a/examples/key-combos-supress-exit.vim b/examples/keymaps-supress-exit.vim similarity index 60% rename from examples/key-combos-supress-exit.vim rename to examples/keymaps-supress-exit.vim index 0fe429e..c1a8de5 100644 --- a/examples/key-combos-supress-exit.vim +++ b/examples/keymaps-supress-exit.vim @@ -1,5 +1,5 @@ " Register key commands and what they do. -let s:barModeCombos = { +let s:barModeKeymaps = { \ '': 'echom "You cant exit using escape."', \ 'q': 'let g:barModeExit = 1' \} @@ -7,5 +7,5 @@ let s:barModeCombos = { " Tell the mode not to exit automatically. let g:barModeExit = 0 -" Enter the mode using the key combos created before. -call libmodal#Enter('BAR', s:barModeCombos, 1) +" Enter the mode using the keymaps created before. +call libmodal#Enter('BAR', s:barModeKeymaps, 1) diff --git a/examples/keymaps.vim b/examples/keymaps.vim new file mode 100644 index 0000000..0df9d41 --- /dev/null +++ b/examples/keymaps.vim @@ -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) diff --git a/examples/layer-simple.vim b/examples/layer-simple.vim index 33a9c0f..41a05f3 100644 --- a/examples/layer-simple.vim +++ b/examples/layer-simple.vim @@ -13,7 +13,4 @@ let s:layer = { \} " Capture the exit function -let s:exitFunc = luaeval("require('libmodal').layer.enter(_A)", s:layer) - -" Call the exit function in 5 seconds. -call timer_start(5000, s:exitFunc) +let s:exitFunc = luaeval("require('libmodal').layer.enter(_A, '')", s:layer) diff --git a/examples/lua/key-combos-manually.lua b/examples/lua/keymaps-manually.lua similarity index 66% rename from examples/lua/key-combos-manually.lua rename to examples/lua/keymaps-manually.lua index 655bcab..2f91bb0 100644 --- a/examples/lua/key-combos-manually.lua +++ b/examples/lua/keymaps-manually.lua @@ -2,11 +2,11 @@ local libmodal = require 'libmodal' -- 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. -function _inputHistory:clear(indexToCheck) - if #self >= indexToCheck then +function input_history:clear(index_to_check) + if #self >= index_to_check then for i, _ in ipairs(self) do self[i] = nil end @@ -14,18 +14,18 @@ function _inputHistory:clear(indexToCheck) end -- 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. - _inputHistory[#_inputHistory + 1] = string.char( + input_history[#input_history + 1] = 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 - if _inputHistory[1] == 'z' then - if _inputHistory[2] == 'f' then - if _inputHistory[3] == 'o' then + if input_history[1] == 'z' then + if input_history[2] == 'f' then + if input_history[3] == 'o' then vim.api.nvim_command "echom 'It works!'" else index = 3 end @@ -33,8 +33,8 @@ local function fooMode() end end - _inputHistory:clear(index) + input_history:clear(index) end -- Enter the mode to begin the demo. -libmodal.mode.enter('FOO', fooMode) +libmodal.mode.enter('FOO', foo_mode) diff --git a/examples/lua/key-combos-submode.lua b/examples/lua/keymaps-submode.lua similarity index 65% rename from examples/lua/key-combos-submode.lua rename to examples/lua/keymaps-submode.lua index f3b3d8c..a06ad00 100644 --- a/examples/lua/key-combos-submode.lua +++ b/examples/lua/keymaps-submode.lua @@ -2,21 +2,22 @@ local libmodal = require('libmodal') -- Recurse counter. -local fooModeRecurse = 0 +local foo_mode_recurse = 0 -- Register 'z' as the map for recursing further (by calling the FooMode function again). -local fooModeCombos = { +local foo_mode_keymaps = +{ z = 'lua FooMode()' } -- define the FooMode() function which is called whenever the user presses 'z' function FooMode() - fooModeRecurse = fooModeRecurse + 1 - libmodal.mode.enter('FOO' .. fooModeRecurse, fooModeCombos) - fooModeRecurse = fooModeRecurse - 1 + foo_mode_recurse = foo_mode_recurse + 1 + libmodal.mode.enter('FOO' .. foo_mode_recurse, foo_mode_keymaps) + foo_mode_recurse = foo_mode_recurse - 1 end -- 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. FooMode() diff --git a/examples/lua/key-combos-supress-exit.lua b/examples/lua/keymaps-supress-exit.lua similarity index 65% rename from examples/lua/key-combos-supress-exit.lua rename to examples/lua/keymaps-supress-exit.lua index 14457ce..83ee1fd 100644 --- a/examples/lua/key-combos-supress-exit.lua +++ b/examples/lua/keymaps-supress-exit.lua @@ -2,7 +2,8 @@ local libmodal = require 'libmodal' -- Register key commands and what they do. -local fooModeCombos = { +local fooModeKeymaps = +{ [''] = 'echom "You cant exit using escape."', q = 'let g:fooModeExit = 1' } @@ -10,5 +11,5 @@ local fooModeCombos = { -- Tell the mode not to exit automatically. vim.g.fooModeExit = false --- Enter the mode using the key combos created before. -libmodal.mode.enter('FOO', fooModeCombos, true) +-- Enter the mode using the keymaps created before. +libmodal.mode.enter('FOO', fooModeKeymaps, true) diff --git a/examples/lua/key-combos.lua b/examples/lua/keymaps.lua similarity index 51% rename from examples/lua/key-combos.lua rename to examples/lua/keymaps.lua index d01971d..fd00453 100644 --- a/examples/lua/key-combos.lua +++ b/examples/lua/keymaps.lua @@ -2,18 +2,19 @@ local libmodal = require 'libmodal' -- 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 'vsplit' end --- Register key combos for splitting windows and then closing windows -local fooModeCombos = { +-- Register keymaps for splitting windows and then closing windows +local fooModeKeymaps = +{ zf = 'split', zfo = 'vsplit', zfc = 'q', - zff = _split_twice + zff = split_twice } --- Enter the mode using the key combos. -libmodal.mode.enter('FOO', fooModeCombos) +-- Enter the mode using the keymaps. +libmodal.mode.enter('FOO', fooModeKeymaps) diff --git a/examples/lua/layer-simple.lua b/examples/lua/layer-simple.lua index 57015d5..ea54a3d 100644 --- a/examples/lua/layer-simple.lua +++ b/examples/lua/layer-simple.lua @@ -1,21 +1,21 @@ -- Imports -local libmodal = require('libmodal') +local libmodal = require 'libmodal' -- create a new layer. -local exitFunc = libmodal.layer.enter({ - n = { -- normal mode mappings - gg = { -- remap `gg` - rhs = 'G', -- map it to `G` - noremap = true, -- don't recursively map. - }, - G = { -- remap `G` - rhs = 'gg', -- map it to `gg` - noremap = true -- don't recursively map. +libmodal.layer.enter( + { + n = { -- normal mode mappings + gg = { -- remap `gg` + rhs = 'G', -- map it to `G` + noremap = true, -- don't recursively map. + }, + G = { -- remap `G` + rhs = 'gg', -- map it to `gg` + noremap = true -- don't recursively map. + } } - } -}) + }, + '' +) --- The layer will deactivate in 5 seconds for this demo. -vim.loop.new_timer():start(5000, 0, vim.schedule_wrap( - function() exitFunc(); print('EXITED.') end -)) +-- The layer will deactivate when you press diff --git a/examples/lua/layer.lua b/examples/lua/layer.lua index ee93020..a58dc7c 100644 --- a/examples/lua/layer.lua +++ b/examples/lua/layer.lua @@ -1,40 +1,28 @@ -- Imports -local libmodal = require('libmodal') +local libmodal = require 'libmodal' -- create a new layer. -local layer = libmodal.Layer.new({ - n = { -- normal mode mappings - gg = { -- remap `gg` +local layer = libmodal.layer.new( +{ + n = + { -- normal mode mappings + gg = -- remap `gg` + { 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` - noremap = true -- don't recursively map. - } + -- other options such as `noremap` and `silent` can be set to `true` here + }, } }) --- enter the `layer`. -layer:enter() - --- add a global function for exiting the mode. -function LibmodalLayerExampleExit() - layer:exit() -end +-- Add an additional mapping for `` to exit the mode +layer:map('n', '', function() layer:exit() end, {}) --- Add an additional mapping for `z`. -layer:map('n', 'z', 'gg', {noremap = true}) - --- add an additional mapping for `q`. -layer:map( - 'n', 'q', ':lua LibmodalLayerExampleExit()', - {noremap = true, silent = true} -) +layer:enter() ---[[ unmap `gg` and `G`. Notice they both return to their defaults, - rather than just not doing anything anymore. ]] +--[[ unmap `gg`. Notice that now both `gg` and `G` return the cursor to the top. ]] 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. diff --git a/examples/lua/prompt-callback.lua b/examples/lua/prompt-callback.lua index c742448..c0d1525 100644 --- a/examples/lua/prompt-callback.lua +++ b/examples/lua/prompt-callback.lua @@ -11,6 +11,7 @@ function FooMode() vim.api.nvim_command 'tabnew' elseif userInput == 'close' then vim.api.nvim_command 'tabclose' + vim.g.fooModeExit = true elseif userInput == 'last' then vim.api.nvim_command 'tablast' end diff --git a/examples/lua/prompt-commands.lua b/examples/lua/prompt-commands.lua index ca1eb3d..652a3ef 100644 --- a/examples/lua/prompt-commands.lua +++ b/examples/lua/prompt-commands.lua @@ -2,11 +2,12 @@ local libmodal = require 'libmodal' -- Define commands through a dictionary. -local commands = { +local commands = +{ new = 'tabnew', close = 'tabclose', last = 'tablast', - exit = libmodal.utils.api.mode_exit + exit = 'let g:fooModeExit = v:true', } -- Begin the prompt. diff --git a/examples/lua/submodes.lua b/examples/lua/submodes.lua index ed3ace3..582b38c 100644 --- a/examples/lua/submodes.lua +++ b/examples/lua/submodes.lua @@ -2,27 +2,27 @@ local libmodal = require 'libmodal' -- Recurse counter -local fooModeRecurse = 1 +local foo_mode_recurse = 1 -- Function which is called whenever the user presses a button function FooMode() -- Append to the input history, the latest button press. local userInput = string.char(vim.g[ -- 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 userInput == 'z' then - fooModeRecurse = fooModeRecurse + 1 + foo_mode_recurse = foo_mode_recurse + 1 Enter() - fooModeRecurse = fooModeRecurse - 1 + foo_mode_recurse = foo_mode_recurse - 1 end end -- Function to wrap around entering the mode so it can be recursively called. function Enter() - libmodal.mode.enter('FOO' .. fooModeRecurse, FooMode) + libmodal.mode.enter('FOO' .. foo_mode_recurse, FooMode) end -- Initially call the function to begin the demo. diff --git a/examples/prompt-callback.vim b/examples/prompt-callback.vim index 65106a9..bf510d1 100644 --- a/examples/prompt-callback.vim +++ b/examples/prompt-callback.vim @@ -8,6 +8,7 @@ function! s:fooMode() abort tabnew elseif userInput == 'close' tabclose + let g:fooModeExit = v:true elseif userInput == 'last' tablast endif diff --git a/lua/libmodal/init.lua b/lua/libmodal/init.lua index c30cbb2..e5a38c1 100644 --- a/lua/libmodal/init.lua +++ b/lua/libmodal/init.lua @@ -1,36 +1,63 @@ ---[[ - /* - * MODULE - */ ---]] - -local libmodal = require('libmodal/src') - ---[[ - /* - * MIRRORS - */ ---]] - -libmodal.layer = {enter = function(keymap) - local layer = libmodal.Layer.new(keymap) - layer:enter() - return function() layer:exit() end -end} - -libmodal.mode = {enter = function(name, instruction, ...) - libmodal.Mode.new(name, instruction, ...):enter() -end} - -libmodal.prompt = {enter = function(name, instruction, ...) - libmodal.Prompt.new(name, instruction, ...):enter() -end} - - ---[[ - /* - * PUBLICIZE MODULE - */ ---]] - -return libmodal +-- TODO: remove the __index here after a period of time to let people remove `libmodal.Layer` from their configurations +return setmetatable( + { + layer = + { + --- Enter a new layer. + --- @param keymap table the keymaps (e.g. `{n = {gg = {rhs = 'G', silent = true}}}`) + --- @param exit_char nil|string a character which can be used to exit the layer from normal mode. + --- @return function|nil exit a function to exit the layer, or `nil` if `exit_char` is passed + enter = function(keymap, exit_char) + local layer = require('libmodal/src/Layer').new(keymap) + layer:enter() + + if exit_char then + layer:map('n', exit_char, function() layer:exit() end, {}) + else + return function() layer:exit() end + end + end, + + --- Create a new layer. + --- @param keymap table the keymaps (e.g. `{n = {gg = {rhs = 'G', silent = true}}}`) + --- @return libmodal.Layer + new = function(keymap) + return require('libmodal/src/Layer').new(keymap) + end, + }, + + mode = + { + --- Enter a mode. + --- @param name string the name of the mode. + --- @param instruction function|string|table a Lua function, keymap dictionary, Vimscript command. + enter = function(name, instruction, supress_exit) + require('libmodal/src/Mode').new(name, instruction, supress_exit):enter() + end + }, + + prompt = + { + --- Enter a prompt. + --- @param name string the name of the prompt + --- @param instruction function|table what to do with user input + --- @param user_completions table|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, + } +) diff --git a/lua/libmodal/src/Indicator/HighlightSegment.lua b/lua/libmodal/src/Indicator/HighlightSegment.lua deleted file mode 100644 index 6683a99..0000000 --- a/lua/libmodal/src/Indicator/HighlightSegment.lua +++ /dev/null @@ -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 diff --git a/lua/libmodal/src/Indicator/init.lua b/lua/libmodal/src/Indicator/init.lua deleted file mode 100644 index 4095d9e..0000000 --- a/lua/libmodal/src/Indicator/init.lua +++ /dev/null @@ -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 diff --git a/lua/libmodal/src/Layer.lua b/lua/libmodal/src/Layer.lua index 754cf41..4bd2a9c 100644 --- a/lua/libmodal/src/Layer.lua +++ b/lua/libmodal/src/Layer.lua @@ -1,253 +1,135 @@ ---[[ - /* - * IMPORTS - */ ---]] - -local api = vim.api -local libmodal_api = require('libmodal/src/utils/api') - ---[[ - /* - * MODULE - */ ---]] - -local Layer = {TYPE = 'libmodal-layer'} - -local _BUFFER_CURRENT = 0 -local _ERR_NO_MAP = 'E5555: API call: E31: No such mapping' -local _RESTORED = nil - -local function convertKeymap(keymapEntry) - local lhs = keymapEntry.lhs - keymapEntry.lhs = nil - - return {lhs, keymapEntry} +--- Remove and return the right-hand side of a `keymap`. +--- @param keymap table the keymap to unpack +--- @return string lhs, table options +local function unpack_keymap_lhs(keymap) + local lhs = keymap.lhs + keymap.lhs = nil + + return lhs, keymap end -local function deconvertKeymap(convertedKeymap) - local rhs = convertedKeymap.rhs - convertedKeymap.rhs = nil +--- Remove and return the right-hand side of a `keymap`. +--- @param keymap table the keymap to unpack +--- @return function|string rhs, table options +local function unpack_keymap_rhs(keymap) + local rhs = keymap.rhs + keymap.rhs = nil - return {rhs, convertedKeymap} + return rhs, keymap end ---[[ - /* - * META `Layer` - */ ---]] - -local _metaLayer = require('libmodal/src/classes').new(Layer.TYPE) - ---------------------------- ---[[ SUMMARY: - * Enter the `Layer`. - * Only activates for the current buffer. -]] ---------------------------- -function _metaLayer:enter() - if self._priorKeymap then +--- @class libmodal.Layer +--- @field private existing_keymap table the keymaps to restore when exiting the mode; generated automatically +--- @field private layer_keymap table the keymaps to apply when entering the mode; provided by user +local Layer = require('libmodal/src/utils/classes').new() + +--- Apply the `Layer`'s keymaps buffer. +function Layer:enter() + if self.existing_keymap then error('This layer has already been entered. `:exit()` before entering again.') end -- add local aliases. - local layerKeymap = self._keymap - local priorKeymap = {} - - --[[ iterate over the new mappings to both: - 1. Populate `priorKeymap` - 2. Map the `layerKeymap` to the buffer. ]] - for mode, newMappings in pairs(layerKeymap) do - -- if `mode` key has not yet been made for `priorKeymap`. - if not priorKeymap[mode] then - priorKeymap[mode] = {} + self.existing_keymap = {} + + --[[ iterate over the new keymaps to both: + 1. Populate a list of keymaps which will be overwritten to `existing_keymap` + 2. Apply the layer's keymappings. ]] + for mode, new_keymaps in pairs(self.layer_keymap) do + -- if `mode` key has not yet been made for `existing_keymap`. + if not self.existing_keymap[mode] then + self.existing_keymap[mode] = {} end -- store the previously mapped keys - for _, bufMap in ipairs(api.nvim_buf_get_keymap(_BUFFER_CURRENT, mode)) do - -- if the new mappings would overwrite this one - if newMappings[bufMap.lhs] then + for _, existing_keymap in ipairs(vim.api.nvim_get_keymap(mode)) do + -- if the new keymaps would overwrite this one + if new_keymaps[existing_keymap.lhs] then -- remove values so that it is in line with `nvim_set_keymap`. - local lhs, keymap = unpack(convertKeymap(bufMap)) - priorKeymap[mode][lhs] = keymap + local lhs, keymap = unpack_keymap_lhs(existing_keymap) + self.existing_keymap[mode][lhs] = keymap end end - -- add the new mappings - for lhs, newMapping in pairs(newMappings) do - local rhs, options = unpack(deconvertKeymap(newMapping)) - api.nvim_buf_set_keymap(_BUFFER_CURRENT, mode, lhs, rhs, options) + -- add the new keymaps + for lhs, new_keymap in pairs(new_keymaps) do + local rhs, options = unpack_keymap_rhs(new_keymap) + vim.keymap.set(mode, lhs, rhs, options) end end - - self._priorKeymap = priorKeymap end --------------------------------------------------------- ---[[ SUMMARY: - * Add a mapping to the mode. -]] ---[[ PARAMS: - * `mode` => the mode that this mapping for. - * `lhs` => the left hand side of the mapping. - * `rhs` => the right hand side of the mapping. - * `options` => options for the mapping. -]] ---[[ SEE ALSO: - * `nvim_buf_set_keymap()` -]] --------------------------------------------------------- -function _metaLayer:_mapToBuffer(mode, lhs, rhs, options) - local priorKeymap = self._priorKeymap - - if not priorKeymap then error( - "You can't map to a buffer without activating the layer first." - ) end - - if not priorKeymap[mode][lhs] then -- the mapping's state has not been saved. - for _, bufMap in - ipairs(api.nvim_buf_get_keymap(_BUFFER_CURRENT, mode)) - do -- check if it exists in the buffer - if bufMap.lhs == lhs then -- add it to the undo list - priorKeymap[mode][lhs] = unpack(convertKeymap(bufMap)) - break +--- Add a keymap to the mode. +--- @param mode string the mode that this keymap for. +--- @param lhs string the left hand side of the keymap. +--- @param rhs function|string the right hand side of the keymap. +--- @param options table options for the keymap. +--- @see `vim.keymap.set` +function Layer:map(mode, lhs, rhs, options) + if self.existing_keymap then -- the layer has been activated + if not self.existing_keymap[mode][lhs] then -- the keymap's state has not been saved. + for _, existing_keymap in ipairs(vim.api.nvim_get_keymap(mode)) do -- check if it has a keymap + if existing_keymap.lhs == lhs then -- add it to the undo list + existing_keymap.lhs = nil + self.existing_keymap[mode][lhs] = existing_keymap + break + end end end + + -- map the `lhs` to `rhs` in `mode` with `options` for the current buffer. + vim.keymap.set(mode, lhs, rhs, options) end - -- map the `lhs` to `rhs` in `mode` with `options` for the current buffer. - api.nvim_buf_set_keymap(_BUFFER_CURRENT, mode, lhs, rhs, options) + -- add the new mapping to the layer's keymap + options.rhs = rhs + self.layer_keymap[mode][lhs] = options end ------------------------------------------------- ---[[ SUMMARY: - * Add a mapping to the mode. -]] ---[[ PARAMS: - * `mode` => the mode that this mapping for. - * `lhs` => the left hand side of the mapping. - * `rhs` => the right hand side of the mapping. - * `options` => options for the mapping. -]] ---[[ SEE ALSO: - * `nvim_buf_set_keymap()` -]] ------------------------------------------------- -function _metaLayer:map(mode, lhs, rhs, options) - if self._priorKeymap then -- the layer has been activated. - self:_mapToBuffer(mode, lhs, rhs, options) +--- Restore one keymapping to its original state. +--- @param mode string the mode of the keymap. +--- @param lhs string the keys which invoke the keymap. +--- @see `vim.api.nvim_del_keymap` +function Layer:unmap(mode, lhs) + if not self.existing_keymap then + error("Don't call this function before activating the layer; just remove from the keymap passed to `Layer.new` instead.") end - -- add the new mapping to the keymap - self._keymap[mode][lhs] = vim.tbl_extend('force', - options, {rhs = rhs} - ) -end - ----------------------------------------------- ---[[ SUMMARY: - * Undo a mapping after `enter()`. -]] ---[[ PARAMS: - * `mode` => the mode to map (e.g. `n`, `i`). - * `lhs` => the mapping to undo. -]] ----------------------------------------------- -function _metaLayer:_unmapFromBuffer(mode, lhs) - local priorKeymap = self._priorKeymap - local priorMapping = self._priorKeymap[mode][lhs] - - if not priorKeymap then error( - "You can't undo a map from a buffer without activating the layer first." - ) end - - if priorMapping then -- there is an older mapping to go back to. - -- undo the mapping - local rhs, deconvertedKeymap = unpack(deconvertKeymap(priorMapping)) - api.nvim_buf_set_keymap(_BUFFER_CURRENT, mode, lhs, rhs, deconvertedKeymap) - - -- set the prior mapping as restored. - priorKeymap[mode][lhs] = _RESTORED + if self.existing_keymap[mode][lhs] then -- there is an older keymap to go back to, so undo this layer_keymap + local rhs, options = unpack_keymap_rhs(self.existing_keymap[mode][lhs]) + vim.keymap.set(mode, lhs, rhs, options) else - -- just delete the buffer mapping. - local noErrors, err = pcall(api.nvim_buf_del_keymap, _BUFFER_CURRENT, mode, lhs) + -- just make the keymap go back to default + local no_errors, err = pcall(vim.api.nvim_del_keymap, mode, lhs) - if not noErrors and err ~= _ERR_NO_MAP then + if not no_errors and err ~= 'E31: No such mapping' then print(err) end end -end - ------------------------------------- ---[[ SUMMARY: - * Remove a mapping from the mode. -]] ---[[ PARAMS: - * `mode` => the mode that this mapping for. - * `lhs` => the left hand side of the mapping. -]] ---[[ SEE ALSO: - * `nvim_buf_del_keymap()` -]] ------------------------------------- -function _metaLayer:unmap(mode, lhs) - -- unmap for the buffer too, if the layer is activated. - if self._priorKeymap then - self:_unmapFromBuffer(mode, lhs) - end - -- remove the mapping from the internal keymap - self._keymap[mode][lhs] = _RESTORED + -- remove this keymap from the list of ones to restore + self.existing_keymap[mode][lhs] = nil end --------------------------- ---[[ SUMMARY: - * Exit the layer. -]] --------------------------- -function _metaLayer:exit() - if not self._priorKeymap then +--- Exit the layer, restoring all previous keymaps. +function Layer:exit() + if not self.existing_keymap then error('This layer has not been entered yet.') end - for mode, mappings in pairs(self._keymap) do - for lhs, _ in pairs(mappings) do - self:_unmapFromBuffer(mode, lhs) + for mode, keymaps in pairs(self.layer_keymap) do + for lhs, _ in pairs(keymaps) do + self:unmap(mode, lhs) end end - self._priorKeymap = _RESTORED + self.existing_keymap = nil end ---[[ - /* - * CLASS `Layer` - */ ---]] - ------------------------------------------------------ ---[[ SUMMARY: - * Create a new `Layer` for the buffer-local keymap. -]] ---[[ PARAMS: - * `mappings` => the list of user mappings to replace. -]] ---[[ RETURNS: - * A new `Layer`. -]] ------------------------------------------------------ -function Layer.new(keymap) - return setmetatable( - {_keymap = keymap}, - _metaLayer - ) -end - ---[[ - /* - * PUBLICIZE `Layer` - */ ---]] - -return Layer +return +{ + --- @param keymap table the keymaps (e.g. `{n = {gg = {rhs = 'G', silent = true}}}`) + --- @return libmodal.Layer + new = function(keymap) + return setmetatable({layer_keymap = keymap}, Layer) + end +} diff --git a/lua/libmodal/src/Mode.lua b/lua/libmodal/src/Mode.lua index 6a50db5..b2234f4 100644 --- a/lua/libmodal/src/Mode.lua +++ b/lua/libmodal/src/Mode.lua @@ -1,32 +1,23 @@ ---[[/* IMPORTS */]] - -local classes = require('libmodal/src/classes') -local globals = require('libmodal/src/globals') -local ParseTable = require('libmodal/src/collections/ParseTable') -local utils = require('libmodal/src/utils') -local Vars = require('libmodal/src/Vars') - ---[[/* MODULE */]] - -local Mode = {TYPE = 'libmodal-mode'} - -local _HELP = '?' -local _TIMEOUT = { - CHAR = 'ø', - LEN = vim.go.timeoutlen, - SEND = function(self) vim.api.nvim_feedkeys(self.CHAR, 'nt', false) end -} -_TIMEOUT.CHAR_NUMBER = string.byte(_TIMEOUT.CHAR) - ---[[ - /* - * META `_metaMode` - */ ---]] - -local _metaMode = classes.new(Mode.TYPE) - -local _metaInputBytes = classes.new(nil, { +local globals = require 'libmodal/src/globals' +local ParseTable = require 'libmodal/src/collections/ParseTable' +local utils = require 'libmodal/src/utils' + +--- @class libmodal.Mode +--- @field private exit libmodal.utils.Vars +--- @field private flush_input_timer unknown +--- @field private help libmodal.utils.Help|nil +--- @field private indicator libmodal.utils.Indicator +--- @field private input libmodal.utils.Vars +--- @field private instruction function|table +--- @field private mappings libmodal.collections.ParseTable +--- @field private name string +--- @field private popups libmodal.collections.Stack +--- @field private supress_exit boolean +--- @field private timeouts_enabled boolean +local Mode = utils.classes.new() + +local InputBytes = utils.classes.new( +{ clear = function(self) for i, _ in ipairs(self) do self[i] = nil @@ -34,158 +25,108 @@ local _metaInputBytes = classes.new(nil, { end }) -classes = nil - ------------------------------------------------------------ ---[[ SUMMARY: - * Execute some `selection` according to a set of determined logic. -]] ---[[ REMARKS: - * Only provides logic for when `self._instruction` is a table of commands. -]] ---[[ PARAMS: - * `selection` => The instruction that is desired to be executed. -]] ------------------------------------------------------------ -function _metaMode._commandTableExecute(instruction) - if type(instruction) == globals.TYPE_FUNC then instruction() - else vim.api.nvim_command(instruction) end +local HELP = '?' +local TIMEOUT = +{ + CHAR = 'ø', + LEN = vim.go.timeoutlen, + SEND = function(self) vim.api.nvim_feedkeys(self.CHAR, 'nt', false) end +} +TIMEOUT.CHAR_NUMBER = string.byte(TIMEOUT.CHAR) + +--- Execute the `instruction`. +--- @param instruction function|string a Lua function or Vimscript command. +function Mode.execute_instruction(instruction) + if type(instruction) == globals.TYPE_FUNC then + instruction() + else + vim.api.nvim_command(instruction) + end end ------------------------------------------------ ---[[ SUMMARY: - * Parse `self.mappings` and see if there is any command to execute. -]] ------------------------------------------------ -function _metaMode:_checkInputForMapping() +--- 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). +function Mode:check_input_for_mapping() -- Stop any running timers - self._flushInputTimer:stop() + self.flush_input_timer:stop() -- Append the latest input to the locally stored input history. - local inputBytes = self.inputBytes - - inputBytes[#inputBytes + 1] = self.input:nvimGet() + self.input_bytes[#self.input_bytes + 1] = self.input:get() -- 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. - local commandType = type(cmd) + local command_type = type(cmd) -- if there was no matching command if not cmd then - if #inputBytes < 2 and inputBytes[1] == string.byte(_HELP) then - self._help:show() + if #self.input_bytes < 2 and self.input_bytes[1] == string.byte(HELP) then + self.help:show() end - inputBytes:clear() + self.input_bytes:clear() -- The command was a table, meaning that it MIGHT match. - elseif commandType == globals.TYPE_TBL - and globals.is_true(self._timeouts.enabled) + elseif command_type == globals.TYPE_TBL + and globals.is_true(self.timeouts_enabled) then -- start the timer - self._flushInputTimer:start( - _TIMEOUT.LEN, 0, vim.schedule_wrap(function() + self.flush_input_timer:start( + TIMEOUT.LEN, 0, vim.schedule_wrap(function() -- Send input to interrupt a blocking `getchar` - _TIMEOUT:SEND() + TIMEOUT:SEND() -- if there is a command, execute it. if cmd[ParseTable.CR] then - self._commandTableExecute(cmd[ParseTable.CR]) + self.execute_instruction(cmd[ParseTable.CR]) end -- clear input - inputBytes:clear() - self._popups:peek():refresh(inputBytes) + self.input_bytes:clear() + self.popups:peek():refresh(self.input_bytes) end) ) -- The command was an actual vim command. else - self._commandTableExecute(cmd) - inputBytes:clear() + self.execute_instruction(cmd) + self.input_bytes:clear() end - self._popups:peek():refresh(inputBytes) + self.popups:peek():refresh(self.input_bytes) end --------------------------- ---[[ SUMMARY: - * Enter `self`'s mode. -]] --------------------------- -function _metaMode:enter() +--- Enter this mode. +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) == globals.TYPE_TBL then -- Initialize the input history variable. - self._popups:push(require('libmodal/src/collections/Popup').new()) + self.popups:push(utils.Popup.new()) end - self._previousModeName = vim.g.libmodalActiveModeName - vim.g.libmodalActiveModeName = self._name + self.previous_mode_name = vim.g.libmodalActiveModeName + vim.g.libmodalActiveModeName = self.name --[[ MODE LOOP. ]] - local continueMode = true - while continueMode do + local continue_mode = true + while continue_mode do -- 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 not noErrors then - utils.show_error(modeResult) - continueMode = true + if not no_errors then + utils.show_error(mode_result) + continue_mode = false else - continueMode = modeResult + continue_mode = mode_result end end - self:_tearDown() -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 + self:tear_down() end -------------------------------- ---[[ SUMMARY: - * Loop an initialized `mode`. -]] ---[[ RETURNS: - * `boolean` => whether or not the mode should continue -]] -------------------------------- -function _metaMode:_inputLoop() +--- Get input from the user. +--- @return boolean more_input +function Mode:get_user_input() -- 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 + if self.supress_exit and globals.is_true(self.exit:get()) then return false end @@ -193,114 +134,103 @@ function _metaMode:_inputLoop() utils.api.nvim_lecho(self.indicator) -- Capture input. - local userInput = utils.api.nvim_input() + local user_input = vim.fn.getchar() -- Return if there was a timeout event. - if userInput == _TIMEOUT.CHAR_NUMBER then + if user_input == TIMEOUT.CHAR_NUMBER then return true end -- 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." else -- The user wants to continue. --[[ 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. ]] - local instructionType = type(self._instruction) - - if instructionType == globals.TYPE_TBL then -- The second argument was a dict. Parse it. - self:_checkInputForMapping() - elseif instructionType == globals.TYPE_STR then -- It is the name of a VimL function. - vim.fn[self._instruction]() - else -- the second argument was a function; execute it. - 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. + self:check_input_for_mapping() + elseif instruction_type == globals.TYPE_STR then -- The instruction is the name of a Vimscript function. + vim.fn[self.instruction]() + else -- The instruction is a function. + self.instruction() end end return true end ------------------------------- ---[[ SUMMARY: - * Remove variables used for a mode. -]] ------------------------------- -function _metaMode:_tearDown() - if type(self._instruction) == globals.TYPE_TBL then - self._flushInputTimer:stop() - self.inputBytes = nil - - self._popups:pop():close() +--- Uninitialize variables from after exiting the mode. +function Mode:tear_down() + if type(self.instruction) == globals.TYPE_TBL then + self.flush_input_timer:stop() + self.input_bytes = nil + + self.popups:pop():close() 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 else - vim.g.libmodalActiveModeName = self._previousModeName + vim.g.libmodalActiveModeName = self.previous_mode_name end - self._winState:restore() utils.api.nvim_redraw() end ---[[ - /* - * CLASS `Mode` - */ ---]] - ------------------------------------------ ---[[ SUMMARY: - * Enter a mode. -]] ---[[ PARAMS: - * `name` => the mode name. - * `instruction` => the mode callback, or mode combo table. - * `...` => optional exit supresion flag. -]] ------------------------------------------ -function Mode.new(name, instruction, ...) - name = vim.trim(name) - - -- 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({...})) +return +{ + --- 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) + name = vim.trim(name) + + -- Inherit the metatable. + local self = setmetatable( + { + exit = utils.Vars.new('exit', name), + indicator = utils.Indicator.mode(name), + input = utils.Vars.new('input', name), + instruction = instruction, + name = name, + }, + Mode + ) - -- 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 type(instruction) == globals.TYPE_TBL then - self:_initMappings() - end + -- If the user provided keymaps + if type(instruction) == globals.TYPE_TBL then + -- Create a timer to perform actions with. + self.flush_input_timer = vim.loop.new_timer() - return self -end + -- 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.input_bytes = setmetatable({}, InputBytes) + + -- Build the parse tree. + self.mappings = ParseTable.new(self.instruction) ---[[ - / - * PUBLICIZE MODULE - */ ---]] + -- Create a table for mode-specific data. + self.popups = require('libmodal/src/collections/Stack').new() -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 +} diff --git a/lua/libmodal/src/Prompt.lua b/lua/libmodal/src/Prompt.lua index 5eb0dc2..3179341 100644 --- a/lua/libmodal/src/Prompt.lua +++ b/lua/libmodal/src/Prompt.lua @@ -1,188 +1,153 @@ ---[[/* IMPORTS */]] - -local globals = require('libmodal/src/globals') -local utils = require('libmodal/src/utils') -local Vars = require('libmodal/src/Vars') - ---[[/* MODULE */]] - -local Prompt = {TYPE = 'libmodal-prompt'} - -local _HELP = 'help' -local _REPLACEMENTS = { +local globals = require 'libmodal/src/globals' +local utils = require 'libmodal/src/utils' + +--- @class libmodal.Prompt +--- @field private completions table|nil +--- @field private exit libmodal.utils.Vars +--- @field private help libmodal.utils.Help|nil +--- @field private indicator libmodal.utils.Indicator +--- @field private input libmodal.utils.Vars +--- @field private instruction function|table +--- @field private name string +local Prompt = utils.classes.new() + +local HELP = 'help' +local REPLACEMENTS = +{ '(', ')', '[', ']', '{', '}', '=', '+', '<', '>', '^', ',', '/', ':', '?', '@', '!', '$', '*', '.', '%', '&', '\\', } -for i, replacement in ipairs(_REPLACEMENTS) do - _REPLACEMENTS[i], _ = vim.pesc(replacement) + +for i, replacement in ipairs(REPLACEMENTS) do + REPLACEMENTS[i], _ = vim.pesc(replacement) end ---[[ - /* - * META `Prompt` - */ ---]] - -local _metaPrompt = require('libmodal/src/classes').new(Prompt.TYPE) - ---------------------------------------------------- ---[[ SUMMARY: - * Execute the specified instruction based on user input. -]] ---[[ PARAMS: - * `userInput` => the input from the user, used to determine how to execute the `self._instruction`. -]] ---------------------------------------------------- -function _metaPrompt:_executeInstruction(userInput) - -- get the instruction for the mode. - local instruction = self._instruction - - if type(instruction) == globals.TYPE_TBL then -- The instruction is a command table. - if instruction[userInput] then -- There is a defined command for the input. - local to_execute = instruction[userInput] +--- Execute the instruction specified by the `user_input`. +--- @param user_input string +function Prompt:execute_instruction(user_input) + if type(self.instruction) == globals.TYPE_TBL then -- The self.instruction is a command table. + if self.instruction[user_input] then -- There is a defined command for the input. + local to_execute = self.instruction[user_input] if type(to_execute) == globals.TYPE_FUNC then to_execute() else - vim.api.nvim_command(instruction[userInput]) + vim.api.nvim_command(to_execute) end - elseif userInput == _HELP then -- The user did not define a 'help' command, so use the default. - self._help:show() + elseif user_input == HELP then -- The user did not define a 'help' command, so use the default. + self.help:show() else -- show an error. utils.api.nvim_show_err(globals.DEFAULT_ERROR_TITLE, 'Unknown command') end - elseif type(instruction) == globals.TYPE_STR then -- The instruction is a function. - vim.fn[instruction]() - else -- attempt to call the instruction. - instruction() + elseif type(self.instruction) == globals.TYPE_STR then -- The self.instruction is a function. + vim.fn[self.instruction]() + else -- attempt to call the self.instruction. + self.instruction() end end ---------------------------------- ---[[ SUMMARY: - * Loop to get user input with `input()`. -]] ---------------------------------- -function _metaPrompt:_inputLoop() - -- If the mode is not handling exit events automatically and the global exit var is true. - if self.exit.supress and globals.is_true(self.exit:nvimGet()) then - return false - end - +--- Get more input from the user. +--- @return boolean more_input +function Prompt:get_user_input() -- clear previous `echo`s. utils.api.nvim_redraw() - -- determine what to do with the input - local function userInputCallback(userInput) - if userInput and string.len(userInput) > 0 then -- The user actually entered something. - self.input:nvimSet(userInput) - self:_executeInstruction(userInput) - else -- indicate we want to leave the prompt - return false - end + local continue_prompt -- will set to true `true` if looping this prompt again + + --- 1. Set `g:ModeInput` to `user_input` + --- 2. Execute any commands indicated by `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. + self.input:set(user_input) + self:execute_instruction(user_input) - return true + local should_exit = self.exit:get() + if should_exit ~= nil then + continue_prompt = not should_exit + end + else -- the user entered nothing. + continue_prompt = false + end end -- echo the highlighting vim.api.nvim_command('echohl ' .. self.indicator.hl) -- set the user input variable - if self._completions then + if self.completions then vim.api.nvim_command('echo "' .. self.indicator.str .. '"') - return vim.ui.select(self._completions, {}, userInputCallback) + vim.ui.select(self.completions, {}, user_input_callback) else - return vim.ui.input({prompt = self.indicator.str}, userInputCallback) + vim.ui.input({prompt = self.indicator.str}, user_input_callback) end + + return continue_prompt == nil and true or continue_prompt end ----------------------------- ---[[ SUMMARY: - * Enter a prompt 'mode'. -]] ----------------------------- -function _metaPrompt:enter() +--- Enter the prompt. +function Prompt:enter() -- enter the mode using a loop. - local continueMode = true - while continueMode do - local noErrors, promptResult = pcall(self._inputLoop, self) + local continue_mode = true + while continue_mode do + local no_errors, prompt_result = pcall(self.get_user_input, self) -- if there were errors. - if not noErrors then - utils.show_error(promptResult) - continueMode = true + if not no_errors then + utils.show_error(prompt_result) + continue_mode = false else - continueMode = promptResult + continue_mode = prompt_result end end end ---[[ - /* - * CLASS `Prompt` - */ ---]] - -------------------------------------------- ---[[ SUMMARY: - * Enter a prompt. -]] ---[[ PARAMS: - * `name` => the prompt name. - * `instruction` => the prompt callback, or mode command table. - * `...` => a completions table. -]] -------------------------------------------- -function Prompt.new(name, instruction, ...) - name = vim.trim(name) - - local self = setmetatable( - { - exit = Vars.new('exit', name), - indicator = require('libmodal/src/Indicator').prompt(name), - input = require('libmodal/src/Vars').new('input', name), - _instruction = instruction, - _name = name - }, - _metaPrompt - ) - - -- get the arguments - local userCompletions, supressExit = unpack({...}) - - self.exit.supress = supressExit or false - - -- get the completion list. - if type(instruction) == globals.TYPE_TBL then -- unload the keys of the mode command table. - -- Create one if the user specified a command table. - local completions = {} - local containedHelp = false - - for command, _ in pairs(instruction) do - completions[#completions + 1] = command - if command == _HELP then containedHelp = true +return +{ + --- Enter a prompt. + --- @param name string the name of the prompt + --- @param instruction function|table what to do with user input + --- @param user_completions table|nil a list of possible inputs, provided by the user + --- @return libmodal.Prompt + new = function(name, instruction, user_completions) + name = vim.trim(name) + + local self = setmetatable( + { + exit = utils.Vars.new('exit', name), + indicator = utils.Indicator.prompt(name), + input = utils.Vars.new('input', name), + instruction = instruction, + name = name + }, + Prompt + ) + + -- get the completion list. + if type(instruction) == globals.TYPE_TBL then -- unload the keys of the mode command table. + -- Create one if the user specified a command table. + local completions = {} + local contained_help = false + + for command, _ in pairs(instruction) do + completions[#completions + 1] = command + if command == HELP then + contained_help = true + end end - end - if not containedHelp then -- assign it. - completions[#completions + 1] = _HELP - self._help = utils.Help.new(instruction, 'COMMAND') + if not contained_help then -- assign it. + completions[#completions + 1] = HELP + self.help = utils.Help.new(instruction, 'COMMAND') + end + + self.completions = completions + elseif user_completions then + -- Use the table that the user gave. + self.completions = user_completions end - self._completions = completions - elseif userCompletions then - -- Use the table that the user gave. - self._completions = userCompletions + return self end - - return self -end - - ---[[ - /* - * PUBLICIZE MODULE - */ ---]] - -return Prompt +} diff --git a/lua/libmodal/src/Vars.lua b/lua/libmodal/src/Vars.lua deleted file mode 100644 index 15f0f2f..0000000 --- a/lua/libmodal/src/Vars.lua +++ /dev/null @@ -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 diff --git a/lua/libmodal/src/classes.lua b/lua/libmodal/src/classes.lua deleted file mode 100644 index c5ede09..0000000 --- a/lua/libmodal/src/classes.lua +++ /dev/null @@ -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 -} diff --git a/lua/libmodal/src/collections/ParseTable.lua b/lua/libmodal/src/collections/ParseTable.lua index 0e57121..2aee254 100644 --- a/lua/libmodal/src/collections/ParseTable.lua +++ b/lua/libmodal/src/collections/ParseTable.lua @@ -1,56 +1,14 @@ ---[[ - /* - * IMPORTS - */ ---]] - -local globals = require('libmodal/src/globals') - ---[[ - /* - * MODULE - */ ---]] - -local _REGEX_ALL = '.' - -local ParseTable = { - CR = 13, -- The number corresponding to 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 -} +--- The number corresponding to in vim. +local CR = 13 +local globals = require 'libmodal/src/globals' + +--- @class libmodal.collections.ParseTable +local ParseTable = require('libmodal/src/utils/classes').new() ----------------------------------- ---[[ SUMMARY: - * Reverse the elements of some table. -]] ---[[ PARAMS: - * `tbl` => the table to reverse. -]] ---[[ RETURNS: - * The reversed `tbl`. -]] ----------------------------------- -local function _table_reverse(tbl) +--- Reverse the order of elements in some `tbl` +--- @param tbl table the table to reverse +--- @return table tbl_reversed +local function table_reverse(tbl) local reversed = {} while #reversed < #tbl do -- look, no variables! @@ -59,196 +17,124 @@ local function _table_reverse(tbl) return reversed end ------------------------------- ---[[ SUMMARY: - * Parse a `key`. -]] ---[[ PARAMS: - * `key` => the key to parse. -]] ---[[ RETURNS: - * The parsed `key`. -]] ------------------------------- -function ParseTable.parse(key) - return ParseTable.stringSplit(key, _REGEX_ALL) +--- @param str string +--- @return table chars of `str` +local function chars(str) + return vim.split(str, '') end ------------------------------------------ ---[[ SUMMARY - * Get `splitKey` from some `parseTable`. -]] ---[[ PARAMS: - * `parseTable` => the table to fetch `splitKey` from. - * `splitKey` => the key split into groups. -]] ------------------------------------------ -local function _get(parseTable, splitKey) - --[[ Get the next character in the combo string. ]] +--- 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 table 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. +local function get(parse_table, lhs_reversed_bytes) + --[[ Get the next character in the keymap string. ]] local k = '' - if #splitKey > 0 then -- There is more input to parse - k = table.remove(splitKey) -- the table should already be `char2nr()`'d - else -- the user input has run out, but there is more in the `parseTable`. - return parseTable + if #lhs_reversed_bytes > 0 then -- There is more input to parse + 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 `parse_table`. + return parse_table end --[[ Parse the `k`. ]] -- Make sure the dicitonary has a key for that value. - if parseTable[k] then - local val = parseTable[k] - local valType = type(val) + if parse_table[k] then + local val = parse_table[k] + local val_type = type(val) - if valType == globals.TYPE_TBL then - if val[ParseTable.CR] and #splitKey < 1 then + if val_type == globals.TYPE_TBL then + if val[CR] and #lhs_reversed_bytes < 1 then return val else - return _get(val, splitKey) + return get(val, lhs_reversed_bytes) 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 end end return nil end ------------------------------------------------- ---[[ SUMMARY: - * Update the values of some `dict` using a `splitKey`. -]] ---[[ PARAMS: - * `parseTable` => the parseTable to update. - * `splitKey` => the key split into groups. -]] ------------------------------------------------- -local function _put(parseTable, splitKey, value) +--- Insert a `value` into `parse_table` at the position indicated by `lhs_reversed_bytes` +--- @param lhs_reversed_bytes table the characters of the left-hand side of the mapping reversed passed to `string.byte` +--- @param value function|string the right-hand-side of the mapping +local function put(parse_table, lhs_reversed_bytes, value) --[[ Get the next character in the table. ]] - local k = string.byte(table.remove(splitKey)) - - if #splitKey > 0 then -- there are still characters left in the key. - if not parseTable[k] then parseTable[k] = {} - -- If there is a previous command mapping in place - else local valueType = type(parseTable[k]) - if valueType == globals.TYPE_STR or valueType == globals.TYPE_FUNC then - -- Swap the mapping to a `CR` - parseTable[k] = {[ParseTable.CR] = parseTable[k]} + local byte = string.byte(table.remove(lhs_reversed_bytes)) + + 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 + -- Make the mapping require hitting enter before executing + parse_table[byte] = {[CR] = parse_table[byte]} end end - -- run _update() again - _put(parseTable[k], splitKey, value) - -- If parseTable[k] is a pre-existing table, don't clobber the table— clobber the `CR` value. - elseif type(parseTable[k]) == globals.TYPE_TBL then - parseTable[k][ParseTable.CR] = value - else parseTable[k] = value -- parseTable[k] is not a table, go ahead and clobber the 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 + parse_table[byte][CR] = value + else + parse_table[byte] = value -- parse_table[k] is not a table, go ahead and clobber the value. end end ---[[ - /* - * META `ParseTable` - */ ---]] - -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)) +--- Retrieve the mapping of `lhs_reversed_bytes` +--- @param key_dict table a list of characters (most recent input first) +--- @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)) end --------------------------------------- ---[[ SUMMARY: - * Get a value from this `ParseTable`. -]] ---[[ PARAMS: - * `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)) +--- Parse `key` and retrieve its value +--- @param key string the left-hand-side of the mapping to retrieve +--- @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) + local parsed_table = chars(string.reverse(key)) -- convert all of the strings to bytes. - for i, v in ipairs(parsedTable) do - parsedTable[i] = string.byte(v) + for i, v in ipairs(parsed_table) do + parsed_table[i] = string.byte(v) end - return _get(self, parsedTable) + return get(self, parsed_table) end ---------------------------------------------- ---[[ SUMMARY: - * Put `value` into the parse tree as `key`. -]] ---[[ PARAMS: - * `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) +--- Parse `key` and assign it to `value`. +--- @param key string the left-hand-side of the mapping +--- @param value function|string the right-hand-side of the mapping +function ParseTable:parse_put(key, value) + put(self, chars(string.reverse(key)), value) end --------------------------------------------------- ---[[ SUMMARY: - * Create the union of `self` and `tableToUnite` -]] ---[[ PARAMS: - * `tableToUnite` => the table to unite with `self.` -]] --------------------------------------------------- -function _metaParseTable:parsePutAll(tableToUnite) - for k, v in pairs(tableToUnite) do - self:parsePut(k, v) +--- `:parse_put` all `{key, value}` pairs in `keys_and_values`. +--- @param keys_and_values table +function ParseTable:parse_put_all(keys_and_values) + for k, v in pairs(keys_and_values) do + self:parse_put(k, v) end end ---[[ - /* - * CLASS `ParseTable` - */ ---]] - ----------------------------------- ---[[ SUMMARY: - * Create a new parse table from a user-defined table. -]] ---[[ 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 +return +{ + 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 + new = function(user_table) + local self = setmetatable({}, ParseTable) ---[[ - /* - * PUBLICIZE MODULE - */ ---]] + -- Parse the passed in table. + self:parse_put_all(user_table) -return ParseTable + -- Return the new `ParseTable`. + return self + end, +} diff --git a/lua/libmodal/src/collections/Popup.lua b/lua/libmodal/src/collections/Popup.lua deleted file mode 100644 index 7533e2d..0000000 --- a/lua/libmodal/src/collections/Popup.lua +++ /dev/null @@ -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 diff --git a/lua/libmodal/src/collections/Stack.lua b/lua/libmodal/src/collections/Stack.lua index 3da3c11..eb225f4 100644 --- a/lua/libmodal/src/collections/Stack.lua +++ b/lua/libmodal/src/collections/Stack.lua @@ -1,103 +1,27 @@ ---[[ - /* - * MODULE `Stack` - */ ---]] +--- @class libmodal.collections.Stack +local Stack = require('libmodal/src/utils/classes').new() -local Stack = {TYPE = 'libmodal-stack'} - ---[[ - /* - * META `Stack` - */ ---]] - -local _metaStack = require('libmodal/src/classes').new(Stack.TYPE) - -_metaStack._len = 0 - --------------------------------- ---[[ SUMMARY: - * Get the foremost value in `self`. -]] ---[[ - * The foremost value in `self`. -]] --------------------------------- -function _metaStack:peek() - return self._top +--- @return unknown top the foremost value of the stack +function Stack:peek() + return self[#self] end -------------------------- ---[[ SUMMARY: - * Remove the foremost value in `self` and return it. -]] ---[[ RETURNS: - * The foremost value in `self`. -]] -------------------------- -function _metaStack:pop() - local previousLen = self._len - - if previousLen < 1 then return nil - end - - -- Store the previous top of the stack. - local previousTop = self._top - - -- Remove the previous top of the stack. - self[previousLen] = nil - - -- Get the new length of the stack - local newLen = previousLen - 1 - - -- Update the values of the stack. - if newLen < 1 then -- the stack is empty - self._len = nil - self._top = nil - else -- there is still something in the stack - self._len = newLen - self._top = self[newLen] - end - - -- Return the previous top of the stack. - return previousTop +--- Remove the foremost value from the stack and return it. +--- @return unknown top the foremost value of the stack +function Stack:pop() + return table.remove(self) end -------------------------------- ---[[ SUMMARY: - * Push some `value` onto `self`. -]] ---[[ PARAMS: - * `value` => the value to append to `self`. -]] -------------------------------- -function _metaStack:push(value) - -- create placeholder so new values are not put into the table until operations have succeeded. - local newLen = self._len + 1 - +--- Push some `value` on to the stack. +--- @param value unknown the value to push onto the stack. +function Stack:push(value) -- Push to the stack - self[newLen] = value - - -- update stack values - self._len = newLen - self._top = value + self[#self + 1] = value end ---[[ - /* - * CLASS `Stack` - */ ---]] - -function Stack.new() - return setmetatable({}, _metaStack) -end - ---[[ - /* - * PUBLICIZE `Stack` - */ ---]] - -return Stack +return +{ + new = function() + return setmetatable({}, Stack) + end, +} diff --git a/lua/libmodal/src/collections/init.lua b/lua/libmodal/src/collections/init.lua index 5ae5101..7fea8e6 100644 --- a/lua/libmodal/src/collections/init.lua +++ b/lua/libmodal/src/collections/init.lua @@ -1,5 +1,5 @@ -return { - ParseTable = require('libmodal/src/collections/ParseTable'), - Popup = require('libmodal/src/collections/Popup'), - Stack = require('libmodal/src/collections/Stack') +return +{ + ParseTable = require 'libmodal/src/collections/ParseTable', + Stack = require 'libmodal/src/collections/Stack' } diff --git a/lua/libmodal/src/globals.lua b/lua/libmodal/src/globals.lua index ce87a68..5fe68eb 100644 --- a/lua/libmodal/src/globals.lua +++ b/lua/libmodal/src/globals.lua @@ -1,24 +1,42 @@ -local _VIM_FALSE = 0 -local _VIM_TRUE = 1 +local VIM_FALSE = 0 +local VIM_TRUE = 1 return { - DEFAULT_ERROR_TITLE = 'vim-libmodal error', + --- The default error title for `nvim-libmodal` errors. + DEFAULT_ERROR_TITLE = 'nvim-libmodal error', + --- The key-code for the escape character. ESC_NR = 27, - TYPE_FUNC = 'function', - TYPE_NUM = 'number', - TYPE_STR = 'string', - TYPE_TBL = 'table', + --- The string which is returned by `type(function() end)` (or any function) + TYPE_FUNC = type(function() end), - VIM_FALSE = _VIM_FALSE, - VIM_TRUE = _VIM_TRUE, + --- The string which is returned by `type(0)` (or any number) + TYPE_NUM = type(0), + --- The string which is returned by `type ''` (or any string) + TYPE_STR = type '', + + --- The string which is returned by `type {}` (or any table) + TYPE_TBL = type {}, + + --- The value of Vimscript's `v:false` + VIM_FALSE = VIM_FALSE, + + --- The value of Vimscript's `v:true` + VIM_TRUE = VIM_TRUE, + + --- Assert some value is either `false` or `v:false`. + --- @param val boolean|number + --- @return boolean is_false = function(val) - return val == false or val == _VIM_FALSE + return val == false or val == VIM_FALSE end, + --- Assert some value is either `true` or `v:true`. + --- @param val boolean|number + --- @return boolean is_true = function(val) - return val == true or val == _VIM_TRUE + return val == true or val == VIM_TRUE end } diff --git a/lua/libmodal/src/init.lua b/lua/libmodal/src/init.lua index 0d332be..7c7cb61 100644 --- a/lua/libmodal/src/init.lua +++ b/lua/libmodal/src/init.lua @@ -1,10 +1,9 @@ -return { - classes = require('libmodal/src/classes'), - collections = require('libmodal/src/collections'), - globals = require('libmodal/src/globals'), - Indicator = require('libmodal/src/Indicator'), - Layer = require('libmodal/src/Layer'), - Mode = require('libmodal/src/Mode'), - Prompt = require('libmodal/src/Prompt'), - utils = require('libmodal/src/utils') +return +{ + collections = require 'libmodal/src/collections', + globals = require 'libmodal/src/globals', + Layer = require 'libmodal/src/Layer', + Mode = require 'libmodal/src/Mode', + Prompt = require 'libmodal/src/Prompt', + utils = require 'libmodal/src/utils', } diff --git a/lua/libmodal/src/utils/Help.lua b/lua/libmodal/src/utils/Help.lua index aebc820..e753976 100644 --- a/lua/libmodal/src/utils/Help.lua +++ b/lua/libmodal/src/utils/Help.lua @@ -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 `longestKeyLen`. +--- Align `tbl` according to the `longest_key_len`. --- @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 -local function tabAlign(tbl, longestKeyLen) - local toPrint = {} +local function align_columns(tbl, longest_key_len) + local to_print = {} for key, value in pairs(tbl) do - toPrint[#toPrint + 1] = key + to_print[#to_print + 1] = key local len = string.len(key) local byte = string.byte(key) -- 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 - toPrint[#toPrint + 1] = ' ' + for _ = len, longest_key_len do + to_print[#to_print + 1] = ' ' end - toPrint[#toPrint + 1] = table.concat( - {' │ ', '\n'}, - (type(value) == globals.TYPE_STR) and value or '' - ) + to_print[#to_print + 1] = ' │ ' .. (type(value) == globals.TYPE_STR and value or vim.inspect(value)) .. '\n' end - return toPrint + return to_print end ---[[/* MODULE */]] - -local Help = {TYPE = 'libmodal-help'} - ---[[/* META `Help` */]] - -local _metaHelp = require('libmodal/src/classes').new(Help.TYPE) - --- Show the contents of this `Help`. -function _metaHelp:show() - for _, helpText in ipairs(self) do - print(helpText) +function Help:show() + for _, help_text in ipairs(self) do + print(help_text) end vim.fn.getchar() end --[[/* CLASS `Help` */]] ---- Create a default help table with `commandsOrMaps` and vim expressions. ---- @param commandsOrMaps table commands or mappings to vim expressions. ---- @return table Help -function Help.new(commandsOrMaps, title) - -- find the longest key in the table. - local longestKeyLen = 0 - for key, _ in pairs(commandsOrMaps) do - local keyLen = string.len(key) - if keyLen > longestKeyLen then - longestKeyLen = keyLen +return +{ + --- Create a default help table with `commands_or_maps` and vim expressions. + --- @param commands_or_maps table commands or mappings to vim expressions. + --- @param title string + --- @return libmodal.utils.Help + new = function(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) + 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 - -- adjust the longest key length if the table header is longer. - if longestKeyLen < string.len(title) then - longestKeyLen = string.len(title) + -- define the separator for the help table. + local help_separator = {} + 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 - - -- 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 +} diff --git a/lua/libmodal/src/utils/Indicator.lua b/lua/libmodal/src/utils/Indicator.lua new file mode 100644 index 0000000..662e273 --- /dev/null +++ b/lua/libmodal/src/utils/Indicator.lua @@ -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 diff --git a/lua/libmodal/src/utils/Popup.lua b/lua/libmodal/src/utils/Popup.lua new file mode 100644 index 0000000..4a6b50b --- /dev/null +++ b/lua/libmodal/src/utils/Popup.lua @@ -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 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 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 +} diff --git a/lua/libmodal/src/utils/Vars.lua b/lua/libmodal/src/utils/Vars.lua new file mode 100644 index 0000000..87f9ce4 --- /dev/null +++ b/lua/libmodal/src/utils/Vars.lua @@ -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 +} diff --git a/lua/libmodal/src/utils/WindowState.lua b/lua/libmodal/src/utils/WindowState.lua deleted file mode 100644 index 27ceb48..0000000 --- a/lua/libmodal/src/utils/WindowState.lua +++ /dev/null @@ -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 diff --git a/lua/libmodal/src/utils/api.lua b/lua/libmodal/src/utils/api.lua index 0c43370..55a8c2e 100644 --- a/lua/libmodal/src/utils/api.lua +++ b/lua/libmodal/src/utils/api.lua @@ -1,7 +1,5 @@ ---[[/* IMPORTS */]] - -local globals = require('libmodal/src/globals') -local HighlightSegment = require('libmodal/src/Indicator/HighlightSegment') +local globals = require 'libmodal/src/globals' +local Indicator = require 'libmodal/src/utils/Indicator' --[[/* MODULE */]] @@ -25,63 +23,32 @@ function api.nvim_bell() vim.api.nvim_command('normal '..string.char(27)) -- escape char end ---- Gets one character of user input, as a number. -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. -]] --------------------------- +--- Run the `mode` command to refresh the screen. function api.nvim_redraw() vim.api.nvim_command 'mode' end ---------------------------------- ---[[ SUMMARY: - * Echo a table of {`hlgroup`, `str`} tables. - * Meant to be read as "nvim list echo". -]] ---[[ PARAMS: - * `hlTables` => the tables to echo with highlights. -]] ---------------------------------- -local lecho_template = { - [1] = "echohl ", - [2] = nil, - [3] = " | echon '", - [4] = nil, - [5] = "'" -} -function api.nvim_lecho(hlTables) +--- Echo a list of `Indicator`s with their associated highlighting. +--- @param indicators libmodal.utils.Indicator|table the indicators to echo +function api.nvim_lecho(indicators) + if indicators.hl then -- wrap the single indicator in a table to form a list of indicators + indicators = {indicators} + end + 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 + vim.api.nvim_command 'echohl None' end --------------------------------------- ---[[ SUMMARY: - * Show a `title` error. -]] ---[[ PARAMS: - * `title` => the title of the error. - * `msg` => the message of the error. -]] --------------------------------------- +--- Show an error. +--- @param title string a succint category of error +--- @param msg string a descriptive reason for the error function api.nvim_show_err(title, msg) - api.nvim_lecho({ - HighlightSegment.new('Title', tostring(title)..'\n'), - HighlightSegment.new('Error', tostring(msg)), - }) + api.nvim_lecho {Indicator.new('Title', tostring(title)..'\n'), Indicator.new('Error', tostring(msg))} vim.fn.getchar() end diff --git a/lua/libmodal/src/utils/classes.lua b/lua/libmodal/src/utils/classes.lua new file mode 100644 index 0000000..c843525 --- /dev/null +++ b/lua/libmodal/src/utils/classes.lua @@ -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, +} diff --git a/lua/libmodal/src/utils/init.lua b/lua/libmodal/src/utils/init.lua index 9a6e44f..af47dff 100644 --- a/lua/libmodal/src/utils/init.lua +++ b/lua/libmodal/src/utils/init.lua @@ -1,11 +1,14 @@ --[[/* MODULE */]] -local utils = {} -utils.api = require('libmodal/src/utils/api') -utils.Help = require('libmodal/src/utils/Help') -utils.WindowState = require('libmodal/src/utils/WindowState') - ---[[/* FUNCTIONS */]] +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', +} --- Show an error from `pcall()`. --- @param pcall_err string the error generated by `pcall()`. @@ -18,6 +21,4 @@ function utils.show_error(pcall_err) ) end ---[[/* PUBLICIZE MODULE */]] - return utils diff --git a/plugin/libmodal.lua b/plugin/libmodal.lua index 1f1b9e3..d54b74a 100644 --- a/plugin/libmodal.lua +++ b/plugin/libmodal.lua @@ -5,8 +5,5 @@ vim.g.libmodalTimeouts = vim.g.libmodalTimeouts or vim.go.timeout -- The default highlight groups (for colors) are specified below. -- Change these default colors by defining or linking the corresponding highlight group. -vim.cmd -[[ - highlight default link LibmodalPrompt ModeMsg - highlight default link LibmodalStar StatusLine -]] +vim.api.nvim_set_hl(0, 'LibmodalPrompt', {default = true, link = 'ModeMsg'}) +vim.api.nvim_set_hl(0, 'LibmodalStar', {default = true, link = 'StatusLine'})