diff --git a/examples/lua/key-combos-manually.lua b/examples/lua/key-combos-manually.lua new file mode 100644 index 0000000..5b615ee --- /dev/null +++ b/examples/lua/key-combos-manually.lua @@ -0,0 +1,30 @@ +local api = vim.api +local libmodal = require('libmodal') + +local barModeInputHistory = {} + +local function clearHistory(indexToCheck) + if #barModeInputHistory >= indexToCheck then + barModeInputHistory = {} + end +end + +barMode = function() + table.insert( + barModeInputHistory, + api.nvim_eval('nr2char(g:barModeInput)') + ) + + local index = 1 + if barModeInputHistory[1] == 'z' then + if barModeInputHistory[2] == 'f' then + if barModeInputHistory[3] == 'o' then + api.nvim_command("echom 'It works!'") + else index = 3 end + else index = 2 end + end + + clearHistory(index) +end + +libmodal.mode.enter('BAR', barMode) diff --git a/examples/lua/key-combos-submode.lua b/examples/lua/key-combos-submode.lua new file mode 100644 index 0000000..1caa48c --- /dev/null +++ b/examples/lua/key-combos-submode.lua @@ -0,0 +1,16 @@ +local libmodal = require('libmodal') + +local barModeRecurse = 0 + +local barModeCombos = { + ['z'] = 'BarModeEnter', +} + +function barMode() + barModeRecurse = barModeRecurse + 1 + libmodal.mode.enter('BAR' .. barModeRecurse, barModeCombos) + barModeRecurse = barModeRecurse - 1 +end + +vim.api.nvim_command('command! BarModeEnter lua barMode()') +barMode() diff --git a/examples/lua/key-combos-supress-exit.lua b/examples/lua/key-combos-supress-exit.lua new file mode 100644 index 0000000..3dac7a7 --- /dev/null +++ b/examples/lua/key-combos-supress-exit.lua @@ -0,0 +1,6 @@ +let s:barModeCombos = { +\ '': 'echom "You cant exit using escape."', +\ 'q': 'let g:barModeExit = 1' +\} + +call libmodal#Enter('BAR', s:barModeCombos, 1) diff --git a/examples/lua/key-combos.lua b/examples/lua/key-combos.lua new file mode 100644 index 0000000..b89ba8d --- /dev/null +++ b/examples/lua/key-combos.lua @@ -0,0 +1,7 @@ +let s:barModeCombos = { +\ 'zf': 'split', +\ 'zfo': 'vsplit', +\ 'zfc': 'tabnew' +\} + +call libmodal#Enter('BAR', s:barModeCombos) diff --git a/examples/lua/prompt-callback.lua b/examples/lua/prompt-callback.lua new file mode 100644 index 0000000..9b9e154 --- /dev/null +++ b/examples/lua/prompt-callback.lua @@ -0,0 +1,13 @@ +let s:commandList = ['new', 'close', 'last'] + +function! s:BarMode() abort + if g:tabModeInput ==# 'new' + execute 'tabnew' + elseif g:tabModeInput ==# 'close' + execute 'tabclose' + elseif g:tabModeInput ==# 'last' + execute 'tablast' + endif +endfunction + +call libmodal#Prompt('TAB', funcref('s:BarMode'), s:commandList) diff --git a/examples/lua/prompt-commands.lua b/examples/lua/prompt-commands.lua new file mode 100644 index 0000000..a8d6c0b --- /dev/null +++ b/examples/lua/prompt-commands.lua @@ -0,0 +1,7 @@ +let s:commands = { +\ 'new': 'tabnew', +\ 'close': 'tabclose', +\ 'last': 'tablast' +\} + +call libmodal#Prompt('TAB', s:commands) diff --git a/examples/lua/submodes.lua b/examples/lua/submodes.lua new file mode 100644 index 0000000..c7d1dca --- /dev/null +++ b/examples/lua/submodes.lua @@ -0,0 +1,12 @@ +let s:barModeRecurse = 0 + +function! s:BarMode() + if g:bar{s:barModeRecurse}ModeInput ==# 'z' + let s:barModeRecurse += 1 + execute 'BarModeEnter' + let s:barModeRecurse -= 1 + endif +endfunction + +command! BarModeEnter call libmodal#Enter('BAR' . s:barModeRecurse, funcref('s:BarMode')) +execute 'BarModeEnter' diff --git a/examples/lua/supress-exit.lua b/examples/lua/supress-exit.lua new file mode 100644 index 0000000..562ed28 --- /dev/null +++ b/examples/lua/supress-exit.lua @@ -0,0 +1,11 @@ +let s:barModeInputHistory = '' + +function! s:BarMode() + if g:barModeInput ==# '' + echom 'You cant leave using .' + elseif g:barModeInput ==# 'q' + let g:barModeExit = 1 + endif +endfunction + +call libmodal#Enter('BAR', funcref('s:BarMode'), 1) diff --git a/lua/libmodal/src/mode/ParseTable/init.lua b/lua/libmodal/src/mode/ParseTable/init.lua index 0689459..a6b7e04 100644 --- a/lua/libmodal/src/mode/ParseTable/init.lua +++ b/lua/libmodal/src/mode/ParseTable/init.lua @@ -14,7 +14,6 @@ local globals = require('libmodal/src/base/globals') --]] local ParseTable = {} -local strings = {} -- not to be returned. Used for split() function. --[[ /* @@ -22,16 +21,16 @@ local strings = {} -- not to be returned. Used for split() function. */ --]] ------------------------------------- +---------------------------------------- --[[ SUMMARY: * Split some `str` using a regex `pattern`. ]] ---[[ +--[[ PARAMS: * `str` => the string to split. * `pattern` => the regex pattern to split `str` with. ]] ------------------------------------- -function strings.split(str, pattern) +---------------------------------------- +local function stringSplit(str, pattern) local split = {} for char in string.gmatch(str, pattern) do table.insert(split, char) @@ -39,6 +38,30 @@ function strings.split(str, pattern) return split end +-------------------------------- +--[[ SUMMARY: + * Reverse the elements of some table. +]] +--[[ PARAMS: + * `tbl` => the table to reverse. +]] +-------------------------------- +local function tableReverse(tbl) + local i = 1 + local halfway = math.floor(#tbl / 2) + while i <= halfway do + -- get the other end of the dict + local j = #tbl + 1 - i + -- copy the value to a placeholder + local placeholder = tbl[j] + -- swap the values + tbl[j] = tbl[i] + tbl[i] = placeholder + -- increment + i = i + 1 + end +end + --[[ /* * CLASS `ParseTable` @@ -72,13 +95,13 @@ function ParseTable.new(userTable) * `false` => when `key` is not ANYWHERE. ]] ---------------------------- - function parseTable:get(key) + function parseTable:get(keyDict) local function parseGet(dict, splitKey) --[[ Get the next character in the combo string. ]] local k = '' if #splitKey > 0 then -- There is more input to parse - k = api.nvim_eval("char2nr('" .. table.remove(splitKey) .. "')") + k = table.remove(splitKey) -- the dict should already be `char2nr()`'d else -- The user input has run out, but there is more in the dictionary. return dict end @@ -103,10 +126,12 @@ function ParseTable.new(userTable) return false end + --[[ Reverse the dict. ]] + tableReverse(keyDict) + + --[[ Get return value. ]] -- run the inner recursive function in order to return the desired result - return parseGet(self, strings.split( - string.reverse(key), '.' - )) + return parseGet(self, keyDict) end ---------------------------------------- @@ -146,7 +171,7 @@ function ParseTable.new(userTable) end -- ‡ -- Run the recursive function. - update(self, strings.split( + update(self, stringSplit( string.reverse(key), '.' )) end diff --git a/lua/libmodal/src/mode/init.lua b/lua/libmodal/src/mode/init.lua index eb52dd9..49c3690 100644 --- a/lua/libmodal/src/mode/init.lua +++ b/lua/libmodal/src/mode/init.lua @@ -18,7 +18,6 @@ local vars = utils.vars local mode = {} mode.ParseTable = require('libmodal/src/mode/ParseTable') -mode.TIMEOUT = 'TIMEOUT' --[[ /* @@ -26,7 +25,16 @@ mode.TIMEOUT = 'TIMEOUT' */ --]] -function mode._clearLocalInput(modeName) +local TIMEOUT_CHAR = 'ø' +local TIMEOUT_NR = api.nvim_eval("char2nr('" .. TIMEOUT_CHAR .. "')") +local TIMEOUT_LEN = api.nvim_get_option('TIMEOUT_LEN') + +---------------------------------------- +--[[ SUMMARY: + * Reset libmodal's internal counter of user input to default. +]] +---------------------------------------- +local function clearLocalInput(modeName) vars.input.instances[modeName] = {} end @@ -38,11 +46,11 @@ end * `modeName` => the name of the mode that is currently active. ]] ------------------------------------ -function mode._comboSelect(modeName) +local function comboSelect(modeName) -- Stop any running timers - if vars.timers.instances[modeName] then - vars.timers.instances[modeName]:stop() - vars.timers.instances[modeName] = nil + if vars.timer.instances[modeName] then + vars.timer.instances[modeName]:stop() + vars.timer.instances[modeName] = nil end -- Append the latest input to the locally stored input history. @@ -53,9 +61,10 @@ function mode._comboSelect(modeName) -- Get the combo dict. local comboTable = vars.combos.instances[modeName] + -- Get the command based on the users input. local cmd = comboTable:get( - table.concat(vars.input.instance[modeName]) + vars.input.instances[modeName] ) -- Get the type of the command. @@ -67,21 +76,19 @@ function mode._comboSelect(modeName) -- The command was a table, meaning that it MIGHT match. elseif commandType == globals.TYPE_TBL then -- Create a new timer - vars.timers.instances[modeName] = vim.loop.new_timer() - -- Get the `&timeoutlen` variable. - local timeoutlen = api.nvim_get_option('timeoutlen') + vars.timer.instances[modeName] = vim.loop.new_timer() -- start the timer - vars.timers.instances[modeName]:start(timeoutlen, 0, + vars.timer.instances[modeName]:start(TIMEOUT_LEN, 0, vim.schedule_wrap(function() -- Send input to interrupt a blocking `getchar` - vim.api.nvim_feedkeys(mode.TIMEOUT, '', false) + api.nvim_feedkeys(TIMEOUT_CHAR, '', false) -- if there is a command, execute it. if cmd[mode.ParseTable.CR] then api.nvim_command(cmd[mode.ParseTable.CR]) end -- clear input - mode._clearLocalInput(modeName) + clearLocalInput(modeName) end) ) -- The command was an actual vim command. @@ -91,10 +98,38 @@ function mode._comboSelect(modeName) end if clearInput then - mode._clearLocalInput(modeName) + clearLocalInput(modeName) end end +------------------------------------------------- +--[[ SUMMARY: + * Set the initial values used for parsing user input as combos. +]] +--[[ PARAMS: + * `modeName` => the name of the mode being initialized. + * `comboTable` => the table of combos being initialized. +]] +------------------------------------------------- +local function initCombos(modeName, comboTable) + -- Placeholder for timeout value. + local doTimeout = nil + + -- Read the correct timeout variable. + if api.nvim_exists('g', vars.timeout.name(modeName)) then + doTimeout = vars.nvim_get(vars.timeout, modeName) + else + doTimeout = vars.libmodalTimeout + end + vars.timeout.instances[modeName] = doTimeout + + -- Build the parse tree. + vars.combos.instances[modeName] = mode.ParseTable.new(comboTable) + + -- Initialize the input history variable. + clearLocalInput(modeName) +end + ------------------------ --[[ SUMMARY: * Enter a mode. @@ -111,7 +146,7 @@ function mode.enter(...) --[[ SETUP. ]] -- Create the indicator for the mode. - local indicator = utils.Indicator:new(args[1]) + local indicator = utils.Indicator.new(args[1]) -- Grab the state of the window. local winState = utils.WindowState.new() @@ -120,14 +155,14 @@ function mode.enter(...) local modeName = string.lower(args[1]) -- Determine whether or not this function should handle exiting automatically. - local handleExitEvents = false - if #args > 2 and args[3] then - handleExitEvents = true + local handleExitEvents = true + if #args > 2 then + handleExitEvents = args[3] == true end -- Determine whether a callback was specified, or a combo table. if type(args[2]) == globals.TYPE_TBL then - mode._initTimeouts(modeName, args[2]) + initCombos(modeName, args[2]) end --[[ MODE LOOP. ]] @@ -137,7 +172,7 @@ function mode.enter(...) -- Try (using pcall) to use the mode. local noErrors = pcall(function() -- If the mode is not handling exit events automatically and the global exit var is true. - if not handleExitEvents and var.nvim_get(vars.exit, modeName) then + if not handleExitEvents and vars.nvim_get(vars.exit, modeName) then continueMode = false return end @@ -147,9 +182,11 @@ function mode.enter(...) -- Capture input. local uinput = api.nvim_input() + -- Return if there was a timeout event. - if uinput == mode.TIMEOUT then return end - -- Otherwise set the input variable to the new input. + if uinput == TIMEOUT_NR then return end + + -- Set the global input variable to the new input. vars.nvim_set(vars.input, modeName, uinput) -- Make sure that the user doesn't want to exit. @@ -158,17 +195,14 @@ function mode.enter(...) return -- If the second argument was a dict, parse it. elseif type(args[2]) == globals.TYPE_TBL then - mode._comboSelect(modeName) + comboSelect(modeName) -- If the second argument was a function, execute it. - else - args[2]() - end - + else args[2]() end end) -- If there were errors, handle them. if not noErrors then - mode._showError() + utils.showError() continueMode = false end end @@ -180,40 +214,10 @@ function mode.enter(...) winState:restore() end -function mode._initTimeouts(modeName, comboTable) - -- Placeholder for timeout value. - local doTimeout = nil - - -- Read the correct timeout variable. - if api.nvim_exists('g', vars.timeout.name(modeName)) then - doTimeout = vars.nvim_get(vars.timeout, modeName) - else - doTimeout = vars.libmodalTimeout - end - vars.timeout.instances[modeName] = doTimeout - - -- Build the parse tree. - vars.combos.instances[modeName] = mode.ParseTable.new(comboTable) - - -- Initialize the input history variable. - mode._clearLocalInput(modeName) -end - -function mode._showError() - api.nvim_bell() - api.nvim_show_err( 'vim-libmodal error', - api.nvim_get_vvar('throwpoint') - .. '\n' .. - api.nvim_get_vvar('exception') - ) -end - --[[ /* * PUBLICIZE MODULE */ --]] -mode.enter('test', {}) return mode - diff --git a/lua/libmodal/src/utils/Indicator/init.lua b/lua/libmodal/src/utils/Indicator/init.lua index 80159fe..61816b0 100644 --- a/lua/libmodal/src/utils/Indicator/init.lua +++ b/lua/libmodal/src/utils/Indicator/init.lua @@ -28,10 +28,10 @@ local Indicator = {} * `modeName` => the name of the mode that this `Indicator` is for. ]] -------------------------------- -function Indicator:new(modeName) +function Indicator.new(modeName) return { Entry.new('LibmodalStar', '*'), - Entry.new( 'None', '*' ), + Entry.new( 'None', ' ' ), Entry.new( 'LibmodalPrompt', tostring(modeName) ), Entry.new('None', ' > '), } diff --git a/lua/libmodal/src/utils/api.lua b/lua/libmodal/src/utils/api.lua index c892490..eb6e781 100644 --- a/lua/libmodal/src/utils/api.lua +++ b/lua/libmodal/src/utils/api.lua @@ -73,10 +73,10 @@ function api.nvim_lecho(hlTables) for _, hlTable in ipairs(hlTables) do api.nvim_command( -- `:echohl` the hlgroup and then `:echon` the string. - "echon execute(['echohl \"" .. hlTable.hl .. "\"', 'echon \"" .. hlTable.str .. "\"'])" + "echohl " .. hlTable.hl .. " | echon '" .. hlTable.str .. "'" ) end - api.nvim_command('echohl None') + -- api.nvim_command('echohl None') end -------------------------- diff --git a/lua/libmodal/src/utils/init.lua b/lua/libmodal/src/utils/init.lua index e286dff..606d6cf 100644 --- a/lua/libmodal/src/utils/init.lua +++ b/lua/libmodal/src/utils/init.lua @@ -11,6 +11,21 @@ utils.Indicator = require('libmodal/src/utils/Indicator') utils.WindowState = require('libmodal/src/utils/WindowState') utils.vars = require('libmodal/src/utils/vars') +--[[ + /* + * FUNCTIONS + */ +--]] + +function utils.showError() + utils.api.nvim_bell() + utils.api.nvim_show_err( 'vim-libmodal error', + utils.api.nvim_get_vvar('throwpoint') + .. '\n' .. + utils.api.nvim_get_vvar('exception') + ) +end + --[[ /* * PUBLICIZE MODULE