diff --git a/autoload/libmodal.vim b/autoload/libmodal.vim index 60307e8..39c5751 100644 --- a/autoload/libmodal.vim +++ b/autoload/libmodal.vim @@ -1,14 +1,3 @@ -let s:winOpenOpts = { -\ 'anchor' : 'SW', -\ 'col' : &columns - 1, -\ 'focusable': v:false, -\ 'height' : 1, -\ 'relative' : 'editor', -\ 'row' : &lines - &cmdheight - 1, -\ 'style' : 'minimal', -\ 'width' : 25, -\} - " SUMMARY: " * Get user input with some `completions`. " PARAMS: @@ -38,6 +27,10 @@ function! libmodal#Enter(...) abort call libmodal#_lua('mode', a:000) endfunction +function! libmodal#Layer(...) abort + call libmodal#_lua('layer') +endfunction + " SUMMARY: " * Runs the nvim-libmodal command prompt loop. The function takes an optional " argument specifying how many times to run (runs until exiting by default). @@ -62,7 +55,7 @@ function! libmodal#_lua(lib, args) \ [ \ a:args[0], \ a:args[1], - \ len(a:args) > 2 ? a:args[2] : v:false + \ get(a:args, 2, v:null) \ ] \) endfunction diff --git a/doc/libmodal.txt b/doc/libmodal.txt index b909be3..0bf9849 100644 --- a/doc/libmodal.txt +++ b/doc/libmodal.txt @@ -657,6 +657,15 @@ When submitting an issue, please describe the following: ============================================================================== 8. Changelog *libmodal-changelog* +0.6.0 ~ + + Additions: ~ + * New module: |libmodal-layer|s. + * Allows for use of built-in modes with overwriting of keymaps. + * New class `libmodal.Layer`. + * New function `libmodal.layer.enter()`. + * New examples for new additions. + 0.5.0 ~ Additions: ~ diff --git a/examples/lua/layer.lua b/examples/lua/layer.lua new file mode 100644 index 0000000..a07bd77 --- /dev/null +++ b/examples/lua/layer.lua @@ -0,0 +1,39 @@ +local libmodal = require('libmodal') + +-- create a new layer. +local layer = libmodal.Layer.new('FOO', { + ['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. + } + } +}) + +-- enter the `layer`. +layer:enter() + +-- add a global function for exiting the mode. +function libmodal_layer_example_exit() + 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 libmodal_layer_example_exit()', + {['noremap'] = true, ['silent'] = true} +) + +--[[ unmap `gg` and `G`. Notice they both return to their defaults, + rather than just not doing anything anymore. ]] +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/lua/libmodal/init.lua b/lua/libmodal/init.lua index 0d78360..602a1d9 100644 --- a/lua/libmodal/init.lua +++ b/lua/libmodal/init.lua @@ -12,6 +12,12 @@ local libmodal = require('libmodal/src') */ --]] +libmodal.layer = {['enter'] = function(name, mappings) + local layer = libmodal.Layer.new(name, mappings) + layer:enter() + return layer.exit +end} + libmodal.mode = {['enter'] = function(name, instruction, ...) libmodal.Mode.new(name, instruction, ...):enter() end} diff --git a/lua/libmodal/src/Layer.lua b/lua/libmodal/src/Layer.lua new file mode 100644 index 0000000..7464f47 --- /dev/null +++ b/lua/libmodal/src/Layer.lua @@ -0,0 +1,254 @@ +--[[ + /* + * IMPORTS + */ +--]] + +local api = vim.api +local classes = require('libmodal/src/classes') + +--[[ + /* + * MODULE + */ +--]] + +local Layer = {} + +local _BUFFER_CURRENT = 0 +local _RESTORED = nil + +local function convertKeymap(keymapEntry) + local lhs = keymapEntry.lhs + keymapEntry.lhs = nil + + return {lhs, keymapEntry} +end + +local function deconvertKeymap(convertedKeymap) + local rhs = convertedKeymap.rhs + convertedKeymap.rhs = nil + + return {rhs, convertedKeymap} +end + +--[[ + /* + * META `Layer` + */ +--]] + +local _metaLayer = classes.new({}) + +--------------------------- +--[[ SUMMARY: + * Enter the `Layer`. + * Only activates for the current buffer. +]] +--------------------------- +function _metaLayer:enter() + -- 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] = {} + 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 + -- remove values so that it is in line with `nvim_set_keymap`. + local lhs, keymap = unpack(convertKeymap(bufMap)) + priorKeymap[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) + end + end + + print(vim.inspect(priorKeymap)) + 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 + end + end + 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) +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) + end + + -- add the new mapping to the keymap + self._keymap[mode][lhs] = vim.tbl_extend('force', + options, {['rhs'] = rhs} + ) + print(vim.inspect(self._priorKeymap)) +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] + + print('unmapping ' .. 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 + + print('reverted mapping') + else + -- just delete the buffer mapping. + local noErrors, err = pcall(api.nvim_buf_del_keymap, _BUFFER_CURRENT, mode, lhs) + + if not noErrors then print(err) end + print('deleted mapping') + 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 +end + +-------------------------- +--[[ SUMMARY: + * Exit the layer. +]] +--[[ + * This function is regenerated every time that `enter` is called, + because what it must do depends on the state of Vim upon calling `enter()`. +]] +-------------------------- +function _metaLayer:exit() + for mode, mappings in pairs(self._keymap) do + for lhs, _ in pairs(mappings) do + self:_unmapFromBuffer(mode, lhs) + end + end + self._priorKeymap = _RESTORED +end + +--[[ + /* + * CLASS `Layer` + */ +--]] + +----------------------------------------------------- +--[[ SUMMARY: + * Create a new `Layer` for `commands`, `mappings`, and `options`. +]] +--[[ PARAMS: + * `name` => the name of the layer. + * `mappings` => the list of user mappings to replace. +]] +--[[ RETURNS: + * A new `Layer`. +]] +----------------------------------------------------- +function Layer.new(name, mappings) + return setmetatable( + {['_keymap'] = mappings, ['name'] = name}, + _metaLayer + ) +end + +--[[ + /* + * PUBLICIZE `Layer` + */ +--]] + +return Layer diff --git a/lua/libmodal/src/Mode/init.lua b/lua/libmodal/src/Mode/init.lua index c693512..5197f9e 100644 --- a/lua/libmodal/src/Mode/init.lua +++ b/lua/libmodal/src/Mode/init.lua @@ -28,7 +28,7 @@ local _TIMEOUT = { ['CHAR'] = 'ΓΈ', ['LEN'] = api.nvim_get_option('timeoutlen'), ['SEND'] = function(__self) - api.nvim_feedkeys(__self.CHAR, '', false) + api.nvim_feedkeys(__self.CHAR, 'nt', false) end } _TIMEOUT.NR = string.byte(_TIMEOUT.CHAR) diff --git a/lua/libmodal/src/init.lua b/lua/libmodal/src/init.lua index 22b6196..ac3f0ab 100644 --- a/lua/libmodal/src/init.lua +++ b/lua/libmodal/src/init.lua @@ -10,6 +10,7 @@ libmodal.classes = require('libmodal/src/classes') libmodal.collections = require('libmodal/src/collections') libmodal.globals = require('libmodal/src/globals') libmodal.Indicator = require('libmodal/src/Indicator') +libmodal.Layer = require('libmodal/src/Layer') libmodal.Mode = require('libmodal/src/Mode') libmodal.Prompt = require('libmodal/src/Prompt') libmodal.utils = require('libmodal/src/utils')