Skip to content

Commit

Permalink
Merge pull request #412 from hinell/master
Browse files Browse the repository at this point in the history
feat(ui): keep track of recently used item
  • Loading branch information
mrjones2014 authored Oct 19, 2023
2 parents 76f96aa + 33e24d1 commit a4a394c
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 29 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 5 additions & 4 deletions lua/legendary/api/executor.lua
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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!`'
Expand All @@ -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

Expand Down
31 changes: 25 additions & 6 deletions lua/legendary/data/itemlist.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down Expand Up @@ -106,16 +108,31 @@ 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
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

Expand Down Expand Up @@ -179,7 +196,7 @@ function ItemList:sort_inplace()
end

if opts.most_recent_first then
if item1 == State.most_recent_item then
if item1 == State.itemgroup_history[opts.itemgroup or ItemList.TOPLEVEL_LIST_ID] then
return true
end
end
Expand Down Expand Up @@ -213,9 +230,11 @@ function ItemList:sort_inplace()
-- 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

Expand Down
6 changes: 4 additions & 2 deletions lua/legendary/data/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ 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 table<string, LegendaryItem>
local M = {}

M.items = ItemList:create()
M.most_recent_item = nil
M.last_executed_item = nil
M.most_recent_filters = nil
M.itemgroup_history = {}

return M
41 changes: 25 additions & 16 deletions lua/legendary/ui/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -23,25 +24,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()
Expand All @@ -51,7 +53,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
Expand Down Expand Up @@ -81,12 +83,19 @@ local function select_inner(opts, context, itemlist)
return
end

State.itemgroup_history[opts.itemgroup or ItemList.TOPLEVEL_LIST_ID] = selected

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
Expand All @@ -96,7 +105,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

0 comments on commit a4a394c

Please sign in to comment.