Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): keep track of recently used item #412

Merged
merged 2 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
hinell marked this conversation as resolved.
Show resolved Hide resolved
-- 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
Loading