feat(performance): Major performance improvements using async v2 from @oberblastmeister (#987)
* start: Working w/ async jobs * short circuit to using bad finder if you pass writer.
This commit is contained in:
@@ -1,15 +1,12 @@
|
||||
require "telescope"
|
||||
|
||||
local a = vim.api
|
||||
|
||||
local async_lib = require "plenary.async_lib"
|
||||
local async_util = async_lib.util
|
||||
local async = require "plenary.async"
|
||||
local await_schedule = async.util.scheduler
|
||||
local channel = require("plenary.async.control").channel
|
||||
local popup = require "plenary.popup"
|
||||
|
||||
local async = async_lib.async
|
||||
local await = async_lib.await
|
||||
local channel = async_util.channel
|
||||
|
||||
require "telescope"
|
||||
|
||||
local actions = require "telescope.actions"
|
||||
local action_set = require "telescope.actions.set"
|
||||
local config = require "telescope.config"
|
||||
@@ -70,12 +67,13 @@ function Picker:new(opts)
|
||||
selection_caret = get_default(opts.selection_caret, config.values.selection_caret),
|
||||
entry_prefix = get_default(opts.entry_prefix, config.values.entry_prefix),
|
||||
initial_mode = get_default(opts.initial_mode, config.values.initial_mode),
|
||||
debounce = get_default(tonumber(opts.debounce), nil),
|
||||
|
||||
default_text = opts.default_text,
|
||||
get_status_text = get_default(opts.get_status_text, config.values.get_status_text),
|
||||
_on_input_filter_cb = opts.on_input_filter_cb or function() end,
|
||||
|
||||
finder = opts.finder,
|
||||
finder = assert(opts.finder, "Finder is required."),
|
||||
sorter = opts.sorter or require("telescope.sorters").empty(),
|
||||
|
||||
all_previewers = opts.previewer,
|
||||
@@ -228,7 +226,7 @@ function Picker:highlight_displayed_rows(results_bufnr, prompt)
|
||||
end
|
||||
|
||||
function Picker:highlight_one_row(results_bufnr, prompt, display, row)
|
||||
local highlights = self:_track("_highlight_time", self.sorter.highlighter, self.sorter, prompt, display)
|
||||
local highlights = self.sorter:highlighter(prompt, display)
|
||||
|
||||
if highlights then
|
||||
for _, hl in ipairs(highlights) do
|
||||
@@ -274,8 +272,6 @@ function Picker:find()
|
||||
self:close_existing_pickers()
|
||||
self:reset_selection()
|
||||
|
||||
assert(self.finder, "Finder is required to do picking")
|
||||
|
||||
self.original_win_id = a.nvim_get_current_win()
|
||||
|
||||
-- User autocmd run it before create Telescope window
|
||||
@@ -346,26 +342,50 @@ function Picker:find()
|
||||
self.prompt_prefix = prompt_prefix
|
||||
self:_reset_prefix_color()
|
||||
|
||||
-- Temporarily disabled: Draw the screen ASAP. This makes things feel speedier.
|
||||
-- vim.cmd [[redraw]]
|
||||
|
||||
-- First thing we want to do is set all the lines to blank.
|
||||
self.max_results = popup_opts.results.height
|
||||
|
||||
-- TODO(scrolling): This may be a hack when we get a little further into implementing scrolling.
|
||||
vim.api.nvim_buf_set_lines(results_bufnr, 0, self.max_results, false, utils.repeated_table(self.max_results, ""))
|
||||
|
||||
-- TODO(status): I would love to get the status text not moving back and forth. Perhaps it is just a problem with
|
||||
-- virtual text & prompt buffers or something though. I can't figure out why it would redraw the way it does.
|
||||
--
|
||||
-- A "hacked" version of this would be to calculate where the area I want the status to go and put a new window there.
|
||||
-- With this method, I do not need to worry about padding or antying, just make it take up X characters or something.
|
||||
local status_updater = self:get_status_updater(prompt_win, prompt_bufnr)
|
||||
local debounced_status = debounce.throttle_leading(status_updater, 50)
|
||||
-- local debounced_status = status_updater
|
||||
|
||||
local tx, rx = channel.mpsc()
|
||||
self.__on_lines = tx.send
|
||||
|
||||
local main_loop = async(function()
|
||||
while true do
|
||||
await(async_lib.scheduler())
|
||||
local find_id = self:_next_find_id()
|
||||
|
||||
local main_loop = async.void(function()
|
||||
self.sorter:_init()
|
||||
|
||||
-- Do filetype last, so that users can register at the last second.
|
||||
pcall(a.nvim_buf_set_option, prompt_bufnr, "filetype", "TelescopePrompt")
|
||||
pcall(a.nvim_buf_set_option, results_bufnr, "filetype", "TelescopeResults")
|
||||
|
||||
-- TODO(async): I wonder if this should actually happen _before_ we nvim_buf_attach.
|
||||
-- This way the buffer would always start with what we think it should when we start the loop.
|
||||
if self.default_text then
|
||||
self:set_prompt(self.default_text)
|
||||
end
|
||||
|
||||
if self.initial_mode == "insert" then
|
||||
vim.cmd [[startinsert!]]
|
||||
elseif self.initial_mode ~= "normal" then
|
||||
error("Invalid setting for initial_mode: " .. self.initial_mode)
|
||||
end
|
||||
|
||||
await_schedule()
|
||||
|
||||
while true do
|
||||
-- Wait for the next input
|
||||
rx.last()
|
||||
|
||||
local _, _, _, first_line, last_line = await(rx.last())
|
||||
self:_reset_track()
|
||||
|
||||
if not vim.api.nvim_buf_is_valid(prompt_bufnr) then
|
||||
@@ -373,74 +393,58 @@ function Picker:find()
|
||||
return
|
||||
end
|
||||
|
||||
if not first_line then
|
||||
first_line = 0
|
||||
end
|
||||
if not last_line then
|
||||
last_line = 1
|
||||
local start_time = vim.loop.hrtime()
|
||||
|
||||
local prompt = self:_get_prompt()
|
||||
local on_input_result = self._on_input_filter_cb(prompt) or {}
|
||||
|
||||
local new_prompt = on_input_result.prompt
|
||||
if new_prompt then
|
||||
prompt = new_prompt
|
||||
end
|
||||
|
||||
if first_line > 0 or last_line > 1 then
|
||||
log.debug("ON_LINES: Bad range", first_line, last_line, self:_get_prompt())
|
||||
return
|
||||
end
|
||||
|
||||
local original_prompt = self:_get_prompt()
|
||||
local on_input_result = self._on_input_filter_cb(original_prompt) or {}
|
||||
|
||||
local prompt = on_input_result.prompt or original_prompt
|
||||
local finder = on_input_result.updated_finder
|
||||
|
||||
if finder then
|
||||
local new_finder = on_input_result.updated_finder
|
||||
if new_finder then
|
||||
self.finder:close()
|
||||
self.finder = finder
|
||||
self.finder = new_finder
|
||||
end
|
||||
|
||||
if self.sorter then
|
||||
self.sorter:_start(prompt)
|
||||
end
|
||||
|
||||
-- TODO: Entry manager should have a "bulk" setter. This can prevent a lot of redraws from display
|
||||
self.sorter:_start(prompt)
|
||||
self.manager = EntryManager:new(self.max_results, self.entry_adder, self.stats)
|
||||
|
||||
local find_id = self:_next_find_id()
|
||||
local process_result = self:get_result_processor(find_id, prompt, debounced_status)
|
||||
local process_complete = self:get_result_completor(self.results_bufnr, find_id, prompt, status_updater)
|
||||
|
||||
local ok, msg = pcall(function()
|
||||
self.finder(prompt, process_result, vim.schedule_wrap(process_complete))
|
||||
self.finder(prompt, process_result, process_complete)
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
log.warn("Failed with msg: ", msg)
|
||||
log.warn("Finder failed with msg: ", msg)
|
||||
end
|
||||
|
||||
local diff_time = (vim.loop.hrtime() - start_time) / 1e6
|
||||
if self.debounce and diff_time < self.debounce then
|
||||
async.util.sleep(self.debounce - diff_time)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Register attach
|
||||
vim.api.nvim_buf_attach(prompt_bufnr, false, {
|
||||
on_lines = tx.send,
|
||||
on_lines = function(...)
|
||||
find_id = self:_next_find_id()
|
||||
|
||||
self._result_completed = false
|
||||
status_updater { completed = false }
|
||||
|
||||
tx.send(...)
|
||||
end,
|
||||
on_detach = function()
|
||||
-- TODO: Can we add a "cleanup" / "teardown" function that completely removes these.
|
||||
self.finder = nil
|
||||
self.previewer = nil
|
||||
self.sorter = nil
|
||||
self.manager = nil
|
||||
|
||||
self.closed = true
|
||||
|
||||
-- TODO: Should we actually do this?
|
||||
collectgarbage()
|
||||
collectgarbage()
|
||||
self:_detach()
|
||||
end,
|
||||
})
|
||||
|
||||
if self.sorter then
|
||||
self.sorter:_init()
|
||||
end
|
||||
async_lib.run(main_loop())
|
||||
status_updater()
|
||||
|
||||
-- TODO: Use WinLeave as well?
|
||||
local on_buf_leave = string.format(
|
||||
[[ autocmd BufLeave <buffer> ++nested ++once :silent lua require('telescope.pickers').on_close_prompt(%s)]],
|
||||
@@ -480,19 +484,8 @@ function Picker:find()
|
||||
|
||||
mappings.apply_keymap(prompt_bufnr, self.attach_mappings, config.values.mappings)
|
||||
|
||||
-- Do filetype last, so that users can register at the last second.
|
||||
pcall(a.nvim_buf_set_option, prompt_bufnr, "filetype", "TelescopePrompt")
|
||||
pcall(a.nvim_buf_set_option, results_bufnr, "filetype", "TelescopeResults")
|
||||
|
||||
if self.default_text then
|
||||
self:set_prompt(self.default_text)
|
||||
end
|
||||
|
||||
if self.initial_mode == "insert" then
|
||||
vim.cmd [[startinsert!]]
|
||||
elseif self.initial_mode ~= "normal" then
|
||||
error("Invalid setting for initial_mode: " .. self.initial_mode)
|
||||
end
|
||||
tx.send()
|
||||
main_loop()
|
||||
end
|
||||
|
||||
function Picker:hide_preview()
|
||||
@@ -791,9 +784,7 @@ function Picker:set_selection(row)
|
||||
end
|
||||
|
||||
local caret = self.selection_caret
|
||||
-- local display = string.format('%s %s', caret,
|
||||
-- (a.nvim_buf_get_lines(results_bufnr, row, row + 1, false)[1] or ''):sub(3)
|
||||
-- )
|
||||
|
||||
local display, display_highlights = entry_display.resolve(self, entry)
|
||||
display = caret .. display
|
||||
|
||||
@@ -939,23 +930,6 @@ function Picker:_reset_track()
|
||||
self.stats.highlights = 0
|
||||
end
|
||||
|
||||
function Picker:_track(key, func, ...)
|
||||
local start, final
|
||||
if self.track then
|
||||
start = vim.loop.hrtime()
|
||||
end
|
||||
|
||||
-- Hack... we just do this so that we can track stuff that returns two values.
|
||||
local res1, res2 = func(...)
|
||||
|
||||
if self.track then
|
||||
final = vim.loop.hrtime()
|
||||
self.stats[key] = final - start + self.stats[key]
|
||||
end
|
||||
|
||||
return res1, res2
|
||||
end
|
||||
|
||||
function Picker:_increment(key)
|
||||
self.stats[key] = (self.stats[key] or 0) + 1
|
||||
end
|
||||
@@ -987,12 +961,12 @@ function Picker:close_existing_pickers()
|
||||
end
|
||||
|
||||
function Picker:get_status_updater(prompt_win, prompt_bufnr)
|
||||
return function()
|
||||
local text = self:get_status_text()
|
||||
return function(opts)
|
||||
if self.closed or not vim.api.nvim_buf_is_valid(prompt_bufnr) then
|
||||
return
|
||||
end
|
||||
local current_prompt = vim.api.nvim_buf_get_lines(prompt_bufnr, 0, 1, false)[1]
|
||||
|
||||
local current_prompt = self:_get_prompt()
|
||||
if not current_prompt then
|
||||
return
|
||||
end
|
||||
@@ -1001,10 +975,11 @@ function Picker:get_status_updater(prompt_win, prompt_bufnr)
|
||||
return
|
||||
end
|
||||
|
||||
local prompt_len = #current_prompt
|
||||
local text = self:get_status_text(opts)
|
||||
local prompt_len = #self.prompt_prefix + #current_prompt
|
||||
|
||||
local padding = string.rep(" ", vim.api.nvim_win_get_width(prompt_win) - prompt_len - #text - 3)
|
||||
vim.api.nvim_buf_clear_namespace(prompt_bufnr, ns_telescope_prompt, 0, 1)
|
||||
local padding = string.rep(" ", vim.api.nvim_win_get_width(prompt_win) - prompt_len - #text)
|
||||
vim.api.nvim_buf_clear_namespace(prompt_bufnr, ns_telescope_prompt, 0, -1)
|
||||
vim.api.nvim_buf_set_virtual_text(prompt_bufnr, ns_telescope_prompt, 0, { { padding .. text, "NonText" } }, {})
|
||||
|
||||
-- TODO: Wait for bfredl
|
||||
@@ -1022,7 +997,7 @@ end
|
||||
function Picker:get_result_processor(find_id, prompt, status_updater)
|
||||
local cb_add = function(score, entry)
|
||||
self.manager:add_entry(self, score, entry)
|
||||
status_updater()
|
||||
status_updater { completed = false }
|
||||
end
|
||||
|
||||
local cb_filter = function(_)
|
||||
@@ -1030,7 +1005,7 @@ function Picker:get_result_processor(find_id, prompt, status_updater)
|
||||
end
|
||||
|
||||
return function(entry)
|
||||
if find_id ~= self._find_id or self.closed or self:is_done() then
|
||||
if find_id ~= self._find_id then
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -1059,61 +1034,62 @@ function Picker:get_result_processor(find_id, prompt, status_updater)
|
||||
end
|
||||
|
||||
function Picker:get_result_completor(results_bufnr, find_id, prompt, status_updater)
|
||||
return function()
|
||||
return vim.schedule_wrap(function()
|
||||
if self.closed == true or self:is_done() then
|
||||
return
|
||||
end
|
||||
|
||||
local selection_strategy = self.selection_strategy or "reset"
|
||||
self:_do_selection(prompt)
|
||||
|
||||
-- TODO: Either: always leave one result or make sure we actually clean up the results when nothing matches
|
||||
if selection_strategy == "row" then
|
||||
if self._selection_row == nil and self.default_selection_index ~= nil then
|
||||
self:set_selection(self:get_row(self.default_selection_index))
|
||||
else
|
||||
self:set_selection(self:get_selection_row())
|
||||
end
|
||||
elseif selection_strategy == "follow" then
|
||||
if self._selection_row == nil and self.default_selection_index ~= nil then
|
||||
self:set_selection(self:get_row(self.default_selection_index))
|
||||
else
|
||||
local index = self.manager:find_entry(self:get_selection())
|
||||
|
||||
if index then
|
||||
local follow_row = self:get_row(index)
|
||||
self:set_selection(follow_row)
|
||||
else
|
||||
self:set_selection(self:get_reset_row())
|
||||
end
|
||||
end
|
||||
elseif selection_strategy == "reset" then
|
||||
if self.default_selection_index ~= nil then
|
||||
self:set_selection(self:get_row(self.default_selection_index))
|
||||
else
|
||||
self:set_selection(self:get_reset_row())
|
||||
end
|
||||
elseif selection_strategy == "closest" then
|
||||
if prompt == "" and self.default_selection_index ~= nil then
|
||||
self:set_selection(self:get_row(self.default_selection_index))
|
||||
else
|
||||
self:set_selection(self:get_reset_row())
|
||||
end
|
||||
else
|
||||
error("Unknown selection strategy: " .. selection_strategy)
|
||||
end
|
||||
|
||||
local current_line = vim.api.nvim_get_current_line():sub(self.prompt_prefix:len() + 1)
|
||||
state.set_global_key("current_line", current_line)
|
||||
|
||||
status_updater()
|
||||
state.set_global_key("current_line", self:_get_prompt())
|
||||
status_updater { completed = true }
|
||||
|
||||
self:clear_extra_rows(results_bufnr)
|
||||
self:highlight_displayed_rows(results_bufnr, prompt)
|
||||
if self.sorter then
|
||||
self.sorter:_finish(prompt)
|
||||
end
|
||||
self.sorter:_finish(prompt)
|
||||
|
||||
self:_on_complete()
|
||||
|
||||
self._result_completed = true
|
||||
end)
|
||||
end
|
||||
|
||||
function Picker:_do_selection(prompt)
|
||||
local selection_strategy = self.selection_strategy or "reset"
|
||||
-- TODO: Either: always leave one result or make sure we actually clean up the results when nothing matches
|
||||
if selection_strategy == "row" then
|
||||
if self._selection_row == nil and self.default_selection_index ~= nil then
|
||||
self:set_selection(self:get_row(self.default_selection_index))
|
||||
else
|
||||
self:set_selection(self:get_selection_row())
|
||||
end
|
||||
elseif selection_strategy == "follow" then
|
||||
if self._selection_row == nil and self.default_selection_index ~= nil then
|
||||
self:set_selection(self:get_row(self.default_selection_index))
|
||||
else
|
||||
local index = self.manager:find_entry(self:get_selection())
|
||||
|
||||
if index then
|
||||
local follow_row = self:get_row(index)
|
||||
self:set_selection(follow_row)
|
||||
else
|
||||
self:set_selection(self:get_reset_row())
|
||||
end
|
||||
end
|
||||
elseif selection_strategy == "reset" then
|
||||
if self.default_selection_index ~= nil then
|
||||
self:set_selection(self:get_row(self.default_selection_index))
|
||||
else
|
||||
self:set_selection(self:get_reset_row())
|
||||
end
|
||||
elseif selection_strategy == "closest" then
|
||||
if prompt == "" and self.default_selection_index ~= nil then
|
||||
self:set_selection(self:get_row(self.default_selection_index))
|
||||
else
|
||||
self:set_selection(self:get_reset_row())
|
||||
end
|
||||
else
|
||||
error("Unknown selection strategy: " .. selection_strategy)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1169,6 +1145,18 @@ function Picker:_reset_highlights()
|
||||
self.highlighter:clear_display()
|
||||
end
|
||||
|
||||
function Picker:_detach()
|
||||
self.finder:close()
|
||||
|
||||
-- TODO: Can we add a "cleanup" / "teardown" function that completely removes these.
|
||||
-- self.finder = nil
|
||||
-- self.previewer = nil
|
||||
-- self.sorter = nil
|
||||
-- self.manager = nil
|
||||
|
||||
self.closed = true
|
||||
end
|
||||
|
||||
pickers._Picker = Picker
|
||||
|
||||
return pickers
|
||||
|
||||
Reference in New Issue
Block a user