feat: Major improvements in API. Particularly relating to entries.

This commit is contained in:
TJ DeVries
2020-09-03 23:56:49 -04:00
parent 737363097b
commit 839f57efb3
13 changed files with 690 additions and 216 deletions

View File

@@ -1 +0,0 @@
plugin/telescope.vim

View File

@@ -42,22 +42,31 @@ function actions.goto_file_selection(prompt_bufnr)
print("[telescope] Nothing currently selected") print("[telescope] Nothing currently selected")
return return
else else
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 local value = entry.value
if not value then if not value then
print("Could not do anything with blank line...") print("Could not do anything with blank line...")
return return
end end
-- TODO: This is not great.
if type(value) == "table" then if type(value) == "table" then
value = entry.display value = entry.display
end end
local sections = vim.split(value, ":") local sections = vim.split(value, ":")
local filename = sections[1] filename = sections[1]
local row = tonumber(sections[2]) row = tonumber(sections[2])
local col = tonumber(sections[3]) col = tonumber(sections[3])
end
vim.cmd(string.format([[bwipeout! %s]], prompt_bufnr)) vim.cmd(string.format([[bwipeout! %s]], prompt_bufnr))

View File

@@ -11,94 +11,69 @@ This will use the default configuration options.
Other configuration options still in flux at the moment 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 actions = require('telescope.actions')
local finders = require('telescope.finders') local finders = require('telescope.finders')
local make_entry = require('telescope.make_entry')
local previewers = require('telescope.previewers') local previewers = require('telescope.previewers')
local pickers = require('telescope.pickers') local pickers = require('telescope.pickers')
local sorters = require('telescope.sorters') local sorters = require('telescope.sorters')
local utils = require('telescope.utils') 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 = {} local builtin = {}
builtin.git_files = function(opts) builtin.git_files = function(opts)
opts = opts or {} 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, { pickers.new(opts, {
prompt = 'Git File', 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, previewer = previewers.cat,
sorter = sorters.get_fuzzy_file(), sorter = sorters.get_fuzzy_file(),
}):find() }):find()
end end
builtin.live_grep = function(opts) builtin.live_grep = function(opts)
local live_grepper = finders.new { opts = opts or {}
fn_command = function(_, prompt)
-- TODO: Make it so that we can start searching on the first character. 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 if not prompt or prompt == "" then
return nil return nil
end end
return { return flatten { vimgrep_arguments, prompt }
command = 'rg', end,
args = {"--vimgrep", prompt}, opts.entry_maker or make_entry.gen_from_vimgrep(opts),
} opts.max_results
end )
}
pickers.new(opts, { pickers.new(opts, {
prompt = 'Live Grep', prompt = 'Live Grep',
finder = live_grepper, finder = live_grepper,
previewer = previewers.vimgrep, previewer = previewers.vimgrep,
}):find() }):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 end
-- TODO: document_symbol -- TODO: document_symbol
-- TODO: workspace_symbol -- TODO: workspace_symbol
builtin.lsp_references = function(opts) 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() local params = vim.lsp.util.make_position_params()
params.context = { includeDeclaration = true } 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 {}) vim.list_extend(locations, vim.lsp.util.locations_to_items(server_results.result) or {})
end end
local results = utils.quickfix_items_to_entries(locations) if vim.tbl_isempty(locations) then
if vim.tbl_isempty(results) then
return return
end end
local reference_picker = pickers.new(opts, { pickers.new(opts, {
prompt = 'LSP References', 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, previewer = previewers.qflist,
sorter = sorters.get_norcalli_sorter(), sorter = sorters.get_norcalli_sorter(),
}):find() }):find()
@@ -124,15 +158,17 @@ end
builtin.quickfix = function(opts) builtin.quickfix = function(opts)
local locations = vim.fn.getqflist() 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 return
end end
pickers.new(opts, { pickers.new(opts, {
prompt = 'Quickfix', prompt = 'Quickfix',
finder = finders.new_table(results), finder = finders.new_table {
results = locations,
entry_maker = make_entry.gen_from_quickfix(opts),
},
previewer = previewers.qflist, previewer = previewers.qflist,
sorter = sorters.get_norcalli_sorter(), sorter = sorters.get_norcalli_sorter(),
}):find() }):find()
@@ -146,15 +182,16 @@ builtin.loclist = function(opts)
value.filename = filename value.filename = filename
end end
local results = utils.quickfix_items_to_entries(locations) if vim.tbl_isempty(locations) then
if vim.tbl_isempty(results) then
return return
end end
pickers.new(opts, { pickers.new(opts, {
prompt = 'Loclist', prompt = 'Loclist',
finder = finders.new_table(results), finder = finders.new_table {
results = locations,
entry_maker = make_entry.gen_from_quickfix(opts),
},
previewer = previewers.qflist, previewer = previewers.qflist,
sorter = sorters.get_norcalli_sorter(), sorter = sorters.get_norcalli_sorter(),
}):find() }):find()
@@ -165,9 +202,12 @@ builtin.grep_string = function(opts)
local search = opts.search or vim.fn.expand("<cword>") local search = opts.search or vim.fn.expand("<cword>")
local file_picker = pickers.new(opts, { pickers.new(opts, {
prompt = 'Find Word', 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, previewer = previewers.vimgrep,
sorter = sorters.get_norcalli_sorter(), sorter = sorters.get_norcalli_sorter(),
}):find() }):find()
@@ -218,27 +258,32 @@ end
-- vim.fn.setreg("+", "nnoremap $TODO :lua require('telescope.builtin').<whatever>()<CR>") -- vim.fn.setreg("+", "nnoremap $TODO :lua require('telescope.builtin').<whatever>()<CR>")
-- TODO: Can we just do the names instead? -- TODO: Can we just do the names instead?
builtin.builtin = function(opts) 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 = {} local objs = {}
for k, v in pairs(builtin) do for k, v in pairs(builtin) do
local debug_info = debug.getinfo(v) local debug_info = debug.getinfo(v)
table.insert(objs, { table.insert(objs, {
vimgrep_str = k,
filename = string.sub(debug_info.source, 2), filename = string.sub(debug_info.source, 2),
lnum = debug_info.linedefined, lnum = debug_info.linedefined,
col = 0, col = 0,
text = k,
start = debug_info.linedefined, start = debug_info.linedefined,
finish = debug_info.lastlinedefined, finish = debug_info.lastlinedefined,
}) })
end end
local entries = utils.quickfix_items_to_entries(objs)
pickers.new(opts, { pickers.new(opts, {
prompt = 'Telescope Builtin', 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, previewer = previewers.qflist,
sorter = sorters.get_norcalli_sorter(), sorter = sorters.get_norcalli_sorter(),
}):find() }):find()
@@ -269,14 +314,10 @@ builtin.fd = function(opts)
pickers.new(opts, { pickers.new(opts, {
prompt = 'Find Files', prompt = 'Find Files',
finder = finders.new { finder = finders.new_oneshot_job(
fn_command = function() {fd_string},
return { make_entry.gen_from_file(opts)
command = fd_string, ),
cwd = cwd,
}
end,
},
previewer = previewers.cat, previewer = previewers.cat,
sorter = sorters.get_fuzzy_file(), sorter = sorters.get_fuzzy_file(),
}):find() }):find()

View File

@@ -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

View File

@@ -1,5 +1,6 @@
local Job = require('plenary.job') local Job = require('plenary.job')
local make_entry = require('telescope.make_entry')
local log = require('telescope.log') local log = require('telescope.log')
local utils = require('telescope.utils') local utils = require('telescope.utils')
@@ -10,35 +11,43 @@ local finders = {}
-- FunctionFinder(my_func) -- FunctionFinder(my_func)
-- JobFinder(my_job_args) -- JobFinder(my_job_args)
---@class Finder local _callable_obj = function()
local Finder = {} local obj = {}
Finder.__index = Finder obj.__index = obj
Finder.__call = function(t, ... ) return t:_find(...) end 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 --- Create a new finder command
--- ---
---@param opts table Keys: ---@param opts table Keys:
-- fn_command function The function to call -- fn_command function The function to call
function Finder:new(opts) function JobFinder:new(opts)
opts = opts or {} opts = opts or {}
-- TODO: Add config for: assert(not opts.results, "`results` should be used with finder.new_table")
-- - cwd
-- TODO: -- TODO:
-- - `types` -- - `types`
-- job -- job
-- pipe -- pipe
-- vim.loop.new_pipe (stdin / stdout). stdout => filter 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 -- rg huge_search | fzf --filter prompt_is > buffer. buffer could do stuff do w/ preview callback
-- string
-- list
-- ...
local obj = setmetatable({ local obj = setmetatable({
results = opts.results, entry_maker = opts.entry_maker or make_entry.from_string,
entry_maker = opts.entry_maker,
fn_command = opts.fn_command, fn_command = opts.fn_command,
static = opts.static, static = opts.static,
state = {}, state = {},
@@ -51,26 +60,7 @@ function Finder:new(opts)
return obj return obj
end end
-- Probably should use the word apply here, since we're apply the callback passed to us by function JobFinder:_find(prompt, process_result, process_complete)
-- 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
if self.job and not self.job.is_shutdown then if self.job and not self.job.is_shutdown then
self.job:shutdown() self.job:shutdown()
end end
@@ -140,22 +130,66 @@ function Finder:_find(prompt, process_result, process_complete)
self.job:start() self.job:start()
end end
--- Return a new Finder --[[ =============================================================
-- Static Finders
--@return Finder
finders.new = function(opts) A static finder has results that never change.
return Finder:new(opts) 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 end
-- TODO: Is this worth making? function StaticFinder:_find(_, process_result, process_complete)
-- finders.new_responsive_job = function(opts) for _, v in ipairs(self.results) do
-- return finders.new { process_result(v)
-- maximum_results = get_default(opts.maximum_results, 2000), end
-- }
-- end
finders.new_job = function(command_generator) process_complete()
return finders.new { 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) fn_command = function(_, prompt)
local command_list = command_generator(prompt) local command_list = command_generator(prompt)
if command_list == nil then if command_list == nil then
@@ -168,19 +202,24 @@ finders.new_job = function(command_generator)
command = command, command = command,
args = command_list, args = command_list,
} }
end end,
entry_maker = entry_maker,
maximum_results = maximum_results,
} }
end 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) finders.new_oneshot_job = function(command_list, entry_maker)
command_list = vim.deepcopy(command_list) command_list = vim.deepcopy(command_list)
local command = table.remove(command_list, 1) local command = table.remove(command_list, 1)
return finders.new { return JobFinder:new {
static = true, static = true,
entry_maker = entry_maker, entry_maker = entry_maker or make_entry.from_string,
fn_command = function() fn_command = function()
return { return {
@@ -191,18 +230,14 @@ finders.new_oneshot_job = function(command_list, entry_maker)
} }
end end
--- Used to create a finder for a Lua table.
-- If you only pass a table of results, then it will use that as the entries.
--
-- If you pass a table, and then a function, it's used as:
-- results table, the results to run on
-- entry_maker function, the function to convert results to entries.
finders.new_table = function(t) finders.new_table = function(t)
return finders.new { return StaticFinder:new(t)
results = t
}
end 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 return finders

View File

@@ -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

View File

@@ -1,6 +1,5 @@
local a = vim.api local a = vim.api
local popup = require('popup') local popup = require('popup')
local has_devicons, devicons = pcall(require, 'nvim-web-devicons')
local actions = require('telescope.actions') local actions = require('telescope.actions')
local log = require('telescope.log') local log = require('telescope.log')
@@ -8,8 +7,6 @@ local mappings = require('telescope.mappings')
local state = require('telescope.state') local state = require('telescope.state')
local utils = require('telescope.utils') local utils = require('telescope.utils')
local Entry = require('telescope.entry')
local get_default = utils.get_default local get_default = utils.get_default
-- TODO: Make this work with deep extend I think. -- TODO: Make this work with deep extend I think.
@@ -255,14 +252,19 @@ function Picker:find()
return return
end end
-- TODO: This really isn't the place to do this. local display
local display = entry.display if type(entry.display) == 'function' then
display = entry:display()
if has_devicons then elseif type(entry.display) == 'string' then
local icon = devicons.get_icon(display, vim.fn.fnamemodify(display, ":e")) display = entry.display
display = (icon or ' ') .. ' ' .. display else
log.info("Weird entry", entry)
return
end 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 display = ' ' .. display
-- log.info("Setting row", row, "with value", entry) -- log.info("Setting row", row, "with value", entry)
@@ -277,10 +279,9 @@ function Picker:find()
end end
)) ))
local process_result = function(line) local process_result = function(entry)
local entry = Entry:new(line) -- TODO: Should we even have valid?
if entry.valid == false then
if not entry.valid then
return return
end end
@@ -559,10 +560,7 @@ pickers.entry_manager = function(max_results, set_entry)
return setmetatable({ return setmetatable({
add_entry = function(self, score, entry) add_entry = function(self, score, entry)
-- TODO: Consider forcing people to make these entries before we add them. assert(type(entry) == "table", "entry must be a table by the time it reaches here")
if type(entry) == "string" then
entry = Entry:new(entry)
end
score = score or 0 score = score or 0
@@ -647,6 +645,10 @@ function pickers.on_close_prompt(prompt_bufnr)
local picker = status.picker local picker = status.picker
picker:close_windows(status) picker:close_windows(status)
if picker.previewer then
picker.previewer:teardown()
end
end end

View File

@@ -8,6 +8,7 @@ local Previewer = {}
Previewer.__index = Previewer Previewer.__index = Previewer
local bat_options = " --style=grid --paging=always " local bat_options = " --style=grid --paging=always "
local previewer_ns = vim.api.nvim_create_namespace('telescope.previewers')
-- --terminal-width=%s -- --terminal-width=%s
-- TODO: We shoudl make sure that all our terminals close all the way. -- TODO: We shoudl make sure that all our terminals close all the way.
@@ -20,6 +21,7 @@ function Previewer:new(opts)
return setmetatable({ return setmetatable({
state = nil, state = nil,
_setup_func = opts.setup, _setup_func = opts.setup,
_teardown_func = opts.teardown,
preview_fn = opts.preview_fn, preview_fn = opts.preview_fn,
}, Previewer) }, Previewer)
end end
@@ -29,13 +31,22 @@ function Previewer:preview(entry, status)
return return
end end
if not self.state and self._setup_func then if not self.state then
if self._setup_func then
self.state = self._setup_func() self.state = self._setup_func()
end end
end
self:teardown()
return self:preview_fn(entry, status) return self:preview_fn(entry, status)
end end
function Previewer:teardown()
if self._teardown_func then
self:_teardown_func()
end
end
previewers.new = function(...) previewers.new = function(...)
return Previewer:new(...) return Previewer:new(...)
end end
@@ -69,20 +80,36 @@ previewers.new_termopen = function(opts)
end end
previewers.vim_buffer = previewers.new { previewers.vim_buffer = previewers.new {
preview_fn = function(_, entry, status) 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 local value = entry.value
if value == nil then filename = vim.split(value, ":")[1]
end
if filename == nil then
return return
end end
local file_name = vim.split(value, ":")[1]
log.trace("Previewing File: %s", file_name) log.trace("Previewing File: %s", filename)
-- vim.fn.termopen( local bufnr = vim.fn.bufnr(filename)
-- string.format("bat --color=always --style=grid %s"), if bufnr == -1 then
-- vim.fn.fnamemodify(file_name, ":p") -- TODO: Is this the best way to load the buffer?... I'm not sure tbh
local bufnr = vim.fn.bufadd(file_name) bufnr = vim.fn.bufadd(bufnr)
vim.fn.bufload(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. -- 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)) -- 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(preview_win, 'winblend', 20)
vim.api.nvim_win_set_option(status.preview_win, 'signcolumn', 'no') vim.api.nvim_win_set_option(status.preview_win, 'signcolumn', 'no')
vim.api.nvim_win_set_option(status.preview_win, 'foldlevel', 100) 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, end,
} }

View File

@@ -99,6 +99,10 @@ utils.path_shorten = (function()
]] ]]
return function(path) return function(path)
if not path then
return path
end
local c_str = ffi.new("char[?]", #path + 1) local c_str = ffi.new("char[?]", #path + 1)
ffi.copy(c_str, path) ffi.copy(c_str, path)
return ffi.string(ffi.C.shorten_dir(c_str)) return ffi.string(ffi.C.shorten_dir(c_str))

View File

@@ -1,15 +1,21 @@
RELOAD('telescope') require('plenary.reload').reload_module('telescope')
local actions = require('telescope.actions')
local finders = require('telescope.finders') local finders = require('telescope.finders')
local previewers = require('telescope.previewers')
local pickers = require('telescope.pickers') local pickers = require('telescope.pickers')
local sorters = require('telescope.sorters') local sorters = require('telescope.sorters')
local utils = require('telescope.utils')
pickers.new({ pickers.new({
prompt = 'Telescope Builtin', 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(), sorter = sorters.get_norcalli_sorter(),
}):find() }):find()

26
media/worflow.txt Normal file
View File

@@ -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.

View File

@@ -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()

130
scratch/clason_finders.lua Normal file
View File

@@ -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