From dea927d0eb22255632dd82d2efff1a68d621c871 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Fri, 8 Oct 2021 07:56:01 -0700 Subject: [PATCH] feat: Add scrolling through results (#1232) * some scrollin * [WIP]: Fri 10 Sep 2021 02:24:20 PM EDT * ok, I think scrolling works * change to 1000 for now, dont need to scroll that far :) --- ftplugin/TelescopeResults.lua | 2 + lua/telescope/entry_manager.lua | 21 --- lua/telescope/finders/async_job_finder.lua | 1 + lua/telescope/log.lua | 4 +- lua/telescope/pickers.lua | 189 +++++++++++---------- 5 files changed, 101 insertions(+), 116 deletions(-) create mode 100644 ftplugin/TelescopeResults.lua diff --git a/ftplugin/TelescopeResults.lua b/ftplugin/TelescopeResults.lua new file mode 100644 index 0000000..1b93571 --- /dev/null +++ b/ftplugin/TelescopeResults.lua @@ -0,0 +1,2 @@ +-- Don't have scrolloff, it makes things weird. +vim.opt.scrolloff = 0 diff --git a/lua/telescope/entry_manager.lua b/lua/telescope/entry_manager.lua index 5055171..1e94c80 100644 --- a/lua/telescope/entry_manager.lua +++ b/lua/telescope/entry_manager.lua @@ -2,27 +2,6 @@ local log = require "telescope.log" local LinkedList = require "telescope.algos.linked_list" ---[[ - -OK, new idea. -We can do linked list here. -To convert at the end to quickfix, just run the list. -... - -start node -end node - -if past loop of must have scores, - then we can just add to end node and shift end node to current node. - etc. - - - always inserts a row, because we clear everything before? - - can also optimize by keeping worst acceptable score around. - ---]] - local EntryManager = {} EntryManager.__index = EntryManager diff --git a/lua/telescope/finders/async_job_finder.lua b/lua/telescope/finders/async_job_finder.lua index 17799e8..5a96d1d 100644 --- a/lua/telescope/finders/async_job_finder.lua +++ b/lua/telescope/finders/async_job_finder.lua @@ -7,6 +7,7 @@ local log = require "telescope.log" return function(opts) log.trace("Creating async_job:", opts) local entry_maker = opts.entry_maker or make_entry.gen_from_string() + local fn_command = function(prompt) local command_list = opts.command_generator(prompt) if command_list == nil then diff --git a/lua/telescope/log.lua b/lua/telescope/log.lua index 0d2e0eb..c74378f 100644 --- a/lua/telescope/log.lua +++ b/lua/telescope/log.lua @@ -1,6 +1,4 @@ -local user = vim.loop.os_getenv "USER" - return require("plenary.log").new { plugin = "telescope", - level = ((user == "tj" or user == "tjdevries") and "debug") or "warn", + level = "info", } diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index 09a062f..6b9db5e 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -213,28 +213,17 @@ function Picker:clear_extra_rows(results_bufnr) end if not ok then - log.debug(msg) + log.debug("Failed to set lines:", msg) end log.trace("Clearing:", worst_line) end -function Picker:highlight_displayed_rows(results_bufnr, prompt) - if not self.sorter or not self.sorter.highlighter then +function Picker:highlight_one_row(results_bufnr, prompt, display, row) + if not self.sorter.highlighter then return end - vim.api.nvim_buf_clear_namespace(results_bufnr, ns_telescope_matching, 0, -1) - - local displayed_rows = vim.api.nvim_buf_get_lines(results_bufnr, 0, -1, false) - for row_index = 1, math.min(#displayed_rows, self.max_results) do - local display = displayed_rows[row_index] - - self:highlight_one_row(results_bufnr, prompt, display, row_index - 1) - end -end - -function Picker:highlight_one_row(results_bufnr, prompt, display, row) local highlights = self.sorter:highlighter(prompt, display) if highlights then @@ -352,22 +341,19 @@ function Picker:find() self.prompt_prefix = prompt_prefix self:_reset_prefix_color() - -- First thing we want to do is set all the lines to blank. - self.max_results = popup_opts.results.height + -- TODO: This could be configurable in the future, but I don't know why you would + -- want to scroll through more than 10,000 items. + -- + -- This just lets us stop doing stuff after tons of things. + self.max_results = 1000 - -- 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 tx, rx = channel.mpsc() - self.__on_lines = tx.send + self._on_lines = tx.send local find_id = self:_next_find_id() @@ -412,25 +398,14 @@ function Picker:find() 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 - - local new_finder = on_input_result.updated_finder - if new_finder then - self.finder:close() - self.finder = new_finder - end + local prompt = self:_get_next_filtered_prompt() -- TODO: Entry manager should have a "bulk" setter. This can prevent a lot of redraws from display if self.cache_picker == false or not (self.cache_picker.is_cached == true) then self.sorter:_start(prompt) self.manager = EntryManager:new(self.max_results, self.entry_adder, self.stats) + vim.api.nvim_buf_clear_namespace(results_bufnr, ns_telescope_matching, 0, -1) 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) @@ -447,26 +422,8 @@ function Picker:find() async.util.sleep(self.debounce - diff_time) end else - -- resume previous picker - local index = 1 - for entry in self.manager:iter() do - self:entry_adder(index, entry, _, true) - index = index + 1 - end - self.cache_picker.is_cached = false - -- if text changed, required to set anew to restart finder; otherwise hl and selection - if self.cache_picker.cached_prompt ~= self.default_text then - self:reset_prompt() - self:set_prompt(self.default_text) - else - -- scheduling required to apply highlighting and selection appropriately - await_schedule(function() - self:highlight_displayed_rows(self.results_bufnr, self.cache_picker.cached_prompt) - if self.cache_picker.selection_row ~= nil then - self:set_selection(self.cache_picker.selection_row) - end - end) - end + -- TODO(scroll): This can only happen once, I don't like where it is. + self:_resume_picker() end end end) @@ -476,10 +433,10 @@ function Picker:find() on_lines = function(...) find_id = self:_next_find_id() - self._result_completed = false status_updater { completed = false } - tx.send(...) + self._on_lines(...) end, + on_detach = function() self:_detach() end, @@ -753,7 +710,7 @@ function Picker:refresh(finder, opts) self._multi = MultiSelect:new() end - self.__on_lines(nil, nil, nil, 0, 1) + self._on_lines(nil, nil, nil, 0, 1) end function Picker:set_selection(row) @@ -856,6 +813,8 @@ function Picker:set_selection(row) self._selection_row = row self:refresh_previewer() + + vim.api.nvim_win_set_cursor(self.results_win, { row + 1, 0 }) end function Picker:refresh_previewer() @@ -918,43 +877,41 @@ function Picker:entry_adder(index, entry, _, insert) self:_increment "displayed" - -- TODO: Don't need to schedule this if we schedule the adder. local offset = insert and 0 or 1 - vim.schedule(function() - if not vim.api.nvim_buf_is_valid(self.results_bufnr) then - log.debug "ON_ENTRY: Invalid buffer" - return - end + if not vim.api.nvim_buf_is_valid(self.results_bufnr) then + log.debug "ON_ENTRY: Invalid buffer" + return + end - -- TODO: Does this every get called? - -- local line_count = vim.api.nvim_win_get_height(self.results_win) - local line_count = vim.api.nvim_buf_line_count(self.results_bufnr) - if row > line_count then - return - end + -- TODO: Does this every get called? + -- local line_count = vim.api.nvim_win_get_height(self.results_win) + local line_count = vim.api.nvim_buf_line_count(self.results_bufnr) + if row > line_count then + return + end - if insert then - if self.sorting_strategy == "descending" then - vim.api.nvim_buf_set_lines(self.results_bufnr, 0, 1, false, {}) - end + if insert then + if self.sorting_strategy == "descending" then + vim.api.nvim_buf_set_lines(self.results_bufnr, 0, 1, false, {}) end + end - local set_ok, msg = pcall(vim.api.nvim_buf_set_lines, self.results_bufnr, row, row + offset, false, { display }) - if set_ok and display_highlights then - self.highlighter:hi_display(row, prefix, display_highlights) - end + local set_ok, msg = pcall(vim.api.nvim_buf_set_lines, self.results_bufnr, row, row + offset, false, { display }) + if set_ok and display_highlights then + self.highlighter:hi_display(row, prefix, display_highlights) + self:highlight_one_row(self.results_bufnr, self:_get_prompt(), display, row) + end - if not set_ok then - log.debug("Failed to set lines...", msg) - end + if not set_ok then + log.debug("Failed to set lines...", msg) + end - -- This pretty much only fails when people leave newlines in their results. - -- So we'll clean it up for them if it fails. - if not set_ok and display:find "\n" then - display = display:gsub("\n", " | ") - vim.api.nvim_buf_set_lines(self.results_bufnr, row, row + 1, false, { display }) - end - end) + -- This pretty much only fails when people leave newlines in their results. + -- So we'll clean it up for them if it fails. + if not set_ok and display:find "\n" then + display = display:gsub("\n", " | ") + vim.api.nvim_buf_set_lines(self.results_bufnr, row, row + 1, false, { display }) + end end function Picker:_reset_track() @@ -1025,6 +982,8 @@ function Picker:get_status_updater(prompt_win, prompt_bufnr) end function Picker:get_result_processor(find_id, prompt, status_updater) + local count = 0 + local cb_add = function(score, entry) self.manager:add_entry(self, score, entry) status_updater { completed = false } @@ -1045,6 +1004,8 @@ function Picker:get_result_processor(find_id, prompt, status_updater) return end + count = count + 1 + -- TODO: Probably should asyncify this / cache this / do something because this probably takes -- a ton of time on large results. log.trace("Processing result... ", entry) @@ -1060,6 +1021,13 @@ function Picker:get_result_processor(find_id, prompt, status_updater) end self.sorter:score(prompt, entry, cb_add, cb_filter) + + -- Only on the first addition do we want to set the selection. + -- This allows us to handle moving the cursor to the bottom or top of the window + -- depending on the strategy. + if count == 1 then + self:_do_selection(prompt) + end end end @@ -1075,12 +1043,9 @@ function Picker:get_result_completor(results_bufnr, find_id, prompt, status_upda status_updater { completed = true } self:clear_extra_rows(results_bufnr) - self:highlight_displayed_rows(results_bufnr, prompt) self.sorter:_finish(prompt) self:_on_complete() - - self._result_completed = true end) end @@ -1226,6 +1191,46 @@ function Picker:_detach() self.closed = true end +function Picker:_get_next_filtered_prompt() + 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 + + local new_finder = on_input_result.updated_finder + if new_finder then + self.finder:close() + self.finder = new_finder + end + + return prompt +end + +function Picker:_resume_picker() + -- resume previous picker + local index = 1 + for entry in self.manager:iter() do + self:entry_adder(index, entry, _, true) + index = index + 1 + end + self.cache_picker.is_cached = false + -- if text changed, required to set anew to restart finder; otherwise hl and selection + if self.cache_picker.cached_prompt ~= self.default_text then + self:reset_prompt() + self:set_prompt(self.default_text) + else + -- scheduling required to apply highlighting and selection appropriately + await_schedule(function() + if self.cache_picker.selection_row ~= nil then + self:set_selection(self.cache_picker.selection_row) + end + end) + end +end + pickers._Picker = Picker return pickers