Files
telescope.nvim/lua/telescope/finders.lua
Stef 0b891ec934 fix: always add index to entry (#2442)
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.
2023-05-24 11:29:36 +02:00

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