59497d6 introduced `sorters.fuzzy_with_index_bias`, which gives a
scoring boost to earlier entries.
However, this sorter relies on an `index` key existing for the entry, which is
only populated by the static finder currently. We should set it from the
other finders, too.
This will allow us to use said sorter everywhere. It will also let us
replicate the behaviour of `fzf --tiebreak=index`:
```
return pickers.new(opts, {
finder = finders.new_oneshot_job(...)
sorter = telescope.extensions.fzf.native_fzf_sorter(),
tiebreak = function(current_entry, existing_entry, _)
return current_entry.index < existing_entry.index
end
}):find()
```
This gives me better results for my "recently opened files" picker.
Other builtin pickers might benefit from this, too.
227 lines
5.5 KiB
Lua
227 lines
5.5 KiB
Lua
local Job = require "plenary.job"
|
|
|
|
local make_entry = require "telescope.make_entry"
|
|
local log = require "telescope.log"
|
|
|
|
local async_static_finder = require "telescope.finders.async_static_finder"
|
|
local async_oneshot_finder = require "telescope.finders.async_oneshot_finder"
|
|
local async_job_finder = require "telescope.finders.async_job_finder"
|
|
|
|
local finders = {}
|
|
|
|
local _callable_obj = function()
|
|
local obj = {}
|
|
|
|
obj.__index = obj
|
|
obj.__call = function(t, ...)
|
|
return t:_find(...)
|
|
end
|
|
|
|
obj.close = function() end
|
|
|
|
return obj
|
|
end
|
|
|
|
--[[ =============================================================
|
|
|
|
JobFinder
|
|
|
|
Uses an external Job to get results. Processes results as they arrive.
|
|
|
|
For more information about how Jobs are implemented, checkout 'plenary.job'
|
|
|
|
-- ============================================================= ]]
|
|
local JobFinder = _callable_obj()
|
|
|
|
--- Create a new finder command
|
|
---
|
|
---@param opts table Keys:
|
|
-- fn_command function The function to call
|
|
function JobFinder:new(opts)
|
|
opts = opts or {}
|
|
|
|
assert(not opts.results, "`results` should be used with finder.new_table")
|
|
assert(not opts.static, "`static` should be used with finder.new_oneshot_job")
|
|
|
|
local obj = setmetatable({
|
|
entry_maker = opts.entry_maker or make_entry.gen_from_string(opts),
|
|
fn_command = opts.fn_command,
|
|
cwd = opts.cwd,
|
|
writer = opts.writer,
|
|
|
|
-- Maximum number of results to process.
|
|
-- Particularly useful for live updating large queries.
|
|
maximum_results = opts.maximum_results,
|
|
}, self)
|
|
|
|
return obj
|
|
end
|
|
|
|
function JobFinder:_find(prompt, process_result, process_complete)
|
|
log.trace "Finding..."
|
|
|
|
if self.job and not self.job.is_shutdown then
|
|
log.debug "Shutting down old job"
|
|
self.job:shutdown()
|
|
end
|
|
|
|
local line_num = 0
|
|
local on_output = function(_, line, _)
|
|
line_num = line_num + 1
|
|
if not line or line == "" then
|
|
return
|
|
end
|
|
|
|
local entry
|
|
if self.entry_maker then
|
|
entry = self.entry_maker(line)
|
|
entry.index = line_num
|
|
else
|
|
entry = line
|
|
end
|
|
|
|
process_result(entry)
|
|
end
|
|
|
|
local opts = self:fn_command(prompt)
|
|
if not opts then
|
|
return
|
|
end
|
|
|
|
local writer = nil
|
|
if opts.writer and Job.is_job(opts.writer) then
|
|
writer = opts.writer
|
|
elseif opts.writer then
|
|
writer = Job:new(opts.writer)
|
|
end
|
|
|
|
self.job = Job:new {
|
|
command = opts.command,
|
|
args = opts.args,
|
|
cwd = opts.cwd or self.cwd,
|
|
|
|
maximum_results = self.maximum_results,
|
|
|
|
writer = writer,
|
|
|
|
enable_recording = false,
|
|
|
|
on_stdout = on_output,
|
|
-- on_stderr = on_output,
|
|
|
|
on_exit = function()
|
|
process_complete()
|
|
end,
|
|
}
|
|
|
|
self.job:start()
|
|
end
|
|
|
|
local DynamicFinder = _callable_obj()
|
|
|
|
function DynamicFinder:new(opts)
|
|
opts = opts or {}
|
|
|
|
assert(not opts.results, "`results` should be used with finder.new_table")
|
|
assert(not opts.static, "`static` should be used with finder.new_oneshot_job")
|
|
|
|
local obj = setmetatable({
|
|
curr_buf = opts.curr_buf,
|
|
fn = opts.fn,
|
|
entry_maker = opts.entry_maker or make_entry.gen_from_string(opts),
|
|
}, self)
|
|
|
|
return obj
|
|
end
|
|
|
|
function DynamicFinder:_find(prompt, process_result, process_complete)
|
|
local results = self.fn(prompt)
|
|
|
|
local result_num = 0
|
|
for _, result in ipairs(results) do
|
|
result_num = result_num + 1
|
|
local entry = self.entry_maker(result)
|
|
entry.index = result_num
|
|
if process_result(entry) then
|
|
return
|
|
end
|
|
end
|
|
|
|
process_complete()
|
|
end
|
|
|
|
--- Return a new Finder
|
|
--
|
|
-- Use at your own risk.
|
|
-- This opts dictionary is likely to change, but you are welcome to use it right now.
|
|
-- I will try not to change it needlessly, but I will change it sometimes and I won't feel bad.
|
|
finders._new = function(opts)
|
|
assert(not opts.results, "finder.new is deprecated with `results`. You should use `finder.new_table`")
|
|
return JobFinder:new(opts)
|
|
end
|
|
|
|
finders.new_async_job = function(opts)
|
|
if opts.writer then
|
|
return finders._new(opts)
|
|
end
|
|
|
|
return async_job_finder(opts)
|
|
end
|
|
|
|
finders.new_job = function(command_generator, entry_maker, _, cwd)
|
|
return async_job_finder {
|
|
command_generator = command_generator,
|
|
entry_maker = entry_maker,
|
|
cwd = cwd,
|
|
}
|
|
end
|
|
|
|
--- One shot job
|
|
---@param command_list string[]: Command list to execute.
|
|
---@param opts table: stuff
|
|
-- @key entry_maker function Optional: function(line: string) => table
|
|
-- @key cwd string
|
|
finders.new_oneshot_job = function(command_list, opts)
|
|
opts = opts or {}
|
|
|
|
assert(not opts.results, "`results` should be used with finder.new_table")
|
|
|
|
command_list = vim.deepcopy(command_list)
|
|
local command = table.remove(command_list, 1)
|
|
|
|
return async_oneshot_finder {
|
|
entry_maker = opts.entry_maker or make_entry.gen_from_string(opts),
|
|
|
|
cwd = opts.cwd,
|
|
maximum_results = opts.maximum_results,
|
|
|
|
fn_command = function()
|
|
return {
|
|
command = command,
|
|
args = command_list,
|
|
}
|
|
end,
|
|
}
|
|
end
|
|
|
|
--- Used to create a finder for a Lua table.
|
|
-- If you only pass a table of results, then it will use that as the entries.
|
|
--
|
|
-- If you pass a table, and then a function, it's used as:
|
|
-- results table, the results to run on
|
|
-- entry_maker function, the function to convert results to entries.
|
|
finders.new_table = function(t)
|
|
return async_static_finder(t)
|
|
end
|
|
|
|
--- Used to create a finder from a function.
|
|
--
|
|
---@param opts table: stuff
|
|
-- @key fn function() => list[string]
|
|
-- @key entry_maker function Optional: function(line: string) => table
|
|
finders.new_dynamic = function(opts)
|
|
return DynamicFinder:new(opts)
|
|
end
|
|
|
|
return finders
|