From ae7fd0d27a72fa3088e84992eb5040853164dad6 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Tue, 29 Sep 2020 22:52:38 -0400 Subject: [PATCH] feat: Add status, better debug, and some associated refactors * [WIP]: Mon 28 Sep 2020 01:08:24 PM EDT * add much much better tracking. so much less hax * status updates, oneshot job updates, etc. * remove temp function * add status function * asdfasdfasdf --- lua/telescope/actions.lua | 5 + lua/telescope/builtin.lua | 2 +- lua/telescope/config.lua | 48 +++ lua/telescope/debounce.lua | 179 ++++++++ lua/telescope/entry_manager.lua | 131 ++++++ lua/telescope/finders.lua | 178 +++++--- lua/telescope/init.lua | 34 -- lua/telescope/log.lua | 2 +- lua/telescope/pickers.lua | 454 ++++++++++----------- lua/telescope/sorters.lua | 128 +++--- lua/tests/automated/find_and_sort_spec.lua | 2 - lua/tests/automated/telescope_spec.lua | 23 +- lua/tests/manual/auto_picker.lua | 80 ++++ lua/tests/manual/large_search.lua | 4 - lua/tests/manual/slow_oneshot.lua | 71 ++++ plugin/telescope.vim | 6 +- scratch/how_long_does_time_take.lua | 8 + scratch/new_index.lua | 9 + scratch/old_perf_debug | 36 ++ scratch/simple_rg.lua | 2 +- scratch/slow_proc.sh | 1 + scratch/temp_goals.txt | 17 + 22 files changed, 981 insertions(+), 439 deletions(-) create mode 100644 lua/telescope/debounce.lua create mode 100644 lua/telescope/entry_manager.lua create mode 100644 lua/tests/manual/auto_picker.lua create mode 100644 lua/tests/manual/slow_oneshot.lua create mode 100644 scratch/how_long_does_time_take.lua create mode 100644 scratch/new_index.lua create mode 100644 scratch/old_perf_debug create mode 100644 scratch/temp_goals.txt diff --git a/lua/telescope/actions.lua b/lua/telescope/actions.lua index c61d5f3..026d5ad 100644 --- a/lua/telescope/actions.lua +++ b/lua/telescope/actions.lua @@ -31,6 +31,11 @@ function actions.move_selection_previous(prompt_bufnr) actions.shift_current_selection(prompt_bufnr, -1) end +function actions.add_selection(prompt_bufnr) + local current_picker = actions.get_current_picker(prompt_bufnr) + current_picker:add_selection(current_picker:get_selection_row()) +end + --- Get the current entry function actions.get_selected_entry(prompt_bufnr) return actions.get_current_picker(prompt_bufnr):get_selection() diff --git a/lua/telescope/builtin.lua b/lua/telescope/builtin.lua index c573d5c..9f339a1 100644 --- a/lua/telescope/builtin.lua +++ b/lua/telescope/builtin.lua @@ -518,7 +518,7 @@ builtin.find_files = function(opts) opts ), previewer = previewers.cat.new(opts), - sorter = sorters.get_fuzzy_file(), + sorter = sorters.get_fuzzy_file(opts), }):find() end diff --git a/lua/telescope/config.lua b/lua/telescope/config.lua index 9b1677e..9c68d05 100644 --- a/lua/telescope/config.lua +++ b/lua/telescope/config.lua @@ -12,6 +12,8 @@ local function first_non_null(...) end end +local actions = require('telescope.actions') + -- TODO: Add other major configuration points here. -- selection_strategy @@ -42,6 +44,8 @@ function config.set_defaults(defaults) set("border", {}) set("borderchars", { '─', '│', '─', '│', '╭', '╮', '╯', '╰'}) + set("get_status_text", function(self) return string.format("%s / %s", self.stats.processed - self.stats.filtered, self.stats.processed) end) + -- Builtin configuration -- List that will be executed. @@ -51,6 +55,50 @@ function config.set_defaults(defaults) -- TODO: Shortenpath -- Decide how to propagate that to all the opts everywhere. + -- TODO: Add motions to keybindings + -- TODO: Add relative line numbers? + set("default_mappings", { + i = { + [""] = actions.move_selection_next, + [""] = actions.move_selection_previous, + + [""] = actions.close, + + [""] = actions.move_selection_next, + [""] = actions.move_selection_previous, + + [""] = actions.goto_file_selection_edit, + [""] = actions.goto_file_selection_split, + [""] = actions.goto_file_selection_vsplit, + [""] = actions.goto_file_selection_tabedit, + + [""] = actions.preview_scrolling_up, + [""] = actions.preview_scrolling_down, + + -- TODO: When we implement multi-select, you can turn this back on :) + -- [""] = actions.add_selection, + }, + + n = { + [""] = actions.close, + [""] = actions.goto_file_selection_edit, + [""] = actions.goto_file_selection_split, + [""] = actions.goto_file_selection_vsplit, + [""] = actions.goto_file_selection_tabedit, + + -- TODO: This would be weird if we switch the ordering. + ["j"] = actions.move_selection_next, + ["k"] = actions.move_selection_previous, + + [""] = actions.move_selection_next, + [""] = actions.move_selection_previous, + + [""] = actions.preview_scrolling_up, + [""] = actions.preview_scrolling_down, + }, + }) + + -- NOT STABLE. DO NOT USE set("horizontal_config", { get_preview_width = function(columns, _) diff --git a/lua/telescope/debounce.lua b/lua/telescope/debounce.lua new file mode 100644 index 0000000..329cdc5 --- /dev/null +++ b/lua/telescope/debounce.lua @@ -0,0 +1,179 @@ +-- Credit: https://gist.github.com/runiq/31aa5c4bf00f8e0843cd267880117201 +-- + +local M = {} + +---Validates args for `throttle()` and `debounce()`. +local function td_validate(fn, ms) + vim.validate{ + fn = { fn, 'f' }, + ms = { + ms, + function(ms) + return type(ms) == 'number' and ms > 0 + end, + "number > 0", + }, + } +end + +--- Throttles a function on the leading edge. Automatically `schedule_wrap()`s. +--- +--@param fn (function) Function to throttle +--@param timeout (number) Timeout in ms +--@returns (function, timer) throttled function and timer. Remember to call +---`timer:close()` at the end or you will leak memory! +function M.throttle_leading(fn, ms) + td_validate(fn, ms) + local timer = vim.loop.new_timer() + local running = false + + local function wrapped_fn(...) + if not running then + timer:start(ms, 0, function() + running = false + end) + running = true + pcall(vim.schedule_wrap(fn), select(1, ...)) + end + end + return wrapped_fn, timer +end + +--- Throttles a function on the trailing edge. Automatically +--- `schedule_wrap()`s. +--- +--@param fn (function) Function to throttle +--@param timeout (number) Timeout in ms +--@param last (boolean, optional) Whether to use the arguments of the last +---call to `fn` within the timeframe. Default: Use arguments of the first call. +--@returns (function, timer) Throttled function and timer. Remember to call +---`timer:close()` at the end or you will leak memory! +function M.throttle_trailing(fn, ms, last) + td_validate(fn, ms) + local timer = vim.loop.new_timer() + local running = false + + local wrapped_fn + if not last then + function wrapped_fn(...) + if not running then + local argv = {...} + local argc = select('#', ...) + + timer:start(ms, 0, function() + running = false + pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) + end) + running = true + end + end + else + local argv, argc + function wrapped_fn(...) + argv = {...} + argc = select('#', ...) + + if not running then + timer:start(ms, 0, function() + running = false + pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) + end) + running = true + end + end + end + return wrapped_fn, timer +end + +--- Debounces a function on the leading edge. Automatically `schedule_wrap()`s. +--- +--@param fn (function) Function to debounce +--@param timeout (number) Timeout in ms +--@returns (function, timer) Debounced function and timer. Remember to call +---`timer:close()` at the end or you will leak memory! +function M.debounce_leading(fn, ms) + td_validate(fn, ms) + local timer = vim.loop.new_timer() + local running = false + + local function wrapped_fn(...) + timer:start(ms, 0, function() + running = false + end) + + if not running then + running = true + pcall(vim.schedule_wrap(fn), select(1, ...)) + end + end + return wrapped_fn, timer +end + +--- Debounces a function on the trailing edge. Automatically +--- `schedule_wrap()`s. +--- +--@param fn (function) Function to debounce +--@param timeout (number) Timeout in ms +--@param first (boolean, optional) Whether to use the arguments of the first +---call to `fn` within the timeframe. Default: Use arguments of the last call. +--@returns (function, timer) Debounced function and timer. Remember to call +---`timer:close()` at the end or you will leak memory! +function M.debounce_trailing(fn, ms, first) + td_validate(fn, ms) + local timer = vim.loop.new_timer() + local wrapped_fn + + if not first then + function wrapped_fn(...) + local argv = {...} + local argc = select('#', ...) + + timer:start(ms, 0, function() + pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) + end) + end + else + local argv, argc + function wrapped_fn(...) + argv = argv or {...} + argc = argc or select('#', ...) + + timer:start(ms, 0, function() + pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) + end) + end + end + return wrapped_fn, timer +end + +--- Test deferment methods (`{throttle,debounce}_{leading,trailing}()`). +--- +--@param bouncer (string) Bouncer function to test +--@param ms (number, optional) Timeout in ms, default 2000. +--@param firstlast (bool, optional) Whether to use the 'other' fn call +---strategy. +function M.test_defer(bouncer, ms, firstlast) + local bouncers = { + tl = M.throttle_leading, + tt = M.throttle_trailing, + dl = M.debounce_leading, + dt = M.debounce_trailing, + } + + local timeout = ms or 2000 + + local bounced = bouncers[bouncer]( + function(i) vim.cmd('echom "' .. bouncer .. ': ' .. i .. '"') end, + timeout, + firstlast + ) + + for i, _ in ipairs{1,2,3,4,5} do + bounced(i) + vim.schedule(function () vim.cmd('echom ' .. i) end) + vim.fn.call("wait", {1000, "v:false"}) + end +end + +return M diff --git a/lua/telescope/entry_manager.lua b/lua/telescope/entry_manager.lua new file mode 100644 index 0000000..bca4e55 --- /dev/null +++ b/lua/telescope/entry_manager.lua @@ -0,0 +1,131 @@ +local log = require("telescope.log") + +local EntryManager = {} +EntryManager.__index = EntryManager + +function EntryManager:new(max_results, set_entry, info) + log.trace("Creating entry_manager...") + + info = info or {} + info.looped = 0 + info.inserted = 0 + + -- state contains list of + -- { + -- score = ... + -- line = ... + -- metadata ? ... + -- } + local entry_state = {} + + set_entry = set_entry or function() end + + return setmetatable({ + set_entry = set_entry, + max_results = max_results, + worst_acceptable_score = math.huge, + + entry_state = entry_state, + info = info, + + num_results = function() + return #entry_state + end, + + get_ordinal = function(self, index) + return self:get_entry(index).ordinal + end, + + get_entry = function(_, index) + return (entry_state[index] or {}).entry + end, + + get_score = function(_, index) + return (entry_state[index] or {}).score + end, + + find_entry = function(_, entry) + if entry == nil then + return nil + end + + for k, v in ipairs(entry_state) do + local existing_entry = v.entry + + -- FIXME: This has the problem of assuming that display will not be the same for two different entries. + if existing_entry.display == entry.display then + return k + end + end + + return nil + end, + + _get_state = function() + return entry_state + end, + }, self) +end + +function EntryManager:should_save_result(index) + return index <= self.max_results +end + +function EntryManager:add_entry(score, entry) + score = score or 0 + + if score >= self.worst_acceptable_score then + return + end + + for index, item in ipairs(self.entry_state) do + self.info.looped = self.info.looped + 1 + + if item.score > score then + return self:insert(index, { + score = score, + entry = entry, + }) + end + + -- Don't add results that are too bad. + if not self:should_save_result(index) then + return + end + end + + return self:insert({ + score = score, + entry = entry, + }) +end + +function EntryManager:insert(index, entry) + if entry == nil then + entry = index + index = #self.entry_state + 1 + end + + -- To insert something, we place at the next available index (or specified index) + -- and then shift all the corresponding items one place. + local next_entry, last_score + repeat + self.info.inserted = self.info.inserted + 1 + next_entry = self.entry_state[index] + + self.set_entry(index, entry.entry) + self.entry_state[index] = entry + + last_score = entry.score + + index = index + 1 + entry = next_entry + until not next_entry or not self:should_save_result(index) + + if not self:should_save_result(index) then + self.worst_acceptable_score = last_score + end +end + + +return EntryManager diff --git a/lua/telescope/finders.lua b/lua/telescope/finders.lua index e9f2b98..a9222e7 100644 --- a/lua/telescope/finders.lua +++ b/lua/telescope/finders.lua @@ -5,11 +5,6 @@ local log = require('telescope.log') local finders = {} --- TODO: We should make a few different "FinderGenerators": --- SimpleListFinder(my_list) --- FunctionFinder(my_func) --- JobFinder(my_job_args) - local _callable_obj = function() local obj = {} @@ -39,19 +34,11 @@ function JobFinder:new(opts) opts = opts or {} assert(not opts.results, "`results` should be used with finder.new_table") - -- TODO: - -- - `types` - -- job - -- pipe - -- vim.loop.new_pipe (stdin / stdout). stdout => filter pipe - -- rg huge_search | fzf --filter prompt_is > buffer. buffer could do stuff do w/ preview callback + assert(not opts.static, "`static` should be used with finder.new_oneshot_job") local obj = setmetatable({ entry_maker = opts.entry_maker or make_entry.from_string, fn_command = opts.fn_command, - static = opts.static, - state = {}, - cwd = opts.cwd, writer = opts.writer, @@ -64,60 +51,30 @@ function JobFinder:new(opts) end function JobFinder:_find(prompt, process_result, process_complete) - START = vim.loop.hrtime() - PERF() - PERF('starting...') + log.trace("Finding...") if self.job and not self.job.is_shutdown then - PERF('...had to shutdown') + log.debug("Shutting down old job") self.job:shutdown() end - log.trace("Finding...") - if self.static and self.done then - log.trace("Using previous results") - for _, v in ipairs(self._cached_lines) do - process_result(v) - end - - process_complete() - PERF('Num Lines: ', self._cached_lines) - PERF('...finished static') - - COMPLETED = true - return - end - - self.done = false - self._cached_lines = {} - local on_output = function(_, line, _) - if not line then + if not line or line == "" then return end - if line ~= "" then - if self.entry_maker then - line = self.entry_maker(line) - end - - process_result(line) - - if self.static then - table.insert(self._cached_lines, line) - end + if self.entry_maker then + line = self.entry_maker(line) end + + process_result(line) end - -- TODO: How to just literally pass a list... - -- TODO: How to configure what should happen here - -- TODO: How to run this over and over? 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 - print("WOW A JOB") writer = opts.writer elseif opts.writer then writer = Job:new(opts.writer) @@ -138,17 +95,124 @@ function JobFinder:_find(prompt, process_result, process_complete) on_stderr = on_output, on_exit = function() - self.done = true - process_complete() - - PERF('done') end, } self.job:start() end +local OneshotJobFinder = _callable_obj() + +function OneshotJobFinder: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({ + fn_command = opts.fn_command, + entry_maker = opts.entry_maker or make_entry.from_string, + + cwd = opts.cwd, + writer = opts.writer, + + maximum_results = opts.maximum_results, + + _started = false, + }, self) + + obj._find = coroutine.wrap(function(finder, _, process_result, process_complete) + local num_execution = 1 + local num_results = 0 + + local results = setmetatable({}, { + __newindex = function(t, k, v) + rawset(t, k, v) + process_result(v) + end + }) + + local job_opts = finder:fn_command(prompt) + if not job_opts then + error(debug.trackeback("expected `job_opts` from fn_command")) + end + + local writer = nil + if job_opts.writer and Job.is_job(job_opts.writer) then + writer = job_opts.writer + elseif job_opts.writer then + writer = Job:new(job_opts.writer) + end + + local on_output = function(_, line) + -- This will call the metamethod, process_result + num_results = num_results + 1 + results[num_results] = finder.entry_maker(line) + end + + local completed = false + local job = Job:new { + command = job_opts.command, + args = job_opts.args, + cwd = job_opts.cwd or finder.cwd, + + maximum_results = finder.maximum_results, + + writer = writer, + + enable_recording = false, + + on_stdout = on_output, + on_stderr = on_output, + + on_exit = function() + process_complete() + completed = true + end, + } + + job:start() + + while true do + finder, _, process_result, process_complete = coroutine.yield() + num_execution = num_execution + 1 + + log.debug("Using previous results", job.is_shutdown, completed, num_execution) + + local current_count = num_results + for index = 1, current_count do + process_result(results[index]) + end + + if completed then + process_complete() + end + end + end) + + return obj +end + +function OneshotJobFinder:old_find(_, process_result, process_complete) + local first_run = false + + if not self._started then + first_run = true + + self._started = true + + end + + -- First time we get called, start on up that job. + -- Every time after that, just use the results LUL + if not first_run then + return + end +end + + + --[[ ============================================================= Static Finders @@ -239,9 +303,7 @@ finders.new_oneshot_job = function(command_list, opts) local command = table.remove(command_list, 1) - return JobFinder:new { - static = true, - + return OneshotJobFinder:new { entry_maker = opts.entry_maker or make_entry.gen_from_string(), cwd = opts.cwd, diff --git a/lua/telescope/init.lua b/lua/telescope/init.lua index ecec931..d6f0192 100644 --- a/lua/telescope/init.lua +++ b/lua/telescope/init.lua @@ -51,38 +51,4 @@ function telescope.setup(opts) require('telescope.config').set_defaults(opts.defaults) end --- Until I have better profiling stuff, this will have to do. -PERF = function(...) end -PERF_DEBUG = PERF_DEBUG or nil -START = nil - -if PERF_DEBUG then - PERF = function(...) - local new_time = (vim.loop.hrtime() - START) / 1E9 - if select('#', ...) == 0 then - vim.schedule(function() - vim.api.nvim_buf_set_lines(PERF_DEBUG, -1, -1, false, { '' }) - end) - return - end - - local to_insert = '' - if START then - to_insert = tostring(new_time) .. ' | ' - end - - for _, v in ipairs({...}) do - if type(v) == 'table' then - to_insert = to_insert .. tostring(#v) .. ' | ' - else - to_insert = to_insert .. tostring(v) .. ' | ' - end - end - - vim.schedule(function() - vim.api.nvim_buf_set_lines(PERF_DEBUG, -1, -1, false, { to_insert }) - end) - end -end - return telescope diff --git a/lua/telescope/log.lua b/lua/telescope/log.lua index 2796dbb..9beabb1 100644 --- a/lua/telescope/log.lua +++ b/lua/telescope/log.lua @@ -1,5 +1,5 @@ return require('plenary.log').new { plugin = 'telescope', - level = (vim.loop.os_getenv("USER") == 'tj' and 'debug') or 'info', + level = (vim.loop.os_getenv("USER") == 'tj' and 'debug') or 'warn', } diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index db6ef5d..b599bb4 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -3,6 +3,7 @@ local popup = require('popup') local actions = require('telescope.actions') local config = require('telescope.config') +local debounce = require('telescope.debounce') local resolve = require('telescope.config.resolve') local layout_strategies = require('telescope.pickers.layout_strategies') local log = require('telescope.log') @@ -10,6 +11,8 @@ local mappings = require('telescope.mappings') local state = require('telescope.state') local utils = require('telescope.utils') +local EntryManager = require('telescope.entry_manager') + local get_default = utils.get_default -- TODO: Make this work with deep extend I think. @@ -26,49 +29,10 @@ end local ns_telescope_selection = a.nvim_create_namespace('telescope_selection') local ns_telescope_matching = a.nvim_create_namespace('telescope_matching') +local ns_telescope_prompt = a.nvim_create_namespace('telescope_prompt') local pickers = {} --- TODO: Add motions to keybindings --- TODO: Add relative line numbers? -local default_mappings = { - i = { - [""] = actions.move_selection_next, - [""] = actions.move_selection_previous, - - [""] = actions.close, - - [""] = actions.move_selection_next, - [""] = actions.move_selection_previous, - - [""] = actions.goto_file_selection_edit, - [""] = actions.goto_file_selection_split, - [""] = actions.goto_file_selection_vsplit, - [""] = actions.goto_file_selection_tabedit, - - [""] = actions.preview_scrolling_up, - [""] = actions.preview_scrolling_down, - }, - - n = { - [""] = actions.close, - [""] = actions.goto_file_selection_edit, - [""] = actions.goto_file_selection_split, - [""] = actions.goto_file_selection_vsplit, - [""] = actions.goto_file_selection_tabedit, - - -- TODO: This would be weird if we switch the ordering. - ["j"] = actions.move_selection_next, - ["k"] = actions.move_selection_previous, - - [""] = actions.move_selection_next, - [""] = actions.move_selection_previous, - - [""] = actions.preview_scrolling_up, - [""] = actions.preview_scrolling_down, - }, -} - -- Picker takes a function (`get_window_options`) that returns the configurations required for three windows: -- prompt -- results @@ -102,18 +66,11 @@ function Picker:new(opts) sorter = opts.sorter, previewer = opts.previewer, - -- opts.mappings => overwrites entire table - -- opts.override_mappings => merges your table in with defaults. - -- Add option to change default - -- opts.attach(bufnr) + _completion_callbacks = {}, + + track = get_default(opts.track, false), + stats = {}, - --[[ - function(map) - map('n', '', actions.close, [opts]) - telescope.apply_mapping - end - --]] - -- mappings = get_default(opts.mappings, default_mappings), attach_mappings = opts.attach_mappings, sorting_strategy = get_default(opts.sorting_strategy, config.values.sorting_strategy), @@ -121,6 +78,7 @@ function Picker:new(opts) layout_strategy = get_default(opts.layout_strategy, config.values.layout_strategy), get_window_options = opts.get_window_options, + get_status_text = get_default(opts.get_status_text, config.values.get_status_text), window = { -- TODO: This won't account for different layouts... @@ -199,7 +157,7 @@ function Picker:get_row(index) if self.sorting_strategy == 'ascending' then return index - 1 else - return self.max_results - index + 1 + return self.max_results - index end end @@ -211,7 +169,7 @@ function Picker:get_index(row) if self.sorting_strategy == 'ascending' then return row + 1 else - return self.max_results - row + 1 + return self.max_results - row end end @@ -219,14 +177,15 @@ function Picker:get_reset_row() if self.sorting_strategy == 'ascending' then return 0 else - return self.max_results + return self.max_results - 1 end end function Picker:clear_extra_rows(results_bufnr) + local worst_line if self.sorting_strategy == 'ascending' then local num_results = self.manager:num_results() - local worst_line = self.max_results - num_results + worst_line = self.max_results - num_results if worst_line <= 0 then return @@ -234,7 +193,7 @@ function Picker:clear_extra_rows(results_bufnr) vim.api.nvim_buf_set_lines(results_bufnr, num_results + 1, self.max_results, false, {}) else - local worst_line = self:get_row(self.manager:num_results()) + worst_line = self:get_row(self.manager:num_results()) if worst_line <= 0 then return end @@ -242,6 +201,8 @@ function Picker:clear_extra_rows(results_bufnr) local empty_lines = utils.repeated_table(worst_line, "") pcall(vim.api.nvim_buf_set_lines, results_bufnr, 0, worst_line, false, empty_lines) end + + log.debug("Clearing:", worst_line) end function Picker:highlight_displayed_rows(results_bufnr, prompt) @@ -260,7 +221,8 @@ function Picker:highlight_displayed_rows(results_bufnr, prompt) end function Picker:highlight_one_row(results_bufnr, prompt, display, row) - local highlights = self.sorter:highlighter(prompt, display) + local highlights = self:_track("_highlight_time", self.sorter.highlighter, self.sorter, prompt, display) + if highlights then for _, hl in ipairs(highlights) do local highlight, start, finish @@ -276,6 +238,8 @@ function Picker:highlight_one_row(results_bufnr, prompt, display, row) error('Invalid higlighter fn') end + self:_increment('highlights') + vim.api.nvim_buf_add_highlight( results_bufnr, ns_telescope_matching, @@ -319,7 +283,6 @@ function Picker:find() popup_opts.preview.minheight = popup_opts.preview.height end - -- TODO: Add back the borders after fixing some stuff in popup.nvim local results_win, results_opts = popup.create('', popup_opts.results) local results_bufnr = a.nvim_win_get_buf(results_win) @@ -334,8 +297,6 @@ function Picker:find() preview_win, preview_opts = popup.create('', popup_opts.preview) preview_bufnr = a.nvim_win_get_buf(preview_win) - -- TODO: For some reason, highlighting is kind of weird on these windows. - -- It may actually be my colorscheme tho... a.nvim_win_set_option(preview_win, 'winhl', 'Normal:TelescopeNormal') a.nvim_win_set_option(preview_win, 'winblend', self.window.winblend) end @@ -346,69 +307,99 @@ function Picker:find() a.nvim_win_set_option(prompt_win, 'winhl', 'Normal:TelescopeNormal') a.nvim_win_set_option(prompt_win, 'winblend', self.window.winblend) - -- a.nvim_buf_set_option(prompt_bufnr, 'buftype', 'prompt') - -- vim.fn.prompt_setprompt(prompt_bufnr, prompt_string) + -- Draw the screen ASAP. This makes things feel speedier. + vim.cmd [[redraw]] + + local prompt_prefix + if false then + prompt_prefix = " > " + a.nvim_buf_set_option(prompt_bufnr, 'buftype', 'prompt') + vim.fn.prompt_setprompt(prompt_bufnr, prompt_prefix) + else + prompt_prefix = "" + end -- First thing we want to do is set all the lines to blank. - self.max_results = popup_opts.results.height - 1 + self.max_results = popup_opts.results.height vim.api.nvim_buf_set_lines(results_bufnr, 0, self.max_results, false, utils.repeated_table(self.max_results, "")) local selection_strategy = self.selection_strategy or 'reset' + local update_status = function() + local text = self:get_status_text() + local current_prompt = vim.api.nvim_buf_get_lines(prompt_bufnr, 0, 1, false)[1] + if not current_prompt then + return + end + + if not vim.api.nvim_win_is_valid(prompt_win) then + return + end + + local padding = string.rep(" ", vim.api.nvim_win_get_width(prompt_win) - #current_prompt - #text - 3) + 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"} }, {}) + + self:_increment("status") + end + + local debounced_status = debounce.throttle_leading(update_status, 50) + local on_lines = function(_, _, _, first_line, last_line) + self:_reset_track() + if not vim.api.nvim_buf_is_valid(prompt_bufnr) then + log.debug("ON_LINES: Invalid prompt_bufnr", prompt_bufnr) return end if first_line > 0 or last_line > 1 then + log.debug("ON_LINES: Bad range", first_line, last_line) return end - local prompt = vim.api.nvim_buf_get_lines(prompt_bufnr, first_line, last_line, false)[1] + local prompt = vim.api.nvim_buf_get_lines(prompt_bufnr, first_line, last_line, false)[1]:sub(#prompt_prefix) -- TODO: Statusbar possibilities here. -- vim.api.nvim_buf_set_virtual_text(prompt_bufnr, 0, 1, { {"hello", "Error"} }, {}) - local filtered_amount = 0 - local displayed_amount = 0 - local displayed_fn_amount = 0 - -- TODO: Entry manager should have a "bulk" setter. This can prevent a lot of redraws from display - self.manager = pickers.entry_manager( - self.max_results, - vim.schedule_wrap(function(index, entry) + local entry_adder = function(index, entry) + local row = self:get_row(index) + + -- If it's less than 0, then we don't need to show it at all. + if row < 0 then + log.debug("ON_ENTRY: Weird row", row) + return + end + + local display + if type(entry.display) == 'function' then + self:_increment("display_fn") + display = entry:display() + elseif type(entry.display) == 'string' then + display = entry.display + else + log.info("Weird entry", entry) + return + end + + -- This is the two spaces to manage the '> ' stuff. + -- Maybe someday we can use extmarks or floaty text or something to draw this and not insert here. + -- until then, insert two spaces + display = ' ' .. display + + self:_increment("displayed") + + -- TODO: Don't need to schedule this if we schedule the adder. + vim.schedule(function() if not vim.api.nvim_buf_is_valid(results_bufnr) then + log.debug("ON_ENTRY: Invalid buffer") return end - local row = self:get_row(index) - -- If it's less than 0, then we don't need to show it at all. - if row < 0 then - return - end - -- TODO: Do we need to also make sure we don't have something bigger than max results? - - local display - if type(entry.display) == 'function' then - displayed_fn_amount = displayed_fn_amount + 1 - display = entry:display() - elseif type(entry.display) == 'string' then - display = entry.display - else - log.info("Weird entry", entry) - return - end - - -- This is the two spaces to manage the '> ' stuff. - -- Maybe someday we can use extmarks or floaty text or something to draw this and not insert here. - -- until then, insert two spaces - display = ' ' .. display - - displayed_amount = displayed_amount + 1 - - -- log.info("Setting row", row, "with value", entry) local set_ok = pcall(vim.api.nvim_buf_set_lines, results_bufnr, row, row + 1, false, {display}) -- This pretty much only fails when people leave newlines in their results. @@ -417,10 +408,14 @@ function Picker:find() display = display:gsub("\n", " | ") vim.api.nvim_buf_set_lines(results_bufnr, row, row + 1, false, {display}) end - end - )) + end) + end + + self.manager = EntryManager:new(self.max_results, entry_adder, self.stats) local process_result = function(entry) + self:_increment("processed") + -- TODO: Should we even have valid? if entry.valid == false then return @@ -430,9 +425,7 @@ function Picker:find() local sort_ok, sort_score = nil, 0 if sorter then - sort_ok, sort_score = pcall(function () - return sorter:score(prompt, entry) - end) + sort_ok, sort_score = self:_track("_sort_time", pcall, sorter.score, sorter, prompt, entry) if not sort_ok then log.warn("Sorting failed with:", prompt, entry, sort_score) @@ -440,16 +433,21 @@ function Picker:find() end if sort_score == -1 then - filtered_amount = filtered_amount + 1 + self:_increment("filtered") log.trace("Filtering out result: ", entry) return end end - self.manager:add_entry(sort_score, entry) + self:_track("_add_time", self.manager.add_entry, self.manager, sort_score, entry) + + debounced_status() end - local process_complete = vim.schedule_wrap(function() + local process_complete = function() + -- prompt_prefix = " hello > " + -- vim.fn.prompt_setprompt(prompt_bufnr, prompt_prefix) + -- TODO: We should either: always leave one result or make sure we actually clean up the results when nothing matches if selection_strategy == 'row' then @@ -472,13 +470,26 @@ function Picker:find() self:clear_extra_rows(results_bufnr) self:highlight_displayed_rows(results_bufnr, prompt) - PERF("Filtered Amount ", filtered_amount) - PERF("Displayed Amount ", displayed_amount) - PERF("Displayed FN Amount", displayed_fn_amount) - end) + + -- TODO: Cleanup. + self.stats._done = vim.loop.hrtime() + self.stats.time = (self.stats._done - self.stats._start) / 1e9 + + local function do_times(key) + self.stats[key] = self.stats["_" .. key] / 1e9 + end + + do_times("sort_time") + do_times("add_time") + do_times("highlight_time") + + self:_on_complete() + + update_status() + end local ok, msg = pcall(function() - return finder(prompt, process_result, process_complete) + return finder(prompt, process_result, vim.schedule_wrap(process_complete)) end) if not ok then @@ -486,18 +497,13 @@ function Picker:find() end end - -- TODO: Uncomment - vim.schedule(function() - on_lines(nil, nil, nil, 0, 1) - end) + on_lines(nil, nil, nil, 0, 1) + update_status() -- Register attach - vim.api.nvim_buf_attach(prompt_bufnr, true, { - on_lines = vim.schedule_wrap(on_lines), - - on_detach = function(...) - -- print("DETACH:", ...) - end, + vim.api.nvim_buf_attach(prompt_bufnr, false, { + on_lines = on_lines, + on_detach = function(...) print("DETACH:", ...) end, }) @@ -538,7 +544,7 @@ function Picker:find() finder = finder, }) - mappings.apply_keymap(prompt_bufnr, self.attach_mappings, default_mappings) + mappings.apply_keymap(prompt_bufnr, self.attach_mappings, config.values.default_mappings) -- Do filetype last, so that users can register at the last second. pcall(a.nvim_buf_set_option, prompt_bufnr, 'filetype', 'TelescopePrompt') @@ -618,9 +624,32 @@ function Picker:move_selection(change) self:set_selection(self:get_selection_row() + change) end +function Picker:add_selection(row) + local entry = self.manager:get_entry(self:get_index(row)) + self.multi_select[entry] = true +end + +function Picker:display_multi_select(results_bufnr) + for entry, _ in pairs(self.multi_select) do + local index = self.manager:find_entry(entry) + if index then + vim.api.nvim_buf_add_highlight( + results_bufnr, + ns_telescope_selection, + "TelescopeMultiSelection", + self:get_row(index), + 0, + -1 + ) + end + end +end + function Picker:reset_selection() self._selection_entry = nil self._selection_row = nil + + self.multi_select = {} end function Picker:set_selection(row) @@ -674,6 +703,10 @@ function Picker:set_selection(row) -- TODO: You should go back and redraw the highlights for this line from the sorter. -- That's the only smart thing to do. + if not a.nvim_buf_is_valid(results_bufnr) then + log.debug("Invalid buf somehow...") + return + end a.nvim_buf_set_lines(results_bufnr, row, row + 1, false, {display}) a.nvim_buf_clear_namespace(results_bufnr, ns_telescope_selection, 0, -1) @@ -686,6 +719,8 @@ function Picker:set_selection(row) -1 ) + self:display_multi_select(results_bufnr) + if prompt and self.sorter.highlighter then self:highlight_one_row(results_bufnr, prompt, display, row) end @@ -705,6 +740,8 @@ function Picker:set_selection(row) self._selection_row = row if status.preview_win and self.previewer then + self:_increment("previewed") + self.previewer:preview( entry, status @@ -712,9 +749,59 @@ function Picker:set_selection(row) end end +function Picker:_reset_track() + self.stats.processed = 0 + self.stats.displayed = 0 + self.stats.display_fn = 0 + self.stats.previewed = 0 + self.stats.status = 0 + + self.stats.filtered = 0 + self.stats.highlights = 0 + + self.stats._sort_time = 0 + self.stats._add_time = 0 + self.stats._highlight_time = 0 + self.stats._start = vim.loop.hrtime() +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] + 1 +end + + +-- TODO: Decide how much we want to use this. +-- Would allow for better debugging of items. +function Picker:register_completion_callback(cb) + table.insert(self._completion_callbacks, cb) +end + +function Picker:_on_complete() + for _, v in ipairs(self._completion_callbacks) do + v(self) + end +end + function Picker:close_existing_pickers() for _, prompt_bufnr in ipairs(state.get_existing_prompts()) do - actions.close(prompt_bufnr) + pcall(actions.close, prompt_bufnr) end end @@ -724,126 +811,6 @@ pickers.new = function(opts, defaults) return Picker:new(opts) end --- TODO: We should consider adding `process_bulk` or `bulk_entry_manager` for things --- that we always know the items and can score quickly, so as to avoid drawing so much. -pickers.entry_manager = function(max_results, set_entry, info) - log.trace("Creating entry_manager...") - - info = info or {} - info.looped = 0 - info.inserted = 0 - - -- state contains list of - -- { - -- score = ... - -- line = ... - -- metadata ? ... - -- } - local entry_state = {} - - set_entry = set_entry or function() end - - local should_save_result = function(index) return index <= max_results + 1 end - local worst_acceptable_score = math.huge - - return setmetatable({ - add_entry = function(self, score, entry) - score = score or 0 - - if score >= worst_acceptable_score then - return - end - - for index, item in ipairs(entry_state) do - info.looped = info.looped + 1 - - if item.score > score then - return self:insert(index, { - score = score, - entry = entry, - }) - end - - -- Don't add results that are too bad. - if not should_save_result(index) then - return - end - end - - return self:insert({ - score = score, - entry = entry, - }) - end, - - insert = function(self, index, entry) - if entry == nil then - entry = index - index = #entry_state + 1 - end - - -- To insert something, we place at the next available index (or specified index) - -- and then shift all the corresponding items one place. - local next_entry, last_score - repeat - info.inserted = info.inserted + 1 - next_entry = entry_state[index] - - set_entry(index, entry.entry) - entry_state[index] = entry - - last_score = entry.score - - index = index + 1 - entry = next_entry - - until not next_entry or not should_save_result(index) - - if not should_save_result(index) then - worst_acceptable_score = last_score - end - end, - - num_results = function() - return #entry_state - end, - - get_ordinal = function(self, index) - return self:get_entry(index).ordinal - end, - - get_entry = function(_, index) - return (entry_state[index] or {}).entry - end, - - get_score = function(_, index) - return (entry_state[index] or {}).score - end, - - find_entry = function(_, entry) - if entry == nil then - return nil - end - - for k, v in ipairs(entry_state) do - local existing_entry = v.entry - - -- FIXME: This has the problem of assuming that display will not be the same for two different entries. - if existing_entry.display == entry.display then - return k - end - end - - return nil - end, - - _get_state = function() - return entry_state - end, - }, {}) -end - - function pickers.on_close_prompt(prompt_bufnr) local status = state.get_status(prompt_bufnr) local picker = status.picker @@ -852,6 +819,9 @@ function pickers.on_close_prompt(prompt_bufnr) picker.previewer:teardown() end + -- TODO: This is an attempt to clear all the memory stuff we may have left. + -- vim.api.nvim_buf_detach(prompt_bufnr) + picker:close_windows(status) end diff --git a/lua/telescope/sorters.lua b/lua/telescope/sorters.lua index b41ab91..bc68fc1 100644 --- a/lua/telescope/sorters.lua +++ b/lua/telescope/sorters.lua @@ -53,51 +53,11 @@ end sorters.Sorter = Sorter -sorters.get_ngram_sorter = function() - return Sorter:new { - scoring_function = function(_, prompt, line) - if prompt == "" or prompt == nil then - return 1 - end - - local ok, result = pcall(function() - local ngram = util.new_ngram { N = 4 } - ngram:add(line) - - local score = ngram:score(prompt) - if score == 0 then - return -1 - end - - -- return math.pow(math.max(score, 0.0001), -1) - return score - end) - - return ok and result or 1 - end - } -end - -sorters.get_levenshtein_sorter = function() - return Sorter:new { - scoring_function = function(_, prompt, line) - local result = require('telescope.algos.string_distance')(prompt, line) - log.info("Sorting result for", prompt, line, " = ", result) - return result - end - } -end - --- TODO: Match on upper case words --- TODO: Match on last match -sorters.get_fuzzy_file = function(opts) - opts = opts or {} - - local ngram_len = opts.ngram_len or 2 +TelescopeCachedTails = TelescopeCachedTails or nil +do local os_sep = util.get_separator() local match_string = '[^' .. os_sep .. ']*$' - - local cached_tails = setmetatable({}, { + TelescopeCachedTails = setmetatable({}, { __index = function(t, k) local tail = string.match(k, match_string) @@ -105,30 +65,35 @@ sorters.get_fuzzy_file = function(opts) return tail end, }) +end - -- TODO: Consider either a faster way of getting these - -- OR we really should just cache them longer - -- OR we need a different way of keeping track of uppercase letters. - local cached_uppers = setmetatable({}, { - __index = function(t, k) - local obj = {} - for i = 1, #k do - local s_byte = k:byte(i, i) - if s_byte <= 90 and s_byte >= 65 then - obj[s_byte] = true - end +TelescopeCachedUppers = TelescopeCachedUppers or setmetatable({}, { + __index = function(t, k) + local obj = {} + for i = 1, #k do + local s_byte = k:byte(i, i) + if s_byte <= 90 and s_byte >= 65 then + obj[s_byte] = true end - - rawset(t, k, obj) - return obj end - }) - local cached_ngrams = {} + rawset(t, k, obj) + return obj + end +}) + +TelescopeCachedNgrams = TelescopeCachedNgrams or {} + +-- TODO: Match on upper case words +-- TODO: Match on last match +sorters.get_fuzzy_file = function(opts) + opts = opts or {} + + local ngram_len = opts.ngram_len or 2 local function overlapping_ngrams(s, n) - if cached_ngrams[s] and cached_ngrams[s][n] then - return cached_ngrams[s][n] + if TelescopeCachedNgrams[s] and TelescopeCachedNgrams[s][n] then + return TelescopeCachedNgrams[s][n] end local R = {} @@ -136,11 +101,11 @@ sorters.get_fuzzy_file = function(opts) R[#R+1] = s:sub(i, i+n-1) end - if not cached_ngrams[s] then - cached_ngrams[s] = {} + if not TelescopeCachedNgrams[s] then + TelescopeCachedNgrams[s] = {} end - cached_ngrams[s][n] = R + TelescopeCachedNgrams[s][n] = R return R end @@ -163,8 +128,8 @@ sorters.get_fuzzy_file = function(opts) -- Contains the original string local contains_string = line_lower:find(prompt_lower, 1, true) - local prompt_uppers = cached_uppers[prompt] - local line_uppers = cached_uppers[line] + local prompt_uppers = TelescopeCachedUppers[prompt] + local line_uppers = TelescopeCachedUppers[line] local uppers_matching = 0 for k, _ in pairs(prompt_uppers) do @@ -174,7 +139,7 @@ sorters.get_fuzzy_file = function(opts) end -- TODO: Consider case senstivity - local tail = cached_tails[line_lower] + local tail = TelescopeCachedTails[line_lower] local contains_tail = tail:find(prompt, 1, true) local consecutive_matches = 0 @@ -224,20 +189,20 @@ sorters.get_fuzzy_file = function(opts) return 1 / denominator end, - highlighter = function(_, prompt, display) + highlighter = opts.highlighter or function(_, prompt, display) return ngram_highlighter(ngram_len, prompt, display) end, } end -sorters.get_generic_fuzzy_sorter = function() +sorters.get_generic_fuzzy_sorter = function(opts) + opts = opts or {} + local ngram_len = 2 - local cached_ngrams = {} - local function overlapping_ngrams(s, n) - if cached_ngrams[s] and cached_ngrams[s][n] then - return cached_ngrams[s][n] + if TelescopeCachedNgrams[s] and TelescopeCachedNgrams[s][n] then + return TelescopeCachedNgrams[s][n] end local R = {} @@ -245,11 +210,11 @@ sorters.get_generic_fuzzy_sorter = function() R[#R+1] = s:sub(i, i+n-1) end - if not cached_ngrams[s] then - cached_ngrams[s] = {} + if not TelescopeCachedNgrams[s] then + TelescopeCachedNgrams[s] = {} end - cached_ngrams[s][n] = R + TelescopeCachedNgrams[s][n] = R return R end @@ -312,10 +277,19 @@ sorters.get_generic_fuzzy_sorter = function() return 1 / denominator end, - highlighter = function(_, prompt, display) + highlighter = opts.highlighter or function(_, prompt, display) return ngram_highlighter(ngram_len, prompt, display) end, } end +-- Bad & Dumb Sorter +sorters.get_levenshtein_sorter = function() + return Sorter:new { + scoring_function = function(_, prompt, line) + return require('telescope.algos.string_distance')(prompt, line) + end + } +end + return sorters diff --git a/lua/tests/automated/find_and_sort_spec.lua b/lua/tests/automated/find_and_sort_spec.lua index 9e6b4cf..b1271f4 100644 --- a/lua/tests/automated/find_and_sort_spec.lua +++ b/lua/tests/automated/find_and_sort_spec.lua @@ -1,8 +1,6 @@ require('plenary.reload').reload_module('plenary') require('plenary.reload').reload_module('telescope') -PERF = function() end - --[[ Goals: diff --git a/lua/tests/automated/telescope_spec.lua b/lua/tests/automated/telescope_spec.lua index 3589a22..5ec3141 100644 --- a/lua/tests/automated/telescope_spec.lua +++ b/lua/tests/automated/telescope_spec.lua @@ -6,8 +6,7 @@ local log = require('telescope.log') log.level = 'info' -- log.use_console = false -local pickers = require('telescope.pickers') -local utils = require('telescope.utils') +local EntryManager = require('telescope.entry_manager') --[[ lua RELOAD('plenary'); require("plenary.test_harness"):test_directory("busted", "./tests/automated") @@ -22,7 +21,7 @@ describe('Picker', function() describe('process_result', function() it('works with one entry', function() - local manager = pickers.entry_manager(5, nil) + local manager = EntryManager:new(5, nil) manager:add_entry(1, "hello") @@ -30,7 +29,7 @@ describe('Picker', function() end) it('works with two entries', function() - local manager = pickers.entry_manager(5, nil) + local manager = EntryManager:new(5, nil) manager:add_entry(1, "hello") manager:add_entry(2, "later") @@ -41,7 +40,7 @@ describe('Picker', function() it('calls functions when inserting', function() local called_count = 0 - local manager = pickers.entry_manager(5, function() called_count = called_count + 1 end) + local manager = EntryManager:new(5, function() called_count = called_count + 1 end) assert(called_count == 0) manager:add_entry(1, "hello") @@ -50,7 +49,7 @@ describe('Picker', function() it('calls functions when inserting twice', function() local called_count = 0 - local manager = pickers.entry_manager(5, function() called_count = called_count + 1 end) + local manager = EntryManager:new(5, function() called_count = called_count + 1 end) assert(called_count == 0) manager:add_entry(1, "hello") @@ -60,7 +59,7 @@ describe('Picker', function() it('correctly sorts lower scores', function() local called_count = 0 - local manager = pickers.entry_manager(5, function() called_count = called_count + 1 end) + local manager = EntryManager:new(5, function() called_count = called_count + 1 end) manager:add_entry(5, "worse result") manager:add_entry(2, "better result") @@ -75,27 +74,23 @@ describe('Picker', function() it('respects max results', function() local called_count = 0 - local manager = pickers.entry_manager(1, function() called_count = called_count + 1 end) + local manager = EntryManager:new(1, function() called_count = called_count + 1 end) manager:add_entry(2, "better result") manager:add_entry(5, "worse result") assert.are.same("better result", manager:get_entry(1)) - - -- once to insert "worse" - -- once to insert "better" - -- and then to move "worse" assert.are.same(1, called_count) end) -- TODO: We should decide if we want to add this or not. -- it('should handle no scores', function() - -- local manager = pickers.entry_manager(5, nil) + -- local manager = EntryManager:new(5, nil) -- manager:add_entry(nil, -- end) it('should allow simple entries', function() - local manager = pickers.entry_manager(5) + local manager = EntryManager:new(5) local counts_executed = 0 manager:add_entry(1, setmetatable({}, { diff --git a/lua/tests/manual/auto_picker.lua b/lua/tests/manual/auto_picker.lua new file mode 100644 index 0000000..141eee4 --- /dev/null +++ b/lua/tests/manual/auto_picker.lua @@ -0,0 +1,80 @@ +RELOAD('telescope') + +local actions = require('telescope.actions') +local finders = require('telescope.finders') +local make_entry = require('telescope.make_entry') +local previewers = require('telescope.previewers') +local pickers = require('telescope.pickers') +local sorters = require('telescope.sorters') +local utils = require('telescope.utils') + +local find_files = function(opts) + opts = opts or {} + + local find_command = opts.find_command + + if not find_command then + if 1 == vim.fn.executable("fd") then + find_command = { 'fd', '--type', 'f' } + elseif 1 == vim.fn.executable("fdfind") then + find_command = { 'fdfind', '--type', 'f' } + elseif 1 == vim.fn.executable("rg") then + find_command = { 'rg', '--files' } + end + end + + if opts.cwd then + opts.cwd = vim.fn.expand(opts.cwd) + end + + opts.entry_maker = opts.entry_maker or make_entry.gen_from_file(opts) + + local p = pickers.new(opts, { + prompt = 'Find Files', + finder = finders.new_oneshot_job( + find_command, + opts + ), + previewer = previewers.cat.new(opts), + sorter = sorters.get_fuzzy_file(), + + track = true, + }) + + local count = 0 + p:register_completion_callback(function(s) + print(count, vim.inspect(s.stats, { + process = function(item) + if type(item) == 'string' and item:sub(1, 1) == '_' then + return nil + end + + return item + end, + })) + + count = count + 1 + end) + + local feed = function(text) + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(text, true, false, true), 'n', true) + end + + p:register_completion_callback(coroutine.wrap(function() + local input = "pickers.lua" + for i = 1, #input do + feed(input:sub(i, i)) + coroutine.yield() + end + + vim.wait(300, function() end) + + vim.cmd [[:q]] + vim.cmd [[:Messages]] + vim.cmd [[stopinsert]] + end)) + + p:find() +end + +find_files() diff --git a/lua/tests/manual/large_search.lua b/lua/tests/manual/large_search.lua index 3ad6b5a..eda5f16 100644 --- a/lua/tests/manual/large_search.lua +++ b/lua/tests/manual/large_search.lua @@ -7,9 +7,6 @@ local previewers = require('telescope.previewers') local pickers = require('telescope.pickers') local sorters = require('telescope.sorters') -PERF_DEBUG = 182 -vim.api.nvim_buf_set_lines(PERF_DEBUG, 0, -1, false, {}) - local cwd = vim.fn.expand("~/build/neovim") pickers.new { @@ -28,7 +25,6 @@ pickers.new { }:find() -COMPLETED = false -- vim.wait(3000, function() -- vim.cmd [[redraw!]] -- return COMPLETED diff --git a/lua/tests/manual/slow_oneshot.lua b/lua/tests/manual/slow_oneshot.lua new file mode 100644 index 0000000..f97c098 --- /dev/null +++ b/lua/tests/manual/slow_oneshot.lua @@ -0,0 +1,71 @@ +RELOAD('telescope') + +local actions = require('telescope.actions') +local finders = require('telescope.finders') +local make_entry = require('telescope.make_entry') +local previewers = require('telescope.previewers') +local pickers = require('telescope.pickers') +local sorters = require('telescope.sorters') +local utils = require('telescope.utils') + +local slow_proc = function(opts) + opts = opts or {} + + if opts.cwd then + opts.cwd = vim.fn.expand(opts.cwd) + end + + opts.entry_maker = opts.entry_maker or make_entry.gen_from_file(opts) + + local p = pickers.new(opts, { + prompt = 'Slow Proc', + finder = finders.new_oneshot_job( + {"./scratch/slow_proc.sh"}, + opts + ), + previewer = previewers.cat.new(opts), + sorter = sorters.get_fuzzy_file(), + + track = true, + }) + + local count = 0 + p:register_completion_callback(function(s) + print(count, vim.inspect(s.stats, { + process = function(item) + if type(item) == 'string' and item:sub(1, 1) == '_' then + return nil + end + + return item + end, + })) + + count = count + 1 + end) + + local feed = function(text) + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(text, true, false, true), 'n', true) + end + + if false then + p:register_completion_callback(coroutine.wrap(function() + local input = "pickers.lua" + for i = 1, #input do + feed(input:sub(i, i)) + coroutine.yield() + end + + vim.wait(300, function() end) + + vim.cmd [[:q]] + vim.cmd [[:Messages]] + vim.cmd [[stopinsert]] + end)) + end + + + p:find() +end + +slow_proc() diff --git a/plugin/telescope.vim b/plugin/telescope.vim index 3775077..5b8e53a 100644 --- a/plugin/telescope.vim +++ b/plugin/telescope.vim @@ -1,6 +1,7 @@ " Sets the highlight for selected items within the picker. highlight default link TelescopeSelection Visual +highlight default link TelescopeMultiSelection Type " "Normal" in the floating windows created by telescope. highlight default link TelescopeNormal Normal @@ -23,8 +24,3 @@ cnoremap (TelescopeFuzzyCommandSearch) e \ "lua require('telescope.builtin').command_history { \ default_text = [=[" . escape(getcmdline(), '"') . "]=] \ }" - - -" TODO: Make a real perf module that works nicer. Probably something for -" another night :) -lua PERF = function(...) end diff --git a/scratch/how_long_does_time_take.lua b/scratch/how_long_does_time_take.lua new file mode 100644 index 0000000..a77e47b --- /dev/null +++ b/scratch/how_long_does_time_take.lua @@ -0,0 +1,8 @@ +local start = vim.loop.hrtime() + +local counts = 1e6 +for _ = 1, counts do + local _ = vim.loop.hrtime() +end + +print(counts, ":", (vim.loop.hrtime() - start) / 1e9) diff --git a/scratch/new_index.lua b/scratch/new_index.lua new file mode 100644 index 0000000..3aa6a91 --- /dev/null +++ b/scratch/new_index.lua @@ -0,0 +1,9 @@ + +local t = setmetatable({}, { + __newindex = function(t, k, v) + print(t, k, v) + end +}) + +-- table.insert(t, "hello") +t[1] = "hello" diff --git a/scratch/old_perf_debug b/scratch/old_perf_debug new file mode 100644 index 0000000..3561a98 --- /dev/null +++ b/scratch/old_perf_debug @@ -0,0 +1,36 @@ + + +-- Until I have better profiling stuff, this will have to do. +PERF = function(...) end +PERF_DEBUG = PERF_DEBUG or nil +START = nil + +if PERF_DEBUG then + PERF = function(...) + local new_time = (vim.loop.hrtime() - START) / 1E9 + if select('#', ...) == 0 then + vim.schedule(function() + vim.api.nvim_buf_set_lines(PERF_DEBUG, -1, -1, false, { '' }) + end) + return + end + + local to_insert = '' + if START then + to_insert = tostring(new_time) .. ' | ' + end + + for _, v in ipairs({...}) do + if type(v) == 'table' then + to_insert = to_insert .. tostring(#v) .. ' | ' + else + to_insert = to_insert .. tostring(v) .. ' | ' + end + end + + vim.schedule(function() + vim.api.nvim_buf_set_lines(PERF_DEBUG, -1, -1, false, { to_insert }) + end) + end +end + diff --git a/scratch/simple_rg.lua b/scratch/simple_rg.lua index 5102611..8bd2586 100644 --- a/scratch/simple_rg.lua +++ b/scratch/simple_rg.lua @@ -5,7 +5,7 @@ local telescope = require('telescope') -- When updating the table, we should call filter on those items -- and then only display ones that pass the filter local rg_finder = telescope.finders.new { - fn_command = function(self, prompt) + fn_command = function(_, prompt) return string.format('rg --vimgrep %s', prompt) end, diff --git a/scratch/slow_proc.sh b/scratch/slow_proc.sh index 580e617..a43811d 100755 --- a/scratch/slow_proc.sh +++ b/scratch/slow_proc.sh @@ -8,3 +8,4 @@ sleep 1 echo "husband" sleep 1 echo "helper" + diff --git a/scratch/temp_goals.txt b/scratch/temp_goals.txt new file mode 100644 index 0000000..6827cad --- /dev/null +++ b/scratch/temp_goals.txt @@ -0,0 +1,17 @@ + +Goal: Implement scrolling + +So we want to start keeping track of more/all of the results + +We still want them sorted, so when you scroll past, + you still get the stuff you'd expect. + + +Notes: + +Probably want some X / Y count + + + +Options +