Files
telescope.nvim/lua/telescope/builtin.lua
Malcolm Ramsay 4cfab37541 Ensure git_files uses the git root directory as cwd (#180)
* fix: Ensure git_files uses the git root directory as cwd

This sets the cwd option of the git_files builtin to use the root of the
git directory when the cwd option doesn't already exist. When git lists
files, it is relative to the root of the git directory, rather than the
current working directory. This caused problems when using git_files in
a subdirectory of the git root (see #174).

This commit fixes the issue by always setting the cwd as the root of the
git directory.

* ref: Use neovim's system caller for command

This removes the need for an additional lua function to extract the shell
response, making use of inbuilt vim functionality.
2020-10-19 14:06:51 -04:00

832 lines
22 KiB
Lua

--[[
A collection of builtin pipelines for telesceope.
Meant for both example and for easy startup.
Any of these functions can just be called directly by doing:
:lua require('telescope.builtin').__name__()
This will use the default configuration options.
Other configuration options still in flux at the moment
--]]
if 1 ~= vim.fn.has('nvim-0.5') then
vim.api.nvim_err_writeln("This plugins requires neovim 0.5")
vim.api.nvim_err_writeln("Please update your neovim.")
return
end
if 2 > vim.o.report then
vim.api.nvim_err_writeln(string.format("[telescope] It seems you have `set report=%s`", vim.o.report))
vim.api.nvim_err_writeln("[telescope] Instead, change 'report' back to its default value. `set report=2`.")
vim.api.nvim_err_writeln("[telescope] If you do not, you will have a bad experience")
end
-- 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 conf = require('telescope.config').values
local filter = vim.tbl_filter
local flatten = vim.tbl_flatten
local builtin = {}
builtin.git_files = function(opts)
opts = opts or {}
if opts.cwd then
opts.cwd = vim.fn.expand(opts.cwd)
else
--- Find root of git directory and remove trailing newline characters
opts.cwd = string.gsub(vim.fn.system("git rev-parse --show-toplevel"), '[\n\r]+', '')
end
-- By creating the entry maker after the cwd options,
-- we ensure the maker uses the cwd options when being created.
opts.entry_maker = opts.entry_maker or make_entry.gen_from_file(opts)
pickers.new(opts, {
prompt_title = 'Git File',
finder = finders.new_oneshot_job(
{ "git", "ls-tree", "--full-tree", "-r", "--name-only", "HEAD" },
opts
),
previewer = previewers.cat.new(opts),
sorter = sorters.get_fuzzy_file(),
}):find()
end
builtin.commands = function()
pickers.new({}, {
prompt_title = 'Commands',
finder = finders.new_table {
results = (function()
local command_iter = vim.api.nvim_get_commands({})
local commands = {}
for _, cmd in pairs(command_iter) do
table.insert(commands, cmd)
end
return commands
end)(),
entry_maker = function(line)
return {
valid = line ~= "",
value = line,
ordinal = line.name,
display = line.name
}
end
},
sorter = sorters.get_generic_fuzzy_sorter(),
attach_mappings = function(prompt_bufnr, map)
local run_command = function()
local selection = actions.get_selected_entry(prompt_bufnr)
actions.close(prompt_bufnr)
local val = selection.value
local cmd = string.format([[:%s ]], val.name)
if val.nargs == "0" then
vim.cmd(cmd)
else
vim.cmd [[stopinsert]]
vim.fn.feedkeys(cmd)
end
end
map('i', '<CR>', run_command)
map('n', '<CR>', run_command)
return true
end
}):find()
end
builtin.live_grep = function(opts)
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 flatten { conf.vimgrep_arguments, prompt }
end,
opts.entry_maker or make_entry.gen_from_vimgrep(opts),
opts.max_results
)
pickers.new(opts, {
prompt_title = 'Live Grep',
finder = live_grepper,
previewer = previewers.vimgrep.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
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 }
local results_lsp = vim.lsp.buf_request_sync(0, "textDocument/references", params, opts.timeout or 10000)
local locations = {}
for _, server_results in pairs(results_lsp) do
if server_results.result then
vim.list_extend(locations, vim.lsp.util.locations_to_items(server_results.result) or {})
end
end
if vim.tbl_isempty(locations) then
return
end
pickers.new(opts, {
prompt_title = 'LSP References',
finder = finders.new_table {
results = locations,
entry_maker = make_entry.gen_from_quickfix(opts),
},
previewer = previewers.qflist.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
builtin.lsp_document_symbols = function(opts)
opts = opts or {}
local params = vim.lsp.util.make_position_params()
local results_lsp = vim.lsp.buf_request_sync(0, "textDocument/documentSymbol", params, opts.timeout or 10000)
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_title = 'LSP Document Symbols',
finder = finders.new_table {
results = locations,
entry_maker = make_entry.gen_from_quickfix(opts)
},
previewer = previewers.vim_buffer.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
builtin.lsp_code_actions = function(opts)
opts = opts or {}
local params = vim.lsp.util.make_range_params()
params.context = {
diagnostics = vim.lsp.util.get_line_diagnostics()
}
local results_lsp, err = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, opts.timeout or 10000)
if err then
print("ERROR: " .. err)
return
end
if not results_lsp or vim.tbl_isempty(results_lsp) then
print("No results from textDocument/codeAction")
return
end
local results = (results_lsp[1] or results_lsp[2]).result;
if #results == 0 then
print("No code actions available")
return
end
for i,x in ipairs(results) do
x.idx = i
end
pickers.new(opts, {
prompt_title = 'LSP Code Actions',
finder = finders.new_table {
results = results,
entry_maker = function(line)
return {
valid = line ~= nil,
value = line,
ordinal = line.idx .. line.title,
display = line.idx .. ': ' .. line.title
}
end
},
attach_mappings = function(prompt_bufnr, map)
local execute = function()
local selection = actions.get_selected_entry(prompt_bufnr)
actions.close(prompt_bufnr)
local val = selection.value
if val.edit or type(val.command) == "table" then
if val.edit then
vim.lsp.util.apply_workspace_edit(val.edit)
end
if type(val.command) == "table" then
vim.lsp.buf.execute_command(val.command)
end
else
vim.lsp.buf.execute_command(val)
end
end
map('i', '<CR>', execute)
map('n', '<CR>', execute)
return true
end,
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
builtin.lsp_workspace_symbols = function(opts)
opts = opts or {}
opts.shorten_path = utils.get_default(opts.shorten_path, true)
local params = {query = opts.query or ''}
local results_lsp = vim.lsp.buf_request_sync(0, "workspace/symbol", params, opts.timeout or 10000)
if not results_lsp or vim.tbl_isempty(results_lsp) then
print("No results from workspace/symbol")
return
end
local locations = {}
for _, server_results in pairs(results_lsp) do
if server_results.result then
vim.list_extend(locations, vim.lsp.util.symbols_to_items(server_results.result, 0) or {})
end
end
if vim.tbl_isempty(locations) then
return
end
pickers.new(opts, {
prompt_title = 'LSP Workspace Symbols',
finder = finders.new_table {
results = locations,
entry_maker = make_entry.gen_from_quickfix(opts)
},
previewer = previewers.qflist.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
builtin.quickfix = function(opts)
opts = opts or {}
local locations = vim.fn.getqflist()
if vim.tbl_isempty(locations) then
return
end
pickers.new(opts, {
prompt_title = 'Quickfix',
finder = finders.new_table {
results = locations,
entry_maker = make_entry.gen_from_quickfix(opts),
},
previewer = previewers.qflist.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
builtin.loclist = function(opts)
local locations = vim.fn.getloclist(0)
local filename = vim.api.nvim_buf_get_name(0)
for _, value in pairs(locations) do
value.filename = filename
end
if vim.tbl_isempty(locations) then
return
end
pickers.new(opts, {
prompt_title = 'Loclist',
finder = finders.new_table {
results = locations,
entry_maker = make_entry.gen_from_quickfix(opts),
},
previewer = previewers.qflist.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
-- Special keys:
-- opts.search -- the string to search.
builtin.grep_string = function(opts)
opts = opts or {}
-- TODO: This should probably check your visual selection as well, if you've got one
local search = opts.search or vim.fn.expand("<cword>")
opts.entry_maker = opts.entry_maker or make_entry.gen_from_vimgrep(opts)
opts.word_match = opts.word_match or nil
pickers.new(opts, {
prompt_title = 'Find Word',
finder = finders.new_oneshot_job(
flatten { conf.vimgrep_arguments, opts.word_match, search},
opts
),
previewer = previewers.vimgrep.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
builtin.oldfiles = function(opts)
opts = opts or {}
pickers.new(opts, {
prompt_title = 'Oldfiles',
finder = finders.new_table(vim.tbl_filter(function(val)
return 0 ~= vim.fn.filereadable(val)
end, vim.v.oldfiles)),
sorter = sorters.get_fuzzy_file(),
previewer = previewers.cat.new(opts),
}):find()
end
builtin.command_history = function(opts)
local history_string = vim.fn.execute('history cmd')
local history_list = vim.split(history_string, "\n")
local results = {}
for i = #history_list, 3, -1 do
local item = history_list[i]
local _, finish = string.find(item, "%d+ +")
table.insert(results, string.sub(item, finish + 1))
end
pickers.new(opts, {
prompt_title = 'Command History',
finder = finders.new_table(results),
sorter = sorters.fuzzy_with_index_bias(),
attach_mappings = function(_, map)
map('i', '<CR>', actions.set_command_line)
-- TODO: Find a way to insert the text... it seems hard.
-- map('i', '<C-i>', actions.insert_value, { expr = true })
return true
end,
}):find()
end
builtin.help_tags = function(opts)
opts = opts or {}
local sourced_file = require('plenary.debug_utils').sourced_filepath()
local base_directory = vim.fn.fnamemodify(sourced_file, ":h:h:h")
local file = base_directory .. "/data/help/tags"
local tags = {}
local f = assert(io.open(file, "rb"))
for line in f:lines() do
table.insert(tags, line)
end
f:close()
pickers.new(opts, {
prompt_title = 'Help',
finder = finders.new_table {
results = tags,
entry_maker = make_entry.gen_from_tagfile(opts),
},
-- TODO: previewer for Vim help
previewer = previewers.help.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
attach_mappings = function(prompt_bufnr, map)
local view_help = function()
local selection = actions.get_selected_entry(prompt_bufnr)
actions.close(prompt_bufnr)
vim.cmd("help " .. selection.value)
end
map('i', '<CR>', view_help)
map('n', '<CR>', view_help)
return true
end
}):find()
end
builtin.reloader = function(opts)
opts = opts or {}
local package_list = vim.tbl_keys(package.loaded)
-- filter out packages we don't want and track the longest package name
opts.column_len = 0
for index, module_name in pairs(package_list) do
if type(require(module_name)) ~= 'table' or module_name:sub(1,1) == "_" or package.searchpath(module_name, package.path) == nil then
table.remove(package_list, index)
elseif #module_name > opts.column_len then
opts.column_len = #module_name
end
end
pickers.new(opts, {
prompt_title = 'Packages',
finder = finders.new_table {
results = package_list,
entry_maker = make_entry.gen_from_packages(opts),
},
-- previewer = previewers.vim_buffer.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
attach_mappings = function(prompt_bufnr, map)
local reload_package = function()
local selection = actions.get_selected_entry(prompt_bufnr)
actions.close(prompt_bufnr)
require('plenary.reload').reload_module(selection.value)
print(string.format("[%s] - module reloaded", selection.value))
end
map('i', '<CR>', reload_package)
map('n', '<CR>', reload_package)
return true
end
}):find()
end
-- TODO: What the heck should we do for accepting this.
-- vim.fn.setreg("+", "nnoremap $TODO :lua require('telescope.builtin').<whatever>()<CR>")
-- 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, {
filename = string.sub(debug_info.source, 2),
lnum = debug_info.linedefined,
col = 0,
text = k,
start = debug_info.linedefined,
finish = debug_info.lastlinedefined,
})
end
pickers.new(opts, {
prompt_title = 'Telescope Builtin',
finder = finders.new_table {
results = objs,
entry_maker = make_entry.gen_from_quickfix(opts),
},
previewer = previewers.qflist.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
-- TODO: Maybe just change this to `find`.
-- Support `find` and maybe let people do other stuff with it as well.
builtin.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' }
elseif 1 == vim.fn.executable("find") then
find_command = { 'find', '-type', 'f' }
end
end
if not find_command then
print("You need to install either find, fd, or rg. You can also submit a PR to add support for another file finder :)")
return
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)
pickers.new(opts, {
prompt_title = 'Find Files',
finder = finders.new_oneshot_job(
find_command,
opts
),
previewer = previewers.cat.new(opts),
sorter = sorters.get_fuzzy_file(opts),
}):find()
end
-- Leave this alias around for people.
builtin.fd = builtin.find_files
-- TODO: I'd like to use the `vim_buffer` previewer, but it doesn't seem to work due to some styling problems.
-- I think it has something to do with nvim_open_win and style='minimal',
-- Status, currently operational.
builtin.buffers = function(opts)
opts = opts or {}
local buffers = filter(function(b)
return
(opts.show_all_buffers
or vim.api.nvim_buf_is_loaded(b))
and 1 == vim.fn.buflisted(b)
end, vim.api.nvim_list_bufs())
if not opts.bufnr_width then
local max_bufnr = math.max(unpack(buffers))
opts.bufnr_width = #tostring(max_bufnr)
end
pickers.new(opts, {
prompt_title = 'Buffers',
finder = finders.new_table {
results = buffers,
entry_maker = make_entry.gen_from_buffer(opts)
},
-- previewer = previewers.vim_buffer.new(opts),
previewer = previewers.vimgrep.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
local function prepare_match(entry, kind)
local entries = {}
if entry.node then
entry["kind"] = kind
table.insert(entries, entry)
else
for name, item in pairs(entry) do
vim.list_extend(entries, prepare_match(item, name))
end
end
return entries
end
builtin.treesitter = function(opts)
opts = opts or {}
opts.show_line = utils.get_default(opts.show_line, true)
local has_nvim_treesitter, _ = pcall(require, 'nvim-treesitter')
if not has_nvim_treesitter then
print('You need to install nvim-treesitter')
return
end
local parsers = require('nvim-treesitter.parsers')
if not parsers.has_parser() then
print('No parser for the current buffer')
return
end
local ts_locals = require('nvim-treesitter.locals')
local bufnr = opts.bufnr or vim.api.nvim_get_current_buf()
local results = {}
for _, definitions in ipairs(ts_locals.get_definitions(bufnr)) do
local entries = prepare_match(definitions)
for _, entry in ipairs(entries) do
table.insert(results, entry)
end
end
if vim.tbl_isempty(results) then
return
end
pickers.new(opts, {
prompt_title = 'Treesitter Symbols',
finder = finders.new_table {
results = results,
entry_maker = make_entry.gen_from_treesitter(opts)
},
previewer = previewers.vim_buffer.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
builtin.planets = function(opts)
opts = opts or {}
local show_pluto = opts.show_pluto or false
local sourced_file = require('plenary.debug_utils').sourced_filepath()
local base_directory = vim.fn.fnamemodify(sourced_file, ":h:h:h")
local globbed_files = vim.fn.globpath(base_directory .. '/data/memes/planets/', '*', true, true)
local acceptable_files = {}
for _, v in ipairs(globbed_files) do
if not show_pluto and v:find("pluto") then
else
table.insert(acceptable_files,vim.fn.fnamemodify(v, ':t'))
end
end
pickers.new {
prompt_title = 'Planets',
finder = finders.new_table {
results = acceptable_files,
entry_maker = function(line)
return {
ordinal = line,
display = line,
filename = base_directory .. '/data/memes/planets/' .. line,
}
end
},
previewer = previewers.cat.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
attach_mappings = function(prompt_bufnr, map)
map('i', '<CR>', function()
local selection = actions.get_selected_entry(prompt_bufnr)
actions.close(prompt_bufnr)
print("Enjoy astronomy! You viewed:", selection.display)
end)
return true
end,
}:find()
end
builtin.current_buffer_fuzzy_find = function(opts)
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
local lines_with_numbers = {}
for k, v in ipairs(lines) do
table.insert(lines_with_numbers, {k, v})
end
pickers.new(opts, {
prompt_title = 'Current Buffer Fuzzy',
finder = finders.new_table {
results = lines_with_numbers,
entry_maker = function(enumerated_line)
return {
display = enumerated_line[2],
ordinal = enumerated_line[2],
lnum = enumerated_line[1],
}
end
},
sorter = sorters.get_generic_fuzzy_sorter(),
attach_mappings = function(prompt_bufnr, map)
local goto_line = function()
local selection = actions.get_selected_entry(prompt_bufnr)
actions.close(prompt_bufnr)
vim.api.nvim_win_set_cursor(0, {selection.lnum, 0})
vim.cmd [[stopinsert]]
end
map('n', '<CR>', goto_line)
map('i', '<CR>', goto_line)
return true
end
}):find()
end
builtin.man_pages = function(opts)
opts = opts or {}
local cmd = opts.man_cmd or "apropos --sections=1 ''"
local f = assert(io.popen(cmd, 'r'))
local pages = assert(f:read('*a'))
f:close()
local lines = {}
for s in pages:gmatch("[^\r\n]+") do
table.insert(lines, s)
end
pickers.new(opts, {
prompt_tile = 'Man',
finder = finders.new_table {
results = lines,
entry_maker = make_entry.gen_from_apropos(opts),
},
previewer = previewers.man.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
attach_mappings = function(prompt_bufnr, map)
local view_manpage = function()
local selection = actions.get_selected_entry(prompt_bufnr)
actions.close(prompt_bufnr)
print(vim.inspect(selection.value))
vim.cmd("Man " .. selection.value)
end
map('i', '<CR>', view_manpage)
map('n', '<CR>', view_manpage)
return true
end
}):find()
end
builtin.colorscheme = function(opts)
opts = opts or {}
local colors = vim.list_extend(opts.colors or {}, vim.fn.getcompletion('', 'color'))
pickers.new(opts,{
prompt = 'Change Colorscheme',
finder = finders.new_table {
results = colors
},
-- TODO: better preview?
sorter = sorters.get_generic_fuzzy_sorter(),
attach_mappings = function(prompt_bufnr, map)
local change_colorscheme = function()
local selection = actions.get_selected_entry(prompt_bufnr)
actions.close(prompt_bufnr)
print(vim.inspect(selection.value))
vim.cmd("colorscheme " .. selection.value)
end
map('i', '<CR>', change_colorscheme)
map('n', '<CR>', change_colorscheme)
return true
end
}):find()
end
builtin.marks = function(opts)
opts = opts or {}
local marks = vim.api.nvim_exec("marks", true)
local marks_table = vim.fn.split(marks, "\n")
-- Pop off the header.
table.remove(marks_table, 1)
pickers.new(opts,{
prompt = 'Marks',
finder = finders.new_table {
results = marks_table,
entry_maker = make_entry.gen_from_marks(opts),
},
previewer = previewers.vimgrep.new(opts),
sorter = sorters.get_generic_fuzzy_sorter(),
}):find()
end
return builtin