From 0621c1c6ba3e561dfb06f58a7b0aedfcd464b590 Mon Sep 17 00:00:00 2001 From: Simon Hauser Date: Fri, 1 Jul 2022 22:20:12 +0200 Subject: [PATCH] break: prefix internal files and add deprecation messages (#2032) --- lua/telescope/builtin/__diagnostics.lua | 150 +++ lua/telescope/builtin/__files.lua | 522 +++++++++ lua/telescope/builtin/__git.lua | 411 +++++++ lua/telescope/builtin/__internal.lua | 1318 ++++++++++++++++++++++ lua/telescope/builtin/__lsp.lua | 389 +++++++ lua/telescope/builtin/diagnostics.lua | 163 +-- lua/telescope/builtin/files.lua | 533 +-------- lua/telescope/builtin/git.lua | 422 +------ lua/telescope/builtin/init.lua | 100 +- lua/telescope/builtin/internal.lua | 1329 +---------------------- lua/telescope/builtin/lsp.lua | 404 +------ 11 files changed, 2913 insertions(+), 2828 deletions(-) create mode 100644 lua/telescope/builtin/__diagnostics.lua create mode 100644 lua/telescope/builtin/__files.lua create mode 100644 lua/telescope/builtin/__git.lua create mode 100644 lua/telescope/builtin/__internal.lua create mode 100644 lua/telescope/builtin/__lsp.lua 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", "", 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 "" + 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", "m", actions.git_reset_mixed) + map("n", "m", actions.git_reset_mixed) + map("i", "s", actions.git_reset_soft) + map("n", "s", actions.git_reset_soft) + map("i", "h", actions.git_reset_hard) + map("n", "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", "", actions.git_track_branch) + map("n", "", actions.git_track_branch) + + map("i", "", actions.git_rebase_branch) + map("n", "", actions.git_rebase_branch) + + map("i", "", actions.git_create_branch) + map("n", "", actions.git_create_branch) + + map("i", "", actions.git_switch_branch) + map("n", "", actions.git_switch_branch) + + map("i", "", actions.git_delete_branch) + map("n", "", actions.git_delete_branch) + + map("i", "", actions.git_merge_branch) + map("n", "", 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", "", actions.git_staging_toggle) + map("n", "", 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", "", actions.remove_selected_picker) + map("n", "", 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", "", actions.set_command_line) + map("n", "", actions.set_command_line) + map("n", "", actions.edit_command_line) + map("i", "", actions.edit_command_line) + + -- TODO: Find a way to insert the text... it seems hard. + -- map('i', '', actions.insert_value, { expr = true }) + + return true + end, + }):find() +end + +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", "", actions.set_search_line) + map("n", "", actions.set_search_line) + map("n", "", actions.edit_search_line) + map("i", "", actions.edit_search_line) + + -- TODO: Find a way to insert the text... it seems hard. + -- map('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("", 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", "", 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, "") 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 ~= "" 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 "" + 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", "", 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 + return require("telescope.builtin").diagnostics + 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) +return m diff --git a/lua/telescope/builtin/files.lua b/lua/telescope/builtin/files.lua index dddd18a..645fc10 100644 --- a/lua/telescope/builtin/files.lua +++ b/lua/telescope/builtin/files.lua @@ -1,522 +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 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", "", 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 "" - 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", +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 + return require("telescope.builtin")[k] + 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) +return m diff --git a/lua/telescope/builtin/git.lua b/lua/telescope/builtin/git.lua index 7db45ef..e6da478 100644 --- a/lua/telescope/builtin/git.lua +++ b/lua/telescope/builtin/git.lua @@ -1,411 +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", +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 + return require("telescope.builtin")["git_" .. k] + 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", "m", actions.git_reset_mixed) - map("n", "m", actions.git_reset_mixed) - map("i", "s", actions.git_reset_soft) - map("n", "s", actions.git_reset_soft) - map("i", "h", actions.git_reset_hard) - map("n", "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", "", actions.git_track_branch) - map("n", "", actions.git_track_branch) - - map("i", "", actions.git_rebase_branch) - map("n", "", actions.git_rebase_branch) - - map("i", "", actions.git_create_branch) - map("n", "", actions.git_create_branch) - - map("i", "", actions.git_switch_branch) - map("n", "", actions.git_switch_branch) - - map("i", "", actions.git_delete_branch) - map("n", "", actions.git_delete_branch) - - map("i", "", actions.git_merge_branch) - map("n", "", 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", "", actions.git_staging_toggle) - map("n", "", 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) +return m diff --git a/lua/telescope/builtin/init.lua b/lua/telescope/builtin/init.lua index 71cb583..f25d059 100644 --- a/lua/telescope/builtin/init.lua +++ b/lua/telescope/builtin/init.lua @@ -60,7 +60,7 @@ end ---@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 @@ -72,7 +72,7 @@ builtin.live_grep = require_on_exported_call("telescope.builtin.files").live_gre ---@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 @@ -84,7 +84,7 @@ builtin.grep_string = require_on_exported_call("telescope.builtin.files").grep_s ---@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 +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 @@ -95,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) @@ -110,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 @@ -119,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 -- -- @@ -137,7 +137,7 @@ builtin.current_buffer_tags = require_on_exported_call("telescope.builtin.files" ---@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: @@ -149,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: @@ -162,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: @@ -176,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: @@ -186,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: @@ -195,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 -- -- @@ -207,14 +207,14 @@ builtin.git_stash = require_on_exported_call("telescope.builtin.git").stash ---@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) ---@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 +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: @@ -222,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. @@ -237,12 +237,12 @@ 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 `` ---@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 `` ---@param opts table: options to pass to the picker @@ -250,58 +250,58 @@ builtin.commands = require_on_exported_call("telescope.builtin.internal").comman ---@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 `` ---@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) ---@field fname_width number: defines the width of the filename section (default: 30) -builtin.loclist = require_on_exported_call("telescope.builtin.internal").loclist +builtin.loclist = require_on_exported_call("telescope.builtin.__internal").loclist --- Lists previously open files, opens on `` ---@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 `` --- - Default keymaps: --- - ``: 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 `` --- - Default keymaps: --- - ``: 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 `` ---@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 `` ---@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 `` ---@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 `` ---@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 `` ---@param opts table: options to pass to the picker @@ -312,58 +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 `` ---@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 `` ---@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 `` --- - Default keymaps: --- - ``: 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 `` ---@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 "" 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 `` ---@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 `` ---@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 `` ---@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 `` ---@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) ---@field fname_width number: defines the width of the filename section (default: 30) -builtin.tagstack = require_on_exported_call("telescope.builtin.internal").tagstack +builtin.tagstack = require_on_exported_call("telescope.builtin.__internal").tagstack --- Lists items from Vim's jumplist, jumps to location on `` ---@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) ---@field fname_width number: defines the width of the filename section (default: 30) -builtin.jumplist = require_on_exported_call("telescope.builtin.internal").jumplist +builtin.jumplist = require_on_exported_call("telescope.builtin.__internal").jumplist -- -- @@ -377,19 +377,19 @@ builtin.jumplist = require_on_exported_call("telescope.builtin.internal").jumpli ---@field include_current_line boolean: include current line (default: false) ---@field trim_text boolean: trim results text (default: false) ---@field fname_width number: defines the width of the filename section (default: 30) -builtin.lsp_references = require_on_exported_call("telescope.builtin.lsp").references +builtin.lsp_references = require_on_exported_call("telescope.builtin.__lsp").references --- Lists LSP incoming calls for word under the cursor, jumps to reference on `` ---@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 +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 `` ---@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 +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 @@ -397,7 +397,7 @@ builtin.lsp_outgoing_calls = require_on_exported_call("telescope.builtin.lsp").o ---@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) -builtin.lsp_definitions = require_on_exported_call("telescope.builtin.lsp").definitions +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 @@ -406,7 +406,7 @@ builtin.lsp_definitions = require_on_exported_call("telescope.builtin.lsp").defi ---@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) -builtin.lsp_type_definitions = require("telescope.builtin.lsp").type_definitions +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 @@ -414,7 +414,7 @@ builtin.lsp_type_definitions = require("telescope.builtin.lsp").type_definitions ---@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) -builtin.lsp_implementations = require_on_exported_call("telescope.builtin.lsp").implementations +builtin.lsp_implementations = require_on_exported_call("telescope.builtin.__lsp").implementations --- Lists LSP document symbols in the current buffer --- - Default keymaps: @@ -424,7 +424,7 @@ builtin.lsp_implementations = require_on_exported_call("telescope.builtin.lsp"). ---@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: @@ -435,7 +435,7 @@ builtin.lsp_document_symbols = require_on_exported_call("telescope.builtin.lsp") ---@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: @@ -445,7 +445,7 @@ builtin.lsp_workspace_symbols = require_on_exported_call("telescope.builtin.lsp" ---@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 -- -- @@ -468,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 4277e2e..b4d4197 100644 --- a/lua/telescope/builtin/internal.lua +++ b/lua/telescope/builtin/internal.lua @@ -1,1318 +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 = 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), +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 - -- 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 + return require("telescope.builtin")[k] + end, +}) - -- 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", "", actions.remove_selected_picker) - map("n", "", 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", "", actions.set_command_line) - map("n", "", actions.set_command_line) - map("n", "", actions.edit_command_line) - map("i", "", actions.edit_command_line) - - -- TODO: Find a way to insert the text... it seems hard. - -- map('i', '', actions.insert_value, { expr = true }) - - return true - end, - }):find() -end - -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", "", actions.set_search_line) - map("n", "", actions.set_search_line) - map("n", "", actions.edit_search_line) - map("i", "", actions.edit_search_line) - - -- TODO: Find a way to insert the text... it seems hard. - -- map('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("", 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", "", 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, "") 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 ~= "" 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 "" - 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) +return m diff --git a/lua/telescope/builtin/lsp.lua b/lua/telescope/builtin/lsp.lua index 480d456..8c297dc 100644 --- a/lua/telescope/builtin/lsp.lua +++ b/lua/telescope/builtin/lsp.lua @@ -1,389 +1,17 @@ -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 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, +}) -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", "", 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) +return m