From a7d140fdea71ab3ab94099a13458b2f6767059d8 Mon Sep 17 00:00:00 2001 From: Hinell Date: Wed, 18 Oct 2023 22:38:49 +0300 Subject: [PATCH 1/2] feat(State): keep track of recently used item * For global itemlist, most recently selected items is kept at `State.most_recent_item` * For itemgroup-relative selection, use `State.most_recent_igroups[id]` --- README.md | 3 ++- lua/legendary/data/itemlist.lua | 19 +++++++++++---- lua/legendary/data/state.lua | 2 ++ lua/legendary/ui/init.lua | 42 +++++++++++++++++++++------------ 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 5bb65372..775d79f5 100644 --- a/README.md +++ b/README.md @@ -385,7 +385,8 @@ require('legendary').setup({ -- } -- }) sort = { - -- sort most recently used item to the top + -- put most recently selected item first, this works + -- both within global and item group lists most_recent_first = true, -- sort user-defined items before built-in items user_items_first = true, diff --git a/lua/legendary/data/itemlist.lua b/lua/legendary/data/itemlist.lua index 70509cc3..a81bfa73 100644 --- a/lua/legendary/data/itemlist.lua +++ b/lua/legendary/data/itemlist.lua @@ -106,12 +106,21 @@ function ItemList:filter(filters, context) end, 'Took %s ms to filter items in context.') end +---@class ItemListSortInplaceOpts +---@field itemgroup string + ---Sort the list *IN PLACE* according to config. ---THIS MODIFIES THE LIST IN PLACE. -function ItemList:sort_inplace() +--- @param opts ItemListSortInplaceOpts +function ItemList:sort_inplace(opts) -- inline require to avoid circular dependency local State = require('legendary.data.state') - local opts = Config.sort + vim.validate({ + itemgroup = { opts.itemgroup, 'string', true }, + }) + + -- Merge Config into local opts + opts = vim.tbl_extend('keep', opts, Config.sort) -- if no items have been added, and the most recent item has not changed, -- we're already sorted @@ -179,8 +188,10 @@ function ItemList:sort_inplace() end if opts.most_recent_first then - if item1 == State.most_recent_item then - return true + if opts.itemgroup then + return item1 == State.itemgroup_history[opts.itemgroup] + else + return item1 == State.most_recent_item end end diff --git a/lua/legendary/data/state.lua b/lua/legendary/data/state.lua index 238f6b9c..bbbc78a6 100644 --- a/lua/legendary/data/state.lua +++ b/lua/legendary/data/state.lua @@ -4,10 +4,12 @@ local ItemList = require('legendary.data.itemlist') ---@field items ItemList ---@field most_recent_item LegendaryItem|nil ---@field most_recent_filters LegendaryItemFilter[]|nil +---@field itemgroup_history ItemList[] local M = {} M.items = ItemList:create() M.most_recent_item = nil M.most_recent_filters = nil +M.itemgroup_history = {} return M diff --git a/lua/legendary/ui/init.lua b/lua/legendary/ui/init.lua index 0b534196..d33b0abb 100644 --- a/lua/legendary/ui/init.lua +++ b/lua/legendary/ui/init.lua @@ -23,25 +23,26 @@ local M = {} ---@overload fun(opts:LegendaryFindOpts,context:LegendaryEditorContext) local function select_inner(opts, context, itemlist) opts = opts or {} + + vim.validate({ + itemgroup = { opts.itemgroup, 'string', true }, + select_prompt = { opts.select_prompt, 'function', true }, + }) + if itemlist then - Log.trace('Relaunching select UI for an item group') - else Log.trace('Launching select UI') - end - - -- if no itemlist passed - if itemlist == nil then - -- if an item group is specified, use that + else + Log.trace('Relaunching select UI for an item group') + -- if no itemlist passed, try to use itemgroup + -- if an item group id is specified, use that local itemgroup = State.items:get_item_group(opts.itemgroup) if itemgroup then itemlist = itemgroup.items + else + Log.error('Expected itemlist, got %s.\n %s', type(itemlist), vim.inspect(itemlist)) end end - -- finally, use full item list if no other lists are specified - itemlist = itemlist or State.items - opts = opts or {} - local prompt = opts.select_prompt or Config.select_prompt if type(prompt) == 'function' then prompt = prompt() @@ -51,7 +52,7 @@ local function select_inner(opts, context, itemlist) -- implementation of `sort_inplace` checks if -- sorting is actually needed and does nothing -- if it does not need to be sorted. - itemlist:sort_inplace() + itemlist:sort_inplace(opts) local filters = opts.filters or {} if type(filters) ~= 'table' then @@ -81,12 +82,23 @@ local function select_inner(opts, context, itemlist) return end + if opts.itemgroup then + State.itemgroup_history[opts.itemgroup] = selected + else + State.most_recent_item = selected + end + if Toolbox.is_itemgroup(selected) then - return select_inner(opts, context, selected.items) + local item_group_id = selected:id() + + local opts_next = vim.tbl_extend('force', opts, { + itemgroup = item_group_id, + }) + + return select_inner(opts_next, context) end Log.trace('Preparing to execute selected item') - State.most_recent_item = selected Executor.exec_item(selected, context) end) end @@ -96,7 +108,7 @@ end function M.select(opts) vim.cmd('doautocmd User LegendaryUiPre') local context = Executor.build_context() - select_inner(opts, context) + select_inner(opts, context, State.items) end return M From 33e24d113aaaff38aada6ebdfa946ea6c4dd0a7c Mon Sep 17 00:00:00 2001 From: Hinell Date: Thu, 19 Oct 2023 19:08:44 +0300 Subject: [PATCH 2/2] feat(ui): apply mrjones2014.diff --- lua/legendary/api/executor.lua | 9 +++++---- lua/legendary/data/itemlist.lua | 22 +++++++++++++++------- lua/legendary/data/state.lua | 6 +++--- lua/legendary/ui/init.lua | 9 +++------ 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/lua/legendary/api/executor.lua b/lua/legendary/api/executor.lua index ee7c1751..3230561b 100644 --- a/lua/legendary/api/executor.lua +++ b/lua/legendary/api/executor.lua @@ -1,6 +1,7 @@ local Toolbox = require('legendary.toolbox') local Log = require('legendary.log') local Config = require('legendary.config') +local State = require('legendary.data.state') local util = require('legendary.util') local function update_item_frecency_score(item) @@ -83,6 +84,7 @@ end function M.exec_item(item, context) vim.schedule(function() M.restore_context(context, function() + State.last_executed_item = item update_item_frecency_score(item) if Toolbox.is_function(item) then item.implementation() @@ -122,12 +124,11 @@ end ---still return true. ---@param ignore_filters boolean|nil whether to ignore the filters used when selecting the item, default false function M.repeat_previous(ignore_filters) - local State = require('legendary.data.state') - if State.most_recent_item then + if State.last_executed_item then if not ignore_filters and State.most_recent_filters then for _, filter in ipairs(State.most_recent_filters) do -- if any filter does not match, abort executions - local err, matches = pcall(filter, State.most_recent_item) + local err, matches = pcall(filter, State.last_executed_item) if not err and not matches then Log.warn( 'Previously executed item no longer matches previously used filters, use `:LegendaryRepeat!`' @@ -138,7 +139,7 @@ function M.repeat_previous(ignore_filters) end end local context = M.build_context() - M.exec_item(State.most_recent_item, context) + M.exec_item(State.last_executed_item, context) end end diff --git a/lua/legendary/data/itemlist.lua b/lua/legendary/data/itemlist.lua index a81bfa73..5d5c51e8 100644 --- a/lua/legendary/data/itemlist.lua +++ b/lua/legendary/data/itemlist.lua @@ -14,6 +14,8 @@ local Log = require('legendary.log') ---@field private sorted boolean local ItemList = class('ItemList') +ItemList.TOPLEVEL_LIST_ID = 'toplevel' + ---@private function ItemList:initialize() self.items = {} @@ -124,7 +126,13 @@ function ItemList:sort_inplace(opts) -- if no items have been added, and the most recent item has not changed, -- we're already sorted - if self.sorted and (not opts.most_recent_first or (self.items[1] == State.most_recent_item)) then + if + self.sorted + and ( + not opts.most_recent_first + or (self.items[1] == State.itemgroup_history[opts.itemgroup or ItemList.TOPLEVEL_LIST_ID]) + ) + then return end @@ -188,10 +196,8 @@ function ItemList:sort_inplace(opts) end if opts.most_recent_first then - if opts.itemgroup then - return item1 == State.itemgroup_history[opts.itemgroup] - else - return item1 == State.most_recent_item + if item1 == State.itemgroup_history[opts.itemgroup or ItemList.TOPLEVEL_LIST_ID] then + return true end end @@ -224,9 +230,11 @@ function ItemList:sort_inplace(opts) -- sort by most recent last, and after other sorts are done -- if most recent is already at top, nothing to do, and attempting to sort will cause -- an error since it doesn't need to be sorted - if opts.most_recent_first and State.most_recent_item and State.most_recent_item ~= self.items[1] then + if + opts.most_recent_first and State.itemgroup_history[opts.itemgroup or ItemList.TOPLEVEL_LIST_ID] ~= self.items[1] + then items = Sorter.mergesort(items, function(item) - return item == State.most_recent_item + return item == State.itemgroup_history[opts.itemgroup or ItemList.TOPLEVEL_LIST_ID] end) end diff --git a/lua/legendary/data/state.lua b/lua/legendary/data/state.lua index bbbc78a6..40249aca 100644 --- a/lua/legendary/data/state.lua +++ b/lua/legendary/data/state.lua @@ -2,13 +2,13 @@ local ItemList = require('legendary.data.itemlist') ---@class LegendaryState ---@field items ItemList ----@field most_recent_item LegendaryItem|nil +---@field last_executed_item LegendaryItem|nil ---@field most_recent_filters LegendaryItemFilter[]|nil ----@field itemgroup_history ItemList[] +---@field itemgroup_history table local M = {} M.items = ItemList:create() -M.most_recent_item = nil +M.last_executed_item = nil M.most_recent_filters = nil M.itemgroup_history = {} diff --git a/lua/legendary/ui/init.lua b/lua/legendary/ui/init.lua index d33b0abb..17aa10e2 100644 --- a/lua/legendary/ui/init.lua +++ b/lua/legendary/ui/init.lua @@ -6,12 +6,13 @@ local Toolbox = require('legendary.toolbox') local Format = require('legendary.ui.format') local Executor = require('legendary.api.executor') local Log = require('legendary.log') +local ItemList = require('legendary.data.itemlist') ---@class LegendaryUi ---@field select fun(opts:LegendaryFindOpts) local M = {} ----@class LegendaryFindOpts +---@class LegendaryFindOpts : ItemListSortInplaceOpts ---@field itemgroup string Find items in this item group only ---@field filters LegendaryItemFilter[] ---@field select_prompt string|fun():string @@ -82,11 +83,7 @@ local function select_inner(opts, context, itemlist) return end - if opts.itemgroup then - State.itemgroup_history[opts.itemgroup] = selected - else - State.most_recent_item = selected - end + State.itemgroup_history[opts.itemgroup or ItemList.TOPLEVEL_LIST_ID] = selected if Toolbox.is_itemgroup(selected) then local item_group_id = selected:id()