--[[ 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 local actions = require('telescope.actions') local finders = require('telescope.finders') local log = require('telescope.log') local make_entry = require('telescope.make_entry') local path = require('telescope.path') local pickers = require('telescope.pickers') local previewers = require('telescope.previewers') 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 = require('telescope.builtin.git').files builtin.git_commits = require('telescope.builtin.git').commits builtin.git_bcommits = require('telescope.builtin.git').bcommits builtin.git_branches = require('telescope.builtin.git').branches builtin.git_status = require('telescope.builtin.git').status 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 = conf.generic_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', '', run_command) map('n', '', 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 = conf.generic_sorter(opts), }):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 = conf.generic_sorter(opts), }):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 = conf.generic_sorter(opts), }):find() end builtin.lsp_code_actions = function(opts) opts = opts or {} local params = vim.lsp.util.make_range_params() params.context = { diagnostics = vim.lsp.diagnostic.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 not results or #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', '', execute) map('n', '', execute) return true end, sorter = conf.generic_sorter(opts), }):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 = conf.generic_sorter(opts), }):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 = conf.generic_sorter(opts), }):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 = conf.generic_sorter(opts), }):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("") 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 = conf.generic_sorter(opts), }):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 = conf.file_sorter(opts), 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', '', actions.set_command_line) -- TODO: Find a way to insert the text... it seems hard. -- map('i', '', actions.insert_value, { expr = true }) return true end, }):find() end builtin.vim_options = function(opts) opts = opts or {} -- Load vim options. local vim_opts = loadfile(utils.data_directory() .. path.separator .. 'options' .. path.separator .. 'options.lua')().options pickers.new(opts, { prompt = 'options', finder = finders.new_table { results = vim_opts, entry_maker = make_entry.gen_from_vimoptions(opts), }, -- TODO: previewer for Vim options -- previewer = previewers.help.new(opts), sorter = sorters.get_fzy_sorter(), attach_mappings = function(prompt_bufnr, map) local edit_option = function() local selection = actions.get_selected_entry(prompt_bufnr) local esc = "" if vim.fn.mode() == "i" then -- TODO: don't make this local esc = vim.api.nvim_replace_termcodes("", true, false, true) end -- TODO: Make this actually work. -- actions.close(prompt_bufnr) -- vim.api.nvim_win_set_var(vim.fn.nvim_get_current_win(), "telescope", 1) -- print(prompt_bufnr) -- print(vim.fn.bufnr()) -- vim.cmd([[ autocmd BufEnter ++nested ++once startinsert!]]) -- print(vim.fn.winheight(0)) -- local prompt_winnr = vim.fn.getbufinfo(prompt_bufnr)[1].windows[1] -- print(prompt_winnr) -- local float_opts = {} -- float_opts.relative = "editor" -- float_opts.anchor = "sw" -- float_opts.focusable = false -- float_opts.style = "minimal" -- float_opts.row = vim.api.nvim_get_option("lines") - 2 -- TODO: include `cmdheight` and `laststatus` in this calculation -- float_opts.col = 2 -- float_opts.height = 10 -- float_opts.width = string.len(selection.last_set_from)+15 -- local buf = vim.fn.nvim_create_buf(false, true) -- vim.fn.nvim_buf_set_lines(buf, 0, 0, false, {"default value: abcdef", "last set from: " .. selection.last_set_from}) -- local status_win = vim.fn.nvim_open_win(buf, false, float_opts) -- -- vim.api.nvim_win_set_option(status_win, "winblend", 100) -- vim.api.nvim_win_set_option(status_win, "winhl", "Normal:PmenuSel") -- -- vim.api.nvim_set_current_win(status_win) -- vim.cmd[[redraw!]] -- vim.cmd("autocmd CmdLineLeave : ++once echom 'beep'") vim.api.nvim_feedkeys(string.format("%s:set %s=%s", esc, selection.name, selection.current_value), "m", true) end map('i', '', edit_option) map('n', '', edit_option) return true end }):find() end builtin.help_tags = function(opts) opts = opts or {} local tags = {} for _, file in pairs(vim.fn.findfile('doc/tags', vim.o.runtimepath, -1)) do local f = assert(io.open(file, "rb")) for line in f:lines() do table.insert(tags, line) end f:close() end pickers.new(opts, { prompt_title = 'Help', finder = finders.new_table { results = tags, entry_maker = make_entry.gen_from_taglist(opts), }, -- TODO: previewer for Vim help previewer = previewers.help.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr, map) local open = function(cmd) local selection = actions.get_selected_entry(prompt_bufnr) actions.close(prompt_bufnr) vim.cmd(cmd .. selection.value) end local nhelp = function() return open("help ") end local vhelp = function() return open("vert bo help ") end local hhelp = function() return open("help ") -- Not sure how explictly make horizontal end local thelp = function() return open("tab help ") end -- Perhaps it would be a good idea to have vsplit,tab,hsplit open -- a builtin action that accepts a command to be ran before creating -- the split or tab map('i', '', nhelp) map('n', '', nhelp) map('i', '', vhelp) map('n', '', vhelp) map('i', '', hhelp) map('n', '', hhelp) map('i', '', thelp) map('n', '', thelp) 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 = conf.generic_sorter(opts), 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', '', reload_package) map('n', '', 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').()") -- 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 = conf.generic_sorter(opts), attach_mappings = function(_, map) map('i', '', actions.run_builtin) return true end }):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 = conf.file_sorter(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 = conf.generic_sorter(opts), }):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 = conf.generic_sorter(opts), }):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 = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr, map) map('i', '', 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 local bufnr = vim.api.nvim_get_current_buf() pickers.new(opts, { prompt_title = 'Current Buffer Fuzzy', finder = finders.new_table { results = lines_with_numbers, entry_maker = function(enumerated_line) return { bufnr = bufnr, display = enumerated_line[2], ordinal = enumerated_line[2], lnum = enumerated_line[1], } end }, sorter = sorters.get_generic_fuzzy_sorter(), attach_mappings = function(prompt_bufnr) actions._goto_file_selection:enhance { post = vim.schedule_wrap(function() local selection = actions.get_selected_entry(prompt_bufnr) vim.api.nvim_win_set_cursor(0, {selection.lnum, 0}) end), } return true end }):find() end builtin.man_pages = function(opts) opts = opts or {} local cmd = opts.man_cmd or "apropos --sections=1 ''" local pages = utils.get_os_command_output(cmd) 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', '', view_manpage) map('n', '', 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', '', change_colorscheme) map('n', '', 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 -- find normal mode mappings builtin.keymaps = function(opts) opts = opts or {} local modes = {"n", "i", "c"} local keymaps_table = {} for _, mode in pairs(modes) do local keymaps_iter = vim.api.nvim_get_keymap(mode) for _, keymap in pairs(keymaps_iter) do table.insert(keymaps_table, keymap) end end pickers.new({}, { prompt_title = 'Key Maps', finder = finders.new_table { results = keymaps_table, entry_maker = function(line) return { valid = line ~= "", value = line, ordinal = line.lhs .. line.rhs, display = line.mode .. ' ' .. utils.display_termcodes(line.lhs) .. ' ' .. line.rhs } end }, sorter = conf.generic_sorter() }):find() end builtin.tags = function(opts) opts = opts or {} local ctags_file = opts.ctags_file or 'tags' if not vim.loop.fs_open(vim.fn.expand(ctags_file), "r", 438) then print('Tags file does not exists. Create one with ctags -R') return end local fd = assert(vim.loop.fs_open(vim.fn.expand(ctags_file), "r", 438)) local stat = assert(vim.loop.fs_fstat(fd)) local data = assert(vim.loop.fs_read(fd, stat.size, 0)) assert(vim.loop.fs_close(fd)) local results = vim.split(data, '\n') pickers.new(opts,{ prompt = 'Tags', finder = finders.new_table { results = results, entry_maker = make_entry.gen_from_ctags(opts), }, previewer = previewers.ctags.new(opts), sorter = conf.generic_sorter(opts), attach_mappings = function(prompt_bufnr) actions._goto_file_selection:enhance { post = function() local selection = actions.get_selected_entry(prompt_bufnr) local scode = string.gsub(selection.scode, '[$]$', '') scode = string.gsub(scode, [[\\]], [[\]]) scode = string.gsub(scode, [[\/]], [[/]]) scode = string.gsub(scode, '[*]', [[\*]]) vim.cmd('norm! gg') vim.fn.search(scode) vim.cmd('norm! zz') end, } return true end }):find() end builtin.current_buffer_tags = function(opts) opts = opts or {} return builtin.tags(vim.tbl_extend("force", {only_current_file = true, hide_filename = true}, opts)) end return builtin