diff options
| author | Simon Hauser <Simon-Hauser@outlook.de> | 2022-07-01 23:29:24 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-01 23:29:24 +0200 |
| commit | 7df95f9b208ba7228a25e7f75fb4cc02d6604cce (patch) | |
| tree | e4933fb547bc886f27f06011a6c4780facfd7642 /lua | |
| parent | 1aa74b231c6f93152c4ac51549a0563dca9b4453 (diff) | |
| parent | e778abfdb457cc47ca47ce9b76905e043e87e598 (diff) | |
Merge pull request #1945 from nvim-telescope/dev
full changelog `:help telescope.changelog-1945`
Diffstat (limited to 'lua')
28 files changed, 3594 insertions, 3102 deletions
diff --git a/lua/telescope/actions/generate.lua b/lua/telescope/actions/generate.lua index 2069b03..703c83d 100644 --- a/lua/telescope/actions/generate.lua +++ b/lua/telescope/actions/generate.lua @@ -24,6 +24,10 @@ ---@brief ]] local actions = require "telescope.actions" +local config = require "telescope.config" +local action_state = require "telescope.actions.state" +local finders = require "telescope.finders" + local action_generate = {} --- Display the keymaps of registered actions similar to which-key.nvim.<br> @@ -54,4 +58,56 @@ action_generate.which_key = function(opts) end end +action_generate.refine = function(prompt_bufnr, opts) + opts = opts or {} + opts.prompt_to_prefix = vim.F.if_nil(opts.prompt_to_prefix, false) + opts.prefix_hl_group = vim.F.if_nil(opts.prompt_hl_group, "TelescopePromptPrefix") + opts.prompt_prefix = vim.F.if_nil(opts.promt_prefix, config.values.prompt_prefix) + opts.reset_multi_selection = vim.F.if_nil(opts.reset_multi_selection, false) + opts.reset_prompt = vim.F.if_nil(opts.reset_prompt, true) + opts.sorter = vim.F.if_nil(opts.sorter, config.values.generic_sorter {}) + + local current_picker = action_state.get_current_picker(prompt_bufnr) + + -- title + if opts.prompt_title then + current_picker.prompt_border:change_title(opts.prompt_title) + end + + if opts.results_title then + current_picker.results_border:change_title(opts.results_title) + end + + local results = {} + for entry in current_picker.manager:iter() do + table.insert(results, entry) + end + + -- if opts.sorter == false, keep older sorter + if opts.sorter then + current_picker.sorter:_destroy() + current_picker.sorter = opts.sorter + current_picker.sorter:_init() + end + + local new_finder = finders.new_table { + results = results, + entry_maker = function(x) + return x + end, + } + + if not opts.reset_multi_selection and action_state.get_current_line() ~= "" then + opts.multi = current_picker._multi + end + + if opts.prompt_to_prefix then + local prompt = action_state.get_current_line() + local current_prefix = current_picker.prompt_prefix + local suffix = current_prefix ~= opts.prompt_prefix and current_prefix or "" + opts.new_prefix = suffix .. prompt .. " " .. opts.prompt_prefix + end + current_picker:refresh(new_finder, opts) +end + return action_generate diff --git a/lua/telescope/actions/init.lua b/lua/telescope/actions/init.lua index 2c4624b..21b651d 100644 --- a/lua/telescope/actions/init.lua +++ b/lua/telescope/actions/init.lua @@ -431,6 +431,8 @@ actions.edit_register = function(prompt_bufnr) end --- Paste the selected register into the buffer +--- +--- Note: only meant to be used inside builtin.registers ---@param prompt_bufnr number: The prompt bufnr actions.paste_register = function(prompt_bufnr) local selection = action_state.get_selected_entry() @@ -1020,7 +1022,9 @@ end actions.delete_buffer = function(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr) current_picker:delete_selection(function(selection) - vim.api.nvim_buf_delete(selection.bufnr, { force = false }) + local force = vim.api.nvim_buf_get_option(selection.bufnr, "buftype") == "terminal" + local ok = pcall(vim.api.nvim_buf_delete, selection.bufnr, { force = force }) + return ok end) end @@ -1059,22 +1063,22 @@ end ---@param prompt_bufnr number: The prompt bufnr actions.which_key = function(prompt_bufnr, opts) opts = opts or {} - opts.max_height = utils.get_default(opts.max_height, 0.4) - opts.only_show_current_mode = utils.get_default(opts.only_show_current_mode, true) - opts.mode_width = utils.get_default(opts.mode_width, 1) - opts.keybind_width = utils.get_default(opts.keybind_width, 7) - opts.name_width = utils.get_default(opts.name_width, 30) - opts.line_padding = utils.get_default(opts.line_padding, 1) - opts.separator = utils.get_default(opts.separator, " -> ") - opts.close_with_action = utils.get_default(opts.close_with_action, true) - opts.normal_hl = utils.get_default(opts.normal_hl, "TelescopePrompt") - opts.border_hl = utils.get_default(opts.border_hl, "TelescopePromptBorder") - opts.winblend = utils.get_default(opts.winblend, config.values.winblend) - opts.column_padding = utils.get_default(opts.column_padding, " ") + opts.max_height = vim.F.if_nil(opts.max_height, 0.4) + opts.only_show_current_mode = vim.F.if_nil(opts.only_show_current_mode, true) + opts.mode_width = vim.F.if_nil(opts.mode_width, 1) + opts.keybind_width = vim.F.if_nil(opts.keybind_width, 7) + opts.name_width = vim.F.if_nil(opts.name_width, 30) + opts.line_padding = vim.F.if_nil(opts.line_padding, 1) + opts.separator = vim.F.if_nil(opts.separator, " -> ") + opts.close_with_action = vim.F.if_nil(opts.close_with_action, true) + opts.normal_hl = vim.F.if_nil(opts.normal_hl, "TelescopePrompt") + opts.border_hl = vim.F.if_nil(opts.border_hl, "TelescopePromptBorder") + opts.winblend = vim.F.if_nil(opts.winblend, config.values.winblend) + opts.column_padding = vim.F.if_nil(opts.column_padding, " ") -- Assigning into 'opts.column_indent' would override a number with a string and -- cause issues with subsequent calls, keep a local copy of the string instead - local column_indent = table.concat(utils.repeated_table(utils.get_default(opts.column_indent, 4), " ")) + local column_indent = table.concat(utils.repeated_table(vim.F.if_nil(opts.column_indent, 4), " ")) -- close on repeated keypress local km_bufs = (function() @@ -1111,9 +1115,9 @@ actions.which_key = function(prompt_bufnr, opts) local make_display = function(mapping) return displayer { - { mapping.mode, utils.get_default(opts.mode_hl, "TelescopeResultsConstant") }, - { mapping.keybind, utils.get_default(opts.keybind_hl, "TelescopeResultsVariable") }, - { mapping.name, utils.get_default(opts.name_hl, "TelescopeResultsFunction") }, + { mapping.mode, vim.F.if_nil(opts.mode_hl, "TelescopeResultsConstant") }, + { mapping.keybind, vim.F.if_nil(opts.keybind_hl, "TelescopeResultsVariable") }, + { mapping.name, vim.F.if_nil(opts.name_hl, "TelescopeResultsFunction") }, } end diff --git a/lua/telescope/actions/utils.lua b/lua/telescope/actions/utils.lua index 9684414..4e81db3 100644 --- a/lua/telescope/actions/utils.lua +++ b/lua/telescope/actions/utils.lua @@ -13,7 +13,7 @@ local utils = {} --- Apply `f` to the entries of the current picker. --- - Notes: ---- - Mapped entries may include results not visible in the results popup. +--- - Mapped entries include all currently filtered results, not just the visible onces. --- - Indices are 1-indexed, whereas rows are 0-indexed. --- - Warning: `map_entries` has no return value. --- - The below example showcases how to collect results diff --git a/lua/telescope/builtin/__diagnostics.lua b/lua/telescope/builtin/__diagnostics.lua new file mode 100644 index 0000000..2614e36 --- /dev/null +++ b/lua/telescope/builtin/__diagnostics.lua @@ -0,0 +1,150 @@ +local conf = require("telescope.config").values +local finders = require "telescope.finders" +local make_entry = require "telescope.make_entry" +local pickers = require "telescope.pickers" +local utils = require "telescope.utils" + +local diagnostics = {} + +local convert_diagnostic_type = function(severities, severity) + -- convert from string to int + if type(severity) == "string" then + -- make sure that e.g. error is uppercased to ERROR + return severities[severity:upper()] + end + -- otherwise keep original value, incl. nil + return severity +end + +local diagnostics_to_tbl = function(opts) + opts = vim.F.if_nil(opts, {}) + local items = {} + local severities = vim.diagnostic.severity + local current_buf = vim.api.nvim_get_current_buf() + + opts.severity = convert_diagnostic_type(severities, opts.severity) + opts.severity_limit = convert_diagnostic_type(severities, opts.severity_limit) + opts.severity_bound = convert_diagnostic_type(severities, opts.severity_bound) + + local diagnosis_opts = { severity = {}, namespace = opts.namespace } + if opts.severity ~= nil then + if opts.severity_limit ~= nil or opts.severity_bound ~= nil then + utils.notify("builtin.diagnostics", { + msg = "Invalid severity parameters. Both a specific severity and a limit/bound is not allowed", + level = "ERROR", + }) + return {} + end + diagnosis_opts.severity = opts.severity + else + if opts.severity_limit ~= nil then + diagnosis_opts.severity["min"] = opts.severity_limit + end + if opts.severity_bound ~= nil then + diagnosis_opts.severity["max"] = opts.severity_bound + end + end + + opts.root_dir = opts.root_dir == true and vim.loop.cwd() or opts.root_dir + + local bufnr_name_map = {} + local filter_diag = function(diagnostic) + if bufnr_name_map[diagnostic.bufnr] == nil then + bufnr_name_map[diagnostic.bufnr] = vim.api.nvim_buf_get_name(diagnostic.bufnr) + end + + local root_dir_test = not opts.root_dir + or string.sub(bufnr_name_map[diagnostic.bufnr], 1, #opts.root_dir) == opts.root_dir + local listed_test = not opts.no_unlisted or vim.api.nvim_buf_get_option(diagnostic.bufnr, "buflisted") + + return root_dir_test and listed_test + end + + local preprocess_diag = function(diagnostic) + return { + bufnr = diagnostic.bufnr, + filename = bufnr_name_map[diagnostic.bufnr], + lnum = diagnostic.lnum + 1, + col = diagnostic.col + 1, + text = vim.trim(diagnostic.message:gsub("[\n]", "")), + type = severities[diagnostic.severity] or severities[1], + } + end + + for _, d in ipairs(vim.diagnostic.get(opts.bufnr, diagnosis_opts)) do + if filter_diag(d) then + table.insert(items, preprocess_diag(d)) + end + end + + -- sort results by bufnr (prioritize cur buf), severity, lnum + table.sort(items, function(a, b) + if a.bufnr == b.bufnr then + if a.type == b.type then + return a.lnum < b.lnum + else + return a.type < b.type + end + else + -- prioritize for current bufnr + if a.bufnr == current_buf then + return true + end + if b.bufnr == current_buf then + return false + end + return a.bufnr < b.bufnr + end + end) + + return items +end + +diagnostics.get = function(opts) + if opts.bufnr ~= 0 then + opts.bufnr = nil + end + if opts.bufnr == nil then + opts.path_display = vim.F.if_nil(opts.path_display, {}) + end + if type(opts.bufnr) == "string" then + opts.bufnr = tonumber(opts.bufnr) + end + + local locations = diagnostics_to_tbl(opts) + + if vim.tbl_isempty(locations) then + utils.notify("builtin.diagnostics", { + msg = "No diagnostics found", + level = "INFO", + }) + return + end + + opts.path_display = vim.F.if_nil(opts.path_display, "hidden") + pickers.new(opts, { + prompt_title = opts.bufnr == nil and "Workspace Diagnostics" or "Document Diagnostics", + finder = finders.new_table { + results = locations, + entry_maker = opts.entry_maker or make_entry.gen_from_diagnostics(opts), + }, + previewer = conf.qflist_previewer(opts), + sorter = conf.prefilter_sorter { + tag = "type", + 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(diagnostics) diff --git a/lua/telescope/builtin/__files.lua b/lua/telescope/builtin/__files.lua new file mode 100644 index 0000000..dddd18a --- /dev/null +++ b/lua/telescope/builtin/__files.lua @@ -0,0 +1,522 @@ +local action_state = require "telescope.actions.state" +local action_set = require "telescope.actions.set" +local finders = require "telescope.finders" +local make_entry = require "telescope.make_entry" +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 log = require "telescope.log" + +local Path = require "plenary.path" + +local flatten = vim.tbl_flatten +local filter = vim.tbl_filter + +local files = {} + +local escape_chars = function(string) + return string.gsub(string, "[%(|%)|\\|%[|%]|%-|%{%}|%?|%+|%*|%^|%$|%.]", { + ["\\"] = "\\\\", + ["-"] = "\\-", + ["("] = "\\(", + [")"] = "\\)", + ["["] = "\\[", + ["]"] = "\\]", + ["{"] = "\\{", + ["}"] = "\\}", + ["?"] = "\\?", + ["+"] = "\\+", + ["*"] = "\\*", + ["^"] = "\\^", + ["$"] = "\\$", + ["."] = "\\.", + }) +end + +-- Special keys: +-- opts.search_dirs -- list of directory to search in +-- opts.grep_open_files -- boolean to restrict search to open files +files.live_grep = function(opts) + local vimgrep_arguments = opts.vimgrep_arguments or conf.vimgrep_arguments + local search_dirs = opts.search_dirs + local grep_open_files = opts.grep_open_files + opts.cwd = opts.cwd and vim.fn.expand(opts.cwd) or vim.loop.cwd() + + local filelist = {} + + if grep_open_files then + local bufnrs = filter(function(b) + if 1 ~= vim.fn.buflisted(b) then + return false + end + return true + end, vim.api.nvim_list_bufs()) + if not next(bufnrs) then + return + end + + for _, bufnr in ipairs(bufnrs) do + local file = vim.api.nvim_buf_get_name(bufnr) + table.insert(filelist, Path:new(file):make_relative(opts.cwd)) + end + elseif search_dirs then + for i, path in ipairs(search_dirs) do + search_dirs[i] = vim.fn.expand(path) + end + end + + local additional_args = {} + if opts.additional_args ~= nil and type(opts.additional_args) == "function" then + additional_args = opts.additional_args(opts) + end + + if opts.type_filter then + additional_args[#additional_args + 1] = "--type=" .. opts.type_filter + end + + if type(opts.glob_pattern) == "string" then + additional_args[#additional_args + 1] = "--glob=" .. opts.glob_pattern + elseif type(opts.glob_pattern) == "table" then + for i = 1, #opts.glob_pattern do + additional_args[#additional_args + 1] = "--glob=" .. opts.glob_pattern[i] + end + end + + 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 + + local search_list = {} + + if search_dirs then + table.insert(search_list, search_dirs) + end + + if grep_open_files then + search_list = filelist + end + + return flatten { vimgrep_arguments, additional_args, "--", prompt, search_list } + end, opts.entry_maker or make_entry.gen_from_vimgrep(opts), opts.max_results, opts.cwd) + + pickers.new(opts, { + prompt_title = "Live Grep", + finder = live_grepper, + previewer = conf.grep_previewer(opts), + -- TODO: It would be cool to use `--json` output for this + -- and then we could get the highlight positions directly. + sorter = sorters.highlighter_only(opts), + attach_mappings = function(_, map) + map("i", "<c-space>", function(prompt_bufnr) + local line = action_state.get_current_line() + require("telescope.actions.generate").refine(prompt_bufnr, { + prompt_title = "Find Word (" .. line .. ")", + sorter = conf.generic_sorter(opts), + }) + end) + return true + end, + }):find() +end + +-- Special keys: +-- opts.search -- the string to search. +-- opts.search_dirs -- list of directory to search in +-- opts.use_regex -- special characters won't be escaped +files.grep_string = function(opts) + -- TODO: This should probably check your visual selection as well, if you've got one + + local vimgrep_arguments = opts.vimgrep_arguments or conf.vimgrep_arguments + local search_dirs = opts.search_dirs + local word = opts.search or vim.fn.expand "<cword>" + local search = opts.use_regex and word or escape_chars(word) + local word_match = opts.word_match + opts.entry_maker = opts.entry_maker or make_entry.gen_from_vimgrep(opts) + + local title_word = word:gsub("\n", "\\n") + + local additional_args = {} + if opts.additional_args ~= nil and type(opts.additional_args) == "function" then + additional_args = opts.additional_args(opts) + end + + local args = flatten { + vimgrep_arguments, + additional_args, + word_match, + "--", + search, + } + + if search_dirs then + for _, path in ipairs(search_dirs) do + table.insert(args, vim.fn.expand(path)) + end + end + + pickers.new(opts, { + prompt_title = "Find Word (" .. title_word .. ")", + finder = finders.new_oneshot_job(args, opts), + previewer = conf.grep_previewer(opts), + sorter = conf.generic_sorter(opts), + }):find() +end + +files.find_files = function(opts) + local find_command = (function() + if opts.find_command then + if type(opts.find_command) == "function" then + return opts.find_command(opts) + end + return opts.find_command + elseif 1 == vim.fn.executable "fd" then + return { "fd", "--type", "f" } + elseif 1 == vim.fn.executable "fdfind" then + return { "fdfind", "--type", "f" } + elseif 1 == vim.fn.executable "rg" then + return { "rg", "--files" } + elseif 1 == vim.fn.executable "find" and vim.fn.has "win32" == 0 then + return { "find", ".", "-type", "f" } + elseif 1 == vim.fn.executable "where" then + return { "where", "/r", ".", "*" } + end + end)() + + if not find_command then + utils.notify("builtin.find_files", { + msg = "You need to install either find, fd, or rg", + level = "ERROR", + }) + return + end + + local command = find_command[1] + local hidden = opts.hidden + local no_ignore = opts.no_ignore + local no_ignore_parent = opts.no_ignore_parent + local follow = opts.follow + local search_dirs = opts.search_dirs + local search_file = opts.search_file + + if search_dirs then + for k, v in pairs(search_dirs) do + search_dirs[k] = vim.fn.expand(v) + end + end + + if command == "fd" or command == "fdfind" or command == "rg" then + if hidden then + table.insert(find_command, "--hidden") + end + if no_ignore then + table.insert(find_command, "--no-ignore") + end + if no_ignore_parent then + table.insert(find_command, "--no-ignore-parent") + end + if follow then + table.insert(find_command, "-L") + end + if search_file then + if command == "rg" then + table.insert(find_command, "-g") + table.insert(find_command, "*" .. search_file .. "*") + else + table.insert(find_command, search_file) + end + end + if search_dirs then + if command ~= "rg" and not search_file then + table.insert(find_command, ".") + end + for _, v in pairs(search_dirs) do + table.insert(find_command, v) + end + end + elseif command == "find" then + if not hidden then + table.insert(find_command, { "-not", "-path", "*/.*" }) + find_command = flatten(find_command) + end + if no_ignore ~= nil then + log.warn "The `no_ignore` key is not available for the `find` command in `find_files`." + end + if no_ignore_parent ~= nil then + log.warn "The `no_ignore_parent` key is not available for the `find` command in `find_files`." + end + if follow then + table.insert(find_command, 2, "-L") + end + if search_file then + table.insert(find_command, "-name") + table.insert(find_command, "*" .. search_file .. "*") + end + if search_dirs then + table.remove(find_command, 2) + for _, v in pairs(search_dirs) do + table.insert(find_command, 2, v) + end + end + elseif command == "where" then + if hidden ~= nil then + log.warn "The `hidden` key is not available for the Windows `where` command in `find_files`." + end + if no_ignore ~= nil then + log.warn "The `no_ignore` key is not available for the Windows `where` command in `find_files`." + end + if no_ignore_parent ~= nil then + log.warn "The `no_ignore_parent` key is not available for the Windows `where` command in `find_files`." + end + if follow ~= nil then + log.warn "The `follow` key is not available for the Windows `where` command in `find_files`." + end + if search_dirs ~= nil then + log.warn "The `search_dirs` key is not available for the Windows `where` command in `find_files`." + end + if search_file ~= nil then + log.warn "The `search_file` key is not available for the Windows `where` command in `find_files`." + end + 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 = conf.file_previewer(opts), + sorter = conf.file_sorter(opts), + }):find() +end + +local function prepare_match(entry, kind) + local entries = {} + + if entry.node then + 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 + +-- TODO: finish docs for opts.show_line +files.treesitter = function(opts) + opts.show_line = vim.F.if_nil(opts.show_line, true) + + local has_nvim_treesitter, _ = pcall(require, "nvim-treesitter") + if not has_nvim_treesitter then + utils.notify("builtin.treesitter", { + msg = "User need to install nvim-treesitter needs to be installed", + level = "ERROR", + }) + return + end + + local parsers = require "nvim-treesitter.parsers" + if not parsers.has_parser(parsers.get_buf_lang(opts.bufnr)) then + utils.notify("builtin.treesitter", { + msg = "No parser for the current buffer", + level = "ERROR", + }) + return + end + + local ts_locals = require "nvim-treesitter.locals" + local results = {} + for _, definition in ipairs(ts_locals.get_definitions(opts.bufnr)) do + local entries = prepare_match(ts_locals.get_local_nodes(definition)) + for _, entry in ipairs(entries) do + entry.kind = vim.F.if_nil(entry.kind, "") + 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 = opts.entry_maker or make_entry.gen_from_treesitter(opts), + }, + previewer = conf.grep_previewer(opts), + sorter = conf.prefilter_sorter { + tag = "kind", + sorter = conf.generic_sorter(opts), + }, + }):find() +end + +files.current_buffer_fuzzy_find = function(opts) + -- All actions are on the current buffer + local filename = vim.fn.expand(vim.api.nvim_buf_get_name(opts.bufnr)) + local filetype = vim.api.nvim_buf_get_option(opts.bufnr, "filetype") + + local lines = vim.api.nvim_buf_get_lines(opts.bufnr, 0, -1, false) + local lines_with_numbers = {} + + for lnum, line in ipairs(lines) do + table.insert(lines_with_numbers, { + lnum = lnum, + bufnr = opts.bufnr, + filename = filename, + text = line, + }) + end + + local ts_ok, ts_parsers = pcall(require, "nvim-treesitter.parsers") + if ts_ok then + filetype = ts_parsers.ft_to_lang(filetype) + end + local _, ts_configs = pcall(require, "nvim-treesitter.configs") + + local parser_ok, parser = pcall(vim.treesitter.get_parser, opts.bufnr, filetype) + local query_ok, query = pcall(vim.treesitter.get_query, filetype, "highlights") + if parser_ok and query_ok and ts_ok and ts_configs.is_enabled("highlight", filetype, opts.bufnr) then + local root = parser:parse()[1]:root() + + local highlighter = vim.treesitter.highlighter.new(parser) + local highlighter_query = highlighter:get_query(filetype) + + local line_highlights = setmetatable({}, { + __index = function(t, k) + local obj = {} + rawset(t, k, obj) + return obj + end, + }) + for id, node in query:iter_captures(root, opts.bufnr, 0, -1) do + local hl = highlighter_query:_get_hl_from_capture(id) + if hl and type(hl) ~= "number" then + local row1, col1, row2, col2 = node:range() + + if row1 == row2 then + local row = row1 + 1 + + for index = col1, col2 do + line_highlights[row][index] = hl + end + else + local row = row1 + 1 + for index = col1, #lines[row] do + line_highlights[row][index] = hl + end + + while row < row2 + 1 do + row = row + 1 + + for index = 0, #(lines[row] or {}) do + line_highlights[row][index] = hl + end + end + end + end + end + + opts.line_highlights = line_highlights + end + + pickers.new(opts, { + prompt_title = "Current Buffer Fuzzy", + finder = finders.new_table { + results = lines_with_numbers, + entry_maker = opts.entry_maker or make_entry.gen_from_buffer_lines(opts), + }, + sorter = conf.generic_sorter(opts), + previewer = conf.grep_previewer(opts), + attach_mappings = function() + action_set.select:enhance { + post = function() + local selection = action_state.get_selected_entry() + vim.api.nvim_win_set_cursor(0, { selection.lnum, 0 }) + end, + } + + return true + end, + }):find() +end + +files.tags = function(opts) + local tagfiles = opts.ctags_file and { opts.ctags_file } or vim.fn.tagfiles() + for i, ctags_file in ipairs(tagfiles) do + tagfiles[i] = vim.fn.expand(ctags_file, true) + end + if vim.tbl_isempty(tagfiles) then + utils.notify("builtin.tags", { + msg = "No tags file found. Create one with ctags -R", + level = "ERROR", + }) + return + end + opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_ctags(opts)) + + pickers.new(opts, { + prompt_title = "Tags", + finder = finders.new_oneshot_job(flatten { "cat", tagfiles }, opts), + previewer = previewers.ctags.new(opts), + sorter = conf.generic_sorter(opts), + attach_mappings = function() + action_set.select:enhance { + post = function() + local selection = action_state.get_selected_entry() + if not selection then + return + end + + if selection.scode then + -- un-escape / then escape required + -- special chars for vim.fn.search() + -- ] ~ * + local scode = selection.scode:gsub([[\/]], "/"):gsub("[%]~*]", function(x) + return "\\" .. x + end) + + vim.cmd "norm! gg" + vim.fn.search(scode) + vim.cmd "norm! zz" + else + vim.api.nvim_win_set_cursor(0, { selection.lnum, 0 }) + end + end, + } + return true + end, + }):find() +end + +files.current_buffer_tags = function(opts) + return files.tags(vim.tbl_extend("force", { + prompt_title = "Current Buffer Tags", + only_current_file = true, + path_display = "hidden", + }, opts)) +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(files) diff --git a/lua/telescope/builtin/__git.lua b/lua/telescope/builtin/__git.lua new file mode 100644 index 0000000..7db45ef --- /dev/null +++ b/lua/telescope/builtin/__git.lua @@ -0,0 +1,411 @@ +local actions = require "telescope.actions" +local action_state = require "telescope.actions.state" +local finders = require "telescope.finders" +local make_entry = require "telescope.make_entry" +local pickers = require "telescope.pickers" +local previewers = require "telescope.previewers" +local utils = require "telescope.utils" +local entry_display = require "telescope.pickers.entry_display" +local strings = require "plenary.strings" +local Path = require "plenary.path" + +local conf = require("telescope.config").values + +local git = {} + +git.files = function(opts) + if opts.is_bare then + utils.notify("builtin.git_files", { + msg = "This operation must be run in a work tree", + level = "ERROR", + }) + return + end + + local show_untracked = vim.F.if_nil(opts.show_untracked, false) + local recurse_submodules = vim.F.if_nil(opts.recurse_submodules, false) + if show_untracked and recurse_submodules then + utils.notify("builtin.git_files", { + msg = "Git does not support both --others and --recurse-submodules", + level = "ERROR", + }) + return + end + + -- By creating the entry maker after the cwd options, + -- we ensure the maker uses the cwd options when being created. + opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_file(opts)) + local git_command = vim.F.if_nil(opts.git_command, { "git", "ls-files", "--exclude-standard", "--cached" }) + + pickers.new(opts, { + prompt_title = "Git Files", + finder = finders.new_oneshot_job( + vim.tbl_flatten { + git_command, + show_untracked and "--others" or nil, + recurse_submodules and "--recurse-submodules" or nil, + }, + opts + ), + previewer = conf.file_previewer(opts), + sorter = conf.file_sorter(opts), + }):find() +end + +git.commits = function(opts) + opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts)) + local git_command = vim.F.if_nil(opts.git_command, { "git", "log", "--pretty=oneline", "--abbrev-commit", "--", "." }) + + pickers.new(opts, { + prompt_title = "Git Commits", + finder = finders.new_oneshot_job(git_command, opts), + previewer = { + previewers.git_commit_diff_to_parent.new(opts), + previewers.git_commit_diff_to_head.new(opts), + previewers.git_commit_diff_as_was.new(opts), + previewers.git_commit_message.new(opts), + }, + sorter = conf.file_sorter(opts), + attach_mappings = function(_, map) + actions.select_default:replace(actions.git_checkout) + map("i", "<c-r>m", actions.git_reset_mixed) + map("n", "<c-r>m", actions.git_reset_mixed) + map("i", "<c-r>s", actions.git_reset_soft) + map("n", "<c-r>s", actions.git_reset_soft) + map("i", "<c-r>h", actions.git_reset_hard) + map("n", "<c-r>h", actions.git_reset_hard) + return true + end, + }):find() +end + +git.stash = function(opts) + opts.show_branch = vim.F.if_nil(opts.show_branch, true) + opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_stash(opts)) + + pickers.new(opts, { + prompt_title = "Git Stash", + finder = finders.new_oneshot_job( + vim.tbl_flatten { + "git", + "--no-pager", + "stash", + "list", + }, + opts + ), + previewer = previewers.git_stash_diff.new(opts), + sorter = conf.file_sorter(opts), + attach_mappings = function() + actions.select_default:replace(actions.git_apply_stash) + return true + end, + }):find() +end + +local get_current_buf_line = function(winnr) + local lnum = vim.api.nvim_win_get_cursor(winnr)[1] + return vim.trim(vim.api.nvim_buf_get_lines(vim.api.nvim_win_get_buf(winnr), lnum - 1, lnum, false)[1]) +end + +git.bcommits = function(opts) + opts.current_line = (opts.current_file == nil) and get_current_buf_line(opts.winnr) or nil + opts.current_file = vim.F.if_nil(opts.current_file, vim.api.nvim_buf_get_name(opts.bufnr)) + opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts)) + local git_command = vim.F.if_nil( + opts.git_command, + { "git", "log", "--pretty=oneline", "--abbrev-commit", "--follow" } + ) + + pickers.new(opts, { + prompt_title = "Git BCommits", + finder = finders.new_oneshot_job( + vim.tbl_flatten { + git_command, + opts.current_file, + }, + opts + ), + previewer = { + previewers.git_commit_diff_to_parent.new(opts), + previewers.git_commit_diff_to_head.new(opts), + previewers.git_commit_diff_as_was.new(opts), + previewers.git_commit_message.new(opts), + }, + sorter = conf.file_sorter(opts), + attach_mappings = function() + actions.select_default:replace(actions.git_checkout_current_buffer) + local transfrom_file = function() + return opts.current_file and Path:new(opts.current_file):make_relative(opts.cwd) or "" + end + + local get_buffer_of_orig = function(selection) + local value = selection.value .. ":" .. transfrom_file() + local content = utils.get_os_command_output({ "git", "--no-pager", "show", value }, opts.cwd) + + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, content) + vim.api.nvim_buf_set_name(bufnr, "Original") + return bufnr + end + + local vimdiff = function(selection, command) + local ft = vim.bo.filetype + vim.cmd "diffthis" + + local bufnr = get_buffer_of_orig(selection) + vim.cmd(string.format("%s %s", command, bufnr)) + vim.bo.filetype = ft + vim.cmd "diffthis" + + vim.api.nvim_create_autocmd("WinClosed", { + buffer = bufnr, + nested = true, + once = true, + callback = function() + vim.api.nvim_buf_delete(bufnr, { force = true }) + end, + }) + end + + actions.select_vertical:replace(function(prompt_bufnr) + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + vimdiff(selection, "leftabove vert sbuffer") + end) + + actions.select_horizontal:replace(function(prompt_bufnr) + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + vimdiff(selection, "belowright sbuffer") + end) + + actions.select_tab:replace(function(prompt_bufnr) + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + vim.cmd("tabedit " .. transfrom_file()) + vimdiff(selection, "leftabove vert sbuffer") + end) + return true + end, + }):find() +end + +git.branches = function(opts) + local format = "%(HEAD)" + .. "%(refname)" + .. "%(authorname)" + .. "%(upstream:lstrip=2)" + .. "%(committerdate:format-local:%Y/%m/%d %H:%M:%S)" + local output = utils.get_os_command_output( + { "git", "for-each-ref", "--perl", "--format", format, opts.pattern }, + opts.cwd + ) + + local results = {} + local widths = { + name = 0, + authorname = 0, + upstream = 0, + committerdate = 0, + } + local unescape_single_quote = function(v) + return string.gsub(v, "\\([\\'])", "%1") + end + local parse_line = function(line) + local fields = vim.split(string.sub(line, 2, -2), "''", true) + local entry = { + head = fields[1], + refname = unescape_single_quote(fields[2]), + authorname = unescape_single_quote(fields[3]), + upstream = unescape_single_quote(fields[4]), + committerdate = fields[5], + } + local prefix + if vim.startswith(entry.refname, "refs/remotes/") then + prefix = "refs/remotes/" + elseif vim.startswith(entry.refname, "refs/heads/") then + prefix = "refs/heads/" + else + return + end + local index = 1 + if entry.head ~= "*" then + index = #results + 1 + end + + entry.name = string.sub(entry.refname, string.len(prefix) + 1) + for key, value in pairs(widths) do + widths[key] = math.max(value, strings.strdisplaywidth(entry[key] or "")) + end + if string.len(entry.upstream) > 0 then + widths.upstream_indicator = 2 + end + table.insert(results, index, entry) + end + for _, line in ipairs(output) do + parse_line(line) + end + if #results == 0 then + return + end + + local displayer = entry_display.create { + separator = " ", + items = { + { width = 1 }, + { width = widths.name }, + { width = widths.authorname }, + { width = widths.upstream_indicator }, + { width = widths.upstream }, + { width = widths.committerdate }, + }, + } + + local make_display = function(entry) + return displayer { + { entry.head }, + { entry.name, "TelescopeResultsIdentifier" }, + { entry.authorname }, + { string.len(entry.upstream) > 0 and "=>" or "" }, + { entry.upstream, "TelescopeResultsIdentifier" }, + { entry.committerdate }, + } + end + + pickers.new(opts, { + prompt_title = "Git Branches", + finder = finders.new_table { + results = results, + entry_maker = function(entry) + entry.value = entry.name + entry.ordinal = entry.name + entry.display = make_display + return make_entry.set_default_entry_mt(entry, opts) + end, + }, + previewer = previewers.git_branch_log.new(opts), + sorter = conf.file_sorter(opts), + attach_mappings = function(_, map) + actions.select_default:replace(actions.git_checkout) + map("i", "<c-t>", actions.git_track_branch) + map("n", "<c-t>", actions.git_track_branch) + + map("i", "<c-r>", actions.git_rebase_branch) + map("n", "<c-r>", actions.git_rebase_branch) + + map("i", "<c-a>", actions.git_create_branch) + map("n", "<c-a>", actions.git_create_branch) + + map("i", "<c-s>", actions.git_switch_branch) + map("n", "<c-s>", actions.git_switch_branch) + + map("i", "<c-d>", actions.git_delete_branch) + map("n", "<c-d>", actions.git_delete_branch) + + map("i", "<c-y>", actions.git_merge_branch) + map("n", "<c-y>", actions.git_merge_branch) + return true + end, + }):find() +end + +git.status = function(opts) + if opts.is_bare then + utils.notify("builtin.git_status", { + msg = "This operation must be run in a work tree", + level = "ERROR", + }) + return + end + + local gen_new_finder = function() + local expand_dir = vim.F.if_nil(opts.expand_dir, true) + local git_cmd = { "git", "status", "-s", "--", "." } + + if expand_dir then + table.insert(git_cmd, #git_cmd - 1, "-u") + end + + local output = utils.get_os_command_output(git_cmd, opts.cwd) + + if #output == 0 then + print "No changes found" + utils.notify("builtin.git_status", { + msg = "No changes found", + level = "WARN", + }) + return + end + + return finders.new_table { + results = output, + entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_status(opts)), + } + end + + local initial_finder = gen_new_finder() + if not initial_finder then + return + end + + pickers.new(opts, { + prompt_title = "Git Status", + finder = initial_finder, + previewer = previewers.git_file_diff.new(opts), + sorter = conf.file_sorter(opts), + attach_mappings = function(prompt_bufnr, map) + actions.git_staging_toggle:enhance { + post = function() + action_state.get_current_picker(prompt_bufnr):refresh(gen_new_finder(), { reset_prompt = true }) + end, + } + + map("i", "<tab>", actions.git_staging_toggle) + map("n", "<tab>", actions.git_staging_toggle) + return true + end, + }):find() +end + +local set_opts_cwd = function(opts) + if opts.cwd then + opts.cwd = vim.fn.expand(opts.cwd) + else + opts.cwd = vim.loop.cwd() + end + + -- Find root of git directory and remove trailing newline characters + local git_root, ret = utils.get_os_command_output({ "git", "rev-parse", "--show-toplevel" }, opts.cwd) + local use_git_root = vim.F.if_nil(opts.use_git_root, true) + + if ret ~= 0 then + local in_worktree = utils.get_os_command_output({ "git", "rev-parse", "--is-inside-work-tree" }, opts.cwd) + local in_bare = utils.get_os_command_output({ "git", "rev-parse", "--is-bare-repository" }, opts.cwd) + + if in_worktree[1] ~= "true" and in_bare[1] ~= "true" then + error(opts.cwd .. " is not a git directory") + elseif in_worktree[1] ~= "true" and in_bare[1] == "true" then + opts.is_bare = true + end + else + if use_git_root then + opts.cwd = git_root[1] + end + end +end + +local function apply_checks(mod) + for k, v in pairs(mod) do + mod[k] = function(opts) + opts = vim.F.if_nil(opts, {}) + + set_opts_cwd(opts) + v(opts) + end + end + + return mod +end + +return apply_checks(git) diff --git a/lua/telescope/builtin/__internal.lua b/lua/telescope/builtin/__internal.lua new file mode 100644 index 0000000..4277e2e --- /dev/null +++ b/lua/telescope/builtin/__internal.lua @@ -0,0 +1,1318 @@ +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 sorters = require "telescope.sorters" +local state = require "telescope.state" +local utils = require "telescope.utils" + +local conf = require("telescope.config").values + +local filter = vim.tbl_filter + +-- 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 + +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 + + 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(_) + 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 + + 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) + 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 + 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 current_picker = action_state.get_current_picker(prompt_bufnr) + local selection_index = current_picker:get_index(current_picker:get_selection_row()) + actions.close(prompt_bufnr) + 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", "<C-x>", actions.remove_selected_picker) + map("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 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" 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 + vim.cmd(cmd) + else + vim.cmd [[stopinsert]] + vim.fn.feedkeys(cmd, "n") + end + 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 + 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(_, _) + action_set.select:replace(function(prompt_bufnr) + local nr = action_state.get_selected_entry().nr + actions.close(prompt_bufnr) + internal.quickfix { nr = nr } + 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 + 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 opts.include_current_session then + for _, buffer in ipairs(vim.split(vim.fn.execute ":buffers! t", "\n")) 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 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 vim.loop.fs_stat(file) and not vim.tbl_contains(results, file) and file ~= current_file then + table.insert(results, file) + end + end + + if opts.cwd_only then + local cwd = vim.loop.cwd() + cwd = cwd:gsub([[\]], [[\\]]) + results = vim.tbl_filter(function(file) + return vim.fn.matchstrpos(file, cwd)[2] ~= -1 + end, results) + end + + pickers.new(opts, { + prompt_title = "Oldfiles", + 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.file_previewer(opts), + }):find() +end + +internal.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 = conf.generic_sorter(opts), + + attach_mappings = function(_, map) + map("i", "<CR>", actions.set_command_line) + map("n", "<CR>", actions.set_command_line) + map("n", "<C-e>", actions.edit_command_line) + map("i", "<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 = vim.split(search_string, "\n") + + 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) + map("i", "<CR>", actions.set_search_line) + map("n", "<CR>", actions.set_search_line) + map("n", "<C-e>", actions.edit_search_line) + map("i", "<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, ",", 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 all_files = vim.api.nvim_get_runtime_file("doc/*", true) + 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 = vim.split(Path:new(file):read(), "\n", 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, 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(vim.tbl_islist(opts.sections), "sections should be a list") + opts.man_cmd = utils.get_lazy_default(opts.man_cmd, function() + local is_darwin = vim.loop.os_uname().sysname == "Darwin" + return is_darwin and { "apropos", " " } or { "apropos", "" } + 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 = filter(function(b) + if 1 ~= vim.fn.buflisted(b) 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(b) then + return false + end + if opts.ignore_current_buffer and b == vim.api.nvim_get_current_buf() then + return false + end + if opts.cwd_only and not string.find(vim.api.nvim_buf_get_name(b), vim.loop.cwd(), 1, true) then + return false + end + return true + end, vim.api.nvim_list_bufs()) + if not next(bufnrs) then + 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 + + local buffers = {} + local default_selection_idx = 1 + for _, 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 + 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, + }):find() +end + +internal.colorscheme = function(opts) + local before_background = vim.o.background + local before_color = vim.api.nvim_exec("colorscheme", true) + local need_restore = true + + 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 color ~= before_color + end, vim.fn.getcompletion("", "color")) + ) + + 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) + + -- don't need previewer + if vim.fn.buflisted(bufnr) ~= 1 then + local deleted = false + local function del_win(win_id) + if win_id and vim.api.nvim_win_is_valid(win_id) then + utils.buf_delete(vim.api.nvim_win_get_buf(win_id)) + pcall(vim.api.nvim_win_close, win_id, true) + end + end + + previewer = previewers.new { + preview_fn = function(_, entry, status) + if not deleted then + deleted = true + del_win(status.preview_win) + del_win(status.preview_border_win) + end + vim.cmd("colorscheme " .. entry.value) + end, + } + else + -- show current buffer content in previewer + previewer = previewers.new_buffer_previewer { + get_buffer_by_name = function() + return p + end, + define_preview = function(self, entry) + 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 + vim.cmd("colorscheme " .. entry.value) + 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 + + actions.close(prompt_bufnr) + need_restore = false + vim.cmd("colorscheme " .. selection.value) + end) + + return true + 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 + 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) + for _, cnf in ipairs { local_marks, global_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 = 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), + }, + -- use levenshtein as n-gram doesn't support <2 char matches + sorter = sorters.get_levenshtein_sorter(), + attach_mappings = function(_, map) + actions.select_default:replace(actions.paste_register) + map("i", "<C-e>", actions.edit_register) + + return true + end, + }):find() +end + +-- TODO: make filtering include the mapping and the action +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) + + 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>") 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) + extract_keymaps(global) + 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 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, jumplist[i].lnum + 1, 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) diff --git a/lua/telescope/builtin/__lsp.lua b/lua/telescope/builtin/__lsp.lua new file mode 100644 index 0000000..480d456 --- /dev/null +++ b/lua/telescope/builtin/__lsp.lua @@ -0,0 +1,389 @@ +local channel = require("plenary.async.control").channel +local action_state = require "telescope.actions.state" +local sorters = require "telescope.sorters" +local conf = require("telescope.config").values +local finders = require "telescope.finders" +local make_entry = require "telescope.make_entry" +local pickers = require "telescope.pickers" +local utils = require "telescope.utils" + +local lsp = {} + +lsp.references = function(opts) + local filepath = vim.api.nvim_buf_get_name(opts.bufnr) + local lnum = vim.api.nvim_win_get_cursor(opts.winnr)[1] + local params = vim.lsp.util.make_position_params(opts.winnr) + local include_current_line = vim.F.if_nil(opts.include_current_line, false) + params.context = { includeDeclaration = vim.F.if_nil(opts.include_declaration, true) } + + vim.lsp.buf_request(opts.bufnr, "textDocument/references", params, function(err, result, ctx, _) + if err then + vim.api.nvim_err_writeln("Error when finding references: " .. err.message) + return + end + + local locations = {} + if result then + local results = vim.lsp.util.locations_to_items(result, vim.lsp.get_client_by_id(ctx.client_id).offset_encoding) + if include_current_line then + locations = vim.tbl_filter(function(v) + -- Remove current line from result + return not (v.filename == filepath and v.lnum == lnum) + end, vim.F.if_nil(results, {})) + else + locations = vim.F.if_nil(results, {}) + 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 = opts.entry_maker or make_entry.gen_from_quickfix(opts), + }, + previewer = conf.qflist_previewer(opts), + sorter = conf.generic_sorter(opts), + push_cursor_on_edit = true, + push_tagstack_on_edit = true, + }):find() + end) +end + +local function call_hierarchy(opts, method, title, direction, item) + vim.lsp.buf_request(opts.bufnr, method, { item = item }, function(err, result) + if err then + vim.api.nvim_err_writeln("Error handling " .. title .. ": " .. err) + return + end + + if not result or vim.tbl_isempty(result) then + return + end + + local locations = {} + for _, ch_call in pairs(result) do + local ch_item = ch_call[direction] + for _, range in pairs(ch_call.fromRanges) do + table.insert(locations, { + filename = vim.uri_to_fname(ch_item.uri), + text = ch_item.name, + lnum = range.start.line + 1, + col = range.start.character + 1, + }) + end + end + + pickers.new(opts, { + prompt_title = title, + 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), + push_cursor_on_edit = true, + push_tagstack_on_edit = true, + }):find() + end) +end + +local function pick_call_hierarchy_item(call_hierarchy_items) + if not call_hierarchy_items then + return + end + if #call_hierarchy_items == 1 then + return call_hierarchy_items[1] + end + local items = {} + for i, item in pairs(call_hierarchy_items) do + local entry = item.detail or item.name + table.insert(items, string.format("%d. %s", i, entry)) + end + local choice = vim.fn.inputlist(items) + if choice < 1 or choice > #items then + return + end + return choice +end + +local function calls(opts, direction) + local params = vim.lsp.util.make_position_params() + vim.lsp.buf_request(opts.bufnr, "textDocument/prepareCallHierarchy", params, function(err, result) + if err then + vim.api.nvim_err_writeln("Error when preparing call hierarchy: " .. err) + return + end + + local call_hierarchy_item = pick_call_hierarchy_item(result) + if not call_hierarchy_item then + return + end + + if direction == "from" then + call_hierarchy(opts, "callHierarchy/incomingCalls", "LSP Incoming Calls", direction, call_hierarchy_item) + else + call_hierarchy(opts, "callHierarchy/outgoingCalls", "LSP Outgoing Calls", direction, call_hierarchy_item) + end + end) +end + +lsp.incoming_calls = function(opts) + calls(opts, "from") +end + +lsp.outgoing_calls = function(opts) + calls(opts, "to") +end + +local function list_or_jump(action, title, opts) + local params = vim.lsp.util.make_position_params(opts.winnr) + vim.lsp.buf_request(opts.bufnr, action, params, function(err, result, ctx, _) + if err then + vim.api.nvim_err_writeln("Error when executing " .. action .. " : " .. err.message) + return + end + local flattened_results = {} + if result then + -- textDocument/definition can return Location or Location[] + if not vim.tbl_islist(result) then + flattened_results = { result } + end + + vim.list_extend(flattened_results, result) + end + + local offset_encoding = vim.lsp.get_client_by_id(ctx.client_id).offset_encoding + + if #flattened_results == 0 then + return + elseif #flattened_results == 1 and opts.jump_type ~= "never" then + if opts.jump_type == "tab" then + vim.cmd "tabedit" + elseif opts.jump_type == "split" then + vim.cmd "new" + elseif opts.jump_type == "vsplit" then + vim.cmd "vnew" + end + vim.lsp.util.jump_to_location(flattened_results[1], offset_encoding) + else + local locations = vim.lsp.util.locations_to_items(flattened_results, offset_encoding) + pickers.new(opts, { + prompt_title = title, + 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), + push_cursor_on_edit = true, + push_tagstack_on_edit = true, + }):find() + end + end) +end + +lsp.definitions = function(opts) + return list_or_jump("textDocument/definition", "LSP Definitions", opts) +end + +lsp.type_definitions = function(opts) + return list_or_jump("textDocument/typeDefinition", "LSP Type Definitions", opts) +end + +lsp.implementations = function(opts) + return list_or_jump("textDocument/implementation", "LSP Implementations", opts) +end + +lsp.document_symbols = function(opts) + local params = vim.lsp.util.make_position_params(opts.winnr) + vim.lsp.buf_request(opts.bufnr, "textDocument/documentSymbol", params, function(err, result, _, _) + if err then + vim.api.nvim_err_writeln("Error when finding document symbols: " .. err.message) + return + end + + if not result or vim.tbl_isempty(result) then + utils.notify("builtin.lsp_document_symbols", { + msg = "No results from textDocument/documentSymbol", + level = "INFO", + }) + return + end + + local locations = vim.lsp.util.symbols_to_items(result or {}, opts.bufnr) or {} + locations = utils.filter_symbols(locations, opts) + if locations == nil then + -- error message already printed in `utils.filter_symbols` + return + end + + if vim.tbl_isempty(locations) then + utils.notify("builtin.lsp_document_symbols", { + msg = "No document_symbol locations found", + level = "INFO", + }) + return + end + + opts.path_display = { "hidden" } + pickers.new(opts, { + prompt_title = "LSP Document Symbols", + finder = finders.new_table { + results = locations, + entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts), + }, + previewer = conf.qflist_previewer(opts), + sorter = conf.prefilter_sorter { + tag = "symbol_type", + sorter = conf.generic_sorter(opts), + }, + push_cursor_on_edit = true, + push_tagstack_on_edit = true, + }):find() + end) +end + +lsp.workspace_symbols = function(opts) + local params = { query = opts.query or "" } + vim.lsp.buf_request(opts.bufnr, "workspace/symbol", params, function(err, server_result, _, _) + if err then + vim.api.nvim_err_writeln("Error when finding workspace symbols: " .. err.message) + return + end + + local locations = vim.lsp.util.symbols_to_items(server_result or {}, opts.bufnr) or {} + locations = utils.filter_symbols(locations, opts) + if locations == nil then + -- error message already printed in `utils.filter_symbols` + return + end + + if vim.tbl_isempty(locations) then + utils.notify("builtin.lsp_workspace_symbols", { + msg = "No results from workspace/symbol. Maybe try a different query: " + .. "'Telescope lsp_workspace_symbols query=example'", + level = "INFO", + }) + return + end + + opts.ignore_filename = vim.F.if_nil(opts.ignore_filename, false) + + pickers.new(opts, { + prompt_title = "LSP Workspace Symbols", + finder = finders.new_table { + results = locations, + entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts), + }, + previewer = conf.qflist_previewer(opts), + sorter = conf.prefilter_sorter { + tag = "symbol_type", + sorter = conf.generic_sorter(opts), + }, + }):find() + end) +end + +local function get_workspace_symbols_requester(bufnr, opts) + local cancel = function() end + + return function(prompt) + local tx, rx = channel.oneshot() + cancel() + _, cancel = vim.lsp.buf_request(bufnr, "workspace/symbol", { query = prompt }, tx) + + -- Handle 0.5 / 0.5.1 handler situation + local err, res = rx() + assert(not err, err) + + local locations = vim.lsp.util.symbols_to_items(res or {}, bufnr) or {} + if not vim.tbl_isempty(locations) then + locations = utils.filter_symbols(locations, opts) or {} + end + return locations + end +end + +lsp.dynamic_workspace_symbols = function(opts) + pickers.new(opts, { + prompt_title = "LSP Dynamic Workspace Symbols", + finder = finders.new_dynamic { + entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts), + fn = get_workspace_symbols_requester(opts.bufnr, opts), + }, + previewer = conf.qflist_previewer(opts), + sorter = sorters.highlighter_only(opts), + attach_mappings = function(_, map) + map("i", "<c-space>", function(prompt_bufnr) + local line = action_state.get_current_line() + require("telescope.actions.generate").refine(prompt_bufnr, { + prompt_title = "LSP Workspace Symbols (" .. line .. ")", + sorter = conf.generic_sorter(opts), + }) + end) + return true + end, + }):find() +end + +local function check_capabilities(feature, bufnr) + local clients = vim.lsp.buf_get_clients(bufnr) + + local supported_client = false + for _, client in pairs(clients) do + supported_client = client.server_capabilities[feature] + if supported_client then + break + end + end + + if supported_client then + return true + else + if #clients == 0 then + utils.notify("builtin.lsp_*", { + msg = "no client attached", + level = "INFO", + }) + else + utils.notify("builtin.lsp_*", { + msg = "server does not support " .. feature, + level = "INFO", + }) + end + return false + end +end + +local feature_map = { + ["document_symbols"] = "documentSymbolProvider", + ["references"] = "referencesProvider", + ["definitions"] = "definitionProvider", + ["type_definitions"] = "typeDefinitionProvider", + ["implementations"] = "implementationProvider", + ["workspace_symbols"] = "workspaceSymbolProvider", + ["incoming_calls"] = "callHierarchyProvider", + ["outgoing_calls"] = "callHierarchyProvider", +} + +local function apply_checks(mod) + for k, v in pairs(mod) do + mod[k] = function(opts) + opts = opts or {} + + local feature_name = feature_map[k] + if feature_name and not check_capabilities(feature_name, opts.bufnr) then + return + end + v(opts) + end + end + + return mod +end + +return apply_checks(lsp) diff --git a/lua/telescope/builtin/diagnostics.lua b/lua/telescope/builtin/diagnostics.lua index 2614e36..5d8f926 100644 --- a/lua/telescope/builtin/diagnostics.lua +++ b/lua/telescope/builtin/diagnostics.lua @@ -1,150 +1,17 @@ -local conf = require("telescope.config").values -local finders = require "telescope.finders" -local make_entry = require "telescope.make_entry" -local pickers = require "telescope.pickers" -local utils = require "telescope.utils" - -local diagnostics = {} - -local convert_diagnostic_type = function(severities, severity) - -- convert from string to int - if type(severity) == "string" then - -- make sure that e.g. error is uppercased to ERROR - return severities[severity:upper()] - end - -- otherwise keep original value, incl. nil - return severity -end - -local diagnostics_to_tbl = function(opts) - opts = vim.F.if_nil(opts, {}) - local items = {} - local severities = vim.diagnostic.severity - local current_buf = vim.api.nvim_get_current_buf() - - opts.severity = convert_diagnostic_type(severities, opts.severity) - opts.severity_limit = convert_diagnostic_type(severities, opts.severity_limit) - opts.severity_bound = convert_diagnostic_type(severities, opts.severity_bound) - - local diagnosis_opts = { severity = {}, namespace = opts.namespace } - if opts.severity ~= nil then - if opts.severity_limit ~= nil or opts.severity_bound ~= nil then - utils.notify("builtin.diagnostics", { - msg = "Invalid severity parameters. Both a specific severity and a limit/bound is not allowed", - level = "ERROR", - }) - return {} - end - diagnosis_opts.severity = opts.severity - else - if opts.severity_limit ~= nil then - diagnosis_opts.severity["min"] = opts.severity_limit - end - if opts.severity_bound ~= nil then - diagnosis_opts.severity["max"] = opts.severity_bound - end - end - - opts.root_dir = opts.root_dir == true and vim.loop.cwd() or opts.root_dir - - local bufnr_name_map = {} - local filter_diag = function(diagnostic) - if bufnr_name_map[diagnostic.bufnr] == nil then - bufnr_name_map[diagnostic.bufnr] = vim.api.nvim_buf_get_name(diagnostic.bufnr) - end - - local root_dir_test = not opts.root_dir - or string.sub(bufnr_name_map[diagnostic.bufnr], 1, #opts.root_dir) == opts.root_dir - local listed_test = not opts.no_unlisted or vim.api.nvim_buf_get_option(diagnostic.bufnr, "buflisted") - - return root_dir_test and listed_test - end - - local preprocess_diag = function(diagnostic) - return { - bufnr = diagnostic.bufnr, - filename = bufnr_name_map[diagnostic.bufnr], - lnum = diagnostic.lnum + 1, - col = diagnostic.col + 1, - text = vim.trim(diagnostic.message:gsub("[\n]", "")), - type = severities[diagnostic.severity] or severities[1], - } - end - - for _, d in ipairs(vim.diagnostic.get(opts.bufnr, diagnosis_opts)) do - if filter_diag(d) then - table.insert(items, preprocess_diag(d)) - end - end - - -- sort results by bufnr (prioritize cur buf), severity, lnum - table.sort(items, function(a, b) - if a.bufnr == b.bufnr then - if a.type == b.type then - return a.lnum < b.lnum - else - return a.type < b.type - end - else - -- prioritize for current bufnr - if a.bufnr == current_buf then - return true - end - if b.bufnr == current_buf then - return false - end - return a.bufnr < b.bufnr - end - end) - - return items -end - -diagnostics.get = function(opts) - if opts.bufnr ~= 0 then - opts.bufnr = nil - end - if opts.bufnr == nil then - opts.path_display = vim.F.if_nil(opts.path_display, {}) - end - if type(opts.bufnr) == "string" then - opts.bufnr = tonumber(opts.bufnr) - end - - local locations = diagnostics_to_tbl(opts) - - if vim.tbl_isempty(locations) then - utils.notify("builtin.diagnostics", { - msg = "No diagnostics found", - level = "INFO", +local m = setmetatable({}, { + __index = function(_, k) + local utils = require "telescope.utils" + utils.notify("builtin", { + msg = string.format( + 'You are using an internal interface. Do not use `require("telescope.builtin.diagnostics").%s`,' + .. ' please use `require("telescope.builtin").diagnostics`! We will remove this endpoint soon!', + k, + k + ), + level = "ERROR", }) - return - end - - opts.path_display = vim.F.if_nil(opts.path_display, "hidden") - pickers.new(opts, { - prompt_title = opts.bufnr == nil and "Workspace Diagnostics" or "Document Diagnostics", - finder = finders.new_table { - results = locations, - entry_maker = opts.entry_maker or make_entry.gen_from_diagnostics(opts), - }, - previewer = conf.qflist_previewer(opts), - sorter = conf.prefilter_sorter { - tag = "type", - 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 require("telescope.builtin").diagnostics + end, +}) -return apply_checks(diagnostics) +return m diff --git a/lua/telescope/builtin/files.lua b/lua/telescope/builtin/files.lua index 3d4b1bf..645fc10 100644 --- a/lua/telescope/builtin/files.lua +++ b/lua/telescope/builtin/files.lua @@ -1,484 +1,17 @@ -local action_state = require "telescope.actions.state" -local action_set = require "telescope.actions.set" -local finders = require "telescope.finders" -local make_entry = require "telescope.make_entry" -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 log = require "telescope.log" - -local Path = require "plenary.path" - -local flatten = vim.tbl_flatten -local filter = vim.tbl_filter - -local files = {} - -local escape_chars = function(string) - return string.gsub(string, "[%(|%)|\\|%[|%]|%-|%{%}|%?|%+|%*|%^|%$|%.]", { - ["\\"] = "\\\\", - ["-"] = "\\-", - ["("] = "\\(", - [")"] = "\\)", - ["["] = "\\[", - ["]"] = "\\]", - ["{"] = "\\{", - ["}"] = "\\}", - ["?"] = "\\?", - ["+"] = "\\+", - ["*"] = "\\*", - ["^"] = "\\^", - ["$"] = "\\$", - ["."] = "\\.", - }) -end - --- Special keys: --- opts.search_dirs -- list of directory to search in --- opts.grep_open_files -- boolean to restrict search to open files -files.live_grep = function(opts) - local vimgrep_arguments = opts.vimgrep_arguments or conf.vimgrep_arguments - local search_dirs = opts.search_dirs - local grep_open_files = opts.grep_open_files - opts.cwd = opts.cwd and vim.fn.expand(opts.cwd) or vim.loop.cwd() - - local filelist = {} - - if grep_open_files then - local bufnrs = filter(function(b) - if 1 ~= vim.fn.buflisted(b) then - return false - end - return true - end, vim.api.nvim_list_bufs()) - if not next(bufnrs) then - return - end - - for _, bufnr in ipairs(bufnrs) do - local file = vim.api.nvim_buf_get_name(bufnr) - table.insert(filelist, Path:new(file):make_relative(opts.cwd)) - end - elseif search_dirs then - for i, path in ipairs(search_dirs) do - search_dirs[i] = vim.fn.expand(path) - end - end - - local additional_args = {} - if opts.additional_args ~= nil and type(opts.additional_args) == "function" then - additional_args = opts.additional_args(opts) - end - - if opts.type_filter then - additional_args[#additional_args + 1] = "--type=" .. opts.type_filter - end - - if opts.glob_pattern then - additional_args[#additional_args + 1] = "--glob=" .. opts.glob_pattern - end - - 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 - - local search_list = {} - - if search_dirs then - table.insert(search_list, search_dirs) - end - - if grep_open_files then - search_list = filelist - end - - return flatten { vimgrep_arguments, additional_args, "--", prompt, search_list } - end, opts.entry_maker or make_entry.gen_from_vimgrep(opts), opts.max_results, opts.cwd) - - pickers.new(opts, { - prompt_title = "Live Grep", - finder = live_grepper, - previewer = conf.grep_previewer(opts), - -- TODO: It would be cool to use `--json` output for this - -- and then we could get the highlight positions directly. - sorter = sorters.highlighter_only(opts), - }):find() -end - --- Special keys: --- opts.search -- the string to search. --- opts.search_dirs -- list of directory to search in --- opts.use_regex -- special characters won't be escaped -files.grep_string = function(opts) - -- TODO: This should probably check your visual selection as well, if you've got one - - local vimgrep_arguments = opts.vimgrep_arguments or conf.vimgrep_arguments - local search_dirs = opts.search_dirs - local word = opts.search or vim.fn.expand "<cword>" - local search = opts.use_regex and word or escape_chars(word) - local word_match = opts.word_match - opts.entry_maker = opts.entry_maker or make_entry.gen_from_vimgrep(opts) - - local title_word = word:gsub("\n", "\\n") - - local additional_args = {} - if opts.additional_args ~= nil and type(opts.additional_args) == "function" then - additional_args = opts.additional_args(opts) - end - - local args = flatten { - vimgrep_arguments, - additional_args, - word_match, - "--", - search, - } - - if search_dirs then - for _, path in ipairs(search_dirs) do - table.insert(args, vim.fn.expand(path)) - end - end - - pickers.new(opts, { - prompt_title = "Find Word (" .. title_word .. ")", - finder = finders.new_oneshot_job(args, opts), - previewer = conf.grep_previewer(opts), - sorter = conf.generic_sorter(opts), - }):find() -end - --- TODO: Maybe just change this to `find`. --- TODO: Support `find` and maybe let people do other stuff with it as well. -files.find_files = function(opts) - local find_command = (function() - if opts.find_command then - return opts.find_command - elseif 1 == vim.fn.executable "fd" then - return { "fd", "--type", "f" } - elseif 1 == vim.fn.executable "fdfind" then - return { "fdfind", "--type", "f" } - elseif 1 == vim.fn.executable "rg" then - return { "rg", "--files" } - elseif 1 == vim.fn.executable "find" and vim.fn.has "win32" == 0 then - return { "find", ".", "-type", "f" } - elseif 1 == vim.fn.executable "where" then - return { "where", "/r", ".", "*" } - end - end)() - - if not find_command then - utils.notify("builtin.find_files", { - msg = "You need to install either find, fd, or rg", +local m = setmetatable({}, { + __index = function(_, k) + local utils = require "telescope.utils" + utils.notify("builtin", { + msg = string.format( + 'You are using an internal interface. Do not use `require("telescope.builtin.files").%s`,' + .. ' please use `require("telescope.builtin").%s`! We will remove this endpoint soon!', + k, + k + ), level = "ERROR", }) - return - end - - local command = find_command[1] - local hidden = opts.hidden - local no_ignore = opts.no_ignore - local follow = opts.follow - local search_dirs = opts.search_dirs - - if search_dirs then - for k, v in pairs(search_dirs) do - search_dirs[k] = vim.fn.expand(v) - end - end - - if command == "fd" or command == "fdfind" or command == "rg" then - if hidden then - table.insert(find_command, "--hidden") - end - if no_ignore then - table.insert(find_command, "--no-ignore") - end - if follow then - table.insert(find_command, "-L") - end - if search_dirs then - if command ~= "rg" then - table.insert(find_command, ".") - end - for _, v in pairs(search_dirs) do - table.insert(find_command, v) - end - end - elseif command == "find" then - if not hidden then - table.insert(find_command, { "-not", "-path", "*/.*" }) - find_command = flatten(find_command) - end - if no_ignore ~= nil then - log.warn "The `no_ignore` key is not available for the `find` command in `find_files`." - end - if follow then - table.insert(find_command, 2, "-L") - end - if search_dirs then - table.remove(find_command, 2) - for _, v in pairs(search_dirs) do - table.insert(find_command, 2, v) - end - end - elseif command == "where" then - if hidden ~= nil then - log.warn "The `hidden` key is not available for the Windows `where` command in `find_files`." - end - if no_ignore ~= nil then - log.warn "The `no_ignore` key is not available for the Windows `where` command in `find_files`." - end - if follow ~= nil then - log.warn "The `follow` key is not available for the Windows `where` command in `find_files`." - end - if search_dirs ~= nil then - log.warn "The `search_dirs` key is not available for the Windows `where` command in `find_files`." - end - 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 = conf.file_previewer(opts), - sorter = conf.file_sorter(opts), - }):find() -end - -local function prepare_match(entry, kind) - local entries = {} - - if entry.node then - 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 - --- TODO: finish docs for opts.show_line -files.treesitter = function(opts) - opts.show_line = utils.get_default(opts.show_line, true) - - local has_nvim_treesitter, _ = pcall(require, "nvim-treesitter") - if not has_nvim_treesitter then - utils.notify("builtin.treesitter", { - msg = "User need to install nvim-treesitter needs to be installed", - level = "ERROR", - }) - return - end - - local parsers = require "nvim-treesitter.parsers" - if not parsers.has_parser(parsers.get_buf_lang(opts.bufnr)) then - utils.notify("builtin.treesitter", { - msg = "No parser for the current buffer", - level = "ERROR", - }) - return - end - - local ts_locals = require "nvim-treesitter.locals" - local results = {} - for _, definition in ipairs(ts_locals.get_definitions(opts.bufnr)) do - local entries = prepare_match(ts_locals.get_local_nodes(definition)) - for _, entry in ipairs(entries) do - entry.kind = vim.F.if_nil(entry.kind, "") - 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 = opts.entry_maker or make_entry.gen_from_treesitter(opts), - }, - previewer = conf.grep_previewer(opts), - sorter = conf.prefilter_sorter { - tag = "kind", - sorter = conf.generic_sorter(opts), - }, - }):find() -end - -files.current_buffer_fuzzy_find = function(opts) - -- All actions are on the current buffer - local filename = vim.fn.expand(vim.api.nvim_buf_get_name(opts.bufnr)) - local filetype = vim.api.nvim_buf_get_option(opts.bufnr, "filetype") - - local lines = vim.api.nvim_buf_get_lines(opts.bufnr, 0, -1, false) - local lines_with_numbers = {} - - for lnum, line in ipairs(lines) do - table.insert(lines_with_numbers, { - lnum = lnum, - bufnr = opts.bufnr, - filename = filename, - text = line, - }) - end - - local ts_ok, ts_parsers = pcall(require, "nvim-treesitter.parsers") - if ts_ok then - filetype = ts_parsers.ft_to_lang(filetype) - end - local _, ts_configs = pcall(require, "nvim-treesitter.configs") - - local parser_ok, parser = pcall(vim.treesitter.get_parser, opts.bufnr, filetype) - local query_ok, query = pcall(vim.treesitter.get_query, filetype, "highlights") - if parser_ok and query_ok and ts_ok and ts_configs.is_enabled("highlight", filetype, opts.bufnr) then - local root = parser:parse()[1]:root() - - local highlighter = vim.treesitter.highlighter.new(parser) - local highlighter_query = highlighter:get_query(filetype) - - local line_highlights = setmetatable({}, { - __index = function(t, k) - local obj = {} - rawset(t, k, obj) - return obj - end, - }) - for id, node in query:iter_captures(root, opts.bufnr, 0, -1) do - local hl = highlighter_query:_get_hl_from_capture(id) - if hl and type(hl) ~= "number" then - local row1, col1, row2, col2 = node:range() - - if row1 == row2 then - local row = row1 + 1 - - for index = col1, col2 do - line_highlights[row][index] = hl - end - else - local row = row1 + 1 - for index = col1, #lines[row] do - line_highlights[row][index] = hl - end - - while row < row2 + 1 do - row = row + 1 - - for index = 0, #(lines[row] or {}) do - line_highlights[row][index] = hl - end - end - end - end - end - - opts.line_highlights = line_highlights - end - - pickers.new(opts, { - prompt_title = "Current Buffer Fuzzy", - finder = finders.new_table { - results = lines_with_numbers, - entry_maker = opts.entry_maker or make_entry.gen_from_buffer_lines(opts), - }, - sorter = conf.generic_sorter(opts), - previewer = conf.grep_previewer(opts), - attach_mappings = function() - action_set.select:enhance { - post = function() - local selection = action_state.get_selected_entry() - vim.api.nvim_win_set_cursor(0, { selection.lnum, 0 }) - end, - } - - return true - end, - }):find() -end - -files.tags = function(opts) - local tagfiles = opts.ctags_file and { opts.ctags_file } or vim.fn.tagfiles() - if vim.tbl_isempty(tagfiles) then - utils.notify("builtin.tags", { - msg = "No tags file found. Create one with ctags -R", - level = "ERROR", - }) - return - end - - local results = {} - for _, ctags_file in ipairs(tagfiles) do - for line in Path:new(vim.fn.expand(ctags_file, true)):iter() do - results[#results + 1] = line - end - end - - pickers.new(opts, { - prompt_title = "Tags", - finder = finders.new_table { - results = results, - entry_maker = opts.entry_maker or make_entry.gen_from_ctags(opts), - }, - previewer = previewers.ctags.new(opts), - sorter = conf.generic_sorter(opts), - attach_mappings = function() - action_set.select:enhance { - post = function() - local selection = action_state.get_selected_entry() - - if selection.scode then - -- un-escape / then escape required - -- special chars for vim.fn.search() - -- ] ~ * - local scode = selection.scode:gsub([[\/]], "/"):gsub("[%]~*]", function(x) - return "\\" .. x - end) - - vim.cmd "norm! gg" - vim.fn.search(scode) - vim.cmd "norm! zz" - else - vim.api.nvim_win_set_cursor(0, { selection.lnum, 0 }) - end - end, - } - return true - end, - }):find() -end - -files.current_buffer_tags = function(opts) - return files.tags(vim.tbl_extend("force", { - prompt_title = "Current Buffer Tags", - only_current_file = true, - path_display = "hidden", - }, opts)) -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 require("telescope.builtin")[k] + end, +}) -return apply_checks(files) +return m diff --git a/lua/telescope/builtin/git.lua b/lua/telescope/builtin/git.lua index 5f6b17e..e6da478 100644 --- a/lua/telescope/builtin/git.lua +++ b/lua/telescope/builtin/git.lua @@ -1,408 +1,17 @@ -local actions = require "telescope.actions" -local action_state = require "telescope.actions.state" -local finders = require "telescope.finders" -local make_entry = require "telescope.make_entry" -local pickers = require "telescope.pickers" -local previewers = require "telescope.previewers" -local utils = require "telescope.utils" -local entry_display = require "telescope.pickers.entry_display" -local strings = require "plenary.strings" -local Path = require "plenary.path" - -local conf = require("telescope.config").values - -local git = {} - -git.files = function(opts) - if opts.is_bare then - utils.notify("builtin.git_files", { - msg = "This operation must be run in a work tree", - level = "ERROR", - }) - return - end - - local show_untracked = utils.get_default(opts.show_untracked, true) - local recurse_submodules = utils.get_default(opts.recurse_submodules, false) - if show_untracked and recurse_submodules then - utils.notify("builtin.git_files", { - msg = "Git does not support both --others and --recurse-submodules", - level = "ERROR", - }) - return - end - - -- By creating the entry maker after the cwd options, - -- we ensure the maker uses the cwd options when being created. - opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_file(opts)) - local git_command = vim.F.if_nil(opts.git_command, { "git", "ls-files", "--exclude-standard", "--cached" }) - - pickers.new(opts, { - prompt_title = "Git Files", - finder = finders.new_oneshot_job( - vim.tbl_flatten { - git_command, - show_untracked and "--others" or nil, - recurse_submodules and "--recurse-submodules" or nil, - }, - opts - ), - previewer = conf.file_previewer(opts), - sorter = conf.file_sorter(opts), - }):find() -end - -git.commits = function(opts) - opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts)) - local git_command = vim.F.if_nil(opts.git_command, { "git", "log", "--pretty=oneline", "--abbrev-commit", "--", "." }) - - pickers.new(opts, { - prompt_title = "Git Commits", - finder = finders.new_oneshot_job(git_command, opts), - previewer = { - previewers.git_commit_diff_to_parent.new(opts), - previewers.git_commit_diff_to_head.new(opts), - previewers.git_commit_diff_as_was.new(opts), - previewers.git_commit_message.new(opts), - }, - sorter = conf.file_sorter(opts), - attach_mappings = function(_, map) - actions.select_default:replace(actions.git_checkout) - map("i", "<c-r>m", actions.git_reset_mixed) - map("n", "<c-r>m", actions.git_reset_mixed) - map("i", "<c-r>s", actions.git_reset_soft) - map("n", "<c-r>s", actions.git_reset_soft) - map("i", "<c-r>h", actions.git_reset_hard) - map("n", "<c-r>h", actions.git_reset_hard) - return true - end, - }):find() -end - -git.stash = function(opts) - opts.show_branch = vim.F.if_nil(opts.show_branch, true) - opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_stash(opts)) - - pickers.new(opts, { - prompt_title = "Git Stash", - finder = finders.new_oneshot_job( - vim.tbl_flatten { - "git", - "--no-pager", - "stash", - "list", - }, - opts - ), - previewer = previewers.git_stash_diff.new(opts), - sorter = conf.file_sorter(opts), - attach_mappings = function() - actions.select_default:replace(actions.git_apply_stash) - return true - end, - }):find() -end - -local get_current_buf_line = function(winnr) - local lnum = vim.api.nvim_win_get_cursor(winnr)[1] - return vim.trim(vim.api.nvim_buf_get_lines(vim.api.nvim_win_get_buf(winnr), lnum - 1, lnum, false)[1]) -end - -git.bcommits = function(opts) - opts.current_line = (opts.current_file == nil) and get_current_buf_line(opts.winnr) or nil - opts.current_file = vim.F.if_nil(opts.current_file, vim.api.nvim_buf_get_name(opts.bufnr)) - opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts)) - local git_command = vim.F.if_nil(opts.git_command, { "git", "log", "--pretty=oneline", "--abbrev-commit" }) - - pickers.new(opts, { - prompt_title = "Git BCommits", - finder = finders.new_oneshot_job( - vim.tbl_flatten { - git_command, - opts.current_file, - }, - opts - ), - previewer = { - previewers.git_commit_diff_to_parent.new(opts), - previewers.git_commit_diff_to_head.new(opts), - previewers.git_commit_diff_as_was.new(opts), - previewers.git_commit_message.new(opts), - }, - sorter = conf.file_sorter(opts), - attach_mappings = function() - actions.select_default:replace(actions.git_checkout_current_buffer) - local transfrom_file = function() - return opts.current_file and Path:new(opts.current_file):make_relative(opts.cwd) or "" - end - - local get_buffer_of_orig = function(selection) - local value = selection.value .. ":" .. transfrom_file() - local content = utils.get_os_command_output({ "git", "--no-pager", "show", value }, opts.cwd) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, content) - vim.api.nvim_buf_set_name(bufnr, "Original") - return bufnr - end - - local vimdiff = function(selection, command) - local ft = vim.bo.filetype - vim.cmd "diffthis" - - local bufnr = get_buffer_of_orig(selection) - vim.cmd(string.format("%s %s", command, bufnr)) - vim.bo.filetype = ft - vim.cmd "diffthis" - - vim.api.nvim_create_autocmd("WinClosed", { - buffer = bufnr, - nested = true, - once = true, - callback = function() - vim.api.nvim_buf_delete(bufnr, { force = true }) - end, - }) - end - - actions.select_vertical:replace(function(prompt_bufnr) - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - vimdiff(selection, "leftabove vert sbuffer") - end) - - actions.select_horizontal:replace(function(prompt_bufnr) - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - vimdiff(selection, "belowright sbuffer") - end) - - actions.select_tab:replace(function(prompt_bufnr) - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - vim.cmd("tabedit " .. transfrom_file()) - vimdiff(selection, "leftabove vert sbuffer") - end) - return true - end, - }):find() -end - -git.branches = function(opts) - local format = "%(HEAD)" - .. "%(refname)" - .. "%(authorname)" - .. "%(upstream:lstrip=2)" - .. "%(committerdate:format-local:%Y/%m/%d %H:%M:%S)" - local output = utils.get_os_command_output( - { "git", "for-each-ref", "--perl", "--format", format, opts.pattern }, - opts.cwd - ) - - local results = {} - local widths = { - name = 0, - authorname = 0, - upstream = 0, - committerdate = 0, - } - local unescape_single_quote = function(v) - return string.gsub(v, "\\([\\'])", "%1") - end - local parse_line = function(line) - local fields = vim.split(string.sub(line, 2, -2), "''", true) - local entry = { - head = fields[1], - refname = unescape_single_quote(fields[2]), - authorname = unescape_single_quote(fields[3]), - upstream = unescape_single_quote(fields[4]), - committerdate = fields[5], - } - local prefix - if vim.startswith(entry.refname, "refs/remotes/") then - prefix = "refs/remotes/" - elseif vim.startswith(entry.refname, "refs/heads/") then - prefix = "refs/heads/" - else - return - end - local index = 1 - if entry.head ~= "*" then - index = #results + 1 - end - - entry.name = string.sub(entry.refname, string.len(prefix) + 1) - for key, value in pairs(widths) do - widths[key] = math.max(value, strings.strdisplaywidth(entry[key] or "")) - end - if string.len(entry.upstream) > 0 then - widths.upstream_indicator = 2 - end - table.insert(results, index, entry) - end - for _, line in ipairs(output) do - parse_line(line) - end - if #results == 0 then - return - end - - local displayer = entry_display.create { - separator = " ", - items = { - { width = 1 }, - { width = widths.name }, - { width = widths.authorname }, - { width = widths.upstream_indicator }, - { width = widths.upstream }, - { width = widths.committerdate }, - }, - } - - local make_display = function(entry) - return displayer { - { entry.head }, - { entry.name, "TelescopeResultsIdentifier" }, - { entry.authorname }, - { string.len(entry.upstream) > 0 and "=>" or "" }, - { entry.upstream, "TelescopeResultsIdentifier" }, - { entry.committerdate }, - } - end - - pickers.new(opts, { - prompt_title = "Git Branches", - finder = finders.new_table { - results = results, - entry_maker = function(entry) - entry.value = entry.name - entry.ordinal = entry.name - entry.display = make_display - return entry - end, - }, - previewer = previewers.git_branch_log.new(opts), - sorter = conf.file_sorter(opts), - attach_mappings = function(_, map) - actions.select_default:replace(actions.git_checkout) - map("i", "<c-t>", actions.git_track_branch) - map("n", "<c-t>", actions.git_track_branch) - - map("i", "<c-r>", actions.git_rebase_branch) - map("n", "<c-r>", actions.git_rebase_branch) - - map("i", "<c-a>", actions.git_create_branch) - map("n", "<c-a>", actions.git_create_branch) - - map("i", "<c-s>", actions.git_switch_branch) - map("n", "<c-s>", actions.git_switch_branch) - - map("i", "<c-d>", actions.git_delete_branch) - map("n", "<c-d>", actions.git_delete_branch) - - map("i", "<c-y>", actions.git_merge_branch) - map("n", "<c-y>", actions.git_merge_branch) - return true - end, - }):find() -end - -git.status = function(opts) - if opts.is_bare then - utils.notify("builtin.git_status", { - msg = "This operation must be run in a work tree", +local m = setmetatable({}, { + __index = function(_, k) + local utils = require "telescope.utils" + utils.notify("builtin", { + msg = string.format( + 'You are using an internal interface. Do not use `require("telescope.builtin.git").%s`,' + .. ' please use `require("telescope.builtin").git_%s`! We will remove this endpoint soon!', + k, + k + ), level = "ERROR", }) - return - end - - local gen_new_finder = function() - local expand_dir = utils.if_nil(opts.expand_dir, true, opts.expand_dir) - local git_cmd = { "git", "status", "-s", "--", "." } - - if expand_dir then - table.insert(git_cmd, #git_cmd - 1, "-u") - end - - local output = utils.get_os_command_output(git_cmd, opts.cwd) - - if #output == 0 then - print "No changes found" - utils.notify("builtin.git_status", { - msg = "No changes found", - level = "WARN", - }) - return - end - - return finders.new_table { - results = output, - entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_status(opts)), - } - end - - local initial_finder = gen_new_finder() - if not initial_finder then - return - end - - pickers.new(opts, { - prompt_title = "Git Status", - finder = initial_finder, - previewer = previewers.git_file_diff.new(opts), - sorter = conf.file_sorter(opts), - attach_mappings = function(prompt_bufnr, map) - actions.git_staging_toggle:enhance { - post = function() - action_state.get_current_picker(prompt_bufnr):refresh(gen_new_finder(), { reset_prompt = true }) - end, - } - - map("i", "<tab>", actions.git_staging_toggle) - map("n", "<tab>", actions.git_staging_toggle) - return true - end, - }):find() -end - -local set_opts_cwd = function(opts) - if opts.cwd then - opts.cwd = vim.fn.expand(opts.cwd) - else - opts.cwd = vim.loop.cwd() - end - - -- Find root of git directory and remove trailing newline characters - local git_root, ret = utils.get_os_command_output({ "git", "rev-parse", "--show-toplevel" }, opts.cwd) - local use_git_root = utils.get_default(opts.use_git_root, true) - - if ret ~= 0 then - local in_worktree = utils.get_os_command_output({ "git", "rev-parse", "--is-inside-work-tree" }, opts.cwd) - local in_bare = utils.get_os_command_output({ "git", "rev-parse", "--is-bare-repository" }, opts.cwd) - - if in_worktree[1] ~= "true" and in_bare[1] ~= "true" then - error(opts.cwd .. " is not a git directory") - elseif in_worktree[1] ~= "true" and in_bare[1] == "true" then - opts.is_bare = true - end - else - if use_git_root then - opts.cwd = git_root[1] - end - end -end - -local function apply_checks(mod) - for k, v in pairs(mod) do - mod[k] = function(opts) - opts = vim.F.if_nil(opts, {}) - - set_opts_cwd(opts) - v(opts) - end - end - - return mod -end + return require("telescope.builtin")["git_" .. k] + end, +}) -return apply_checks(git) +return m diff --git a/lua/telescope/builtin/init.lua b/lua/telescope/builtin/init.lua index 4a99f3f..f25d059 100644 --- a/lua/telescope/builtin/init.lua +++ b/lua/telescope/builtin/init.lua @@ -54,35 +54,37 @@ end ---@param opts table: options to pass to the picker ---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer) ---@field grep_open_files boolean: if true, restrict search to open files only, mutually exclusive with `search_dirs` ----@field search_dirs table: directory/directories to search in, mutually exclusive with `grep_open_files` ----@field glob_pattern string: argument to be used with `--glob`, e.g. "*.toml", can use the opposite "!*.toml" +---@field search_dirs table: directory/directories/files to search, mutually exclusive with `grep_open_files` +---@field glob_pattern string|table: argument to be used with `--glob`, e.g. "*.toml", can use the opposite "!*.toml" ---@field type_filter string: argument to be used with `--type`, e.g. "rust", see `rg --type-list` ---@field additional_args function: function(opts) which returns a table of additional arguments to be passed on ---@field max_results number: define a upper result value ---@field disable_coordinates boolean: don't show the line & row numbers (default: false) -builtin.live_grep = require_on_exported_call("telescope.builtin.files").live_grep +builtin.live_grep = require_on_exported_call("telescope.builtin.__files").live_grep --- Searches for the string under your cursor in your current working directory ---@param opts table: options to pass to the picker ---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer) ---@field search string: the query to search ----@field search_dirs table: directory/directories to search in +---@field search_dirs table: directory/directories/files to search ---@field use_regex boolean: if true, special characters won't be escaped, allows for using regex (default: false) ---@field word_match string: can be set to `-w` to enable exact word matches ---@field additional_args function: function(opts) which returns a table of additional arguments to be passed on ---@field disable_coordinates boolean: don't show the line and row numbers (default: false) ---@field only_sort_text boolean: only sort the text, not the file, line or row (default: false) -builtin.grep_string = require_on_exported_call("telescope.builtin.files").grep_string +builtin.grep_string = require_on_exported_call("telescope.builtin.__files").grep_string --- Search for files (respecting .gitignore) ---@param opts table: options to pass to the picker ---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer) ----@field find_command table: command line arguments for `find_files` to use for the search, overrides default: config +---@field find_command function|table: cmd to use for the search. Can be a fn(opts) -> tbl (default: autodetect) ---@field follow boolean: if true, follows symlinks (i.e. uses `-L` flag for the `find` command) ---@field hidden boolean: determines whether to show hidden files or not (default: false) ---@field no_ignore boolean: show files ignored by .gitignore, .ignore, etc. (default: false) ----@field search_dirs table: directory/directories to search in -builtin.find_files = require_on_exported_call("telescope.builtin.files").find_files +---@field no_ignore_parent boolean: show files ignored by .gitignore, .ignore, etc. in parent dirs. (default: false) +---@field search_dirs table: directory/directories/files to search +---@field search_file string: specify a filename to search for +builtin.find_files = require_on_exported_call("telescope.builtin.__files").find_files --- This is an alias for the `find_files` picker builtin.fd = builtin.find_files @@ -93,12 +95,12 @@ builtin.fd = builtin.find_files ---@field show_line boolean: if true, shows the row:column that the result is found at (default: true) ---@field bufnr number: specify the buffer number where treesitter should run. (default: current buffer) ---@field symbol_highlights table: string -> string. Matches symbol with hl_group -builtin.treesitter = require_on_exported_call("telescope.builtin.files").treesitter +builtin.treesitter = require_on_exported_call("telescope.builtin.__files").treesitter --- Live fuzzy search inside of the currently open buffer ---@param opts table: options to pass to the picker ---@field skip_empty_lines boolean: if true we dont display empty lines (default: false) -builtin.current_buffer_fuzzy_find = require_on_exported_call("telescope.builtin.files").current_buffer_fuzzy_find +builtin.current_buffer_fuzzy_find = require_on_exported_call("telescope.builtin.__files").current_buffer_fuzzy_find --- Lists tags in current directory with tag location file preview (users are required to run ctags -R to generate tags --- or update when introducing new changes) @@ -108,7 +110,7 @@ builtin.current_buffer_fuzzy_find = require_on_exported_call("telescope.builtin. ---@field show_line boolean: if true, shows the content of the line the tag is found on in the picker (default: true) ---@field only_sort_tags boolean: if true we will only sort tags (default: false) ---@field fname_width number: defines the width of the filename section (default: 30) -builtin.tags = require_on_exported_call("telescope.builtin.files").tags +builtin.tags = require_on_exported_call("telescope.builtin.__files").tags --- Lists all of the tags for the currently open buffer, with a preview ---@param opts table: options to pass to the picker @@ -117,7 +119,7 @@ builtin.tags = require_on_exported_call("telescope.builtin.files").tags ---@field show_line boolean: if true, shows the content of the line the tag is found on in the picker (default: true) ---@field only_sort_tags boolean: if true we will only sort tags (default: false) ---@field fname_width number: defines the width of the filename section (default: 30) -builtin.current_buffer_tags = require_on_exported_call("telescope.builtin.files").current_buffer_tags +builtin.current_buffer_tags = require_on_exported_call("telescope.builtin.__files").current_buffer_tags -- -- @@ -132,10 +134,10 @@ builtin.current_buffer_tags = require_on_exported_call("telescope.builtin.files" ---@param opts table: options to pass to the picker ---@field cwd string: specify the path of the repo ---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true) ----@field show_untracked boolean: if true, adds `--others` flag to command and shows untracked files (default: true) +---@field show_untracked boolean: if true, adds `--others` flag to command and shows untracked files (default: false) ---@field recurse_submodules boolean: if true, adds the `--recurse-submodules` flag to command (default: false) ---@field git_command table: command that will be exectued. {"git","ls-files","--exclude-standard","--cached"} -builtin.git_files = require_on_exported_call("telescope.builtin.git").files +builtin.git_files = require_on_exported_call("telescope.builtin.__git").files --- Lists commits for current directory with diff preview --- - Default keymaps: @@ -147,7 +149,7 @@ builtin.git_files = require_on_exported_call("telescope.builtin.git").files ---@field cwd string: specify the path of the repo ---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true) ---@field git_command table: command that will be exectued. {"git","log","--pretty=oneline","--abbrev-commit","--","."} -builtin.git_commits = require_on_exported_call("telescope.builtin.git").commits +builtin.git_commits = require_on_exported_call("telescope.builtin.__git").commits --- Lists commits for current buffer with diff preview --- - Default keymaps or your overriden `select_` keys: @@ -160,7 +162,7 @@ builtin.git_commits = require_on_exported_call("telescope.builtin.git").commits ---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true) ---@field current_file string: specify the current file that should be used for bcommits (default: current buffer) ---@field git_command table: command that will be exectued. {"git","log","--pretty=oneline","--abbrev-commit"} -builtin.git_bcommits = require_on_exported_call("telescope.builtin.git").bcommits +builtin.git_bcommits = require_on_exported_call("telescope.builtin.__git").bcommits --- List branches for current directory, with output from `git log --oneline` shown in the preview window --- - Default keymaps: @@ -174,7 +176,7 @@ builtin.git_bcommits = require_on_exported_call("telescope.builtin.git").bcommit ---@field cwd string: specify the path of the repo ---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true) ---@field pattern string: specify the pattern to match all refs -builtin.git_branches = require_on_exported_call("telescope.builtin.git").branches +builtin.git_branches = require_on_exported_call("telescope.builtin.__git").branches --- Lists git status for current directory --- - Default keymaps: @@ -184,7 +186,7 @@ builtin.git_branches = require_on_exported_call("telescope.builtin.git").branche ---@field cwd string: specify the path of the repo ---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true) ---@field git_icons table: string -> string. Matches name with icon (see source code, make_entry.lua git_icon_defaults) -builtin.git_status = require_on_exported_call("telescope.builtin.git").status +builtin.git_status = require_on_exported_call("telescope.builtin.__git").status --- Lists stash items in current repository --- - Default keymaps: @@ -193,7 +195,7 @@ builtin.git_status = require_on_exported_call("telescope.builtin.git").status ---@field cwd string: specify the path of the repo ---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true) ---@field show_branch boolean: if we should display the branch name for git stash entries (default: true) -builtin.git_stash = require_on_exported_call("telescope.builtin.git").stash +builtin.git_stash = require_on_exported_call("telescope.builtin.__git").stash -- -- @@ -204,14 +206,15 @@ builtin.git_stash = require_on_exported_call("telescope.builtin.git").stash --- Lists all of the community maintained pickers built into Telescope ---@param opts table: options to pass to the picker ---@field include_extensions boolean: if true will show the pickers of the installed extensions (default: false) -builtin.builtin = require_on_exported_call("telescope.builtin.internal").builtin +---@field use_default_opts boolean: if the selected picker should use its default options (default: false) +builtin.builtin = require_on_exported_call("telescope.builtin.__internal").builtin --- Opens the previous picker in the identical state (incl. multi selections) --- - Notes: --- - Requires `cache_picker` in setup or when having invoked pickers, see |telescope.defaults.cache_picker| ---@param opts table: options to pass to the picker ---@field cache_index number: what picker to resume, where 1 denotes most recent (default: 1) -builtin.resume = require_on_exported_call("telescope.builtin.internal").resume +builtin.resume = require_on_exported_call("telescope.builtin.__internal").resume --- Opens a picker over previously cached pickers in their preserved states (incl. multi selections) --- - Default keymaps: @@ -219,12 +222,12 @@ builtin.resume = require_on_exported_call("telescope.builtin.internal").resume --- - Notes: --- - Requires `cache_picker` in setup or when having invoked pickers, see |telescope.defaults.cache_picker| ---@param opts table: options to pass to the picker -builtin.pickers = require_on_exported_call("telescope.builtin.internal").pickers +builtin.pickers = require_on_exported_call("telescope.builtin.__internal").pickers --- Use the telescope... ---@param opts table: options to pass to the picker ---@field show_pluto boolean: we love pluto (default: false, because its a hidden feature) -builtin.planets = require_on_exported_call("telescope.builtin.internal").planets +builtin.planets = require_on_exported_call("telescope.builtin.__internal").planets --- Lists symbols inside of `data/telescope-sources/*.json` found in your runtime path --- or found in `stdpath("data")/telescope/symbols/*.json`. The second path can be customized. @@ -234,69 +237,71 @@ builtin.planets = require_on_exported_call("telescope.builtin.internal").planets ---@param opts table: options to pass to the picker ---@field symbol_path string: specify the second path. Default: `stdpath("data")/telescope/symbols/*.json` ---@field sources table: specify a table of sources you want to load this time -builtin.symbols = require_on_exported_call("telescope.builtin.internal").symbols +builtin.symbols = require_on_exported_call("telescope.builtin.__internal").symbols --- Lists available plugin/user commands and runs them on `<cr>` ---@param opts table: options to pass to the picker ---@field show_buf_command boolean: show buf local command (Default: true) -builtin.commands = require_on_exported_call("telescope.builtin.internal").commands +builtin.commands = require_on_exported_call("telescope.builtin.__internal").commands --- Lists items in the quickfix list, jumps to location on `<cr>` ---@param opts table: options to pass to the picker ----@field ignore_filename boolean: dont show filenames (default: true) +---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) +---@field fname_width number: defines the width of the filename section (default: 30) ---@field nr number: specify the quickfix list number -builtin.quickfix = require_on_exported_call("telescope.builtin.internal").quickfix +builtin.quickfix = require_on_exported_call("telescope.builtin.__internal").quickfix --- Lists all quickfix lists in your history and open them with `builtin.quickfix`. It seems that neovim --- only keeps the full history for 10 lists ---@param opts table: options to pass to the picker -builtin.quickfixhistory = require_on_exported_call("telescope.builtin.internal").quickfixhistory +builtin.quickfixhistory = require_on_exported_call("telescope.builtin.__internal").quickfixhistory --- Lists items from the current window's location list, jumps to location on `<cr>` ---@param opts table: options to pass to the picker ----@field ignore_filename boolean: dont show filenames (default: true) +---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) -builtin.loclist = require_on_exported_call("telescope.builtin.internal").loclist +---@field fname_width number: defines the width of the filename section (default: 30) +builtin.loclist = require_on_exported_call("telescope.builtin.__internal").loclist --- Lists previously open files, opens on `<cr>` ---@param opts table: options to pass to the picker ---@field only_cwd boolean: show only files in the cwd (default: false) ---@field cwd_only boolean: alias for only_cwd -builtin.oldfiles = require_on_exported_call("telescope.builtin.internal").oldfiles +builtin.oldfiles = require_on_exported_call("telescope.builtin.__internal").oldfiles --- Lists commands that were executed recently, and reruns them on `<cr>` --- - Default keymaps: --- - `<C-e>`: open the command line with the text of the currently selected result populated in it ---@param opts table: options to pass to the picker -builtin.command_history = require_on_exported_call("telescope.builtin.internal").command_history +builtin.command_history = require_on_exported_call("telescope.builtin.__internal").command_history --- Lists searches that were executed recently, and reruns them on `<cr>` --- - Default keymaps: --- - `<C-e>`: open a search window with the text of the currently selected search result populated in it ---@param opts table: options to pass to the picker -builtin.search_history = require_on_exported_call("telescope.builtin.internal").search_history +builtin.search_history = require_on_exported_call("telescope.builtin.__internal").search_history --- Lists vim options, allows you to edit the current value on `<cr>` ---@param opts table: options to pass to the picker -builtin.vim_options = require_on_exported_call("telescope.builtin.internal").vim_options +builtin.vim_options = require_on_exported_call("telescope.builtin.__internal").vim_options --- Lists available help tags and opens a new window with the relevant help info on `<cr>` ---@param opts table: options to pass to the picker ---@field lang string: specify language (default: vim.o.helplang) ---@field fallback boolean: fallback to en if language isn't installed (default: true) -builtin.help_tags = require_on_exported_call("telescope.builtin.internal").help_tags +builtin.help_tags = require_on_exported_call("telescope.builtin.__internal").help_tags --- Lists manpage entries, opens them in a help window on `<cr>` ---@param opts table: options to pass to the picker ---@field sections table: a list of sections to search, use `{ "ALL" }` to search in all sections (default: { "1" }) ---@field man_cmd function: that returns the man command. (Default: `apropos ""` on linux, `apropos " "` on macos) -builtin.man_pages = require_on_exported_call("telescope.builtin.internal").man_pages +builtin.man_pages = require_on_exported_call("telescope.builtin.__internal").man_pages --- Lists lua modules and reloads them on `<cr>` ---@param opts table: options to pass to the picker ---@field column_len number: define the max column len for the module name (default: dynamic, longest module name) -builtin.reloader = require_on_exported_call("telescope.builtin.internal").reloader +builtin.reloader = require_on_exported_call("telescope.builtin.__internal").reloader --- Lists open buffers in current neovim instance, opens selected buffer on `<cr>` ---@param opts table: options to pass to the picker @@ -307,56 +312,58 @@ builtin.reloader = require_on_exported_call("telescope.builtin.internal").reload ---@field sort_lastused boolean: Sorts current and last buffer to the top and selects the lastused (default: false) ---@field sort_mru boolean: Sorts all buffers after most recent used. Not just the current and last one (default: false) ---@field bufnr_width number: Defines the width of the buffer numbers in front of the filenames (default: dynamic) -builtin.buffers = require_on_exported_call("telescope.builtin.internal").buffers +builtin.buffers = require_on_exported_call("telescope.builtin.__internal").buffers --- Lists available colorschemes and applies them on `<cr>` ---@param opts table: options to pass to the picker ---@field enable_preview boolean: if true, will preview the selected color -builtin.colorscheme = require_on_exported_call("telescope.builtin.internal").colorscheme +builtin.colorscheme = require_on_exported_call("telescope.builtin.__internal").colorscheme --- Lists vim marks and their value, jumps to the mark on `<cr>` ---@param opts table: options to pass to the picker -builtin.marks = require_on_exported_call("telescope.builtin.internal").marks +builtin.marks = require_on_exported_call("telescope.builtin.__internal").marks --- Lists vim registers, pastes the contents of the register on `<cr>` --- - Default keymaps: --- - `<C-e>`: edit the contents of the currently selected register ---@param opts table: options to pass to the picker -builtin.registers = require_on_exported_call("telescope.builtin.internal").registers +builtin.registers = require_on_exported_call("telescope.builtin.__internal").registers --- Lists normal mode keymappings, runs the selected keymap on `<cr>` ---@param opts table: options to pass to the picker ---@field modes table: a list of short-named keymap modes to search (default: { "n", "i", "c", "x" }) ---@field show_plug boolean: if true, the keymaps for which the lhs contains "<Plug>" are also shown (default: true) -builtin.keymaps = require_on_exported_call("telescope.builtin.internal").keymaps +builtin.keymaps = require_on_exported_call("telescope.builtin.__internal").keymaps --- Lists all available filetypes, sets currently open buffer's filetype to selected filetype in Telescope on `<cr>` ---@param opts table: options to pass to the picker -builtin.filetypes = require_on_exported_call("telescope.builtin.internal").filetypes +builtin.filetypes = require_on_exported_call("telescope.builtin.__internal").filetypes --- Lists all available highlights ---@param opts table: options to pass to the picker -builtin.highlights = require_on_exported_call("telescope.builtin.internal").highlights +builtin.highlights = require_on_exported_call("telescope.builtin.__internal").highlights --- Lists vim autocommands and goes to their declaration on `<cr>` ---@param opts table: options to pass to the picker -builtin.autocommands = require_on_exported_call("telescope.builtin.internal").autocommands +builtin.autocommands = require_on_exported_call("telescope.builtin.__internal").autocommands --- Lists spelling suggestions for the current word under the cursor, replaces word with selected suggestion on `<cr>` ---@param opts table: options to pass to the picker -builtin.spell_suggest = require_on_exported_call("telescope.builtin.internal").spell_suggest +builtin.spell_suggest = require_on_exported_call("telescope.builtin.__internal").spell_suggest --- Lists the tag stack for the current window, jumps to tag on `<cr>` ---@param opts table: options to pass to the picker ----@field ignore_filename boolean: dont show filenames (default: true) +---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) -builtin.tagstack = require_on_exported_call("telescope.builtin.internal").tagstack +---@field fname_width number: defines the width of the filename section (default: 30) +builtin.tagstack = require_on_exported_call("telescope.builtin.__internal").tagstack --- Lists items from Vim's jumplist, jumps to location on `<cr>` ---@param opts table: options to pass to the picker ----@field ignore_filename boolean: dont show filenames (default: true) +---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) -builtin.jumplist = require_on_exported_call("telescope.builtin.internal").jumplist +---@field fname_width number: defines the width of the filename section (default: 30) +builtin.jumplist = require_on_exported_call("telescope.builtin.__internal").jumplist -- -- @@ -369,63 +376,76 @@ builtin.jumplist = require_on_exported_call("telescope.builtin.internal").jumpli ---@field include_declaration boolean: include symbol declaration in the lsp references (default: true) ---@field include_current_line boolean: include current line (default: false) ---@field trim_text boolean: trim results text (default: false) -builtin.lsp_references = require_on_exported_call("telescope.builtin.lsp").references +---@field fname_width number: defines the width of the filename section (default: 30) +builtin.lsp_references = require_on_exported_call("telescope.builtin.__lsp").references + +--- Lists LSP incoming calls for word under the cursor, jumps to reference on `<cr>` +---@param opts table: options to pass to the picker +---@field show_line boolean: show results text (default: true) +---@field trim_text boolean: trim results text (default: false) +builtin.lsp_incoming_calls = require_on_exported_call("telescope.builtin.__lsp").incoming_calls + +--- Lists LSP outgoing calls for word under the cursor, jumps to reference on `<cr>` +---@param opts table: options to pass to the picker +---@field show_line boolean: show results text (default: true) +---@field trim_text boolean: trim results text (default: false) +builtin.lsp_outgoing_calls = require_on_exported_call("telescope.builtin.__lsp").outgoing_calls --- Goto the definition of the word under the cursor, if there's only one, otherwise show all options in Telescope ---@param opts table: options to pass to the picker ---@field jump_type string: how to goto definition if there is only one, values: "tab", "split", "vsplit", "never" ----@field ignore_filename boolean: dont show filenames (default: true) +---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) -builtin.lsp_definitions = require_on_exported_call("telescope.builtin.lsp").definitions +---@field fname_width number: defines the width of the filename section (default: 30) +builtin.lsp_definitions = require_on_exported_call("telescope.builtin.__lsp").definitions --- Goto the definition of the type of the word under the cursor, if there's only one, --- otherwise show all options in Telescope ---@param opts table: options to pass to the picker ---@field jump_type string: how to goto definition if there is only one, values: "tab", "split", "vsplit", "never" ----@field ignore_filename boolean: dont show filenames (default: true) +---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) -builtin.lsp_type_definitions = require("telescope.builtin.lsp").type_definitions +---@field fname_width number: defines the width of the filename section (default: 30) +builtin.lsp_type_definitions = require("telescope.builtin.__lsp").type_definitions --- Goto the implementation of the word under the cursor if there's only one, otherwise show all options in Telescope ---@param opts table: options to pass to the picker ---@field jump_type string: how to goto implementation if there is only one, values: "tab", "split", "vsplit", "never" ----@field ignore_filename boolean: dont show filenames (default: true) +---@field show_line boolean: show results text (default: true) ---@field trim_text boolean: trim results text (default: false) -builtin.lsp_implementations = require_on_exported_call("telescope.builtin.lsp").implementations +---@field fname_width number: defines the width of the filename section (default: 30) +builtin.lsp_implementations = require_on_exported_call("telescope.builtin.__lsp").implementations --- Lists LSP document symbols in the current buffer --- - Default keymaps: --- - `<C-l>`: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`) ---@param opts table: options to pass to the picker ----@field ignore_filename boolean: dont show filenames (default: true) ---@field show_line boolean: if true, shows the content of the line the tag is found on (default: false) ---@field symbols string|table: filter results by symbol kind(s) ---@field ignore_symbols string|table: list of symbols to ignore ---@field symbol_highlights table: string -> string. Matches symbol with hl_group -builtin.lsp_document_symbols = require_on_exported_call("telescope.builtin.lsp").document_symbols +builtin.lsp_document_symbols = require_on_exported_call("telescope.builtin.__lsp").document_symbols --- Lists LSP document symbols in the current workspace --- - Default keymaps: --- - `<C-l>`: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`) ---@param opts table: options to pass to the picker ---@field query string: for what to query the workspace (default: "") ----@field ignore_filename boolean: dont show filenames (default: false) ---@field show_line boolean: if true, shows the content of the line the tag is found on (default: false) ---@field symbols string|table: filter results by symbol kind(s) ---@field ignore_symbols string|table: list of symbols to ignore ---@field symbol_highlights table: string -> string. Matches symbol with hl_group -builtin.lsp_workspace_symbols = require_on_exported_call("telescope.builtin.lsp").workspace_symbols +builtin.lsp_workspace_symbols = require_on_exported_call("telescope.builtin.__lsp").workspace_symbols --- Dynamically lists LSP for all workspace symbols --- - Default keymaps: --- - `<C-l>`: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`) ---@param opts table: options to pass to the picker ----@field ignore_filename boolean: dont show filenames (default: false) ---@field show_line boolean: if true, shows the content of the line the symbol is found on (default: false) ---@field symbols string|table: filter results by symbol kind(s) ---@field ignore_symbols string|table: list of symbols to ignore ---@field symbol_highlights table: string -> string. Matches symbol with hl_group -builtin.lsp_dynamic_workspace_symbols = require_on_exported_call("telescope.builtin.lsp").dynamic_workspace_symbols +builtin.lsp_dynamic_workspace_symbols = require_on_exported_call("telescope.builtin.__lsp").dynamic_workspace_symbols -- -- @@ -448,7 +468,7 @@ builtin.lsp_dynamic_workspace_symbols = require_on_exported_call("telescope.buil ---@field no_sign boolean: hide DiagnosticSigns from Results (default: false) ---@field line_width number: set length of diagnostic entry text in Results ---@field namespace number: limit your diagnostics to a specific namespace -builtin.diagnostics = require_on_exported_call("telescope.builtin.diagnostics").get +builtin.diagnostics = require_on_exported_call("telescope.builtin.__diagnostics").get local apply_config = function(mod) local pickers_conf = require("telescope.config").pickers diff --git a/lua/telescope/builtin/internal.lua b/lua/telescope/builtin/internal.lua index 1f6612a..b4d4197 100644 --- a/lua/telescope/builtin/internal.lua +++ b/lua/telescope/builtin/internal.lua @@ -1,1383 +1,17 @@ -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 sorters = require "telescope.sorters" -local state = require "telescope.state" -local utils = require "telescope.utils" - -local conf = require("telescope.config").values - -local filter = vim.tbl_filter - --- 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 - -local internal = {} - -internal.builtin = function(opts) - opts.include_extensions = utils.get_default(opts.include_extensions, 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 - - 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 { - value = entry, - text = entry.text, - display = entry.text, - ordinal = entry.text, - filename = entry.filename, - } - end, - }, - previewer = previewers.builtin.new(opts), - sorter = conf.generic_sorter(opts), - attach_mappings = function(_) - actions.select_default:replace(function(_) - 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 - - 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](opts) - else - -- Call appropriate telescope builtin - require("telescope.builtin")[selection.text](opts) - 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 - 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 current_picker = action_state.get_current_picker(prompt_bufnr) - local selection_index = current_picker:get_index(current_picker:get_selection_row()) - actions.close(prompt_bufnr) - 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", "<C-x>", actions.remove_selected_picker) - map("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 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" 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 { - 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) - 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.", +local m = setmetatable({}, { + __index = function(_, k) + local utils = require "telescope.utils" + utils.notify("builtin", { + msg = string.format( + 'You are using an internal interface. Do not use `require("telescope.builtin.internal").%s`,' + .. ' please use `require("telescope.builtin").%s`! We will remove this endpoint soon!', + k, + k + ), 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 { - value = entry, - ordinal = entry[1] .. " " .. entry[2], - display = entry[1] .. " " .. entry[2], - } - 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 - vim.cmd(cmd) - else - vim.cmd [[stopinsert]] - vim.fn.feedkeys(cmd, "n") - end - 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 - 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 { - value = entry.title or "Untitled", - ordinal = entry.title or "Untitled", - display = entry.title or "Untitled", - nr = entry.nr, - id = entry.id, - items = entry.items, - } - 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(_, _) - action_set.select:replace(function(prompt_bufnr) - local nr = action_state.get_selected_entry().nr - actions.close(prompt_bufnr) - internal.quickfix { nr = nr } - 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 - 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 = utils.get_default(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 opts.include_current_session then - for _, buffer in ipairs(vim.split(vim.fn.execute ":buffers! t", "\n")) 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 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 vim.loop.fs_stat(file) and not vim.tbl_contains(results, file) and file ~= current_file then - table.insert(results, file) - end - end - - if opts.cwd_only then - local cwd = vim.loop.cwd() - cwd = cwd:gsub([[\]], [[\\]]) - results = vim.tbl_filter(function(file) - return vim.fn.matchstrpos(file, cwd)[2] ~= -1 - end, results) - end - - pickers.new(opts, { - prompt_title = "Oldfiles", - 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.file_previewer(opts), - }):find() -end - -internal.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 = conf.generic_sorter(opts), - - attach_mappings = function(_, map) - map("i", "<CR>", actions.set_command_line) - map("n", "<CR>", actions.set_command_line) - map("n", "<C-e>", actions.edit_command_line) - map("i", "<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 = vim.split(search_string, "\n") - - 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) - map("i", "<CR>", actions.set_search_line) - map("n", "<CR>", actions.set_search_line) - map("n", "<C-e>", actions.edit_search_line) - map("i", "<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) - -- Load vim options. - local vim_opts = loadfile(Path:new({ utils.data_directory(), "options", "options.lua" }):absolute())().options - - pickers.new(opts, { - prompt_title = "options", - finder = finders.new_table { - results = vim_opts, - entry_maker = opts.entry_maker or make_entry.gen_from_vimoptions(opts), - }, - -- TODO: previewer for Vim options - -- previewer = previewers.help.new(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 - -- TODO: don't make this local - esc = vim.api.nvim_replace_termcodes("<esc>", true, false, true) - end - - -- TODO: Make this actually work. - - -- actions.close(prompt_bufnr) - -- vim.api.nvim_win_set_var(vim.api.nvim_get_current_win(), "telescope", 1) - -- print(prompt_bufnr) - -- print(vim.fn.bufnr()) - -- vim.cmd([[ autocmd BufEnter <buffer> ++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: inc `cmdheight` and `laststatus` in this calc - -- float_opts.col = 2 - -- float_opts.height = 10 - -- float_opts.width = string.len(selection.last_set_from)+15 - -- local buf = vim.api.nvim_create_buf(false, true) - -- vim.api.nvim_buf_set_lines(buf, 0, 0, false, - -- {"default value: abcdef", "last set from: " .. selection.last_set_from}) - -- local status_win = vim.api.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) - - return true - end, - }):find() -end - -internal.help_tags = function(opts) - opts.lang = utils.get_default(opts.lang, vim.o.helplang) - opts.fallback = utils.get_default(opts.fallback, true) - opts.file_ignore_patterns = {} - - local langs = vim.split(opts.lang, ",", 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 all_files = vim.api.nvim_get_runtime_file("doc/*", true) - 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 = vim.split(Path:new(file):read(), "\n", 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, 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 { - value = entry.name .. "@" .. entry.lang, - display = entry.name, - ordinal = entry.name, - filename = entry.filename, - cmd = entry.cmd, - } - 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 = utils.get_default(opts.sections, { "1" }) - assert(vim.tbl_islist(opts.sections), "sections should be a list") - opts.man_cmd = utils.get_lazy_default(opts.man_cmd, function() - local is_darwin = vim.loop.os_uname().sysname == "Darwin" - return is_darwin and { "apropos", " " } or { "apropos", "" } - 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 = filter(function(b) - if 1 ~= vim.fn.buflisted(b) 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(b) then - return false - end - if opts.ignore_current_buffer and b == vim.api.nvim_get_current_buf() then - return false - end - if opts.cwd_only and not string.find(vim.api.nvim_buf_get_name(b), vim.loop.cwd(), 1, true) then - return false - end - return true - end, vim.api.nvim_list_bufs()) - if not next(bufnrs) then - 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 - - local buffers = {} - local default_selection_idx = 1 - for _, 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 - 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, - }):find() -end - -internal.colorscheme = function(opts) - local before_background = vim.o.background - local before_color = vim.api.nvim_exec("colorscheme", true) - local need_restore = true - - 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 color ~= before_color - end, vim.fn.getcompletion("", "color")) - ) - - 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) - - -- don't need previewer - if vim.fn.buflisted(bufnr) ~= 1 then - local deleted = false - local function del_win(win_id) - if win_id and vim.api.nvim_win_is_valid(win_id) then - utils.buf_delete(vim.api.nvim_win_get_buf(win_id)) - pcall(vim.api.nvim_win_close, win_id, true) - end - end - - previewer = previewers.new { - preview_fn = function(_, entry, status) - if not deleted then - deleted = true - del_win(status.preview_win) - del_win(status.preview_border_win) - end - vim.cmd("colorscheme " .. entry.value) - end, - } - else - -- show current buffer content in previewer - previewer = previewers.new_buffer_previewer { - get_buffer_by_name = function() - return p - end, - define_preview = function(self, entry) - 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 - vim.cmd("colorscheme " .. entry.value) - 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 - - actions.close(prompt_bufnr) - need_restore = false - vim.cmd("colorscheme " .. selection.value) - end) - - return true - 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 - 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) - for _, cnf in ipairs { local_marks, global_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 = 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), - }, - -- use levenshtein as n-gram doesn't support <2 char matches - sorter = sorters.get_levenshtein_sorter(), - attach_mappings = function(_, map) - actions.select_default:replace(actions.paste_register) - map("i", "<C-e>", actions.edit_register) - - return true - end, - }):find() -end - --- TODO: make filtering include the mapping and the action -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) - - 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>") 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) - extract_keymaps(global) - 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 autocmd_table = {} - - local pattern = {} - pattern.BUFFER = "<buffer=%d+>" - pattern.EVENT = "[%a]+" - pattern.GROUP = "[%a%d_:]+" - pattern.INDENT = "^%s%s%s%s" -- match indentation of 4 spaces - - local event, group, ft_pat, cmd, source_file, source_lnum - local current_event, current_group, current_ft - - local inner_loop = function(line) - -- capture group and event - group, event = line:match("^(" .. pattern.GROUP .. ")%s+(" .. pattern.EVENT .. ")") - -- ..or just an event - if event == nil then - event = line:match("^(" .. pattern.EVENT .. ")") - end - - if event then - group = group or "<anonymous>" - if event ~= current_event or group ~= current_group then - current_event = event - current_group = group - end - return - end - - -- non event/group lines - ft_pat = line:match(pattern.INDENT .. "(%S+)") - if ft_pat then - if ft_pat:match "^%d+" then - ft_pat = "<buffer=" .. ft_pat .. ">" - end - current_ft = ft_pat - - -- is there a command on the same line? - cmd = line:match(pattern.INDENT .. "%S+%s+(.+)") - - return - end - - if current_ft and cmd == nil then - -- trim leading spaces - cmd = line:gsub("^%s+", "") - return - end - - if current_ft and cmd then - source_file = line:match "Last set from (.*) line %d*$" or line:match "Last set from (.*)$" - source_lnum = line:match "line (%d*)$" or "1" - if source_file then - local autocmd = {} - autocmd.event = current_event - autocmd.group = current_group - autocmd.ft_pattern = current_ft - autocmd.command = cmd - autocmd.source_file = source_file - autocmd.source_lnum = source_lnum - table.insert(autocmd_table, autocmd) - - cmd = nil - end - end - end - - local cmd_output = vim.fn.execute("verb autocmd *", "silent") - for line in cmd_output:gmatch "[^\r\n]+" do - inner_loop(line) - end - - pickers.new(opts, { - prompt_title = "autocommands", - finder = finders.new_table { - results = autocmd_table, - 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(function(_, type) - local selection = action_state.get_selected_entry() - if selection == nil then - utils.__warn_no_selection "builtin.autocommands" - return - end - - actions.close(prompt_bufnr) - vim.cmd(action_state.select_key_to_edit_key(type) .. " " .. 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 - - 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, jumplist[i].lnum + 1, 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 require("telescope.builtin")[k] + end, +}) -return apply_checks(internal) +return m diff --git a/lua/telescope/builtin/lsp.lua b/lua/telescope/builtin/lsp.lua index 2ec9f2d..8c297dc 100644 --- a/lua/telescope/builtin/lsp.lua +++ b/lua/telescope/builtin/lsp.lua @@ -1,292 +1,17 @@ -local channel = require("plenary.async.control").channel - -local conf = require("telescope.config").values -local finders = require "telescope.finders" -local make_entry = require "telescope.make_entry" -local pickers = require "telescope.pickers" -local utils = require "telescope.utils" - -local lsp = {} - -lsp.references = function(opts) - local filepath = vim.api.nvim_buf_get_name(opts.bufnr) - local lnum = vim.api.nvim_win_get_cursor(opts.winnr)[1] - local params = vim.lsp.util.make_position_params(opts.winnr) - local include_current_line = vim.F.if_nil(opts.include_current_line, false) - params.context = { includeDeclaration = vim.F.if_nil(opts.include_declaration, true) } - - vim.lsp.buf_request(opts.bufnr, "textDocument/references", params, function(err, result, ctx, _) - if err then - vim.api.nvim_err_writeln("Error when finding references: " .. err.message) - return - end - - local locations = {} - if result then - local results = vim.lsp.util.locations_to_items(result, vim.lsp.get_client_by_id(ctx.client_id).offset_encoding) - if include_current_line then - locations = vim.tbl_filter(function(v) - -- Remove current line from result - return not (v.filename == filepath and v.lnum == lnum) - end, vim.F.if_nil(results, {})) - else - locations = vim.F.if_nil(results, {}) - 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 = opts.entry_maker or make_entry.gen_from_quickfix(opts), - }, - previewer = conf.qflist_previewer(opts), - sorter = conf.generic_sorter(opts), - push_cursor_on_edit = true, - push_tagstack_on_edit = true, - }):find() - end) -end - -local function list_or_jump(action, title, opts) - opts = opts or {} - - local params = vim.lsp.util.make_position_params(opts.winnr) - vim.lsp.buf_request(opts.bufnr, action, params, function(err, result, ctx, _) - if err then - vim.api.nvim_err_writeln("Error when executing " .. action .. " : " .. err.message) - return - end - local flattened_results = {} - if result then - -- textDocument/definition can return Location or Location[] - if not vim.tbl_islist(result) then - flattened_results = { result } - end - - vim.list_extend(flattened_results, result) - end - - local offset_encoding = vim.lsp.get_client_by_id(ctx.client_id).offset_encoding - - if #flattened_results == 0 then - return - elseif #flattened_results == 1 and opts.jump_type ~= "never" then - if opts.jump_type == "tab" then - vim.cmd "tabedit" - elseif opts.jump_type == "split" then - vim.cmd "new" - elseif opts.jump_type == "vsplit" then - vim.cmd "vnew" - end - vim.lsp.util.jump_to_location(flattened_results[1], offset_encoding) - else - local locations = vim.lsp.util.locations_to_items(flattened_results, offset_encoding) - pickers.new(opts, { - prompt_title = title, - 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), - push_cursor_on_edit = true, - push_tagstack_on_edit = true, - }):find() - end - end) -end - -lsp.definitions = function(opts) - return list_or_jump("textDocument/definition", "LSP Definitions", opts) -end - -lsp.type_definitions = function(opts) - return list_or_jump("textDocument/typeDefinition", "LSP Type Definitions", opts) -end - -lsp.implementations = function(opts) - return list_or_jump("textDocument/implementation", "LSP Implementations", opts) -end - -lsp.document_symbols = function(opts) - local params = vim.lsp.util.make_position_params(opts.winnr) - vim.lsp.buf_request(opts.bufnr, "textDocument/documentSymbol", params, function(err, result, _, _) - if err then - vim.api.nvim_err_writeln("Error when finding document symbols: " .. err.message) - return - end - - if not result or vim.tbl_isempty(result) then - utils.notify("builtin.lsp_document_symbols", { - msg = "No results from textDocument/documentSymbol", - level = "INFO", - }) - return - end - - local locations = vim.lsp.util.symbols_to_items(result or {}, opts.bufnr) or {} - locations = utils.filter_symbols(locations, opts) - if locations == nil then - -- error message already printed in `utils.filter_symbols` - return - end - - if vim.tbl_isempty(locations) then - utils.notify("builtin.lsp_document_symbols", { - msg = "No document_symbol locations found", - level = "INFO", - }) - return - end - - opts.ignore_filename = opts.ignore_filename or true - pickers.new(opts, { - prompt_title = "LSP Document Symbols", - finder = finders.new_table { - results = locations, - entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts), - }, - previewer = conf.qflist_previewer(opts), - sorter = conf.prefilter_sorter { - tag = "symbol_type", - sorter = conf.generic_sorter(opts), - }, - push_cursor_on_edit = true, - push_tagstack_on_edit = true, - }):find() - end) -end - -lsp.workspace_symbols = function(opts) - local params = { query = opts.query or "" } - vim.lsp.buf_request(opts.bufnr, "workspace/symbol", params, function(err, server_result, _, _) - if err then - vim.api.nvim_err_writeln("Error when finding workspace symbols: " .. err.message) - return - end - - local locations = vim.lsp.util.symbols_to_items(server_result or {}, opts.bufnr) or {} - locations = utils.filter_symbols(locations, opts) - if locations == nil then - -- error message already printed in `utils.filter_symbols` - return - end - - if vim.tbl_isempty(locations) then - utils.notify("builtin.lsp_workspace_symbols", { - msg = "No results from workspace/symbol. Maybe try a different query: " - .. "'Telescope lsp_workspace_symbols query=example'", - level = "INFO", - }) - return - end - - opts.ignore_filename = utils.get_default(opts.ignore_filename, false) - - pickers.new(opts, { - prompt_title = "LSP Workspace Symbols", - finder = finders.new_table { - results = locations, - entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts), - }, - previewer = conf.qflist_previewer(opts), - sorter = conf.prefilter_sorter { - tag = "symbol_type", - sorter = conf.generic_sorter(opts), - }, - }):find() - end) -end - -local function get_workspace_symbols_requester(bufnr, opts) - local cancel = function() end - - return function(prompt) - local tx, rx = channel.oneshot() - cancel() - _, cancel = vim.lsp.buf_request(bufnr, "workspace/symbol", { query = prompt }, tx) - - -- Handle 0.5 / 0.5.1 handler situation - local err, res = rx() - assert(not err, err) - - local locations = vim.lsp.util.symbols_to_items(res or {}, bufnr) or {} - if not vim.tbl_isempty(locations) then - locations = utils.filter_symbols(locations, opts) or {} - end - return locations - end -end - -lsp.dynamic_workspace_symbols = function(opts) - pickers.new(opts, { - prompt_title = "LSP Dynamic Workspace Symbols", - finder = finders.new_dynamic { - entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts), - fn = get_workspace_symbols_requester(opts.bufnr, opts), - }, - previewer = conf.qflist_previewer(opts), - sorter = conf.generic_sorter(opts), - }):find() -end - -local function check_capabilities(feature, bufnr) - local clients = vim.lsp.buf_get_clients(bufnr) - - local supported_client = false - for _, client in pairs(clients) do - supported_client = client.server_capabilities[feature] - if supported_client then - break - end - end - - if supported_client then - return true - else - if #clients == 0 then - utils.notify("builtin.lsp_*", { - msg = "no client attached", - level = "INFO", - }) - else - utils.notify("builtin.lsp_*", { - msg = "server does not support " .. feature, - level = "INFO", - }) - end - return false - end -end - -local feature_map = { - ["document_symbols"] = "documentSymbolProvider", - ["references"] = "referencesProvider", - ["definitions"] = "definitionProvider", - ["type_definitions"] = "typeDefinitionProvider", - ["implementations"] = "implementationProvider", - ["workspace_symbols"] = "workspaceSymbolProvider", -} - -local function apply_checks(mod) - for k, v in pairs(mod) do - mod[k] = function(opts) - opts = opts or {} - - local feature_name = feature_map[k] - if feature_name and not check_capabilities(feature_name, opts.bufnr) then - return - end - v(opts) - end - end - - return mod -end - -return apply_checks(lsp) +local m = setmetatable({}, { + __index = function(_, k) + local utils = require "telescope.utils" + utils.notify("builtin", { + msg = string.format( + 'You are using an internal interface. Do not use `require("telescope.builtin.lsp").%s`,' + .. ' please use `require("telescope.builtin").lsp_%s`! We will remove this endpoint soon!', + k, + k + ), + level = "ERROR", + }) + return require("telescope.builtin")["lsp_" .. k] + end, +}) + +return m diff --git a/lua/telescope/config.lua b/lua/telescope/config.lua index f5cc3c6..8198ee2 100644 --- a/lua/telescope/config.lua +++ b/lua/telescope/config.lua @@ -1,7 +1,6 @@ local strings = require "plenary.strings" local deprecated = require "telescope.deprecated" local sorters = require "telescope.sorters" -local if_nil = vim.F.if_nil local os_sep = require("plenary.path").path.sep local has_win = vim.fn.has "win32" == 1 @@ -69,7 +68,7 @@ config.descriptions = {} config.pickers = _TelescopeConfigurationPickers function config.set_pickers(pickers) - pickers = if_nil(pickers, {}) + pickers = vim.F.if_nil(pickers, {}) for k, v in pairs(pickers) do config.pickers[k] = v @@ -182,7 +181,8 @@ append( - "reset" (default) - "follow" - "row" - - "closest"]] + - "closest" + - "none"]] ) append( @@ -593,8 +593,17 @@ append( highlighting, which falls back to regex-based highlighting. `true`: treesitter highlighting for all available filetypes `false`: regex-based highlighting for all filetypes - `table`: table of filetypes for which to attach treesitter - highlighting + `table`: following nvim-treesitters highlighting options: + It contains two keys: + - enable boolean|table: if boolean, enable all ts + highlighing with that flag, + disable still considered. + Containing a list of filetypes, + that are enabled, disabled + ignored because it doesnt make + any sense in this case. + - disable table: containing a list of filetypes + that are disabled Default: true - msg_bg_fillchar: Character to fill background of unpreviewable buffers with Default: "╱" @@ -783,6 +792,17 @@ append( Example: { "%.npz" } -- ignore all npz files See: https://www.lua.org/manual/5.1/manual.html#5.4.1 for more information about lua regex + Note: `file_ignore_patterns` will be used in all pickers that have a + file associated. This might lead to the problem that lsp_ pickers + aren't displaying results because they might be ignored by + `file_ignore_patterns`. For example, setting up node_modules as ignored + will never show node_modules in any results, even if you are + interested in lsp_ results. + + If you only want `file_ignore_patterns` for `find_files` and + `grep_string`/`live_grep` it is suggested that you setup `gitignore` + and have fd and or ripgrep installed because both tools will not show + `gitignore`d files on default. Default: nil]] ) @@ -865,8 +885,8 @@ append( -- @param tele_defaults table: (optional) a table containing all of the defaults -- for telescope [defaults to `telescope_defaults`] function config.set_defaults(user_defaults, tele_defaults) - user_defaults = if_nil(user_defaults, {}) - tele_defaults = if_nil(tele_defaults, telescope_defaults) + user_defaults = vim.F.if_nil(user_defaults, {}) + tele_defaults = vim.F.if_nil(tele_defaults, telescope_defaults) -- Check if using layout keywords outside of `layout_config` deprecated.options(user_defaults) @@ -874,8 +894,8 @@ function config.set_defaults(user_defaults, tele_defaults) local function get(name, default_val) if name == "layout_config" then return smarter_depth_2_extend( - if_nil(user_defaults[name], {}), - vim.tbl_deep_extend("keep", if_nil(config.values[name], {}), if_nil(default_val, {})) + vim.F.if_nil(user_defaults[name], {}), + vim.tbl_deep_extend("keep", vim.F.if_nil(config.values[name], {}), vim.F.if_nil(default_val, {})) ) end if name == "history" or name == "cache_picker" or name == "preview" then @@ -884,8 +904,8 @@ function config.set_defaults(user_defaults, tele_defaults) end return smarter_depth_2_extend( - if_nil(user_defaults[name], {}), - vim.tbl_deep_extend("keep", if_nil(config.values[name], {}), if_nil(default_val, {})) + vim.F.if_nil(user_defaults[name], {}), + vim.tbl_deep_extend("keep", vim.F.if_nil(config.values[name], {}), vim.F.if_nil(default_val, {})) ) end return first_non_null(user_defaults[name], config.values[name], default_val) diff --git a/lua/telescope/config/resolve.lua b/lua/telescope/config/resolve.lua index 0c92ac6..ad8936e 100644 --- a/lua/telescope/config/resolve.lua +++ b/lua/telescope/config/resolve.lua @@ -91,8 +91,6 @@ That's the next step to scrolling. --]] -local get_default = require("telescope.utils").get_default - local resolver = {} local _resolve_map = {} @@ -129,9 +127,6 @@ end] = function(selector, val) end end --- Tables TODO: --- ... {70, max} - -- function: -- Function must have same signature as get_window_layout -- function(self, max_columns, max_lines): number @@ -143,6 +138,26 @@ end] = function(_, val) return val end +_resolve_map[function(val) + return type(val) == "table" and val[1] >= 0 and val[1] < 1 and val["max"] ~= nil +end] = + function(selector, val) + return function(...) + local selected = select(selector, ...) + return math.min(math.floor(val[1] * selected), val["max"]) + end + end + +_resolve_map[function(val) + return type(val) == "table" and val[1] >= 0 and val[1] < 1 and val["min"] ~= nil +end] = + function(selector, val) + return function(...) + local selected = select(selector, ...) + return math.max(math.floor(val[1] * selected), val["min"]) + end + end + -- Add padding option _resolve_map[function(val) return type(val) == "table" and val["padding"] ~= nil @@ -164,7 +179,7 @@ end] = function(selector, val) end --- Converts input to a function that returns the height. ---- The input must take one of four forms: +--- The input must take one of five forms: --- 1. 0 <= number < 1 <br> --- This means total height as a percentage. --- 2. 1 <= number <br> @@ -172,7 +187,10 @@ end --- 3. function <br> --- Must have signature: --- function(self, max_columns, max_lines): number ---- 4. table of the form: {padding = `foo`} <br> +--- 4. table of the form: { val, max = ..., min = ... } <br> +--- val has to be in the first form 0 <= val < 1 and only one is given, +--- `min` or `max` as fixed number +--- 5. table of the form: {padding = `foo`} <br> --- where `foo` has one of the previous three forms. <br> --- The height is then set to be the remaining space after padding. --- For example, if the window has height 50, and the input is {padding = 5}, @@ -190,7 +208,7 @@ resolver.resolve_height = function(val) end --- Converts input to a function that returns the width. ---- The input must take one of four forms: +--- The input must take one of five forms: --- 1. 0 <= number < 1 <br> --- This means total width as a percentage. --- 2. 1 <= number <br> @@ -198,7 +216,10 @@ end --- 3. function <br> --- Must have signature: --- function(self, max_columns, max_lines): number ---- 4. table of the form: {padding = `foo`} <br> +--- 4. table of the form: { val, max = ..., min = ... } <br> +--- val has to be in the first form 0 <= val < 1 and only one is given, +--- `min` or `max` as fixed number +--- 5. table of the form: {padding = `foo`} <br> --- where `foo` has one of the previous three forms. <br> --- The width is then set to be the remaining space after padding. --- For example, if the window has width 100, and the input is {padding = 5}, @@ -286,9 +307,9 @@ resolver.win_option = function(val, default) end return { - preview = get_default(val.preview, val_to_set), - results = get_default(val.results, val_to_set), - prompt = get_default(val.prompt, val_to_set), + preview = vim.F.if_nil(val.preview, val_to_set), + results = vim.F.if_nil(val.results, val_to_set), + prompt = vim.F.if_nil(val.prompt, val_to_set), } end end diff --git a/lua/telescope/finders/async_oneshot_finder.lua b/lua/telescope/finders/async_oneshot_finder.lua index c8c2957..e3bd0ad 100644 --- a/lua/telescope/finders/async_oneshot_finder.lua +++ b/lua/telescope/finders/async_oneshot_finder.lua @@ -30,6 +30,7 @@ return function(opts) end end, results = results, + entry_maker = entry_maker, }, { __call = function(_, prompt, process_result, process_complete) if not job_started then diff --git a/lua/telescope/finders/async_static_finder.lua b/lua/telescope/finders/async_static_finder.lua index 941d858..7a684f9 100644 --- a/lua/telescope/finders/async_static_finder.lua +++ b/lua/telescope/finders/async_static_finder.lua @@ -24,6 +24,7 @@ return function(opts) return setmetatable({ results = results, + entry_maker = entry_maker, close = function() end, }, { __call = function(_, _, process_result, process_complete) diff --git a/lua/telescope/make_entry.lua b/lua/telescope/make_entry.lua index 703a00a..64993df 100644 --- a/lua/telescope/make_entry.lua +++ b/lua/telescope/make_entry.lua @@ -1,3 +1,40 @@ +---@tag telescope.make_entry + +---@brief [[ +--- +--- Each picker has a finder made up of two parts, the results which are the +--- data to be displayed, and the entry_maker. These entry_makers are functions +--- returned from make_entry functions. These will be referrd to as +--- entry_makers in the following documentation. +--- +--- Every entry maker returns a function which accepts the data to be used for +--- an entry. This function will return an entry table (or nil, meaning skip +--- this entry) which contains of the - following important keys: +--- - value any: value key can be anything but still required +--- - valid bool: is an optional key because it defaults to true but if the key +--- is set to false it will not be displayed by the picker. (optional) +--- - ordinal string: is the text that is used for filtering (required) +--- - display string|function: is either a string of the text that is being +--- displayed or a function receiving the entry at a later stage, when the entry +--- is actually being displayed. A function can be useful here if complex +--- calculation have to be done. `make_entry` can also return a second value +--- a highlight array which will then apply to the line. Highlight entry in +--- this array has the following signature `{ { start_col, end_col }, hl_group }` +--- (required). +--- - filename string: will be interpreted by the default `<cr>` action as +--- open this file (optional) +--- - bufnr number: will be interpreted by the default `<cr>` action as open +--- this buffer (optional) +--- - lnum number: lnum value which will be interpreted by the default `<cr>` +--- action as a jump to this line (optional) +--- - col number: col value which will be interpreted by the default `<cr>` +--- action as a jump to this column (optional) +--- +--- More information on easier displaying, see |telescope.pickers.entry_display| +--- +--- TODO: Document something we call `entry_index` +---@brief ]] + local entry_display = require "telescope.pickers.entry_display" local utils = require "telescope.utils" local strings = require "plenary.strings" @@ -26,8 +63,55 @@ local lsp_type_highlight = { ["Variable"] = "TelescopeResultsVariable", } +local get_filename_fn = function() + local bufnr_name_cache = {} + return function(bufnr) + bufnr = vim.F.if_nil(bufnr, 0) + local c = bufnr_name_cache[bufnr] + if c then + return c + end + + local n = vim.api.nvim_buf_get_name(bufnr) + bufnr_name_cache[bufnr] = n + return n + end +end + +local handle_entry_index = function(opts, t, k) + local override = ((opts or {}).entry_index or {})[k] + if not override then + return + end + + local val, save = override(t, opts) + if save then + rawset(t, k, val) + end + return val +end + local make_entry = {} +make_entry.set_default_entry_mt = function(tbl, opts) + return setmetatable({}, { + __index = function(t, k) + local override = handle_entry_index(opts, t, k) + if override then + return override + end + + -- Only hit tbl once + local val = tbl[k] + if val then + rawset(t, k, val) + end + + return val + end, + }) +end + do local lookup_keys = { display = 1, @@ -35,13 +119,18 @@ do value = 1, } - local mt_string_entry = { - __index = function(t, k) - return rawget(t, rawget(lookup_keys, k)) - end, - } + function make_entry.gen_from_string(opts) + local mt_string_entry = { + __index = function(t, k) + local override = handle_entry_index(opts, t, k) + if override then + return override + end + + return rawget(t, rawget(lookup_keys, k)) + end, + } - function make_entry.gen_from_string() return function(line) return setmetatable({ line, @@ -82,6 +171,11 @@ do end mt_file_entry.__index = function(t, k) + local override = handle_entry_index(opts, t, k) + if override then + return override + end + local raw = rawget(mt_file_entry, k) if raw then return raw @@ -133,9 +227,6 @@ do return { filename, lnum, col, text } end - --- Special options: - --- - disable_coordinates: Don't show the line & row numbers - --- - only_sort_text: Only sort via the text. Ignore filename and other items function make_entry.gen_from_vimgrep(opts) local mt_vimgrep_entry @@ -205,6 +296,11 @@ do end, __index = function(t, k) + local override = handle_entry_index(opts, t, k) + if override then + return override + end + local raw = rawget(mt_vimgrep_entry, k) if raw then return raw @@ -257,13 +353,13 @@ function make_entry.gen_from_git_stash(opts) local _, branch_name = string.match(splitted[2], "^([WIP on|On]+) (.+)") local commit_info = splitted[3] - return { + return make_entry.set_default_entry_mt({ value = stash_idx, ordinal = commit_info, branch_name = branch_name, commit_info = commit_info, display = make_display, - } + }, opts) end end @@ -297,52 +393,61 @@ function make_entry.gen_from_git_commits(opts) msg = "<empty commit message>" end - return { + return make_entry.set_default_entry_mt({ value = sha, ordinal = sha .. " " .. msg, msg = msg, display = make_display, current_file = opts.current_file, - } + }, opts) end end function make_entry.gen_from_quickfix(opts) opts = opts or {} + local show_line = vim.F.if_nil(opts.show_line, true) - local displayer = entry_display.create { - separator = "▏", - items = { - { width = 8 }, - { width = 0.45 }, - { remaining = true }, - }, + local hidden = utils.is_path_hidden(opts) + local items = { + { width = vim.F.if_nil(opts.fname_width, 30) }, + { remaining = true }, } + if hidden then + items[1] = 8 + end + if not show_line then + table.remove(items, 1) + end - local make_display = function(entry) - local filename = utils.transform_path(opts, entry.filename) + local displayer = entry_display.create { separator = "▏", items = items } - local line_info = { table.concat({ entry.lnum, entry.col }, ":"), "TelescopeResultsLineNr" } + local make_display = function(entry) + local input = {} + if not hidden then + table.insert(input, string.format("%s:%d:%d", utils.transform_path(opts, entry.filename), entry.lnum, entry.col)) + else + table.insert(input, string.format("%4d:%2d", entry.lnum, entry.col)) + end - if opts.trim_text then - entry.text = entry.text:gsub("^%s*(.-)%s*$", "%1") + if show_line then + local text = entry.text + if opts.trim_text then + text = text:gsub("^%s*(.-)%s*$", "%1") + end + text = text:gsub(".* | ", "") + table.insert(input, text) end - return displayer { - line_info, - entry.text:gsub(".* | ", ""), - filename, - } + return displayer(input) end + local get_filename = get_filename_fn() return function(entry) - local filename = entry.filename or vim.api.nvim_buf_get_name(entry.bufnr) - - return { - valid = true, + local filename = vim.F.if_nil(entry.filename, get_filename(entry.bufnr)) + return make_entry.set_default_entry_mt({ value = entry, - ordinal = (not opts.ignore_filename and filename or "") .. " " .. entry.text, + ordinal = (not hidden and filename or "") .. " " .. entry.text, display = make_display, bufnr = entry.bufnr, @@ -352,7 +457,7 @@ function make_entry.gen_from_quickfix(opts) text = entry.text, start = entry.start, finish = entry.finish, - } + }, opts) end end @@ -361,14 +466,22 @@ function make_entry.gen_from_lsp_symbols(opts) local bufnr = opts.bufnr or vim.api.nvim_get_current_buf() + -- Default we have two columns, symbol and type(unbound) + -- If path is not hidden then its, filepath, symbol and type(still unbound) + -- If show_line is also set, type is bound to len 8 local display_items = { - { width = opts.symbol_width or 25 }, -- symbol - { width = opts.symbol_type_width or 8 }, -- symbol type - { remaining = true }, -- filename{:optional_lnum+col} OR content preview + { width = opts.symbol_width or 25 }, + { remaining = true }, } - if opts.ignore_filename and opts.show_line then - table.insert(display_items, 2, { width = 6 }) + local hidden = utils.is_path_hidden(opts) + if not hidden then + table.insert(display_items, 1, { width = vim.F.if_nil(opts.fname_width, 30) }) + end + + if opts.show_line then + -- bound type to len 8 or custom + table.insert(display_items, #display_items, { width = opts.symbol_type_width or 8 }) end local displayer = entry_display.create { @@ -376,51 +489,42 @@ function make_entry.gen_from_lsp_symbols(opts) hl_chars = { ["["] = "TelescopeBorder", ["]"] = "TelescopeBorder" }, items = display_items, } + local type_highlight = vim.F.if_nil(opts.symbol_highlights or lsp_type_highlight) local make_display = function(entry) local msg - -- what to show in the last column: filename or symbol information - if opts.ignore_filename then -- ignore the filename and show line preview instead - -- TODO: fixme - if ignore_filename is set for workspace, bufnr will be incorrect - msg = vim.api.nvim_buf_get_lines(bufnr, entry.lnum - 1, entry.lnum, false)[1] or "" - msg = vim.trim(msg) - else - local filename = utils.transform_path(opts, entry.filename) - - if opts.show_line then -- show inline line info - filename = filename .. " [" .. entry.lnum .. ":" .. entry.col .. "]" - end - msg = filename + if opts.show_line then + msg = vim.trim(vim.F.if_nil(vim.api.nvim_buf_get_lines(bufnr, entry.lnum - 1, entry.lnum, false)[1], "")) end - local type_highlight = opts.symbol_highlights or lsp_type_highlight - local display_columns = { - entry.symbol_name, - { entry.symbol_type:lower(), type_highlight[entry.symbol_type], type_highlight[entry.symbol_type] }, - msg, - } - - if opts.ignore_filename and opts.show_line then - table.insert(display_columns, 2, { entry.lnum .. ":" .. entry.col, "TelescopeResultsLineNr" }) + if hidden then + return displayer { + entry.symbol_name, + { entry.symbol_type:lower(), type_highlight[entry.symbol_type] }, + msg, + } + else + return displayer { + utils.transform_path(opts, entry.filename), + entry.symbol_name, + { entry.symbol_type:lower(), type_highlight[entry.symbol_type] }, + msg, + } end - - return displayer(display_columns) end + local get_filename = get_filename_fn() return function(entry) - local filename = entry.filename or vim.api.nvim_buf_get_name(entry.bufnr) + local filename = vim.F.if_nil(entry.filename, get_filename(entry.bufnr)) local symbol_msg = entry.text local symbol_type, symbol_name = symbol_msg:match "%[(.+)%]%s+(.*)" - local ordinal = "" - if not opts.ignore_filename and filename then + if not hidden and filename then ordinal = filename .. " " end ordinal = ordinal .. symbol_name .. " " .. (symbol_type or "unknown") - return { - valid = true, - + return make_entry.set_default_entry_mt({ value = entry, ordinal = ordinal, display = make_display, @@ -432,7 +536,7 @@ function make_entry.gen_from_lsp_symbols(opts) symbol_type = symbol_type, start = entry.start, finish = entry.finish, - } + }, opts) end end @@ -460,8 +564,9 @@ function make_entry.gen_from_buffer(opts) local cwd = vim.fn.expand(opts.cwd or vim.loop.cwd()) local make_display = function(entry) + -- bufnr_width + modes + icon + 3 spaces + : + lnum + opts.__prefix = opts.bufnr_width + 4 + icon_width + 3 + 1 + #tostring(entry.lnum) local display_bufname = utils.transform_path(opts, entry.filename) - local icon, hl_group = utils.get_devicons(entry.filename, disable_devicons) return displayer { @@ -483,9 +588,7 @@ function make_entry.gen_from_buffer(opts) local indicator = entry.flag .. hidden .. readonly .. changed local line_count = vim.api.nvim_buf_line_count(entry.bufnr) - return { - valid = true, - + return make_entry.set_default_entry_mt({ value = bufname, ordinal = entry.bufnr .. " : " .. bufname, display = make_display, @@ -495,7 +598,7 @@ function make_entry.gen_from_buffer(opts) -- account for potentially stale lnum as getbufinfo might not be updated or from resuming buffers picker lnum = entry.info.lnum ~= 0 and math.max(math.min(entry.info.lnum, line_count), 1) or 1, indicator = indicator, - } + }, opts) end end @@ -537,13 +640,12 @@ function make_entry.gen_from_treesitter(opts) return displayer(display_columns) end + local get_filename = get_filename_fn() return function(entry) local ts_utils = require "nvim-treesitter.ts_utils" local start_row, start_col, end_row, _ = ts_utils.get_node_range(entry.node) local node_text = vim.treesitter.get_node_text(entry.node, bufnr) - return { - valid = true, - + return make_entry.set_default_entry_mt({ value = entry.node, kind = entry.kind, ordinal = node_text .. " " .. (entry.kind or "unknown"), @@ -551,14 +653,14 @@ function make_entry.gen_from_treesitter(opts) node_text = node_text, - filename = vim.api.nvim_buf_get_name(bufnr), + filename = get_filename(bufnr), -- need to add one since the previewer substacts one lnum = start_row + 1, col = start_col, text = node_text, start = start_row, finish = end_row, - } + }, opts) end end @@ -573,14 +675,12 @@ function make_entry.gen_from_packages(opts) end return function(module_name) - local entry = { + return make_entry.set_default_entry_mt({ valid = module_name ~= "", value = module_name, ordinal = module_name, - } - entry.display = make_display(module_name) - - return entry + display = make_display(module_name), + }, opts) end end @@ -622,21 +722,21 @@ function make_entry.gen_from_apropos(opts) cmd = vim.split(cmd, ",")[1] return keyword and sections[section] - and { + and make_entry.set_default_entry_mt({ value = cmd, description = desc, ordinal = cmd, display = make_display, section = section, keyword = keyword, - } + }, opts) or nil end end -function make_entry.gen_from_marks(_) +function make_entry.gen_from_marks(opts) return function(item) - return { + return make_entry.set_default_entry_mt({ value = item.line, ordinal = item.line, display = item.line, @@ -644,11 +744,11 @@ function make_entry.gen_from_marks(_) col = item.col, start = item.lnum, filename = item.filename, - } + }, opts) end end -function make_entry.gen_from_registers(_) +function make_entry.gen_from_registers(opts) local displayer = entry_display.create { separator = " ", hl_chars = { ["["] = "TelescopeBorder", ["]"] = "TelescopeBorder" }, @@ -667,13 +767,12 @@ function make_entry.gen_from_registers(_) end return function(entry) - return { - valid = true, + return make_entry.set_default_entry_mt({ value = entry, ordinal = entry, content = vim.fn.getreg(entry), display = make_display, - } + }, opts) end end @@ -706,7 +805,7 @@ function make_entry.gen_from_keymaps(opts) end return function(entry) - return { + return make_entry.set_default_entry_mt({ mode = entry.mode, lhs = get_lhs(entry), desc = get_desc(entry), @@ -715,22 +814,22 @@ function make_entry.gen_from_keymaps(opts) value = entry, ordinal = entry.mode .. " " .. get_lhs(entry) .. " " .. get_desc(entry), display = make_display, - } + }, opts) end end -function make_entry.gen_from_highlights() +function make_entry.gen_from_highlights(opts) local make_display = function(entry) local display = entry.value return display, { { { 0, #display }, display } } end return function(entry) - return { + return make_entry.set_default_entry_mt({ value = entry, display = make_display, ordinal = entry, - } + }, opts) end end @@ -751,12 +850,12 @@ function make_entry.gen_from_picker(opts) end return function(entry) - return { + return make_entry.set_default_entry_mt({ value = entry, text = entry.prompt_title, - ordinal = string.format("%s %s", entry.prompt_title, utils.get_default(entry.default_text, "")), + ordinal = string.format("%s %s", entry.prompt_title, vim.F.if_nil(entry.default_text, "")), display = make_display, - } + }, opts) end end @@ -799,117 +898,61 @@ function make_entry.gen_from_buffer_lines(opts) return end - return { - valid = true, + return make_entry.set_default_entry_mt({ ordinal = entry.text, display = make_display, filename = entry.filename, lnum = entry.lnum, text = entry.text, - } + }, opts) end end -function make_entry.gen_from_vimoptions() - local process_one_opt = function(o) - local ok, value_origin - - local option = { - name = "", - description = "", - current_value = "", - default_value = "", - value_type = "", - set_by_user = false, - last_set_from = "", - } - - local is_global = false - for _, v in ipairs(o.scope) do - if v == "global" then - is_global = true - end - end - - if not is_global then - return - end - - if is_global then - option.name = o.full_name - - ok, option.current_value = pcall(vim.api.nvim_get_option, o.full_name) - if not ok then - return - end - - local str_funcname = o.short_desc() - option.description = assert(loadstring(str_funcname))() - -- if #option.description > opts.desc_col_length then - -- opts.desc_col_length = #option.description - -- end - - if o.defaults ~= nil then - option.default_value = o.defaults.if_true.vim or o.defaults.if_true.vi - end - - if type(option.default_value) == "function" then - option.default_value = "Macro: " .. option.default_value() - end - - option.value_type = (type(option.current_value) == "boolean" and "bool" or type(option.current_value)) - - if option.current_value ~= option.default_value then - option.set_by_user = true - value_origin = vim.fn.execute("verbose set " .. o.full_name .. "?") - if string.match(value_origin, "Last set from") then - -- TODO: parse file and line number as separate items - option.last_set_from = value_origin:gsub("^.*Last set from ", "") - end - end - - return option - end - end - +function make_entry.gen_from_vimoptions(opts) local displayer = entry_display.create { separator = "", hl_chars = { ["["] = "TelescopeBorder", ["]"] = "TelescopeBorder" }, items = { { width = 25 }, { width = 12 }, + { width = 11 }, { remaining = true }, }, } local make_display = function(entry) return displayer { - { entry.name, "Keyword" }, - { "[" .. entry.value_type .. "]", "Type" }, - utils.display_termcodes(tostring(entry.current_value)), - entry.description, + { entry.value.name, "Keyword" }, + { "[" .. entry.value.type .. "]", "Type" }, + { "[" .. entry.value.scope .. "]", "Identifier" }, + utils.display_termcodes(tostring(entry.value.value)), } end - return function(line) - local entry = process_one_opt(line) - if not entry then - return - end + return function(o) + local entry = { + display = make_display, + value = { + name = o.name, + value = o.default, + type = o.type, + scope = o.scope, + }, + ordinal = string.format("%s %s %s", o.name, o.type, o.scope), + } - entry.valid = true - entry.display = make_display - entry.value = line - entry.ordinal = line.full_name - -- entry.raw_value = d.raw_value - -- entry.last_set_from = d.last_set_from + local ok, value = pcall(vim.api.nvim_get_option, o.name) + if ok then + entry.value.value = value + entry.ordinal = entry.ordinal .. " " .. utils.display_termcodes(tostring(value)) + else + entry.ordinal = entry.ordinal .. " " .. utils.display_termcodes(tostring(o.default)) + end - return entry + return make_entry.set_default_entry_mt(entry, opts) end end ---- Special options: ---- - only_sort_tags: Only sort via tag name. Ignore filename and other items function make_entry.gen_from_ctags(opts) opts = opts or {} @@ -960,6 +1003,11 @@ function make_entry.gen_from_ctags(opts) local mt = {} mt.__index = function(t, k) + local override = handle_entry_index(opts, t, k) + if override then + return override + end + if k == "path" then local retpath = Path:new({ t.filename }):absolute() if not vim.loop.fs_access(retpath, "R", nil) then @@ -969,6 +1017,7 @@ function make_entry.gen_from_ctags(opts) end end + local current_file_cache = {} return function(line) if line == "" or line:sub(1, 1) == "!" then return nil @@ -986,8 +1035,14 @@ function make_entry.gen_from_ctags(opts) file = string.gsub(file, "/", "\\") end - if opts.only_current_file and file ~= current_file then - return nil + if opts.only_current_file then + if current_file_cache[file] == nil then + current_file_cache[file] = Path:new(file):normalize(cwd) == current_file + end + + if current_file_cache[file] == false then + return nil + end end local tag_entry = {} @@ -1031,11 +1086,12 @@ function make_entry.gen_from_diagnostics(opts) end)() local display_items = { - { width = utils.if_nil(signs, 8, 10) }, + { width = signs ~= nil and 10 or 8 }, { remaining = true }, } local line_width = vim.F.if_nil(opts.line_width, 0.5) - if not utils.is_path_hidden(opts) then + local hidden = utils.is_path_hidden(opts) + if not hidden then table.insert(display_items, 2, { width = line_width }) end local displayer = entry_display.create { @@ -1053,10 +1109,6 @@ function make_entry.gen_from_diagnostics(opts) "Diagnostic" .. entry.type, } - --TODO(conni2461): I dont like that this is symbol lnum:col | msg | filename - -- i want: symbol filename:lnum:col | msg - -- or : symbol lnum:col | msg - -- I think this is more natural return displayer { line_info, entry.text, @@ -1065,20 +1117,20 @@ function make_entry.gen_from_diagnostics(opts) end return function(entry) - return { + return make_entry.set_default_entry_mt({ value = entry, - ordinal = ("%s %s"):format(not opts.ignore_filename and entry.filename or "", entry.text), + ordinal = ("%s %s"):format(not hidden and entry.filename or "", entry.text), display = make_display, filename = entry.filename, type = entry.type, lnum = entry.lnum, col = entry.col, text = entry.text, - } + }, opts) end end -function make_entry.gen_from_autocommands(_) +function make_entry.gen_from_autocommands(opts) local displayer = entry_display.create { separator = "▏", items = { @@ -1091,32 +1143,30 @@ function make_entry.gen_from_autocommands(_) local make_display = function(entry) return displayer { - { entry.event, "vimAutoEvent" }, - { entry.group, "vimAugroup" }, - { entry.ft_pattern, "vimAutoCmdSfxList" }, - entry.command, + { entry.value.event, "vimAutoEvent" }, + { entry.value.group_name, "vimAugroup" }, + { entry.value.pattern, "vimAutoCmdSfxList" }, + entry.value.command, } end - -- TODO: <action> dump current filtered items to buffer return function(entry) - return { - event = entry.event, - group = entry.group, - ft_pattern = entry.ft_pattern, - command = entry.command, - value = string.format("+%d %s", entry.source_lnum, entry.source_file), - source_file = entry.source_file, - source_lnum = entry.source_lnum, + local group_name = vim.F.if_nil(entry.group_name, "<anonymous>") + return make_entry.set_default_entry_mt({ + value = { + event = entry.event, + group_name = group_name, + pattern = entry.pattern, + command = entry.command, + }, -- - valid = true, - ordinal = entry.event .. " " .. entry.group .. " " .. entry.ft_pattern .. " " .. entry.command, + ordinal = entry.event .. " " .. group_name .. " " .. entry.pattern .. " " .. entry.command, display = make_display, - } + }, opts) end end -function make_entry.gen_from_commands(_) +function make_entry.gen_from_commands(opts) local displayer = entry_display.create { separator = "▏", items = { @@ -1149,7 +1199,7 @@ function make_entry.gen_from_commands(_) end return function(entry) - return { + return make_entry.set_default_entry_mt({ name = entry.name, bang = entry.bang, nargs = entry.nargs, @@ -1157,10 +1207,9 @@ function make_entry.gen_from_commands(_) definition = entry.definition, -- value = entry, - valid = true, ordinal = entry.name, display = make_display, - } + }, opts) end end @@ -1219,13 +1268,13 @@ function make_entry.gen_from_git_status(opts) end local mod, file = string.match(entry, "(..).*%s[->%s]?(.+)") - return { + return setmetatable({ value = file, status = mod, ordinal = entry, display = make_display, path = Path:new({ opts.cwd, file }):absolute(), - } + }, opts) end end diff --git a/lua/telescope/pickers.lua b/lua/telescope/pickers.lua index 12d6209..82ccda9 100644 --- a/lua/telescope/pickers.lua +++ b/lua/telescope/pickers.lua @@ -24,8 +24,6 @@ local p_window = require "telescope.pickers.window" local EntryManager = require "telescope.entry_manager" local MultiSelect = require "telescope.pickers.multi" -local get_default = utils.get_default - local truncate = require("plenary.strings").truncate local strdisplaywidth = require("plenary.strings").strdisplaywidth @@ -65,26 +63,27 @@ function Picker:new(opts) -- pcall(v.clear) -- end - local layout_strategy = get_default(opts.layout_strategy, config.values.layout_strategy) + local layout_strategy = vim.F.if_nil(opts.layout_strategy, config.values.layout_strategy) local obj = setmetatable({ - prompt_title = get_default(opts.prompt_title, config.values.prompt_title), - results_title = get_default(opts.results_title, config.values.results_title), + prompt_title = vim.F.if_nil(opts.prompt_title, config.values.prompt_title), + results_title = vim.F.if_nil(opts.results_title, config.values.results_title), -- either whats passed in by the user or whats defined by the previewer preview_title = opts.preview_title, - prompt_prefix = get_default(opts.prompt_prefix, config.values.prompt_prefix), - wrap_results = get_default(opts.wrap_results, config.values.wrap_results), - selection_caret = get_default(opts.selection_caret, config.values.selection_caret), - entry_prefix = get_default(opts.entry_prefix, config.values.entry_prefix), - multi_icon = get_default(opts.multi_icon, config.values.multi_icon), + prompt_prefix = vim.F.if_nil(opts.prompt_prefix, config.values.prompt_prefix), + wrap_results = vim.F.if_nil(opts.wrap_results, config.values.wrap_results), + selection_caret = vim.F.if_nil(opts.selection_caret, config.values.selection_caret), + entry_prefix = vim.F.if_nil(opts.entry_prefix, config.values.entry_prefix), + multi_icon = vim.F.if_nil(opts.multi_icon, config.values.multi_icon), - initial_mode = get_default(opts.initial_mode, config.values.initial_mode), + initial_mode = vim.F.if_nil(opts.initial_mode, config.values.initial_mode), _original_mode = vim.api.nvim_get_mode().mode, - debounce = get_default(tonumber(opts.debounce), nil), + debounce = vim.F.if_nil(tonumber(opts.debounce), nil), + _finder_attached = true, default_text = opts.default_text, - get_status_text = get_default(opts.get_status_text, config.values.get_status_text), + get_status_text = vim.F.if_nil(opts.get_status_text, config.values.get_status_text), _on_input_filter_cb = opts.on_input_filter_cb or function() end, finder = assert(opts.finder, "Finder is required."), @@ -95,7 +94,7 @@ function Picker:new(opts) default_selection_index = opts.default_selection_index, - get_selection_window = get_default(opts.get_selection_window, config.values.get_selection_window), + get_selection_window = vim.F.if_nil(opts.get_selection_window, config.values.get_selection_window), cwd = opts.cwd, @@ -106,32 +105,32 @@ function Picker:new(opts) and opts._multi or MultiSelect:new(), - track = get_default(opts.track, false), + track = vim.F.if_nil(opts.track, false), stats = {}, attach_mappings = opts.attach_mappings, - file_ignore_patterns = get_default(opts.file_ignore_patterns, config.values.file_ignore_patterns), + file_ignore_patterns = vim.F.if_nil(opts.file_ignore_patterns, config.values.file_ignore_patterns), - scroll_strategy = get_default(opts.scroll_strategy, config.values.scroll_strategy), - sorting_strategy = get_default(opts.sorting_strategy, config.values.sorting_strategy), - tiebreak = get_default(opts.tiebreak, config.values.tiebreak), - selection_strategy = get_default(opts.selection_strategy, config.values.selection_strategy), + scroll_strategy = vim.F.if_nil(opts.scroll_strategy, config.values.scroll_strategy), + sorting_strategy = vim.F.if_nil(opts.sorting_strategy, config.values.sorting_strategy), + tiebreak = vim.F.if_nil(opts.tiebreak, config.values.tiebreak), + selection_strategy = vim.F.if_nil(opts.selection_strategy, config.values.selection_strategy), - push_cursor_on_edit = get_default(opts.push_cursor_on_edit, false), - push_tagstack_on_edit = get_default(opts.push_tagstack_on_edit, false), + push_cursor_on_edit = vim.F.if_nil(opts.push_cursor_on_edit, false), + push_tagstack_on_edit = vim.F.if_nil(opts.push_tagstack_on_edit, false), layout_strategy = layout_strategy, layout_config = config.smarter_depth_2_extend(opts.layout_config or {}, config.values.layout_config or {}), - __cycle_layout_list = get_default(opts.cycle_layout_list, config.values.cycle_layout_list), + __cycle_layout_list = vim.F.if_nil(opts.cycle_layout_list, config.values.cycle_layout_list), window = { - winblend = get_default( + winblend = vim.F.if_nil( opts.winblend, type(opts.window) == "table" and opts.window.winblend or config.values.winblend ), - border = get_default(opts.border, type(opts.window) == "table" and opts.window.border or config.values.border), - borderchars = get_default( + border = vim.F.if_nil(opts.border, type(opts.window) == "table" and opts.window.border or config.values.border), + borderchars = vim.F.if_nil( opts.borderchars, type(opts.window) == "table" and opts.window.borderchars or config.values.borderchars ), @@ -505,10 +504,12 @@ function Picker:find() -- Register attach vim.api.nvim_buf_attach(prompt_bufnr, false, { on_lines = function(...) - find_id = self:_next_find_id() + if self._finder_attached then + find_id = self:_next_find_id() - status_updater { completed = false } - self._on_lines(...) + status_updater { completed = false } + self._on_lines(...) + end end, on_detach = function() @@ -692,7 +693,7 @@ end --- --- Example usage in telescope: --- - `actions.delete_buffer()` ----@param delete_cb function: called with each deleted selection +---@param delete_cb function: called for each selection fn(s) -> bool|nil (true|nil removes the entry from the results) function Picker:delete_selection(delete_cb) vim.validate { delete_cb = { delete_cb, "f" } } local original_selection_strategy = self.selection_strategy @@ -718,8 +719,10 @@ function Picker:delete_selection(delete_cb) return x > y end) for _, index in ipairs(selection_index) do - local selection = table.remove(self.finder.results, index) - delete_cb(selection) + local delete_cb_return = delete_cb(self.finder.results[index]) + if delete_cb_return == nil or delete_cb_return == true then + table.remove(self.finder.results, index) + end end if used_multi_select then @@ -908,7 +911,7 @@ function Picker:refresh(finder, opts) local handle = type(opts.new_prefix) == "table" and unpack or function(x) return x end - self:change_prompt_prefix(handle(opts.new_prefix)) + self:change_prompt_prefix(handle(opts.new_prefix), opts.prefix_hl_group) end if finder then @@ -962,6 +965,9 @@ function Picker:set_selection(row) state.set_global_key("selected_entry", entry) if not entry then + -- also refresh previewer when there is no entry selected, so the preview window is cleared + self._selection_entry = entry + self:refresh_previewer() return end @@ -1065,10 +1071,6 @@ end --- Refresh the previewer based on the current `status` of the picker function Picker:refresh_previewer() local status = state.get_status(self.prompt_bufnr) - if not self._selection_entry then - -- if selection_entry is nil there is nothing to be previewed - return - end if self.previewer and status.preview_win and a.nvim_win_is_valid(status.preview_win) then self:_increment "previewed" @@ -1365,6 +1367,16 @@ function Picker:_do_selection(prompt) else self:set_selection(self:get_reset_row()) end + elseif selection_strategy == "none" then + if self._selection_entry then + local old_entry, old_row = self._selection_entry, self._selection_row + self:reset_selection() -- required to reset selection before updating prefix + if old_row >= 0 then + self:update_prefix(old_entry, old_row) + self.highlighter:hi_multiselect(old_row, self:is_multi_selected(old_entry)) + end + end + return else error("Unknown selection strategy: " .. selection_strategy) end @@ -1495,6 +1507,11 @@ function Picker:_reset_highlights() vim.api.nvim_buf_clear_namespace(self.results_bufnr, ns_telescope_matching, 0, -1) end +-- Toggles whether finder is attached to prompt buffer input +function Picker:_toggle_finder_attach() + self._finder_attached = not self._finder_attached +end + function Picker:_detach() self.finder:close() diff --git a/lua/telescope/pickers/entry_display.lua b/lua/telescope/pickers/entry_display.lua index b1a1473..6e520f4 100644 --- a/lua/telescope/pickers/entry_display.lua +++ b/lua/telescope/pickers/entry_display.lua @@ -1,3 +1,63 @@ +---@tag telescope.pickers.entry_display + +---@brief [[ +--- Entry Display is used to format each entry shown in the result panel. +--- +--- Entry Display create() will give us a function based on the configuration +--- of column widths we pass into it. We then can use this function n times to +--- return a string based on structured input. +--- +--- Note that if you call `create()` inside `make_display` it will be called for +--- every single entry. So it is suggested to do this outside of `make_display` +--- for the best performance. +--- +--- The create function will use the column widths passed to it in +--- configaration.items. Each item in that table is the number of characters in +--- the column. It's also possible for the final column to not have a fixed +--- width, this will be shown in the configuartion as 'remaining = true'. +--- +--- An example of this configuration is shown for the buffers picker +--- <code> +--- local displayer = entry_display.create { +--- separator = " ", +--- items = { +--- { width = opts.bufnr_width }, +--- { width = 4 }, +--- { width = icon_width }, +--- { remaining = true }, +--- }, +--- } +--- </code> +--- +--- This shows 4 columns, the first is defined in the opts as the width we'll +--- use when display_string the number of the buffer. The second has a fixed +--- width of 4 and the 3rd column's widht will be decided by the width of the +--- icons we use. The fourth column will use the remaining space. Finally we +--- have also defined the seperator between each column will be the space " ". +--- +--- An example of how the display reference will be used is shown, again for +--- the buffers picker: +--- <code> +--- return displayer { +--- { entry.bufnr, "TelescopeResultsNumber" }, +--- { entry.indicator, "TelescopeResultsComment" }, +--- { icon, hl_group }, +--- display_bufname .. ":" .. entry.lnum, +--- } +--- </code> +--- +--- There are two types of values each column can have. Either a simple String +--- or a table containing the String as well as the hl_group. +--- +--- The displayer can return values, string and an optional highlights. +--- String is all the text to be displayed for this entry as a single string. If +--- parts of the string are to be highlighted they will be described in the +--- highlights table. +--- +--- For better understanding of how create() and displayer are used it's best to look +--- at the code in make_entry.lua. +---@brief ]] + local strings = require "plenary.strings" local state = require "telescope.state" local resolve = require "telescope.config.resolve" diff --git a/lua/telescope/pickers/layout_strategies.lua b/lua/telescope/pickers/layout_strategies.lua index 3a84759..5e74b85 100644 --- a/lua/telescope/pickers/layout_strategies.lua +++ b/lua/telescope/pickers/layout_strategies.lua @@ -53,7 +53,6 @@ local resolve = require "telescope.config.resolve" local p_window = require "telescope.pickers.window" -local if_nil = vim.F.if_nil local get_border_size = function(opts) if opts.window.border == false then @@ -125,7 +124,7 @@ local function validate_layout_config(strategy_name, configuration, values, defa local valid_configuration_keys = get_valid_configuration_keys(configuration) -- If no default_layout_config provided, check Telescope's config values - default_layout_config = if_nil(default_layout_config, require("telescope.config").values.layout_config) + default_layout_config = vim.F.if_nil(default_layout_config, require("telescope.config").values.layout_config) local result = {} local get_value = function(k) @@ -263,7 +262,7 @@ local function make_documented_layout(name, layout_config, layout) validate_layout_config( name, layout_config, - vim.tbl_deep_extend("keep", if_nil(override_layout, {}), if_nil(self.layout_config, {})) + vim.tbl_deep_extend("keep", vim.F.if_nil(override_layout, {}), vim.F.if_nil(self.layout_config, {})) ) ) end @@ -320,7 +319,7 @@ layout_strategies.horizontal = make_documented_layout( -- Cap over/undersized width (with previewer) width, w_space = calc_size_and_spacing(width, max_columns, bs, 2, 4, 1) - preview.width = resolve.resolve_width(if_nil(layout_config.preview_width, function(_, cols) + preview.width = resolve.resolve_width(vim.F.if_nil(layout_config.preview_width, function(_, cols) if cols < 150 then return math.floor(cols * 0.4) elseif cols < 200 then @@ -588,7 +587,7 @@ layout_strategies.cursor = make_documented_layout( -- Cap over/undersized width (with preview) width, w_space = calc_size_and_spacing(width, max_columns, bs, 2, 4, 0) - preview.width = resolve.resolve_width(if_nil(layout_config.preview_width, 2 / 3))(self, width, max_lines) + preview.width = resolve.resolve_width(vim.F.if_nil(layout_config.preview_width, 2 / 3))(self, width, max_lines) prompt.width = width - preview.width - w_space results.width = prompt.width else @@ -694,7 +693,11 @@ layout_strategies.vertical = make_documented_layout( -- Cap over/undersized height (with previewer) height, h_space = calc_size_and_spacing(height, max_lines, bs, 3, 6, 2) - preview.height = resolve.resolve_height(if_nil(layout_config.preview_height, 0.5))(self, max_columns, height) + preview.height = resolve.resolve_height(vim.F.if_nil(layout_config.preview_height, 0.5))( + self, + max_columns, + height + ) else -- Cap over/undersized height (without previewer) height, h_space = calc_size_and_spacing(height, max_lines, bs, 2, 4, 1) @@ -764,8 +767,8 @@ layout_strategies.flex = make_documented_layout( horizontal = "Options to pass when switching to horizontal layout", }), function(self, max_columns, max_lines, layout_config) - local flip_columns = if_nil(layout_config.flip_columns, 100) - local flip_lines = if_nil(layout_config.flip_lines, 20) + local flip_columns = vim.F.if_nil(layout_config.flip_columns, 100) + local flip_lines = vim.F.if_nil(layout_config.flip_lines, 20) if max_columns < flip_columns and max_lines > flip_lines then self.__flex_strategy = "vertical" @@ -851,7 +854,7 @@ layout_strategies.bottom_pane = make_documented_layout( local tbln max_lines, tbln = calc_tabline(max_lines) - local height = if_nil(resolve.resolve_height(layout_config.height)(self, max_columns, max_lines), 25) + local height = vim.F.if_nil(resolve.resolve_height(layout_config.height)(self, max_columns, max_lines), 25) if type(layout_config.height) == "table" and type(layout_config.height.padding) == "number" then -- Since bottom_pane only has padding at the top, we only need half as much padding in total -- This doesn't match the vim help for `resolve.resolve_height`, but it matches expectations @@ -874,7 +877,7 @@ layout_strategies.bottom_pane = make_documented_layout( -- Cap over/undersized width (with preview) local width, w_space = calc_size_and_spacing(max_columns, max_columns, bs, 2, 4, 0) - preview.width = resolve.resolve_width(if_nil(layout_config.preview_width, 0.5))(self, width, max_lines) + preview.width = resolve.resolve_width(vim.F.if_nil(layout_config.preview_width, 0.5))(self, width, max_lines) results.width = width - preview.width - w_space else results.width = prompt.width diff --git a/lua/telescope/previewers/buffer_previewer.lua b/lua/telescope/previewers/buffer_previewer.lua index 0db4365..f9e77d2 100644 --- a/lua/telescope/previewers/buffer_previewer.lua +++ b/lua/telescope/previewers/buffer_previewer.lua @@ -386,6 +386,10 @@ previewers.new_buffer_previewer = function(opts) opts.define_preview(self, entry, status) vim.schedule(function() + if not self or not self.state or not self.state.bufnr then + return + end + if vim.api.nvim_buf_is_valid(self.state.bufnr) then vim.api.nvim_buf_call(self.state.bufnr, function() vim.cmd "do User TelescopePreviewerLoaded" @@ -715,8 +719,12 @@ previewers.git_stash_diff = defaulter(function(opts) value = entry.value, bufname = self.state.bufname, cwd = opts.cwd, + callback = function(bufnr) + if vim.api.nvim_buf_is_valid(bufnr) then + putils.regex_highlighter(bufnr, "diff") + end + end, }) - putils.regex_highlighter(self.state.bufnr, "diff") end, } end, {}) @@ -741,10 +749,12 @@ previewers.git_commit_diff_to_parent = defaulter(function(opts) bufname = self.state.bufname, cwd = opts.cwd, callback = function(bufnr) - search_cb_jump(self, bufnr, opts.current_line) + if vim.api.nvim_buf_is_valid(bufnr) then + search_cb_jump(self, bufnr, opts.current_line) + putils.regex_highlighter(bufnr, "diff") + end end, }) - putils.regex_highlighter(self.state.bufnr, "diff") end, } end, {}) @@ -770,10 +780,12 @@ previewers.git_commit_diff_to_head = defaulter(function(opts) bufname = self.state.bufname, cwd = opts.cwd, callback = function(bufnr) - search_cb_jump(self, bufnr, opts.current_line) + if vim.api.nvim_buf_is_valid(bufnr) then + search_cb_jump(self, bufnr, opts.current_line) + putils.regex_highlighter(bufnr, "diff") + end end, }) - putils.regex_highlighter(self.state.bufnr, "diff") end, } end, {}) @@ -799,10 +811,12 @@ previewers.git_commit_diff_as_was = defaulter(function(opts) bufname = self.state.bufname, cwd = opts.cwd, callback = function(bufnr) - search_cb_jump(self, bufnr, opts.current_line) + if vim.api.nvim_buf_is_valid(bufnr) then + search_cb_jump(self, bufnr, opts.current_line) + putils.regex_highlighter(bufnr, ft) + end end, }) - putils.highlighter(self.state.bufnr, ft) end, } end, {}) @@ -864,8 +878,12 @@ previewers.git_file_diff = defaulter(function(opts) value = entry.value, bufname = self.state.bufname, cwd = opts.cwd, + callback = function(bufnr) + if vim.api.nvim_buf_is_valid(bufnr) then + putils.regex_highlighter(bufnr, "diff") + end + end, }) - putils.regex_highlighter(self.state.bufnr, "diff") end end, } @@ -881,12 +899,12 @@ previewers.autocommands = defaulter(function(_) end, get_buffer_by_name = function(_, entry) - return entry.group + return entry.value.group_name end, define_preview = function(self, entry, status) local results = vim.tbl_filter(function(x) - return x.group == entry.group + return x.value.group_name == entry.value.group_name end, status.picker.finder.results) if self.state.last_set_bufnr then @@ -894,9 +912,9 @@ previewers.autocommands = defaulter(function(_) end local selected_row = 0 - if self.state.bufname ~= entry.group then + if self.state.bufname ~= entry.value.group_name then local display = {} - table.insert(display, string.format(" augroup: %s - [ %d entries ]", entry.group, #results)) + table.insert(display, string.format(" augroup: %s - [ %d entries ]", entry.value.group_name, #results)) -- TODO: calculate banner width/string in setup() -- TODO: get column characters to be the same HL group as border table.insert(display, string.rep("─", vim.fn.getwininfo(status.preview_win)[1].width)) @@ -905,7 +923,10 @@ previewers.autocommands = defaulter(function(_) if item == entry then selected_row = idx end - table.insert(display, string.format(" %-14s▏%-08s %s", item.event, item.ft_pattern, item.command)) + table.insert( + display, + string.format(" %-14s▏%-08s %s", item.value.event, item.value.ft_pattern, item.value.command) + ) end vim.api.nvim_buf_set_option(self.state.bufnr, "filetype", "vim") diff --git a/lua/telescope/previewers/previewer.lua b/lua/telescope/previewers/previewer.lua index 834df2c..f986dac 100644 --- a/lua/telescope/previewers/previewer.lua +++ b/lua/telescope/previewers/previewer.lua @@ -1,3 +1,5 @@ +local utils = require "telescope.utils" + local Previewer = {} Previewer.__index = Previewer @@ -25,11 +27,19 @@ function Previewer:new(opts) _send_input = opts.send_input, _scroll_fn = opts.scroll_fn, preview_fn = opts.preview_fn, + _empty_bufnr = nil, }, Previewer) end function Previewer:preview(entry, status) if not entry then + if not self._empty_bufnr then + self._empty_bufnr = vim.api.nvim_create_buf(false, true) + end + + if vim.api.nvim_buf_is_valid(self._empty_bufnr) then + vim.api.nvim_win_set_buf(status.preview_win, self._empty_bufnr) + end return end @@ -47,7 +57,11 @@ end function Previewer:title(entry, dynamic) if dynamic == true and self._dyn_title_fn ~= nil then if entry == nil then - return nil + if self._title_fn ~= nil then + return self:_title_fn() + else + return "" + end end return self:_dyn_title_fn(entry) end @@ -57,6 +71,9 @@ function Previewer:title(entry, dynamic) end function Previewer:teardown() + if self._empty_bufnr then + utils.buf_delete(self._empty_bufnr) + end if self._teardown_func then self:_teardown_func() end diff --git a/lua/telescope/previewers/utils.lua b/lua/telescope/previewers/utils.lua index d250894..b1d1d5e 100644 --- a/lua/telescope/previewers/utils.lua +++ b/lua/telescope/previewers/utils.lua @@ -82,12 +82,26 @@ end utils.highlighter = function(bufnr, ft, opts) opts = opts or {} opts.preview = opts.preview or {} - opts.preview.treesitter = vim.F.if_nil( - opts.preview.treesitter, - type(conf.preview) == "table" and conf.preview.treesitter - ) - local ts_highlighting = opts.preview.treesitter == true - or type(opts.preview.treesitter) == "table" and vim.tbl_contains(opts.preview.treesitter, ft) + opts.preview.treesitter = vim.F.if_nil(opts.preview.treesitter, conf.preview.treesitter) + if type(opts.preview.treesitter) == "boolean" then + local temp = { enable = opts.preview.treesitter } + opts.preview.treesitter = temp + end + + local ts_highlighting = (function() + if type(opts.preview.treesitter.enable) == "table" then + if vim.tbl_contains(opts.preview.treesitter.enable, ft) then + return true + end + return false + end + + if vim.tbl_contains(vim.F.if_nil(opts.preview.treesitter.disable, {}), ft) then + return false + end + + return opts.preview.treesitter.enable == nil or opts.preview.treesitter.enable == true + end)() local ts_success if ts_highlighting then diff --git a/lua/telescope/sorters.lua b/lua/telescope/sorters.lua index 8e266b3..3fb263a 100644 --- a/lua/telescope/sorters.lua +++ b/lua/telescope/sorters.lua @@ -602,7 +602,7 @@ end sorters.prefilter = function(opts) local sorter = opts.sorter - opts.delimiter = util.get_default(opts.delimiter, ":") + opts.delimiter = vim.F.if_nil(opts.delimiter, ":") sorter._delimiter = opts.delimiter sorter.tags = create_tag_set(opts.tag) sorter.filter_function = filter_function(opts) diff --git a/lua/telescope/utils.lua b/lua/telescope/utils.lua index 2a8de58..356d102 100644 --- a/lua/telescope/utils.lua +++ b/lua/telescope/utils.lua @@ -1,3 +1,10 @@ +---@tag telescope.utils +---@config { ["module"] = "telescope.utils" } + +---@brief [[ +--- Utilities for writing telescope pickers +---@brief ]] + local Path = require "plenary.path" local Job = require "plenary.job" @@ -13,6 +20,7 @@ utils.get_separator = function() end utils.if_nil = function(x, was_nil, was_not_nil) + log.error "telescope.utils.if_nil is deprecated and will be removed. Please use vim.F.if_nil" if x == nil then return was_nil else @@ -21,6 +29,7 @@ utils.if_nil = function(x, was_nil, was_not_nil) end utils.get_default = function(x, default) + log.error "telescope.utils.get_default is deprecated and will be removed. Please use vim.F.if_nil" return utils.if_nil(x, default, x) end @@ -191,7 +200,7 @@ utils.path_tail = (function() end)() utils.is_path_hidden = function(opts, path_display) - path_display = path_display or utils.get_default(opts.path_display, require("telescope.config").values.path_display) + path_display = path_display or vim.F.if_nil(opts.path_display, require("telescope.config").values.path_display) return path_display == nil or path_display == "hidden" @@ -210,6 +219,16 @@ local calc_result_length = function(truncate_len) return type(truncate_len) == "number" and len - truncate_len or len end +--- Transform path is a util function that formats a path based on path_display +--- found in `opts` or the default value from config. +--- It is meant to be used in make_entry to have a uniform interface for +--- builtins as well as extensions utilizing the same user configuration +--- Note: It is only supported inside `make_entry`/`make_display` the use of +--- this function outside of telescope might yield to undefined behavior and will +--- not be addressed by us +---@param opts table: The opts the users passed into the picker. Might contains a path_display key +---@param path string: The path that should be formated +---@return string: The transformed path ready to be displayed utils.transform_path = function(opts, path) if path == nil then return @@ -218,7 +237,7 @@ utils.transform_path = function(opts, path) return path end - local path_display = utils.get_default(opts.path_display, require("telescope.config").values.path_display) + local path_display = vim.F.if_nil(opts.path_display, require("telescope.config").values.path_display) local transformed_path = path @@ -257,7 +276,10 @@ utils.transform_path = function(opts, path) if opts.__length == nil then opts.__length = calc_result_length(path_display.truncate) end - transformed_path = truncate(transformed_path, opts.__length, nil, -1) + if opts.__prefix == nil then + opts.__prefix = 0 + end + transformed_path = truncate(transformed_path, opts.__length - opts.__prefix, nil, -1) end end diff --git a/lua/tests/automated/resolver_spec.lua b/lua/tests/automated/resolver_spec.lua index 0251ec0..f30a323 100644 --- a/lua/tests/automated/resolver_spec.lua +++ b/lua/tests/automated/resolver_spec.lua @@ -75,6 +75,14 @@ describe("telescope.config.resolve", function() end end) + it("should handle percentages with min/max boundary", function() + eq(20, resolve.resolve_width { 0.1, min = 20 }(nil, 40, 120)) + eq(30, resolve.resolve_height { 0.1, min = 20 }(nil, 40, 300)) + + eq(24, resolve.resolve_width { 0.4, max = 80 }(nil, 60, 60)) + eq(80, resolve.resolve_height { 0.4, max = 80 }(nil, 60, 300)) + end) + it("should handle fixed size", function() local fixed = { 5, 8, 13, 21, 34 } for _, s in ipairs(test_sizes) do |
