Skip to content

Commit

Permalink
feat: precede opened or related buffers (#274)
Browse files Browse the repository at this point in the history
* refactor: separate sorting logic to expand

* feat: precede relational buffers

* test: add tests for sorters

* fix: check if the buffer is really opened in sorters (#276)

* fix: check if the buffer is really opened in sorters

The check whether the buffer is loaded is added in the enumeration of
all the buffers in the list in sorters to ensure the proper sorting.

* test: try to fix sorter tests according to changes

* test: deal with non-loaded buffers in tests

* refactor: use more effective logic to filter bufs

* docs: add note for `preceding` option

* feat: deal with the case config changed after init

* refactor: reduce ambiguity between sorter/matcher

---------

Co-authored-by: Alexey Chernov <[email protected]>
  • Loading branch information
delphinus and aclex authored Dec 9, 2024
1 parent 872602f commit a358728
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 8 deletions.
15 changes: 15 additions & 0 deletions doc/telescope-frecency.txt
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,21 @@ these priorities below.
},
}
<
*telescope-frecency-configuration-preceding*
preceding ~

Default: `nil`
Type: `"opened"|"same_repo"`

You can precede entries related to already opened ones. `"opened"` means that it
precedes opened entries above other ones. `"same_repo"` means that it precedes
entries coming under the same repositories opened entries does.

Real world example is described in the PR below.

feat: precede opened or related buffers by delphinus · Pull Request #274
https://github.com/nvim-telescope/telescope-frecency.nvim/pull/274

*telescope-frecency-configuration-recency_values*
recency_values ~

Expand Down
12 changes: 11 additions & 1 deletion lua/frecency/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ local os_util = require "frecency.os_util"
---@field scoring_function? fun(recency: integer, fzy_score: number): number default: see lua/frecency/config.lua
---@field max_timestamps? integer default: 10
---@field path_display? table default: nil
---@field preceding? "opened"|"same_repo" default: nil
---@field show_filter_column? boolean|string[] default: true
---@field show_scores? boolean default: false
---@field show_unindexed? boolean default: true
Expand Down Expand Up @@ -53,6 +54,7 @@ local Config = {}
---@field scoring_function fun(recency: integer, fzy_score: number): number default: see lua/frecency/config.lua
---@field max_timestamps integer default: 10
---@field path_display? table default: nil
---@field preceding? "opened"|"same_repo" default: nil
---@field show_filter_column boolean|string[] default: true
---@field show_scores boolean default: false
---@field show_unindexed boolean default: true
Expand Down Expand Up @@ -81,6 +83,7 @@ Config.new = function()
matcher = true,
max_timestamps = true,
path_display = true,
preceding = true,
scoring_function = true,
show_filter_column = true,
show_scores = true,
Expand Down Expand Up @@ -177,7 +180,7 @@ Config.setup = function(ext_config)
debug = { opts.debug, "b" },
default_workspace = { opts.default_workspace, "s", true },
disable_devicons = { opts.disable_devicons, "b" },
enable_prompt_mappings={opts.enable_prompt_mappings,'b'},
enable_prompt_mappings = { opts.enable_prompt_mappings, "b" },
filter_delimiter = { opts.filter_delimiter, "s" },
hide_current_buffer = { opts.hide_current_buffer, "b" },
ignore_patterns = { opts.ignore_patterns, "t" },
Expand All @@ -195,6 +198,13 @@ Config.setup = function(ext_config)
end,
"positive number",
},
preceding = {
opts.preceding,
function(v)
return v == "opened" or v == "same_repo" or v == nil
end,
'"opened" or "same_repo" or nil',
},
show_filter_column = { opts.show_filter_column, { "b", "t" }, true },
show_scores = { opts.show_scores, "b" },
show_unindexed = { opts.show_unindexed, "b" },
Expand Down
9 changes: 5 additions & 4 deletions lua/frecency/finder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local recency = require "frecency.recency"
local log = require "frecency.log"
local timer = require "frecency.timer"
local lazy_require = require "frecency.lazy_require"
local Sorter = require "frecency.sorter"
local Job = lazy_require "plenary.job" --[[@as FrecencyPlenaryJob]]
local async = lazy_require "plenary.async" --[[@as FrecencyPlenaryAsync]]

Expand All @@ -25,6 +26,7 @@ local async = lazy_require "plenary.async" --[[@as FrecencyPlenaryAsync]]
---@field private seen table<string, boolean>
---@field private process table<string, { obj: VimSystemObj, done: boolean }>
---@field private state FrecencyState
---@field private sorter FrecencySorter
local Finder = {
---@type fun(): string[]?
cmd = (function()
Expand Down Expand Up @@ -81,6 +83,7 @@ Finder.new = function(database, entry_maker, need_scandir, paths, state, finder_
paths = paths,
process = {},
state = state,
sorter = Sorter.new(),

seen = {},
entries = {},
Expand Down Expand Up @@ -315,11 +318,9 @@ function Finder:get_results(workspaces, epoch)
end
timer.track "making results"

table.sort(files, function(a, b)
return a.score > b.score or (a.score == b.score and a.path > b.path)
end)
local sorted = self.sorter:sort(files)
timer.track "sorting finish"
return files
return sorted
end

function Finder:close()
Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions lua/frecency/picker.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ local State = require "frecency.state"
local Finder = require "frecency.finder"
local config = require "frecency.config"
local fs = require "frecency.fs"
local fuzzy_sorter = require "frecency.fuzzy_sorter"
local substr_sorter = require "frecency.substr_sorter"
local fuzzy_matcher = require "frecency.fuzzy_matcher"
local substr_matcher = require "frecency.substr_matcher"
local lazy_require = require "frecency.lazy_require"
local Path = lazy_require "plenary.path" --[[@as FrecencyPlenaryPath]]
local actions = lazy_require "telescope.actions"
Expand Down Expand Up @@ -98,7 +98,7 @@ function Picker:start(opts)
prompt_title = "Frecency",
finder = finder,
previewer = telescope_config.values.file_previewer(opts),
sorter = config.matcher == "default" and substr_sorter() or fuzzy_sorter(opts),
sorter = config.matcher == "default" and substr_matcher() or fuzzy_matcher(opts),
on_input_filter_cb = self:on_input_filter_cb(opts),
attach_mappings = function(prompt_bufnr)
return self:attach_mappings(prompt_bufnr)
Expand Down
18 changes: 18 additions & 0 deletions lua/frecency/sorter/default.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---@class FrecencySorterDefault: FrecencySorter
local Default = {}

---@return FrecencySorterDefault
Default.new = function()
return setmetatable({}, { __index = Default })
end

---@param files FrecencyDatabaseEntry[]
---@return FrecencyDatabaseEntry[]
function Default.sort(_, files)
table.sort(files, function(a, b)
return a.score > b.score or (a.score == b.score and a.path > b.path)
end)
return files
end

return Default
16 changes: 16 additions & 0 deletions lua/frecency/sorter/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
local config = require "frecency.config"
local Default = require "frecency.sorter.default"
local Opened = require "frecency.sorter.opened"
local SameRepo = require "frecency.sorter.same_repo"

---@class FrecencySorter
---@field new fun(): FrecencySorter
---@field sort fun(self: FrecencySorter, files: FrecencyDatabaseEntry[]): FrecencyDatabaseEntry[]

return {
---@return FrecencySorter
new = function()
local Klass = config.preceding == "opened" and Opened or config.preceding == "same_repo" and SameRepo or Default
return Klass.new()
end,
}
38 changes: 38 additions & 0 deletions lua/frecency/sorter/opened.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
local Default = require "frecency.sorter.default"

---@class FrecencySorterOpened: FrecencySorterDefault
---@field protected buffers string[]
---@field protected buffers_map table<string, boolean>
local Opened = setmetatable({}, { __index = Default })

---@return FrecencySorterOpened
Opened.new = function()
local self = setmetatable(Default.new(), { __index = Opened }) --[[@as FrecencySorterOpened]]
local bufnrs = vim.api.nvim_list_bufs()
self.buffers = {}
self.buffers_map = {}
for _, bufnr in ipairs(bufnrs) do
local is_loaded = vim.api.nvim_buf_is_loaded(bufnr)
if is_loaded then
local buffer = vim.api.nvim_buf_get_name(bufnr)
table.insert(self.buffers, buffer)
self.buffers_map[buffer] = true
end
end
return self
end

function Opened:sort(files)
local sorted = Default.sort(self, files)
---@type FrecencyDatabaseEntry[], FrecencyDatabaseEntry[]
local result, others = {}, {}
for _, entry in ipairs(sorted) do
table.insert(self.buffers_map[entry.path] and result or others, entry)
end
for _, entry in ipairs(others) do
table.insert(result, entry)
end
return result
end

return Opened
44 changes: 44 additions & 0 deletions lua/frecency/sorter/same_repo.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
local Default = require "frecency.sorter.default"
local Opened = require "frecency.sorter.opened"

---@class FrecencySorterSameRepo: FrecencySorterOpened
---@field private repos string[]
local SameRepo = setmetatable({}, { __index = Opened })

---@return FrecencySorterSameRepo
SameRepo.new = function()
local self = setmetatable(Opened.new(), { __index = SameRepo }) --[[@as FrecencySorterSameRepo]]
self.repos = {}
for _, buffer in ipairs(self.buffers) do
local repo = vim.fs.root(buffer, ".git")
if repo then
table.insert(self.repos, repo)
end
end
return self
end

function SameRepo:sort(files)
local sorted = Default.sort(self, files)
if #self.repos == 0 then
return sorted
end
---@type FrecencyDatabaseEntry[], FrecencyDatabaseEntry[]
local result, others = {}, {}
for _, entry in ipairs(sorted) do
local matched
for _, repo in ipairs(self.repos) do
matched = not not entry.path:find(repo, 1, true)
if matched then
break
end
end
table.insert(matched and result or others, entry)
end
for _, entry in ipairs(others) do
table.insert(result, entry)
end
return result
end

return SameRepo
File renamed without changes.
97 changes: 97 additions & 0 deletions lua/frecency/tests/sorter_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
local Default = require "frecency.sorter.default"
local Opened = require "frecency.sorter.opened"
local SameRepo = require "frecency.sorter.same_repo"

---@param text string
local function parse_text(text)
local entries = {}
for line in vim.gsplit(text, "\n", { plain = true, trimempty = true }) do
local part = vim.split(line, "%s+", { trimempty = true })
if #part == 2 then
table.insert(entries, { score = tonumber(part[1]), path = part[2] })
end
end
assert(#entries > 0)
return entries
end

local entries = [[
10 /path/to/project_A/style.css
20 /path/to/project_B/main.c
40 /path/to/project_C/lib/main.ts
60 /path/to/project_A/image.jpg
80 /path/to/project_B/Makefile
100 /path/to/project_A/index.html
]]

describe("frecency.sorter", function()
for _, c in ipairs {
{
M = Default,
name = "Default",
entries = [[
100 /path/to/project_A/index.html
80 /path/to/project_B/Makefile
60 /path/to/project_A/image.jpg
40 /path/to/project_C/lib/main.ts
20 /path/to/project_B/main.c
10 /path/to/project_A/style.css
]],
},
{
M = Opened,
name = "Opened",
entries = [[
80 /path/to/project_B/Makefile
60 /path/to/project_A/image.jpg
100 /path/to/project_A/index.html
40 /path/to/project_C/lib/main.ts
20 /path/to/project_B/main.c
10 /path/to/project_A/style.css
]],
},
{
M = SameRepo,
name = "SameRepo",
entries = [[
100 /path/to/project_A/index.html
80 /path/to/project_B/Makefile
60 /path/to/project_A/image.jpg
20 /path/to/project_B/main.c
10 /path/to/project_A/style.css
40 /path/to/project_C/lib/main.ts
]],
},
} do
it(("%s sorter returns valid entries"):format(c.name), function()
local originals = {
nvim_list_bufs = vim.api.nvim_list_bufs,
nvim_buf_get_name = vim.api.nvim_buf_get_name,
nvim_buf_is_loaded = vim.api.nvim_buf_is_loaded,
root = vim.fs.root,
}
---@diagnostic disable-next-line: duplicate-set-field
vim.api.nvim_list_bufs = function()
return { 1, 2 }
end
---@diagnostic disable-next-line: duplicate-set-field
vim.api.nvim_buf_get_name = function(bufnr)
return ({ "/path/to/project_A/image.jpg", "/path/to/project_B/Makefile", "/path/to/project_A/index.html" })[bufnr]
end
---@diagnostic disable-next-line: duplicate-set-field
vim.api.nvim_buf_is_loaded = function(bufnr)
return ({ true, true, false })[bufnr]
end
---@diagnostic disable-next-line: duplicate-set-field
vim.fs.root = function(path, _)
return (path:match "(.*project_.)")
end
local sorter = c.M.new()
assert.are.same(parse_text(c.entries), sorter:sort(parse_text(entries)))
vim.api.nvim_list_bufs = originals.nvim_list_bufs
vim.api.nvim_buf_get_name = originals.nvim_buf_get_name
vim.api.nvim_buf_is_loaded = originals.nvim_buf_is_loaded
vim.fs.root = originals.root
end)
end
end)

0 comments on commit a358728

Please sign in to comment.