Files
telescope.nvim/lua/telescope/builtin/__internal.lua
Jaehaks 175178e388 fix(__internal) : fix slash problem in oldfiles (#3260)
* fix(__internal) : fix slash problem in oldfiles

- Problems

Problem with slash and backslash being mixed up is chronic issue of
neovim in Windows. it makes telescope prompt perceive same path
differently when it execute to oldfiles picker.

some function like `nvim_buf_get_name()` or `vim.v.oldfiles` give paths
which are mixed up with slash and backslash.

- What it did
For windows, it always needs to change slash(/) to backslash(\) when
function which deal with path because entry_maker works properly only
the case that path string has \ not /.

- Effect

1) oldfiles picker doesn't show duplicated list
2) `defaults.path_display` configuration feature will works well at
   oldfiles pikcer

* fix(__internal) : Repeated comments are erased
2024-09-22 03:35:27 +00:00

1518 lines
45 KiB
Lua

local actions = require "telescope.actions"
local action_set = require "telescope.actions.set"
local action_state = require "telescope.actions.state"
local finders = require "telescope.finders"
local make_entry = require "telescope.make_entry"
local Path = require "plenary.path"
local pickers = require "telescope.pickers"
local previewers = require "telescope.previewers"
local p_window = require "telescope.pickers.window"
local state = require "telescope.state"
local utils = require "telescope.utils"
local conf = require("telescope.config").values
-- Makes sure aliased options are set correctly
local function apply_cwd_only_aliases(opts)
local has_cwd_only = opts.cwd_only ~= nil
local has_only_cwd = opts.only_cwd ~= nil
if has_only_cwd and not has_cwd_only then
-- Internally, use cwd_only
opts.cwd_only = opts.only_cwd
opts.only_cwd = nil
end
return opts
end
---@return boolean
local function buf_in_cwd(bufname, cwd)
if cwd:sub(-1) ~= Path.path.sep then
cwd = cwd .. Path.path.sep
end
local bufname_prefix = bufname:sub(1, #cwd)
return bufname_prefix == cwd
end
local internal = {}
internal.builtin = function(opts)
opts.include_extensions = vim.F.if_nil(opts.include_extensions, false)
opts.use_default_opts = vim.F.if_nil(opts.use_default_opts, false)
local objs = {}
for k, v in pairs(require "telescope.builtin") do
local debug_info = debug.getinfo(v)
table.insert(objs, {
filename = string.sub(debug_info.source, 2),
text = k,
})
end
local title = "Telescope Builtin"
if opts.include_extensions then
title = "Telescope Pickers"
for ext, funcs in pairs(require("telescope").extensions) do
for func_name, func_obj in pairs(funcs) do
-- Only include exported functions whose name doesn't begin with an underscore
if type(func_obj) == "function" and string.sub(func_name, 0, 1) ~= "_" then
local debug_info = debug.getinfo(func_obj)
table.insert(objs, {
filename = string.sub(debug_info.source, 2),
text = string.format("%s : %s", ext, func_name),
})
end
end
end
end
table.sort(objs, function(a, b)
return a.text < b.text
end)
opts.bufnr = vim.api.nvim_get_current_buf()
opts.winnr = vim.api.nvim_get_current_win()
pickers
.new(opts, {
prompt_title = title,
finder = finders.new_table {
results = objs,
entry_maker = function(entry)
return make_entry.set_default_entry_mt({
value = entry,
text = entry.text,
display = entry.text,
ordinal = entry.text,
filename = entry.filename,
}, opts)
end,
},
previewer = previewers.builtin.new(opts),
sorter = conf.generic_sorter(opts),
attach_mappings = function(_)
actions.select_default:replace(function(prompt_bufnr)
local selection = action_state.get_selected_entry()
if not selection then
utils.__warn_no_selection "builtin.builtin"
return
end
-- we do this to avoid any surprises
opts.include_extensions = nil
local picker_opts
if not opts.use_default_opts then
picker_opts = opts
end
actions.close(prompt_bufnr)
vim.schedule(function()
if string.match(selection.text, " : ") then
-- Call appropriate function from extensions
local split_string = vim.split(selection.text, " : ")
local ext = split_string[1]
local func = split_string[2]
require("telescope").extensions[ext][func](picker_opts)
else
-- Call appropriate telescope builtin
require("telescope.builtin")[selection.text](picker_opts)
end
end)
end)
return true
end,
})
:find()
end
internal.resume = function(opts)
opts = opts or {}
opts.cache_index = vim.F.if_nil(opts.cache_index, 1)
local cached_pickers = state.get_global_key "cached_pickers"
if cached_pickers == nil or vim.tbl_isempty(cached_pickers) then
utils.notify("builtin.resume", {
msg = "No cached picker(s).",
level = "INFO",
})
return
end
local picker = cached_pickers[opts.cache_index]
if picker == nil then
utils.notify("builtin.resume", {
msg = string.format("Index too large as there are only '%s' pickers cached", #cached_pickers),
level = "ERROR",
})
return
end
-- reset layout strategy and get_window_options if default as only one is valid
-- and otherwise unclear which was actually set
if picker.layout_strategy == conf.layout_strategy then
picker.layout_strategy = nil
end
if picker.get_window_options == p_window.get_window_options then
picker.get_window_options = nil
end
picker.cache_picker.index = opts.cache_index
-- avoid partial `opts.cache_picker` at picker creation
if opts.cache_picker ~= false then
picker.cache_picker = vim.tbl_extend("keep", opts.cache_picker or {}, picker.cache_picker)
else
picker.cache_picker.disabled = true
end
opts.cache_picker = nil
picker.previewer = picker.all_previewers
if picker.hidden_previewer then
picker.hidden_previewer = nil
opts.previewer = vim.F.if_nil(opts.previewer, false)
end
opts.resumed_picker = true
pickers.new(opts, picker):find()
end
internal.pickers = function(opts)
local cached_pickers = state.get_global_key "cached_pickers"
if cached_pickers == nil or vim.tbl_isempty(cached_pickers) then
utils.notify("builtin.pickers", {
msg = "No cached picker(s).",
level = "INFO",
})
return
end
opts = opts or {}
-- clear cache picker for immediate pickers.new and pass option to resumed picker
if opts.cache_picker ~= nil then
opts._cache_picker = opts.cache_picker
opts.cache_picker = nil
end
pickers
.new(opts, {
prompt_title = "Pickers",
finder = finders.new_table {
results = cached_pickers,
entry_maker = make_entry.gen_from_picker(opts),
},
previewer = previewers.pickers.new(opts),
sorter = conf.generic_sorter(opts),
cache_picker = false,
attach_mappings = function(_, map)
actions.select_default:replace(function(prompt_bufnr)
local curr_picker = action_state.get_current_picker(prompt_bufnr)
local curr_entry = action_state.get_selected_entry()
if not curr_entry then
return
end
actions.close(prompt_bufnr)
local selection_index, _ = utils.list_find(function(v)
if curr_entry.value == v.value then
return true
end
return false
end, curr_picker.finder.results)
opts.cache_picker = opts._cache_picker
opts["cache_index"] = selection_index
opts["initial_mode"] = cached_pickers[selection_index].initial_mode
internal.resume(opts)
end)
map({ "i", "n" }, "<C-x>", actions.remove_selected_picker)
return true
end,
})
:find()
end
internal.planets = function(opts)
local show_pluto = opts.show_pluto or false
local show_moon = opts.show_moon or false
local sourced_file = require("plenary.debug_utils").sourced_filepath()
local base_directory = vim.fn.fnamemodify(sourced_file, ":h: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 (show_pluto or not v:find "pluto") and (show_moon or not v:find "moon") then
table.insert(acceptable_files, vim.fn.fnamemodify(v, ":t"))
end
end
pickers
.new(opts, {
prompt_title = "Planets",
finder = finders.new_table {
results = acceptable_files,
entry_maker = function(line)
return make_entry.set_default_entry_mt({
ordinal = line,
display = line,
filename = base_directory .. "/data/memes/planets/" .. line,
}, opts)
end,
},
previewer = previewers.cat.new(opts),
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.planets"
return
end
actions.close(prompt_bufnr)
print("Enjoy astronomy! You viewed:", selection.display)
end)
return true
end,
})
:find()
end
internal.symbols = function(opts)
local initial_mode = vim.fn.mode()
local files = vim.api.nvim_get_runtime_file("data/telescope-sources/*.json", true)
local data_path = (function()
if not opts.symbol_path then
return Path:new { vim.fn.stdpath "data", "telescope", "symbols" }
else
return Path:new { opts.symbol_path }
end
end)()
if data_path:exists() then
for _, v in ipairs(require("plenary.scandir").scan_dir(data_path:absolute(), { search_pattern = "%.json$" })) do
table.insert(files, v)
end
end
if #files == 0 then
utils.notify("builtin.symbols", {
msg = "No sources found! Check out https://github.com/nvim-telescope/telescope-symbols.nvim "
.. "for some prebuild symbols or how to create you own symbol source.",
level = "ERROR",
})
return
end
local sources = {}
if opts.sources then
for _, v in ipairs(files) do
for _, s in ipairs(opts.sources) do
if v:find(s) then
table.insert(sources, v)
end
end
end
else
sources = files
end
local results = {}
for _, source in ipairs(sources) do
local data = vim.json.decode(Path:new(source):read())
for _, entry in ipairs(data) do
table.insert(results, entry)
end
end
pickers
.new(opts, {
prompt_title = "Symbols",
finder = finders.new_table {
results = results,
entry_maker = function(entry)
return make_entry.set_default_entry_mt({
value = entry,
ordinal = entry[1] .. " " .. entry[2],
display = entry[1] .. " " .. entry[2],
}, opts)
end,
},
sorter = conf.generic_sorter(opts),
attach_mappings = function(_)
if initial_mode == "i" then
actions.select_default:replace(actions.insert_symbol_i)
else
actions.select_default:replace(actions.insert_symbol)
end
return true
end,
})
:find()
end
internal.commands = function(opts)
pickers
.new(opts, {
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
local need_buf_command = vim.F.if_nil(opts.show_buf_command, true)
if need_buf_command then
local buf_command_iter = vim.api.nvim_buf_get_commands(0, {})
buf_command_iter[true] = nil -- remove the redundant entry
for _, cmd in pairs(buf_command_iter) do
table.insert(commands, cmd)
end
end
return commands
end)(),
entry_maker = opts.entry_maker or make_entry.gen_from_commands(opts),
},
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.commands"
return
end
actions.close(prompt_bufnr)
local val = selection.value
local cmd = string.format([[:%s ]], val.name)
if val.nargs == "0" then
local cr = vim.api.nvim_replace_termcodes("<cr>", true, false, true)
cmd = cmd .. cr
end
vim.cmd [[stopinsert]]
vim.api.nvim_feedkeys(cmd, "nt", false)
end)
return true
end,
})
:find()
end
internal.quickfix = function(opts)
local qf_identifier = opts.id or vim.F.if_nil(opts.nr, "$")
local locations = vim.fn.getqflist({ [opts.id and "id" or "nr"] = qf_identifier, items = true }).items
if vim.tbl_isempty(locations) then
utils.notify("builtin.quickfix", { msg = "No quickfix items", level = "INFO" })
return
end
pickers
.new(opts, {
prompt_title = "Quickfix",
finder = finders.new_table {
results = locations,
entry_maker = opts.entry_maker or make_entry.gen_from_quickfix(opts),
},
previewer = conf.qflist_previewer(opts),
sorter = conf.generic_sorter(opts),
})
:find()
end
internal.quickfixhistory = function(opts)
local qflists = {}
for i = 1, 10 do -- (n)vim keeps at most 10 quickfix lists in full
-- qf weirdness: id = 0 gets id of quickfix list nr
local qflist = vim.fn.getqflist { nr = i, id = 0, title = true, items = true }
if not vim.tbl_isempty(qflist.items) then
table.insert(qflists, qflist)
end
end
local entry_maker = opts.make_entry
or function(entry)
return make_entry.set_default_entry_mt({
value = entry.title or "Untitled",
ordinal = entry.title or "Untitled",
display = entry.title or "Untitled",
nr = entry.nr,
id = entry.id,
items = entry.items,
}, opts)
end
local qf_entry_maker = make_entry.gen_from_quickfix(opts)
pickers
.new(opts, {
prompt_title = "Quickfix History",
finder = finders.new_table {
results = qflists,
entry_maker = entry_maker,
},
previewer = previewers.new_buffer_previewer {
title = "Quickfix List Preview",
dyn_title = function(_, entry)
return entry.title
end,
get_buffer_by_name = function(_, entry)
return "quickfixlist_" .. tostring(entry.nr)
end,
define_preview = function(self, entry)
if self.state.bufname then
return
end
local entries = vim.tbl_map(function(i)
return qf_entry_maker(i):display()
end, entry.items)
vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, entries)
end,
},
sorter = conf.generic_sorter(opts),
attach_mappings = function(_, map)
action_set.select:replace(function(prompt_bufnr)
local nr = action_state.get_selected_entry().nr
actions.close(prompt_bufnr)
internal.quickfix { nr = nr }
end)
map({ "i", "n" }, "<C-q>", function(prompt_bufnr)
local nr = action_state.get_selected_entry().nr
actions.close(prompt_bufnr)
vim.cmd(nr .. "chistory")
vim.cmd "botright copen"
end)
return true
end,
})
:find()
end
internal.loclist = function(opts)
local locations = vim.fn.getloclist(0)
local filenames = {}
for _, value in pairs(locations) do
local bufnr = value.bufnr
if filenames[bufnr] == nil then
filenames[bufnr] = vim.api.nvim_buf_get_name(bufnr)
end
value.filename = filenames[bufnr]
end
if vim.tbl_isempty(locations) then
utils.notify("builtin.loclist", { msg = "No loclist items", level = "INFO" })
return
end
pickers
.new(opts, {
prompt_title = "Loclist",
finder = finders.new_table {
results = locations,
entry_maker = opts.entry_maker or make_entry.gen_from_quickfix(opts),
},
previewer = conf.qflist_previewer(opts),
sorter = conf.generic_sorter(opts),
})
:find()
end
internal.oldfiles = function(opts)
opts = apply_cwd_only_aliases(opts)
opts.include_current_session = vim.F.if_nil(opts.include_current_session, true)
local current_buffer = vim.api.nvim_get_current_buf()
local current_file = vim.api.nvim_buf_get_name(current_buffer)
local results = {}
if utils.iswin then -- for slash problem in windows
current_file = current_file:gsub("/", "\\")
end
if opts.include_current_session then
for _, buffer in ipairs(utils.split_lines(vim.fn.execute ":buffers! t")) do
local match = tonumber(string.match(buffer, "%s*(%d+)"))
local open_by_lsp = string.match(buffer, "line 0$")
if match and not open_by_lsp then
local file = vim.api.nvim_buf_get_name(match)
if utils.iswin then
file = file:gsub("/", "\\")
end
if vim.loop.fs_stat(file) and match ~= current_buffer then
table.insert(results, file)
end
end
end
end
for _, file in ipairs(vim.v.oldfiles) do
if utils.iswin then
file = file:gsub("/", "\\")
end
local file_stat = vim.loop.fs_stat(file)
if file_stat and file_stat.type == "file" and not vim.tbl_contains(results, file) and file ~= current_file then
table.insert(results, file)
end
end
if opts.cwd_only or opts.cwd then
local cwd = opts.cwd_only and vim.loop.cwd() or opts.cwd
results = vim.tbl_filter(function(file)
return buf_in_cwd(file, cwd)
end, results)
end
pickers
.new(opts, {
prompt_title = "Oldfiles",
__locations_input = true,
finder = finders.new_table {
results = results,
entry_maker = opts.entry_maker or make_entry.gen_from_file(opts),
},
sorter = conf.file_sorter(opts),
previewer = conf.grep_previewer(opts),
})
:find()
end
internal.command_history = function(opts)
local history_string = vim.fn.execute "history cmd"
local history_list = utils.split_lines(history_string)
local results = {}
local filter_fn = opts.filter_fn
for i = #history_list, 3, -1 do
local item = history_list[i]
local _, finish = string.find(item, "%d+ +")
local cmd = string.sub(item, finish + 1)
if filter_fn then
if filter_fn(cmd) then
table.insert(results, cmd)
end
else
table.insert(results, cmd)
end
end
pickers
.new(opts, {
prompt_title = "Command History",
finder = finders.new_table(results),
sorter = conf.generic_sorter(opts),
attach_mappings = function(_, map)
actions.select_default:replace(actions.set_command_line)
map({ "i", "n" }, "<C-e>", actions.edit_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
internal.search_history = function(opts)
local search_string = vim.fn.execute "history search"
local search_list = utils.split_lines(search_string)
local results = {}
for i = #search_list, 3, -1 do
local item = search_list[i]
local _, finish = string.find(item, "%d+ +")
table.insert(results, string.sub(item, finish + 1))
end
pickers
.new(opts, {
prompt_title = "Search History",
finder = finders.new_table(results),
sorter = conf.generic_sorter(opts),
attach_mappings = function(_, map)
actions.select_default:replace(actions.set_search_line)
map({ "i", "n" }, "<C-e>", actions.edit_search_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
internal.vim_options = function(opts)
local res = {}
for _, v in pairs(vim.api.nvim_get_all_options_info()) do
table.insert(res, v)
end
table.sort(res, function(left, right)
return left.name < right.name
end)
pickers
.new(opts, {
prompt_title = "options",
finder = finders.new_table {
results = res,
entry_maker = opts.entry_maker or make_entry.gen_from_vimoptions(opts),
},
sorter = conf.generic_sorter(opts),
attach_mappings = function()
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.vim_options"
return
end
local esc = ""
if vim.fn.mode() == "i" then
esc = vim.api.nvim_replace_termcodes("<esc>", true, false, true)
end
vim.api.nvim_feedkeys(
string.format("%s:set %s=%s", esc, selection.value.name, selection.value.value),
"m",
true
)
end)
return true
end,
})
:find()
end
internal.help_tags = function(opts)
opts.lang = vim.F.if_nil(opts.lang, vim.o.helplang)
opts.fallback = vim.F.if_nil(opts.fallback, true)
opts.file_ignore_patterns = {}
local langs = vim.split(opts.lang, ",", { trimempty = true })
if opts.fallback and not vim.tbl_contains(langs, "en") then
table.insert(langs, "en")
end
local langs_map = {}
for _, lang in ipairs(langs) do
langs_map[lang] = true
end
local tag_files = {}
local function add_tag_file(lang, file)
if langs_map[lang] then
if tag_files[lang] then
table.insert(tag_files[lang], file)
else
tag_files[lang] = { file }
end
end
end
local help_files = {}
local rtp = vim.o.runtimepath
-- extend the runtime path with all plugins not loaded by lazy.nvim
local lazy = package.loaded["lazy.core.util"]
if lazy and lazy.get_unloaded_rtp then
local paths = lazy.get_unloaded_rtp ""
if #paths > 0 then
rtp = rtp .. "," .. table.concat(paths, ",")
end
end
local all_files = vim.fn.globpath(rtp, "doc/*", 1, 1)
for _, fullpath in ipairs(all_files) do
local file = utils.path_tail(fullpath)
if file == "tags" then
add_tag_file("en", fullpath)
elseif file:match "^tags%-..$" then
local lang = file:sub(-2)
add_tag_file(lang, fullpath)
else
help_files[file] = fullpath
end
end
local tags = {}
local tags_map = {}
local delimiter = string.char(9)
for _, lang in ipairs(langs) do
for _, file in ipairs(tag_files[lang] or {}) do
local lines = utils.split_lines(Path:new(file):read(), { trimempty = true })
for _, line in ipairs(lines) do
-- TODO: also ignore tagComment starting with ';'
if not line:match "^!_TAG_" then
local fields = vim.split(line, delimiter, { trimempty = true })
if #fields == 3 and not tags_map[fields[1]] then
if fields[1] ~= "help-tags" or fields[2] ~= "tags" then
table.insert(tags, {
name = fields[1],
filename = help_files[fields[2]],
cmd = fields[3],
lang = lang,
})
tags_map[fields[1]] = true
end
end
end
end
end
end
pickers
.new(opts, {
prompt_title = "Help",
finder = finders.new_table {
results = tags,
entry_maker = function(entry)
return make_entry.set_default_entry_mt({
value = entry.name .. "@" .. entry.lang,
display = entry.name,
ordinal = entry.name,
filename = entry.filename,
cmd = entry.cmd,
}, opts)
end,
},
previewer = previewers.help.new(opts),
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
action_set.select:replace(function(_, cmd)
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.help_tags"
return
end
actions.close(prompt_bufnr)
if cmd == "default" or cmd == "horizontal" then
vim.cmd("help " .. selection.value)
elseif cmd == "vertical" then
vim.cmd("vert help " .. selection.value)
elseif cmd == "tab" then
vim.cmd("tab help " .. selection.value)
end
end)
return true
end,
})
:find()
end
internal.man_pages = function(opts)
opts.sections = vim.F.if_nil(opts.sections, { "1" })
assert(utils.islist(opts.sections), "sections should be a list")
opts.man_cmd = utils.get_lazy_default(opts.man_cmd, function()
local uname = vim.loop.os_uname()
local sysname = string.lower(uname.sysname)
if sysname == "darwin" then
local major_version = tonumber(vim.fn.matchlist(uname.release, [[^\(\d\+\)\..*]])[2]) or 0
return major_version >= 22 and { "apropos", "." } or { "apropos", " " }
elseif sysname == "freebsd" then
return { "apropos", "." }
else
return { "apropos", "" }
end
end)
opts.entry_maker = opts.entry_maker or make_entry.gen_from_apropos(opts)
opts.env = { PATH = vim.env.PATH, MANPATH = vim.env.MANPATH }
pickers
.new(opts, {
prompt_title = "Man",
finder = finders.new_oneshot_job(opts.man_cmd, opts),
previewer = previewers.man.new(opts),
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
action_set.select:replace(function(_, cmd)
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.man_pages"
return
end
local args = selection.section .. " " .. selection.value
actions.close(prompt_bufnr)
if cmd == "default" or cmd == "horizontal" then
vim.cmd("Man " .. args)
elseif cmd == "vertical" then
vim.cmd("vert Man " .. args)
elseif cmd == "tab" then
vim.cmd("tab Man " .. args)
end
end)
return true
end,
})
:find()
end
internal.reloader = function(opts)
local package_list = vim.tbl_keys(package.loaded)
-- filter out packages we don't want and track the longest package name
local 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 > column_len then
column_len = #module_name
end
end
opts.column_len = vim.F.if_nil(opts.column_len, column_len)
pickers
.new(opts, {
prompt_title = "Packages",
finder = finders.new_table {
results = package_list,
entry_maker = opts.entry_maker or make_entry.gen_from_packages(opts),
},
-- previewer = previewers.vim_buffer.new(opts),
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.reloader"
return
end
actions.close(prompt_bufnr)
require("plenary.reload").reload_module(selection.value)
utils.notify("builtin.reloader", {
msg = string.format("[%s] - module reloaded", selection.value),
level = "INFO",
})
end)
return true
end,
})
:find()
end
internal.buffers = function(opts)
opts = apply_cwd_only_aliases(opts)
local bufnrs = vim.tbl_filter(function(bufnr)
if 1 ~= vim.fn.buflisted(bufnr) then
return false
end
-- only hide unloaded buffers if opts.show_all_buffers is false, keep them listed if true or nil
if opts.show_all_buffers == false and not vim.api.nvim_buf_is_loaded(bufnr) then
return false
end
if opts.ignore_current_buffer and bufnr == vim.api.nvim_get_current_buf() then
return false
end
local bufname = vim.api.nvim_buf_get_name(bufnr)
if opts.cwd_only and not buf_in_cwd(bufname, vim.loop.cwd()) then
return false
end
if not opts.cwd_only and opts.cwd and not buf_in_cwd(bufname, opts.cwd) then
return false
end
return true
end, vim.api.nvim_list_bufs())
if not next(bufnrs) then
utils.notify("builtin.buffers", { msg = "No buffers found with the provided options", level = "INFO" })
return
end
if opts.sort_mru then
table.sort(bufnrs, function(a, b)
return vim.fn.getbufinfo(a)[1].lastused > vim.fn.getbufinfo(b)[1].lastused
end)
end
if type(opts.sort_buffers) == "function" then
table.sort(bufnrs, opts.sort_buffers)
end
local buffers = {}
local default_selection_idx = 1
for i, bufnr in ipairs(bufnrs) do
local flag = bufnr == vim.fn.bufnr "" and "%" or (bufnr == vim.fn.bufnr "#" and "#" or " ")
if opts.sort_lastused and not opts.ignore_current_buffer and flag == "#" then
default_selection_idx = 2
end
local element = {
bufnr = bufnr,
flag = flag,
info = vim.fn.getbufinfo(bufnr)[1],
}
if opts.sort_lastused and (flag == "#" or flag == "%") then
local idx = ((buffers[1] ~= nil and buffers[1].flag == "%") and 2 or 1)
table.insert(buffers, idx, element)
else
if opts.select_current and flag == "%" then
default_selection_idx = i
end
table.insert(buffers, element)
end
end
if not opts.bufnr_width then
local max_bufnr = math.max(unpack(bufnrs))
opts.bufnr_width = #tostring(max_bufnr)
end
pickers
.new(opts, {
prompt_title = "Buffers",
finder = finders.new_table {
results = buffers,
entry_maker = opts.entry_maker or make_entry.gen_from_buffer(opts),
},
previewer = conf.grep_previewer(opts),
sorter = conf.generic_sorter(opts),
default_selection_index = default_selection_idx,
attach_mappings = function(_, map)
map({ "i", "n" }, "<M-d>", actions.delete_buffer)
return true
end,
})
:find()
end
internal.colorscheme = function(opts)
local before_background = vim.o.background
local before_color = vim.api.nvim_exec2("colorscheme", { output = true }).output
local need_restore = not not opts.enable_preview
local colors = opts.colors or { before_color }
if not vim.tbl_contains(colors, before_color) then
table.insert(colors, 1, before_color)
end
colors = vim.list_extend(
colors,
vim.tbl_filter(function(color)
return not vim.tbl_contains(colors, color)
end, vim.fn.getcompletion("", "color"))
)
-- if lazy is available, extend the colors list with unloaded colorschemes
local lazy = package.loaded["lazy.core.util"]
if lazy and lazy.get_unloaded_rtp then
local paths = lazy.get_unloaded_rtp ""
local all_files = vim.fn.globpath(table.concat(paths, ","), "colors/*", 1, 1)
for _, f in ipairs(all_files) do
local color = vim.fn.fnamemodify(f, ":t:r")
if not vim.tbl_contains(colors, color) then
table.insert(colors, color)
end
end
end
if opts.ignore_builtins then
-- stylua: ignore
local builtins = {
"blue", "darkblue", "default", "delek", "desert", "elflord", "evening",
"habamax", "industry", "koehler", "lunaperche", "morning", "murphy",
"pablo", "peachpuff", "quiet", "retrobox", "ron", "shine", "slate",
"sorbet", "torte", "vim", "wildcharm", "zaibatsu", "zellner",
}
colors = vim.tbl_filter(function(color)
return not vim.tbl_contains(builtins, color)
end, colors)
end
local previewer
if opts.enable_preview then
-- define previewer
local bufnr = vim.api.nvim_get_current_buf()
local p = vim.api.nvim_buf_get_name(bufnr)
-- show current buffer content in previewer
previewer = previewers.new_buffer_previewer {
get_buffer_by_name = function()
return p
end,
define_preview = function(self)
if vim.loop.fs_stat(p) then
conf.buffer_previewer_maker(p, self.state.bufnr, { bufname = self.state.bufname })
else
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, lines)
end
end,
}
end
local picker = pickers.new(opts, {
prompt_title = "Change Colorscheme",
finder = finders.new_table {
results = colors,
},
sorter = conf.generic_sorter(opts),
previewer = previewer,
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.colorscheme"
return
end
need_restore = false
actions.close(prompt_bufnr)
vim.cmd.colorscheme(selection.value)
end)
return true
end,
on_complete = {
function()
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.colorscheme"
return
end
if opts.enable_preview then
vim.cmd.colorscheme(selection.value)
end
end,
},
})
if opts.enable_preview then
-- rewrite picker.close_windows. restore color if needed
local close_windows = picker.close_windows
picker.close_windows = function(status)
close_windows(status)
if need_restore then
vim.o.background = before_background
vim.cmd.colorscheme(before_color)
end
end
-- rewrite picker.set_selection so that color schemes can be previewed when the current
-- selection is shifted using the keyboard or if an item is clicked with the mouse
local set_selection = picker.set_selection
picker.set_selection = function(self, row)
set_selection(self, row)
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.colorscheme"
return
end
if opts.enable_preview then
vim.cmd.colorscheme(selection.value)
end
end
end
picker:find()
end
internal.marks = function(opts)
local local_marks = {
items = vim.fn.getmarklist(opts.bufnr),
name_func = function(_, line)
return vim.api.nvim_buf_get_lines(opts.bufnr, line - 1, line, false)[1]
end,
}
local global_marks = {
items = vim.fn.getmarklist(),
name_func = function(mark, _)
-- get buffer name if it is opened, otherwise get file name
return vim.api.nvim_get_mark(mark, {})[4]
end,
}
local marks_table = {}
local marks_others = {}
local bufname = vim.api.nvim_buf_get_name(opts.bufnr)
local all_marks = {}
opts.mark_type = vim.F.if_nil(opts.mark_type, "all")
if opts.mark_type == "all" then
all_marks = { local_marks, global_marks }
elseif opts.mark_type == "local" then
all_marks = { local_marks }
elseif opts.mark_type == "global" then
all_marks = { global_marks }
end
for _, cnf in ipairs(all_marks) do
for _, v in ipairs(cnf.items) do
-- strip the first single quote character
local mark = string.sub(v.mark, 2, 3)
local _, lnum, col, _ = unpack(v.pos)
local name = cnf.name_func(mark, lnum)
-- same format to :marks command
local line = string.format("%s %6d %4d %s", mark, lnum, col - 1, name)
local row = {
line = line,
lnum = lnum,
col = col,
filename = utils.path_expand(v.file or bufname),
}
-- non alphanumeric marks goes to last
if mark:match "%w" then
table.insert(marks_table, row)
else
table.insert(marks_others, row)
end
end
end
marks_table = vim.fn.extend(marks_table, marks_others)
pickers
.new(opts, {
prompt_title = "Marks",
finder = finders.new_table {
results = marks_table,
entry_maker = opts.entry_maker or make_entry.gen_from_marks(opts),
},
previewer = conf.grep_previewer(opts),
sorter = conf.generic_sorter(opts),
push_cursor_on_edit = true,
push_tagstack_on_edit = true,
})
:find()
end
internal.registers = function(opts)
local registers_table = { '"', "-", "#", "=", "/", "*", "+", ":", ".", "%" }
-- named
for i = 0, 9 do
table.insert(registers_table, tostring(i))
end
-- alphabetical
for i = 65, 90 do
table.insert(registers_table, string.char(i))
end
pickers
.new(opts, {
prompt_title = "Registers",
finder = finders.new_table {
results = registers_table,
entry_maker = opts.entry_maker or make_entry.gen_from_registers(opts),
},
sorter = conf.generic_sorter(opts),
attach_mappings = function(_, map)
actions.select_default:replace(actions.paste_register)
map({ "i", "n" }, "<C-e>", actions.edit_register)
return true
end,
})
:find()
end
internal.keymaps = function(opts)
opts.modes = vim.F.if_nil(opts.modes, { "n", "i", "c", "x" })
opts.show_plug = vim.F.if_nil(opts.show_plug, true)
opts.only_buf = vim.F.if_nil(opts.only_buf, false)
local keymap_encountered = {} -- used to make sure no duplicates are inserted into keymaps_table
local keymaps_table = {}
local max_len_lhs = 0
-- helper function to populate keymaps_table and determine max_len_lhs
local function extract_keymaps(keymaps)
for _, keymap in pairs(keymaps) do
local keymap_key = keymap.buffer .. keymap.mode .. keymap.lhs -- should be distinct for every keymap
if not keymap_encountered[keymap_key] then
keymap_encountered[keymap_key] = true
if
(opts.show_plug or not string.find(keymap.lhs, "<Plug>"))
and (not opts.lhs_filter or opts.lhs_filter(keymap.lhs))
and (not opts.filter or opts.filter(keymap))
then
table.insert(keymaps_table, keymap)
max_len_lhs = math.max(max_len_lhs, #utils.display_termcodes(keymap.lhs))
end
end
end
end
for _, mode in pairs(opts.modes) do
local global = vim.api.nvim_get_keymap(mode)
local buf_local = vim.api.nvim_buf_get_keymap(0, mode)
if not opts.only_buf then
extract_keymaps(global)
end
extract_keymaps(buf_local)
end
opts.width_lhs = max_len_lhs + 1
pickers
.new(opts, {
prompt_title = "Key Maps",
finder = finders.new_table {
results = keymaps_table,
entry_maker = opts.entry_maker or make_entry.gen_from_keymaps(opts),
},
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.keymaps"
return
end
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(selection.value.lhs, true, false, true), "t", true)
return actions.close(prompt_bufnr)
end)
return true
end,
})
:find()
end
internal.filetypes = function(opts)
local filetypes = vim.fn.getcompletion("", "filetype")
pickers
.new(opts, {
prompt_title = "Filetypes",
finder = finders.new_table {
results = filetypes,
},
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
if selection == nil then
print "[telescope] Nothing currently selected"
return
end
actions.close(prompt_bufnr)
vim.cmd("setfiletype " .. selection[1])
end)
return true
end,
})
:find()
end
internal.highlights = function(opts)
local highlights = vim.fn.getcompletion("", "highlight")
pickers
.new(opts, {
prompt_title = "Highlights",
finder = finders.new_table {
results = highlights,
entry_maker = opts.entry_maker or make_entry.gen_from_highlights(opts),
},
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.highlights"
return
end
actions.close(prompt_bufnr)
vim.cmd("hi " .. selection.value)
end)
return true
end,
previewer = previewers.highlights.new(opts),
})
:find()
end
internal.autocommands = function(opts)
local autocmds = vim.api.nvim_get_autocmds {}
table.sort(autocmds, function(lhs, rhs)
return lhs.event < rhs.event
end)
pickers
.new(opts, {
prompt_title = "autocommands",
finder = finders.new_table {
results = autocmds,
entry_maker = opts.entry_maker or make_entry.gen_from_autocommands(opts),
},
previewer = previewers.autocommands.new(opts),
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
action_set.select:replace_if(function()
local selection = action_state.get_selected_entry()
if selection == nil then
return false
end
local val = selection.value
local cb = val.callback
if vim.is_callable(cb) then
if type(cb) ~= "string" then
local f = type(cb) == "function" and cb or rawget(getmetatable(cb), "__call")
local info = debug.getinfo(f, "S")
local file = info.source:match "^@(.+)"
local lnum = info.linedefined
if file and (lnum or 0) > 0 then
selection.filename, selection.lnum, selection.col = file, lnum, 1
return false
end
end
end
local group_name = val.group_name ~= "<anonymous>" and val.group_name or ""
local output =
vim.fn.execute("verb autocmd " .. group_name .. " " .. val.event .. " " .. val.pattern, "silent")
for line in output:gmatch "[^\r\n]+" do
local source_file = line:match "Last set from (.*) line %d*$" or line:match "Last set from (.*)$"
if source_file and source_file ~= "Lua" then
selection.filename = source_file
local source_lnum = line:match "line (%d*)$" or "1"
selection.lnum = tonumber(source_lnum)
selection.col = 1
return false
end
end
return true
end, function()
local selection = action_state.get_selected_entry()
actions.close(prompt_bufnr)
print("You selected autocmd: " .. vim.inspect(selection.value))
end)
return true
end,
})
:find()
end
internal.spell_suggest = function(opts)
local cursor_word = vim.fn.expand "<cword>"
local suggestions = vim.fn.spellsuggest(cursor_word)
pickers
.new(opts, {
prompt_title = "Spelling Suggestions",
finder = finders.new_table {
results = suggestions,
},
sorter = conf.generic_sorter(opts),
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
if selection == nil then
utils.__warn_no_selection "builtin.spell_suggest"
return
end
action_state.get_current_picker(prompt_bufnr)._original_mode = "i"
actions.close(prompt_bufnr)
vim.cmd('normal! "_ciw' .. selection[1])
vim.cmd "stopinsert"
end)
return true
end,
})
:find()
end
internal.tagstack = function(opts)
opts = opts or {}
local tagstack = vim.fn.gettagstack().items
local tags = {}
for i = #tagstack, 1, -1 do
local tag = tagstack[i]
tag.bufnr = tag.from[1]
if vim.api.nvim_buf_is_valid(tag.bufnr) then
tags[#tags + 1] = tag
tag.filename = vim.fn.bufname(tag.bufnr)
tag.lnum = tag.from[2]
tag.col = tag.from[3]
tag.text = vim.api.nvim_buf_get_lines(tag.bufnr, tag.lnum - 1, tag.lnum, false)[1] or ""
end
end
if vim.tbl_isempty(tags) then
utils.notify("builtin.tagstack", {
msg = "No tagstack available",
level = "WARN",
})
return
end
pickers
.new(opts, {
prompt_title = "TagStack",
finder = finders.new_table {
results = tags,
entry_maker = make_entry.gen_from_quickfix(opts),
},
previewer = conf.qflist_previewer(opts),
sorter = conf.generic_sorter(opts),
})
:find()
end
internal.jumplist = function(opts)
opts = opts or {}
local jumplist = vim.fn.getjumplist()[1]
-- reverse the list
local sorted_jumplist = {}
for i = #jumplist, 1, -1 do
if vim.api.nvim_buf_is_valid(jumplist[i].bufnr) then
jumplist[i].text = vim.api.nvim_buf_get_lines(jumplist[i].bufnr, jumplist[i].lnum - 1, jumplist[i].lnum, false)[1]
or ""
table.insert(sorted_jumplist, jumplist[i])
end
end
pickers
.new(opts, {
prompt_title = "Jumplist",
finder = finders.new_table {
results = sorted_jumplist,
entry_maker = make_entry.gen_from_quickfix(opts),
},
previewer = conf.qflist_previewer(opts),
sorter = conf.generic_sorter(opts),
})
:find()
end
local function apply_checks(mod)
for k, v in pairs(mod) do
mod[k] = function(opts)
opts = opts or {}
v(opts)
end
end
return mod
end
return apply_checks(internal)