diff --git a/doc/table_structures/FUNCTIONS.md b/doc/table_structures/FUNCTIONS.md index ff9e1706..9f529d08 100644 --- a/doc/table_structures/FUNCTIONS.md +++ b/doc/table_structures/FUNCTIONS.md @@ -32,6 +32,27 @@ local functions = { } ``` +## Specifying mode + +By default, the funciton is shown and run in `*` (all) modes. +You can use `mode` property to narrow function's scope, so it always run in specified mode: + +```lua +local functions = { + { + mode = 'x', description = 'My function', + function() print('only runs in charwise-visual and selection mode!') end + }, + { + description = 'Buffer: git: stage selected hunk' + mode = { 'x', 'V', 'v' }, + function() + gitsigns.stage_hunk({ vim.fn.line('.'), vim.fn.line('v') }) + end + } +} +``` + ## Options You can also pass options via the `opts` property: diff --git a/lua/legendary/data/function.lua b/lua/legendary/data/function.lua index 5c5c43fc..eb965070 100644 --- a/lua/legendary/data/function.lua +++ b/lua/legendary/data/function.lua @@ -2,8 +2,24 @@ local class = require('legendary.vendor.middleclass') local util = require('legendary.util') local Filters = require('legendary.data.filters') +---Check if modes is an array of strings or itself a string +---@param modes table +---@return boolean +local is_list_of_strings_or_string = function(modes) + if modes == nil or type(modes) == 'string' then + return true + end + for _, mode in ipairs(modes) do + if type(mode) ~= 'string' then + return false + end + end + return true +end + ---@class Function ---@field implementation function +---@field mode_mappings string[] ---@field description string ---@field opts table ---@field filters (function[])|nil @@ -15,6 +31,11 @@ Function:include(Filters) ---@diagnostic disable-line function Function:parse(tbl) -- luacheck: no unused vim.validate({ ['1'] = { tbl[1], { 'function' } }, + mode = { + tbl.mode, + is_list_of_strings_or_string, + 'item.mode should contain only strings of modes: n, i, v etc.', + }, description = { util.get_desc(tbl), { 'string' } }, opts = { tbl.opts, { 'table' }, true }, }) @@ -26,6 +47,29 @@ function Function:parse(tbl) -- luacheck: no unused instance.opts = tbl.opts or {} instance:parse_filters(tbl.filters) + -- By default, function can be run in all modes, so mode_mapping is empty + instance.mode_mappings = vim.tbl_islist(tbl.mode) and tbl.mode or {} + + -- If tbl = { fn, mode = "n" } + if type(tbl.mode) == 'string' then + table.insert(instance.mode_mappings, tbl.mode) + end + -- only tbl = { fn, mode = { 'n' } } + -- Reassing implementation with a current-mode hanlder + if not vim.tbl_isempty(instance.mode_mappings) then + local impl = instance.implementation + instance.implementation = function() + local modeCurrent = vim.fn.mode() + for _, modeInstance in ipairs(instance.mode_mappings) do + if modeCurrent == modeInstance then + impl() + end + end + end + end + -- mode_mapping is going to remain static during legendary runtime + -- so we can cache current mapping state + instance._mode_switched = vim.tbl_islist(instance.mode_mappings) and not vim.tbl_isempty(instance.mode_mappings) return instance end @@ -43,4 +87,19 @@ function Function:frecency_id() return string.format(' %s', self.description) end +--- Intended to be used by UI filters +function Function:modeSwitched() + return self._mode_switched +end + +function Function:modes() + if self:modeSwitched() then + return self.mode_mappings + else + -- Just use all modes for UI filtering + -- it's half-assed because UI filtering is ugly ¯\_(ツ)_/¯ + return { 'n', 'V', 'v', 'x', 's', 'o', '' } + end +end + return Function diff --git a/lua/legendary/filters.lua b/lua/legendary/filters.lua index 3c369160..b60570cf 100644 --- a/lua/legendary/filters.lua +++ b/lua/legendary/filters.lua @@ -10,8 +10,9 @@ local M = {} ---@return LegendaryItemFilter function M.mode(mode) return function(item) - -- include everything that isn't a keymap since they aren't tied to a mode - if not Toolbox.is_keymap(item) then + -- allow only keymaps and functions + -- TODO: include commands + if not (Toolbox.is_keymap(item) or Toolbox.is_function(item)) then return true end @@ -26,9 +27,9 @@ function M.mode(mode) end -- filter where any filter_modes match any item:modes() - return #vim.tbl_filter(function(keymap_mode) + return #vim.tbl_filter(function(item_mode) return #vim.tbl_filter(function(filter_mode) - return filter_mode == keymap_mode + return filter_mode == item_mode end, filter_modes) > 0 end, item:modes()) > 0 end diff --git a/lua/legendary/ui/format.lua b/lua/legendary/ui/format.lua index 07f9d019..4edd9d44 100644 --- a/lua/legendary/ui/format.lua +++ b/lua/legendary/ui/format.lua @@ -38,11 +38,15 @@ function M.default_format(item) item.description, } elseif Toolbox.is_function(item) then + -- stylua: ignore start return { - Config.icons.fn, + item--[[@as keymap ]]:modeSwitched() + and table.concat(item--[[@as Keymap]]:modes(), ', ') + or Config.icons.fn, '', item.description, } + -- stylua: ignore end elseif Toolbox.is_itemgroup(item) then return { item.icon or Config.icons.itemgroup,