From 839f57efb37cba7a9542b67b31370e1babaf194a Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Thu, 3 Sep 2020 23:56:49 -0400 Subject: [PATCH] feat: Major improvements in API. Particularly relating to entries. --- file.txt | 1 - lua/telescope/actions.lua | 39 +++-- lua/telescope/builtin.lua | 197 ++++++++++++++++---------- lua/telescope/entry.lua | 27 ---- lua/telescope/finders.lua | 157 ++++++++++++-------- lua/telescope/make_entry.lua | 162 +++++++++++++++++++++ lua/telescope/pickers.lua | 36 ++--- lua/telescope/previewers.lua | 56 ++++++-- lua/telescope/utils.lua | 4 + lua/tests/manual/newline_tables.lua | 16 ++- media/worflow.txt | 26 ++++ scratch/batched_finder_and_sorter.lua | 55 +++++++ scratch/clason_finders.lua | 130 +++++++++++++++++ 13 files changed, 690 insertions(+), 216 deletions(-) delete mode 100644 file.txt delete mode 100644 lua/telescope/entry.lua create mode 100644 lua/telescope/make_entry.lua create mode 100644 media/worflow.txt create mode 100644 scratch/batched_finder_and_sorter.lua create mode 100644 scratch/clason_finders.lua diff --git a/file.txt b/file.txt deleted file mode 100644 index 4ea4078..0000000 --- a/file.txt +++ /dev/null @@ -1 +0,0 @@ -plugin/telescope.vim diff --git a/lua/telescope/actions.lua b/lua/telescope/actions.lua index f1b4a38..dd47f4d 100644 --- a/lua/telescope/actions.lua +++ b/lua/telescope/actions.lua @@ -42,23 +42,32 @@ function actions.goto_file_selection(prompt_bufnr) print("[telescope] Nothing currently selected") return else - local value = entry.value - if not value then - print("Could not do anything with blank line...") - return + local filename, row, col + if entry.filename then + filename = entry.filename + -- TODO: Check for off-by-one + row = entry.row or entry.lnum + col = entry.col + else + -- TODO: Might want to remove this and force people + -- to put stuff into `filename` + local value = entry.value + if not value then + print("Could not do anything with blank line...") + return + end + + if type(value) == "table" then + value = entry.display + end + + local sections = vim.split(value, ":") + + filename = sections[1] + row = tonumber(sections[2]) + col = tonumber(sections[3]) end - -- TODO: This is not great. - if type(value) == "table" then - value = entry.display - end - - local sections = vim.split(value, ":") - - local filename = sections[1] - local row = tonumber(sections[2]) - local col = tonumber(sections[3]) - vim.cmd(string.format([[bwipeout! %s]], prompt_bufnr)) a.nvim_set_current_win(picker.original_win_id or 0) diff --git a/lua/telescope/builtin.lua b/lua/telescope/builtin.lua index d207b99..0430d09 100644 --- a/lua/telescope/builtin.lua +++ b/lua/telescope/builtin.lua @@ -11,94 +11,69 @@ This will use the default configuration options. Other configuration options still in flux at the moment --]] +-- TODO: Give some bonus weight to files we've picked before +-- TODO: Give some bonus weight to oldfiles + 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 flatten = vim.tbl_flatten + +-- TODO: Support silver search here. +-- TODO: Support normal grep here (in case neither are installed). +local vimgrep_arguments = {'rg', '--color=never', '--no-heading', '--with-filename', '--line-number', '--column'} + local builtin = {} builtin.git_files = function(opts) opts = opts or {} - local make_entry = ( - opts.shorten_path - and function(value) - local result = { - valid = true, - display = utils.path_shorten(value), - ordinal = value, - value = value - } - - return result - end) - - or nil - pickers.new(opts, { prompt = 'Git File', - finder = finders.new_oneshot_job({ "git", "ls-files" }, make_entry), + finder = finders.new_oneshot_job( + { "git", "ls-files", "-o", "--exclude-standard", "-c" }, + make_entry.gen_from_file(opts) + ), previewer = previewers.cat, sorter = sorters.get_fuzzy_file(), }):find() end builtin.live_grep = function(opts) - local live_grepper = finders.new { - fn_command = function(_, prompt) - -- TODO: Make it so that we can start searching on the first character. + opts = opts or {} + + local live_grepper = finders.new_job(function(prompt) + -- TODO: Probably could add some options for smart case and whatever else rg offers. + if not prompt or prompt == "" then return nil end - return { - command = 'rg', - args = {"--vimgrep", prompt}, - } - end - } + return flatten { vimgrep_arguments, prompt } + end, + opts.entry_maker or make_entry.gen_from_vimgrep(opts), + opts.max_results + ) pickers.new(opts, { prompt = 'Live Grep', finder = live_grepper, previewer = previewers.vimgrep, }):find() - - -- TODO: Incorporate this. - -- Weight the results somehow to be more likely to be the ones that you've opened. - -- local old_files = {} - -- for _, f in ipairs(vim.v.oldfiles) do - -- old_files[f] = true - -- end - - -- local oldfiles_sorter = sorters.new { - -- scoring_function = function(prompt, entry) - -- local line = entry.value - - -- if not line then - -- return - -- end - - -- local _, finish = string.find(line, ":") - -- local filename = string.sub(line, 1, finish - 1) - -- local expanded_fname = vim.fn.fnamemodify(filename, ':p') - -- if old_files[expanded_fname] then - -- print("Found oldfiles: ", entry.value) - -- return 0 - -- else - -- return 1 - -- end - -- end - -- } end -- TODO: document_symbol -- TODO: workspace_symbol builtin.lsp_references = function(opts) + opts = opts or {} + opts.shorten_path = utils.get_default(opts.shorten_path, true) + local params = vim.lsp.util.make_position_params() params.context = { includeDeclaration = true } @@ -108,15 +83,74 @@ builtin.lsp_references = function(opts) vim.list_extend(locations, vim.lsp.util.locations_to_items(server_results.result) or {}) end - local results = utils.quickfix_items_to_entries(locations) - - if vim.tbl_isempty(results) then + if vim.tbl_isempty(locations) then return end - local reference_picker = pickers.new(opts, { + pickers.new(opts, { prompt = 'LSP References', - finder = finders.new_table(results), + finder = finders.new_table { + results = locations, + entry_maker = make_entry.gen_from_quickfix(opts), + }, + previewer = previewers.qflist, + sorter = sorters.get_norcalli_sorter(), + }):find() +end + +builtin.lsp_document_symbols = function(opts) + local params = vim.lsp.util.make_position_params() + local results_lsp = vim.lsp.buf_request_sync(0, "textDocument/documentSymbol", params) + + if not results_lsp or vim.tbl_isempty(results_lsp) then + print("No results from textDocument/documentSymbol") + return + end + + local locations = {} + for _, server_results in pairs(results_lsp) do + vim.list_extend(locations, vim.lsp.util.symbols_to_items(server_results.result, 0) or {}) + end + + if vim.tbl_isempty(locations) then + return + end + + pickers.new(opts, { + prompt = 'LSP Document Symbols', + finder = finders.new_table { + results = locations, + entry_maker = make_entry.gen_from_quickfix(opts) + }, + previewer = previewers.vim_buffer, + sorter = sorters.get_norcalli_sorter(), + }):find() +end + +builtin.lsp_workspace_symbols = function(opts) + local params = {query = opts.query or ''} + local results_lsp = vim.lsp.buf_request_sync(0, "workspace/symbol", params, 1000) + + if not results_lsp or vim.tbl_isempty(results_lsp) then + print("No results from textDocument/documentSymbol") + return + end + + local locations = {} + for _, server_results in pairs(results_lsp) do + vim.list_extend(locations, vim.lsp.util.symbols_to_items(server_results.result, 0) or {}) + end + + if vim.tbl_isempty(locations) then + return + end + + pickers.new(opts, { + prompt = 'LSP Workspace Symbols', + finder = finders.new_table { + results = locations, + entry_maker = make_entry.gen_from_quickfix(opts) + }, previewer = previewers.qflist, sorter = sorters.get_norcalli_sorter(), }):find() @@ -124,15 +158,17 @@ end builtin.quickfix = function(opts) local locations = vim.fn.getqflist() - local results = utils.quickfix_items_to_entries(locations) - if vim.tbl_isempty(results) then + if vim.tbl_isempty(locations) then return end pickers.new(opts, { prompt = 'Quickfix', - finder = finders.new_table(results), + finder = finders.new_table { + results = locations, + entry_maker = make_entry.gen_from_quickfix(opts), + }, previewer = previewers.qflist, sorter = sorters.get_norcalli_sorter(), }):find() @@ -146,15 +182,16 @@ builtin.loclist = function(opts) value.filename = filename end - local results = utils.quickfix_items_to_entries(locations) - - if vim.tbl_isempty(results) then + if vim.tbl_isempty(locations) then return end pickers.new(opts, { prompt = 'Loclist', - finder = finders.new_table(results), + finder = finders.new_table { + results = locations, + entry_maker = make_entry.gen_from_quickfix(opts), + }, previewer = previewers.qflist, sorter = sorters.get_norcalli_sorter(), }):find() @@ -165,9 +202,12 @@ builtin.grep_string = function(opts) local search = opts.search or vim.fn.expand("") - local file_picker = pickers.new(opts, { + pickers.new(opts, { prompt = 'Find Word', - finder = finders.new_oneshot_job {'rg', '--vimgrep', search}, + finder = finders.new_oneshot_job( + flatten { vimgrep_arguments, search}, + make_entry.gen_from_vimgrep(opts) + ), previewer = previewers.vimgrep, sorter = sorters.get_norcalli_sorter(), }):find() @@ -218,27 +258,32 @@ end -- vim.fn.setreg("+", "nnoremap $TODO :lua require('telescope.builtin').()") -- TODO: Can we just do the names instead? builtin.builtin = function(opts) + opts = opts or {} + opts.hide_filename = utils.get_default(opts.hide_filename, true) + opts.ignore_filename = utils.get_default(opts.ignore_filename, true) + local objs = {} for k, v in pairs(builtin) do local debug_info = debug.getinfo(v) table.insert(objs, { - vimgrep_str = k, filename = string.sub(debug_info.source, 2), lnum = debug_info.linedefined, col = 0, + text = k, start = debug_info.linedefined, finish = debug_info.lastlinedefined, }) end - local entries = utils.quickfix_items_to_entries(objs) - pickers.new(opts, { prompt = 'Telescope Builtin', - finder = finders.new_table(entries), + finder = finders.new_table { + results = objs, + entry_maker = make_entry.gen_from_quickfix(opts), + }, previewer = previewers.qflist, sorter = sorters.get_norcalli_sorter(), }):find() @@ -269,14 +314,10 @@ builtin.fd = function(opts) pickers.new(opts, { prompt = 'Find Files', - finder = finders.new { - fn_command = function() - return { - command = fd_string, - cwd = cwd, - } - end, - }, + finder = finders.new_oneshot_job( + {fd_string}, + make_entry.gen_from_file(opts) + ), previewer = previewers.cat, sorter = sorters.get_fuzzy_file(), }):find() diff --git a/lua/telescope/entry.lua b/lua/telescope/entry.lua deleted file mode 100644 index 97b5414..0000000 --- a/lua/telescope/entry.lua +++ /dev/null @@ -1,27 +0,0 @@ -local Entry = {} -Entry.__index = Entry - --- TODO: Can we / should we make it so that "display" and "ordinal" are just values, instead of functions. --- It seems like that's what you'd want... No need to call the functions a million times. - --- Pass in a table, that contains some state --- Table determines it's ordinal value -function Entry:new(line_or_obj) - if type(line_or_obj) == "string" then - return setmetatable({ - valid = line_or_obj ~= "", - - value = line_or_obj, - ordinal = line_or_obj, - display = line_or_obj, - }, self) - else - return line_or_obj - end -end - -function Entry:__tostring() - return "<" .. self.display .. ">" -end - -return Entry diff --git a/lua/telescope/finders.lua b/lua/telescope/finders.lua index b44dd71..7682aeb 100644 --- a/lua/telescope/finders.lua +++ b/lua/telescope/finders.lua @@ -1,5 +1,6 @@ local Job = require('plenary.job') +local make_entry = require('telescope.make_entry') local log = require('telescope.log') local utils = require('telescope.utils') @@ -10,35 +11,43 @@ local finders = {} -- FunctionFinder(my_func) -- JobFinder(my_job_args) ----@class Finder -local Finder = {} +local _callable_obj = function() + local obj = {} -Finder.__index = Finder -Finder.__call = function(t, ... ) return t:_find(...) end + obj.__index = obj + obj.__call = function(t, ...) return t:_find(...) 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 Finder:new(opts) +function JobFinder:new(opts) opts = opts or {} - -- TODO: Add config for: - -- - cwd - + 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 - -- string - -- list - -- ... local obj = setmetatable({ - results = opts.results, - - entry_maker = opts.entry_maker, + entry_maker = opts.entry_maker or make_entry.from_string, fn_command = opts.fn_command, static = opts.static, state = {}, @@ -51,26 +60,7 @@ function Finder:new(opts) return obj end --- Probably should use the word apply here, since we're apply the callback passed to us by --- the picker... But I'm not sure how we want to say that. - --- find_incremental --- find_prompt --- process_prompt --- process_search --- do_your_job --- process_plz -function Finder:_find(prompt, process_result, process_complete) - if self.results then - assert(type(self.results) == 'table', "self.results must be a table") - for _, v in ipairs(self.results) do - process_result(v) - end - - process_complete() - return - end - +function JobFinder:_find(prompt, process_result, process_complete) if self.job and not self.job.is_shutdown then self.job:shutdown() end @@ -140,22 +130,66 @@ function Finder:_find(prompt, process_result, process_complete) self.job:start() end ---- Return a new Finder --- ---@return Finder -finders.new = function(opts) - return Finder:new(opts) +--[[ ============================================================= +Static Finders + +A static finder has results that never change. +They are passed in directly as a result. +-- ============================================================= ]] +local StaticFinder = _callable_obj() + +function StaticFinder:new(opts) + assert(opts, "Options are required. See documentation for usage") + + local input_results + if vim.tbl_islist(opts) then + input_results = opts + else + input_results = opts.results + end + + local entry_maker = opts.entry_maker or make_entry.gen_from_string() + + assert(input_results) + assert(input_results, "Results are required for static finder") + assert(type(input_results) == 'table', "self.results must be a table") + + local results = {} + for _, v in ipairs(input_results) do + table.insert(results, entry_maker(v)) + end + + return setmetatable({ results = results }, self) end --- TODO: Is this worth making? --- finders.new_responsive_job = function(opts) --- return finders.new { --- maximum_results = get_default(opts.maximum_results, 2000), --- } --- end +function StaticFinder:_find(_, process_result, process_complete) + for _, v in ipairs(self.results) do + process_result(v) + end -finders.new_job = function(command_generator) - return finders.new { + process_complete() +end + + +-- local + + +--- 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) + if opts.results then + print("finder.new is deprecated with `results`. You should use `finder.new_table`") + return StaticFinder:new(opts) + end + + return JobFinder:new(opts) +end + +finders.new_job = function(command_generator, entry_maker, maximum_results) + return JobFinder:new { fn_command = function(_, prompt) local command_list = command_generator(prompt) if command_list == nil then @@ -168,19 +202,24 @@ finders.new_job = function(command_generator) command = command, args = command_list, } - end + end, + + entry_maker = entry_maker, + maximum_results = maximum_results, } end +---@param command_list string[] Command list to execute. +---@param entry_maker function Optional: function(line: string) => table finders.new_oneshot_job = function(command_list, entry_maker) command_list = vim.deepcopy(command_list) local command = table.remove(command_list, 1) - return finders.new { + return JobFinder:new { static = true, - entry_maker = entry_maker, + entry_maker = entry_maker or make_entry.from_string, fn_command = function() return { @@ -191,18 +230,14 @@ finders.new_oneshot_job = function(command_list, entry_maker) } 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 finders.new { - results = t - } + return StaticFinder:new(t) end --- We should add a few utility functions here... --- --- finders.new_job --- finders.new_one_shot_job --- finders.new_table - --- finders.Finder = Finder - return finders diff --git a/lua/telescope/make_entry.lua b/lua/telescope/make_entry.lua new file mode 100644 index 0000000..aca8894 --- /dev/null +++ b/lua/telescope/make_entry.lua @@ -0,0 +1,162 @@ +local has_devicons, devicons = pcall(require, 'nvim-web-devicons') + +local utils = require('telescope.utils') + +local make_entry = {} + +make_entry.types = { + GENERIC = 0, + FILE = 1, +} + +local transform_devicons +if has_devicons then + transform_devicons = function(filename, display, opts) + if opts.disable_devicons then + return display + end + + return (devicons.get_icon(filename, string.match(filename, '%a+$')) or ' ') .. ' ' .. display + end +else + transform_devicons = function(_, display, _) + return display + end +end + +function make_entry.gen_from_string() + return function(line) + return { + valid = line ~= "", + entry_type = make_entry.types.SIMPLE, + + value = line, + ordinal = line, + display = line, + } + end +end + +function make_entry.gen_from_file(opts) + opts = opts or {} + + local make_display = function(line) + local display = line + if opts.shorten_path then + display = utils.path_shorten(line) + end + + display = transform_devicons(line, display, opts) + + return display + end + + return function(line) + local entry = { + ordinal = line, + value = line, + + entry_type = make_entry.types.FILE, + filename = line, + } + + entry.display = make_display(line) + + return entry + end +end + +function make_entry.gen_from_vimgrep(opts) + opts = opts or {} + + local display_string = "%s:%s%s" + + local make_display = function(entry) + local display = entry.value + + local display_filename + if opts.shorten_path then + display_filename = utils.path_shorten(entry.filename) + else + display_filename = entry.filename + end + + local coordinates = "" + if not opts.disable_coordinates then + coordinates = string.format("%s:%s:", entry.lnum, entry.col) + end + + display = transform_devicons( + entry.filename, + string.format(display_string, display_filename, coordinates, entry.text), + opts + ) + + return display + end + + return function(line) + -- TODO: Consider waiting to do this string.find + -- TODO: Is this the fastest way to get each of these? + -- Or could we just walk the text and check for colons faster? + local _, _, filename, lnum, col, text = string.find(line, [[([^:]+):(%d+):(%d+):(.*)]]) + + return { + valid = line ~= "", + + value = line, + ordinal = line, + display = make_display, + + entry_type = make_entry.types.FILE, + filename = filename, + lnum = lnum, + col = col, + text = text, + } + end +end + +function make_entry.gen_from_quickfix(opts) + opts = opts or {} + + local make_display = function(entry) + local to_concat = {} + + if not opts.hide_filename then + local filename = entry.filename + if opts.shorten_path then + filename = utils.path_shorten(filename) + end + + table.insert(to_concat, filename) + table.insert(to_concat, ":") + end + + table.insert(to_concat, entry.text) + + return table.concat(to_concat, "") + end + + return function(entry) + return { + valid = true, + + value = entry, + ordinal = ( + not opts.ignore_filename and entry.filename + or '' + ) .. ' ' .. entry.text, + display = make_display, + + filename = entry.filename, + lnum = entry.lnum, + col = entry.col, + text = entry.text, + start = entry.start, + finish = entry.finish, + } + end +end + +return make_entry diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index 23b5917..f109db7 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -1,6 +1,5 @@ local a = vim.api local popup = require('popup') -local has_devicons, devicons = pcall(require, 'nvim-web-devicons') local actions = require('telescope.actions') local log = require('telescope.log') @@ -8,8 +7,6 @@ local mappings = require('telescope.mappings') local state = require('telescope.state') local utils = require('telescope.utils') -local Entry = require('telescope.entry') - local get_default = utils.get_default -- TODO: Make this work with deep extend I think. @@ -255,14 +252,19 @@ function Picker:find() return end - -- TODO: This really isn't the place to do this. - local display = entry.display - - if has_devicons then - local icon = devicons.get_icon(display, vim.fn.fnamemodify(display, ":e")) - display = (icon or ' ') .. ' ' .. display + local display + if type(entry.display) == 'function' then + 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 -- log.info("Setting row", row, "with value", entry) @@ -277,10 +279,9 @@ function Picker:find() end )) - local process_result = function(line) - local entry = Entry:new(line) - - if not entry.valid then + local process_result = function(entry) + -- TODO: Should we even have valid? + if entry.valid == false then return end @@ -559,10 +560,7 @@ pickers.entry_manager = function(max_results, set_entry) return setmetatable({ add_entry = function(self, score, entry) - -- TODO: Consider forcing people to make these entries before we add them. - if type(entry) == "string" then - entry = Entry:new(entry) - end + assert(type(entry) == "table", "entry must be a table by the time it reaches here") score = score or 0 @@ -647,6 +645,10 @@ function pickers.on_close_prompt(prompt_bufnr) local picker = status.picker picker:close_windows(status) + + if picker.previewer then + picker.previewer:teardown() + end end diff --git a/lua/telescope/previewers.lua b/lua/telescope/previewers.lua index 224cda0..c1acbee 100644 --- a/lua/telescope/previewers.lua +++ b/lua/telescope/previewers.lua @@ -8,6 +8,7 @@ local Previewer = {} Previewer.__index = Previewer local bat_options = " --style=grid --paging=always " +local previewer_ns = vim.api.nvim_create_namespace('telescope.previewers') -- --terminal-width=%s -- TODO: We shoudl make sure that all our terminals close all the way. @@ -20,6 +21,7 @@ function Previewer:new(opts) return setmetatable({ state = nil, _setup_func = opts.setup, + _teardown_func = opts.teardown, preview_fn = opts.preview_fn, }, Previewer) end @@ -29,13 +31,22 @@ function Previewer:preview(entry, status) return end - if not self.state and self._setup_func then - self.state = self._setup_func() + if not self.state then + if self._setup_func then + self.state = self._setup_func() + end end + self:teardown() return self:preview_fn(entry, status) end +function Previewer:teardown() + if self._teardown_func then + self:_teardown_func() + end +end + previewers.new = function(...) return Previewer:new(...) end @@ -69,20 +80,36 @@ previewers.new_termopen = function(opts) end previewers.vim_buffer = previewers.new { - preview_fn = function(_, entry, status) - local value = entry.value - if value == nil then + setup = function() return { last_set_bufnr = nil } end, + + teardown = function(self) + if self.state.last_set_bufnr then + vim.api.nvim_buf_clear_namespace(self.state.last_set_bufnr, previewer_ns, 0, -1) + end + end, + + preview_fn = function(self, entry, status) + local filename = entry.filename + + if filename == nil then + local value = entry.value + filename = vim.split(value, ":")[1] + end + + if filename == nil then return end - local file_name = vim.split(value, ":")[1] - log.trace("Previewing File: %s", file_name) + log.trace("Previewing File: %s", filename) - -- vim.fn.termopen( - -- string.format("bat --color=always --style=grid %s"), - -- vim.fn.fnamemodify(file_name, ":p") - local bufnr = vim.fn.bufadd(file_name) - vim.fn.bufload(bufnr) + local bufnr = vim.fn.bufnr(filename) + if bufnr == -1 then + -- TODO: Is this the best way to load the buffer?... I'm not sure tbh + bufnr = vim.fn.bufadd(bufnr) + vim.fn.bufload(bufnr) + end + + self.state.last_set_bufnr = bufnr -- TODO: We should probably call something like this because we're not always getting highlight and all that stuff. -- api.nvim_command('doautocmd filetypedetect BufRead ' .. vim.fn.fnameescape(filename)) @@ -92,6 +119,11 @@ previewers.vim_buffer = previewers.new { -- vim.api.nvim_win_set_option(preview_win, 'winblend', 20) vim.api.nvim_win_set_option(status.preview_win, 'signcolumn', 'no') vim.api.nvim_win_set_option(status.preview_win, 'foldlevel', 100) + + if entry.lnum then + vim.api.nvim_buf_add_highlight(bufnr, previewer_ns, "Visual", entry.lnum - 1, 0, -1) + -- print("LNUM:", entry.lnum) + end end, } diff --git a/lua/telescope/utils.lua b/lua/telescope/utils.lua index 030d942..b95f626 100644 --- a/lua/telescope/utils.lua +++ b/lua/telescope/utils.lua @@ -99,6 +99,10 @@ utils.path_shorten = (function() ]] return function(path) + if not path then + return path + end + local c_str = ffi.new("char[?]", #path + 1) ffi.copy(c_str, path) return ffi.string(ffi.C.shorten_dir(c_str)) diff --git a/lua/tests/manual/newline_tables.lua b/lua/tests/manual/newline_tables.lua index 879d006..26d2b89 100644 --- a/lua/tests/manual/newline_tables.lua +++ b/lua/tests/manual/newline_tables.lua @@ -1,15 +1,21 @@ -RELOAD('telescope') +require('plenary.reload').reload_module('telescope') -local actions = require('telescope.actions') local finders = require('telescope.finders') -local previewers = require('telescope.previewers') local pickers = require('telescope.pickers') local sorters = require('telescope.sorters') -local utils = require('telescope.utils') pickers.new({ prompt = 'Telescope Builtin', - finder = finders.new_table({"hello\nworld", "other", "item"}), + finder = finders.new_table { + results = {"hello\nworld", "other", "item"}, + entry_maker = false and function(line) + return { + value = line, + ordinal = line, + display = "wow: // " .. line, + } + end, + }, sorter = sorters.get_norcalli_sorter(), }):find() diff --git a/media/worflow.txt b/media/worflow.txt new file mode 100644 index 0000000..e12d7ec --- /dev/null +++ b/media/worflow.txt @@ -0,0 +1,26 @@ ++-------------------------------------------------------------------+ +| Picker:find()--------------------+ +------>Picker | +| | ^ | | | +| | | v | | +| | +----------------+ +----------------+ | +| +->| Finder + | Sorter | | +| +----------------+ +----------------+ | +| [1] | +| | +| | +| | +| | +| | +| | ++-------------------------------------------------------------------+ + +Picker starts a `finder`. + Finder returns a list of `entries` to Picker. + Picker can optionally sort w/ `Sorter`. + Picker can optionally preview selected with `Previewer` + + Then you can map stuff in the picker to decide what to do next. + + + + diff --git a/scratch/batched_finder_and_sorter.lua b/scratch/batched_finder_and_sorter.lua new file mode 100644 index 0000000..c3e188b --- /dev/null +++ b/scratch/batched_finder_and_sorter.lua @@ -0,0 +1,55 @@ +local actions = require('telescope.actions') +local finders = require('telescope.finders') +local previewers = require('telescope.previewers') +local pickers = require('telescope.pickers') +local sorters = require('telescope.sorters') +local utils = require('telescope.utils') + +local Job = require('plenary.job') + +-- local live_grepper = finders.new { +-- fn_command = function(_, prompt) +-- -- TODO: Make it so that we can start searching on the first character. +-- if not prompt or prompt == "" then +-- return nil +-- end + +-- return { +-- command = 'rg', +-- args = {"--vimgrep", prompt}, +-- } +-- end +-- } + +local f = function(prompt, process_result, process_complete) + local fzf = Job:new { + command = 'fzf'; + + writer = Job:new { + command = "fdfind", + args = nil, + cwd = "/home/tj/build/neovim", + + enable_handlers = false, + }, + + -- Still doesn't work if you don't pass these args and just run `fzf` + args = {'--no-sort', '--filter', prompt}; + } + + + local start = vim.fn.reltime() + print(vim.inspect(fzf:sync()), vim.fn.reltimestr(vim.fn.reltime(start))) +end + + +-- Process all the files +-- f("", nil, nil) +-- Filter on nvimexec +f("nvim/executor", nil, nil) + +-- pickers.new({}, { +-- prompt = 'Live Grep', +-- finder = f, +-- previewer = previewers.vimgrep, +-- }):find() diff --git a/scratch/clason_finders.lua b/scratch/clason_finders.lua new file mode 100644 index 0000000..bf9a9e4 --- /dev/null +++ b/scratch/clason_finders.lua @@ -0,0 +1,130 @@ +vim.cmd [[packadd! plenary.nvim]] +vim.cmd [[packadd! popup.nvim]] +vim.cmd [[packadd! telescope.nvim]] + +local finders = require('telescope.finders') +local previewers = require('telescope.previewers') +local pickers = require('telescope.pickers') +local sorters = require('telescope.sorters') +local utils = require('telescope.utils') + +local rgargs = {'--color=never', '--no-heading', '--with-filename', '--line-number', '--column', '--smart-case'} +-- grep typed string in current directory (live, not fuzzy!) +finders.rg_live = function(opts) + local live_grepper = finders.new { + fn_command = function(_, prompt) + if not prompt or prompt == "" then + return nil + end + + return { + command = 'rg', + args = vim.tbl_flatten{rgargs, prompt}, + } + end + } + + pickers.new(opts, { + prompt = 'Live Grep', + finder = live_grepper, + previewer = previewers.vimgrep, + }):find() +end + +-- fuzzy grep string in current directory (slow!) +finders.rg = function(opts) + opts = opts or {} + + local search = opts.search or '' + + pickers.new(opts, { + prompt = 'Find Word', + finder = finders.new_oneshot_job(vim.tbl_flatten{'rg', rgargs, search}), + previewer = previewers.vimgrep, + sorter = sorters.get_norcalli_sorter(), + }):find() +end + +-- fuzzy find files in current directory (may be slow in root dir) +finders.fd = function(opts) + pickers.new(opts, { + prompt = 'Find Files', + finder = finders.new_oneshot_job {"fd"}, + previewer = previewers.bat, + sorter = sorters.get_fuzzy_file(), + }):find() +end + +-- fuzzy find in references to symbol under cursor +finders.lsp_references = function(opts) + local params = vim.lsp.util.make_position_params() + params.context = { includeDeclaration = false } + + local results_lsp = vim.lsp.buf_request_sync(0, "textDocument/references", params) + local locations = {} + for _, server_results in pairs(results_lsp) do + vim.list_extend(locations, vim.lsp.util.locations_to_items(server_results.result) or {}) + end + + local results = utils.quickfix_items_to_entries(locations) + + if vim.tbl_isempty(results) then + return + end + + pickers.new(opts, { + prompt = 'LSP References', + finder = finders.new_table(results), + previewer = previewers.qflist, + sorter = sorters.get_norcalli_sorter(), + }):find() +end + +-- fuzzy find in document symbols +finders.lsp_document_symbols = function(opts) + local params = vim.lsp.util.make_position_params() + local results_lsp = vim.lsp.buf_request_sync(0, "textDocument/documentSymbol", params) + local locations = {} + for _, server_results in pairs(results_lsp) do + vim.list_extend(locations, vim.lsp.util.symbols_to_items(server_results.result, 0) or {}) + end + + local results = utils.quickfix_items_to_entries(locations) + + if vim.tbl_isempty(results) then + return + end + + pickers.new(opts, { + prompt = 'LSP Document Symbols', + finder = finders.new_table(results), + previewer = previewers.qflist, + sorter = sorters.get_norcalli_sorter(), + }):find() +end + +-- fuzzy find in all workspace symbols (may need longer timeout!) +finders.lsp_workspace_symbols = function(opts) + local params = {query = ''} + local results_lsp = vim.lsp.buf_request_sync(0, "workspace/symbol", params, 1000) + local locations = {} + for _, server_results in pairs(results_lsp) do + vim.list_extend(locations, vim.lsp.util.symbols_to_items(server_results.result, 0) or {}) + end + + local results = utils.quickfix_items_to_entries(locations) + + if vim.tbl_isempty(results) then + return + end + + pickers.new(opts, { + prompt = 'LSP Workspace Symbols', + finder = finders.new_table(results), + previewer = previewers.qflist, + sorter = sorters.get_norcalli_sorter(), + }):find() +end + + +return finders