diff --git a/README.md b/README.md index 5e44996..5b10922 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ See [vim-libmodal][libmodal] and the [docs](./doc) for more information. Alterna # Requirements -* Neovim 0.4+ - * Eventually 0.5 will be required. +* Neovim 0.4+. + * For compatability with `vim-libmodal`, Neovim 0.5+. * `vim-libmodal` is _not_ installed. [libmodal]: https://github.com/Iron-E/vim-libmodal diff --git a/doc/libmodal.txt b/doc/libmodal.txt index 116fbc6..6c50ae0 100644 --- a/doc/libmodal.txt +++ b/doc/libmodal.txt @@ -144,11 +144,17 @@ FUNCTIONS *libmodal-usage-functions* that |getchar()| completes. The user input is received through `g:{name}ModeInput` (see above). - *Error you cannot pass a function to Lua from Vimscript! -        - If you try to use a |funcref()| for {instruction}, it -        will fail, GUARANTEED. This is because of |E5004|. -        - If you want to use a |funcref()| for {instruction}, you -        must use a |Lua| `function` instead. + *Error you cannot pass a funcref to Lua from Vimscript! +        - If you want to use a |funcref()| for {instruction}, it +        must be the name of the function as a `string`. +        - This only works on Neovim 0.5+. Example: > +        " VIMSCRIPT +        function! s:foo() abort +        echo 'It works' +        call getchar() +        endfunction +        lua require('libmodal').mode.enter('FOO', 's:foo') +< Note: Some QoL features are available by default when specifying a `dict`/`table` value for {instruction} that @@ -249,11 +255,17 @@ FUNCTIONS *libmodal-usage-functions* every time that |input()| completes. The user input is received through `g:{name}ModeInput` (see above). - *Error you cannot pass a function to Lua from Vimscript! -        - If you try to use a |funcref()| for {instruction}, it -        will fail, GUARANTEED. This is because of |E5004|. -        - If you want to use a |funcref()| for {instruction}, you -        must use a |Lua| `function` instead. + *Error you cannot pass a funcref to Lua from Vimscript! +        - If you want to use a |funcref()| for {instruction}, it +        must be the name of the function as a `string`. +        - This only works on Neovim 0.5+. Example: > +        " VIMSCRIPT +        function! s:foo() abort +        echo 'It works' +        call getchar() +        endfunction +        lua require('libmodal').prompt.enter('FOO', 's:foo') +< Note: If you want to create commands with arguments, you will need to use a `function`. @@ -715,6 +727,25 @@ When submitting an issue, please describe the following: ============================================================================== 8. Changelog *libmodal-changelog* +0.7.0 ~ + + Additions: ~ + * Ability to pass `function`s into |libmodal-mode| from Vimscript. + * Ability to pass `function`s into |libmodal-prompt| from Vimscript. + * Add examples for doing almost everything that this plugin can do, from + Vimscript (although I still think Lua makes it easier). + +0.6.3 ~ + + Fixes: ~ + * Fix being unable to paste into Vim's command line after importing + the `libmodal.util.api` table. + +0.6.2 ~ + + Fixes: ~ + * Remove unused variables + 0.6.1 ~ Fixes: ~ diff --git a/examples/key-combos-manually.vim b/examples/key-combos-manually.vim new file mode 100644 index 0000000..9d71d1c --- /dev/null +++ b/examples/key-combos-manually.vim @@ -0,0 +1,36 @@ +" Keep track of the user's input history manually. +let s:inputHistory = [] + +" Clear the input history if it grows too long for our usage. +function! s:clear(indexToCheck) abort + if len(s:inputHistory) > a:indexToCheck + for i in range(len(s:inputHistory)) + let s:inputHistory[i] = v:null + endfor + endif +endfunction + +" This is the function that will be called whenever the user presses a button. +function! s:fooMode() abort + " Append to the input history, the latest button press. + let s:inputHistory = add(s:inputHistory, nr2char(g:fooModeInput)) " The input is a character number. + + " Custom logic to test for each character index to see if it matches the 'zfo' mapping. + let l:index = 0 + if s:inputHistory[0] == 'z' + if get(s:inputHistory, 1, v:null) == 'f' + if get(s:inputHistory, 2, v:null) == 'o' + echom 'It works!' + else + let l:index = 2 + endif + else + let l:index = 1 + endif + endif + + call s:clear(l:index) +endfunction + +" Enter the mode to begin the demo. +lua require('libmodal').mode.enter('FOO', 's:fooMode') diff --git a/examples/key-combos-submode.vim b/examples/key-combos-submode.vim index f596cba..7eb0474 100644 --- a/examples/key-combos-submode.vim +++ b/examples/key-combos-submode.vim @@ -1,14 +1,18 @@ +" Recurse counter. let s:barModeRecurse = 0 +" Register 'z' as the map for recursing further (by calling the BarMode function again). let s:barModeCombos = { \ '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) let s:barModeRecurse -= 1 endfunction +" Call BarMode() initially to begin the demo. command! BarModeEnter call s:BarMode() execute 'BarModeEnter' diff --git a/examples/key-combos-supress-exit.vim b/examples/key-combos-supress-exit.vim index b5528e0..0fe429e 100644 --- a/examples/key-combos-supress-exit.vim +++ b/examples/key-combos-supress-exit.vim @@ -1,7 +1,11 @@ +" Register key commands and what they do. let s:barModeCombos = { \ '': 'echom "You cant exit using escape."', \ 'q': 'let g:barModeExit = 1' \} +" 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) diff --git a/examples/key-combos.vim b/examples/key-combos.vim index 3009d43..8fff30d 100644 --- a/examples/key-combos.vim +++ b/examples/key-combos.vim @@ -1,7 +1,9 @@ +" 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/layer-simple.vim b/examples/layer-simple.vim new file mode 100644 index 0000000..33a9c0f --- /dev/null +++ b/examples/layer-simple.vim @@ -0,0 +1,19 @@ +" Create a new layer. +let s:layer = { +\ 'n': { +\ 'gg': { +\ 'rhs': 'G', +\ 'noremap': v:true, +\ }, +\ 'G': { +\ 'rhs': 'gg', +\ 'noremap': v:true +\ } +\ } +\} + +" 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) diff --git a/examples/lua/key-combos-manually.lua b/examples/lua/key-combos-manually.lua index a15858b..6e65cd0 100644 --- a/examples/lua/key-combos-manually.lua +++ b/examples/lua/key-combos-manually.lua @@ -1,8 +1,11 @@ +-- Imports local api = vim.api local libmodal = require('libmodal') +-- Keep track of the user's input history manually. local _inputHistory = {} +-- Clear the input history if it grows too long for our usage. function _inputHistory:clear(indexToCheck) if #self >= indexToCheck then for i, _ in ipairs(self) do @@ -11,11 +14,15 @@ function _inputHistory:clear(indexToCheck) end end +-- This is the function that will be called whenever the user presses a button. local function fooMode() + -- Append to the input history, the latest button press. _inputHistory[#_inputHistory + 1] = string.char( + -- The input is a character number. api.nvim_get_var('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 @@ -30,4 +37,5 @@ local function fooMode() _inputHistory:clear(index) end +-- Enter the mode to begin the demo. libmodal.mode.enter('FOO', fooMode) diff --git a/examples/lua/key-combos-submode.lua b/examples/lua/key-combos-submode.lua index b661d5f..5bf57b5 100644 --- a/examples/lua/key-combos-submode.lua +++ b/examples/lua/key-combos-submode.lua @@ -1,14 +1,19 @@ +-- Imports local libmodal = require('libmodal') +-- Recurse counter. local fooModeRecurse = 0 +-- Register 'z' as the map for recursing further (by calling the FooMode function again). local fooModeCombos = { ['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 end +-- Call FooMode() initially to begin the demo. FooMode() diff --git a/examples/lua/key-combos-supress-exit.lua b/examples/lua/key-combos-supress-exit.lua index 51f866f..9f7a018 100644 --- a/examples/lua/key-combos-supress-exit.lua +++ b/examples/lua/key-combos-supress-exit.lua @@ -1,8 +1,14 @@ +-- Imports local libmodal = require('libmodal') + +-- Register key commands and what they do. local fooModeCombos = { [''] = 'echom "You cant exit using escape."', ['q'] = 'let g:fooModeExit = 1' } +-- Tell the mode not to exit automatically. vim.api.nvim_set_var('fooModeExit', 0) + +-- Enter the mode using the key combos created before. libmodal.mode.enter('FOO', fooModeCombos, true) diff --git a/examples/lua/key-combos.lua b/examples/lua/key-combos.lua index df72a0b..1dc7686 100644 --- a/examples/lua/key-combos.lua +++ b/examples/lua/key-combos.lua @@ -1,8 +1,12 @@ +-- Imports local libmodal = require('libmodal') + +-- Register key combos for splitting windows and then closing windows local fooModeCombos = { ['zf'] = 'split', ['zfo'] = 'vsplit', ['zfc'] = 'q' } +-- Enter the mode using the key combos. libmodal.mode.enter('FOO', fooModeCombos) diff --git a/examples/lua/layer-simple.lua b/examples/lua/layer-simple.lua index b62ada2..c0c72fc 100644 --- a/examples/lua/layer-simple.lua +++ b/examples/lua/layer-simple.lua @@ -1,3 +1,4 @@ +-- Imports local libmodal = require('libmodal') -- create a new layer. @@ -14,7 +15,7 @@ local exitFunc = libmodal.layer.enter({ } }) --- the layer will deactivate in 5 seconds. +-- 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 )) diff --git a/examples/lua/layer.lua b/examples/lua/layer.lua index 5d35244..034a956 100644 --- a/examples/lua/layer.lua +++ b/examples/lua/layer.lua @@ -1,3 +1,4 @@ +-- Imports local libmodal = require('libmodal') -- create a new layer. diff --git a/examples/lua/prompt-callback.lua b/examples/lua/prompt-callback.lua index 8ef93f3..9bca1b0 100644 --- a/examples/lua/prompt-callback.lua +++ b/examples/lua/prompt-callback.lua @@ -1,7 +1,11 @@ +-- Imports local libmodal = require('libmodal') local api = vim.api + +-- The list of commands. Providing this will allow for autocomplete. local commandList = {'new', 'close', 'last'} +-- The function which will be called whenever the user enters a command. function FooMode() local userInput = vim.api.nvim_get_var('fooModeInput') if userInput == 'new' then @@ -13,4 +17,5 @@ function FooMode() end end +-- Enter the prompt. libmodal.prompt.enter('FOO', FooMode, commandList) diff --git a/examples/lua/prompt-commands.lua b/examples/lua/prompt-commands.lua index dc63913..62e9355 100644 --- a/examples/lua/prompt-commands.lua +++ b/examples/lua/prompt-commands.lua @@ -1,8 +1,12 @@ +-- Import local libmodal = require('libmodal') + +-- Define commands through a dictionary. local commands = { ['new'] = 'tabnew', ['close'] = 'tabclose', ['last'] = 'tablast' } +-- Begin the prompt. libmodal.prompt.enter('FOO', commands) diff --git a/examples/lua/submodes.lua b/examples/lua/submodes.lua index 0e55e3b..4347e66 100644 --- a/examples/lua/submodes.lua +++ b/examples/lua/submodes.lua @@ -1,11 +1,18 @@ +-- Imports local libmodal = require('libmodal') + +-- Recurse counter local fooModeRecurse = 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.api.nvim_get_var( + -- The input is a character number. 'foo' .. tostring(fooModeRecurse) .. 'ModeInput' )) + -- If the user pressed 'z', then increase the counter and recurse. if userInput == 'z' then fooModeRecurse = fooModeRecurse + 1 Enter() @@ -13,8 +20,10 @@ function FooMode() end end +-- Function to wrap around entering the mode so it can be recursively called. function Enter() libmodal.mode.enter('FOO' .. fooModeRecurse, FooMode) end +-- Initially call the function to begin the demo. Enter() diff --git a/examples/lua/supress-exit.lua b/examples/lua/supress-exit.lua index ed75360..0df74fc 100644 --- a/examples/lua/supress-exit.lua +++ b/examples/lua/supress-exit.lua @@ -1,17 +1,25 @@ +-- Imports local api = vim.api local libmodal = require('libmodal') +-- Function which is called whenever the user presses a button local function fooMode() + -- Append to the input history, the latest button press. local userInput = string.char( + -- The input is a character number. api.nvim_get_var('fooModeInput') ) if userInput == '' then api.nvim_command("echom 'You cant leave using .'") elseif userInput == 'q' then + -- If the user presses 'q', libmodal will exit the mode. api.nvim_set_var('fooModeExit', true) end end +-- Tell libmodal not to exit the mode immediately. api.nvim_set_var('fooModeExit', 0) + +-- Enter the mode. libmodal.mode.enter('FOO', fooMode, true) diff --git a/examples/prompt-callback.vim b/examples/prompt-callback.vim new file mode 100644 index 0000000..65106a9 --- /dev/null +++ b/examples/prompt-callback.vim @@ -0,0 +1,17 @@ +" This is the list of commands— used for auto completion. +let s:commandList = ['new', 'close', 'last'] + +" This function will be called whenever a command is entered. +function! s:fooMode() abort + let l:userInput = g:fooModeInput + if userInput == 'new' + tabnew + elseif userInput == 'close' + tabclose + elseif userInput == 'last' + tablast + endif +endfunction + +" You have to convert s:commandList from a Vimscript list to a lua table using luaeval(). +call luaeval("require('libmodal').prompt.enter('FOO', 's:fooMode', _A)", s:commandList) diff --git a/examples/submodes.vim b/examples/submodes.vim new file mode 100644 index 0000000..7d6e4ed --- /dev/null +++ b/examples/submodes.vim @@ -0,0 +1,21 @@ +" This is a counter. +let s:fooModeRecurse = 1 + +" This is a function to increase the counter every time that 'z' is pressed. +function! s:fooMode() abort + let l:userInput = nr2char(g:foo{s:fooModeRecurse}ModeInput) + + if l:userInput == 'z' + let s:fooModeRecurse += 1 + call s:enter() + let s:fooModeRecurse -= 1 + endif +endfunction + +" This function wraps around calling libmodal so that the other function can recursively call it. +function! s:enter() abort + call luaeval("require('libmodal').mode.enter('FOO'.._A, 's:fooMode')", s:fooModeRecurse) +endfunction + +" Begin the recursion. +call s:enter() diff --git a/examples/supress-exit.vim b/examples/supress-exit.vim new file mode 100644 index 0000000..6f95d72 --- /dev/null +++ b/examples/supress-exit.vim @@ -0,0 +1,15 @@ +" Function which is called every time the user presses a button. +function! s:fooMode() abort + let l:userInput = nr2char(g:fooModeInput) + + if l:userInput == '' + echom 'You cant leave using .' + elseif l:userInput == 'q' + let g:fooModeExit = v:true + endif +endfunction + +" Tell the mode not to exit automatically. +let g:fooModeExit = v:false +" Begin the mode. +lua require('libmodal').mode.enter('FOO', 's:fooMode', true) diff --git a/lua/libmodal/src/Mode.lua b/lua/libmodal/src/Mode.lua index 914da4c..7b46624 100644 --- a/lua/libmodal/src/Mode.lua +++ b/lua/libmodal/src/Mode.lua @@ -10,6 +10,7 @@ local ParseTable = require('libmodal/src/collections/ParseTable') local utils = require('libmodal/src/utils') local Vars = require('libmodal/src/Vars') +local vim = vim local api = vim.api --[[ @@ -199,14 +200,21 @@ function _metaMode:_inputLoop() -- Set the global input variable to the new input. self.input:nvimSet(userInput) - -- Make sure that the user doesn't want to exit. - if not self.exit.supress - and userInput == globals.ESC_NR then return false - -- If the second argument was a dict, parse it. - elseif type(self._instruction) == globals.TYPE_TBL then - self:_checkInputForMapping() - else -- the second argument was a function; execute it. - self._instruction() + if not self.exit.supress and userInput == 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 and vim.fn then -- It is the name of a VimL function. This only works in Neovim 0.5+. + vim.fn[self._instruction]() + else -- the second argument was a function; execute it. + self._instruction() + end end return true diff --git a/lua/libmodal/src/Prompt.lua b/lua/libmodal/src/Prompt.lua index fa64d47..96cfd15 100644 --- a/lua/libmodal/src/Prompt.lua +++ b/lua/libmodal/src/Prompt.lua @@ -7,6 +7,7 @@ local globals = require('libmodal/src/globals') local utils = require('libmodal/src/utils') +local vim = vim local api = vim.api --[[ @@ -64,16 +65,18 @@ function _metaPrompt:_inputLoop() local instruction = self._instruction -- determine what to do with the input - if string.len(userInput) > 0 then -- the user actually entered something + if string.len(userInput) > 0 then -- The user actually entered something. self.input:nvimSet(userInput) - 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. + 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. api.nvim_command(instruction[userInput]) - elseif userInput == _HELP then -- the user did not define a 'help' command, so use the default. + elseif userInput == _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 and vim.fn then -- The instruction is a function. Works on Neovim 0.5+. + vim.fn[instruction]() else -- attempt to call the instruction. instruction() end