Regenerate nvim config
This commit is contained in:
@ -0,0 +1,324 @@
|
||||
local uv = vim.loop
|
||||
|
||||
local Object = require "plenary.class"
|
||||
local log = require "plenary.log"
|
||||
|
||||
local async = require "plenary.async"
|
||||
local channel = require("plenary.async").control.channel
|
||||
local utils = require "telescope.utils"
|
||||
|
||||
local M = {}
|
||||
|
||||
local AsyncJob = {}
|
||||
AsyncJob.__index = AsyncJob
|
||||
|
||||
function AsyncJob.new(opts)
|
||||
local self = setmetatable({}, AsyncJob)
|
||||
|
||||
self.command, self.uv_opts = M.convert_opts(opts)
|
||||
|
||||
self.stdin = opts.stdin or M.NullPipe()
|
||||
self.stdout = opts.stdout or M.NullPipe()
|
||||
self.stderr = opts.stderr or M.NullPipe()
|
||||
|
||||
if opts.cwd and opts.cwd ~= "" then
|
||||
self.uv_opts.cwd = utils.path_expand(opts.cwd)
|
||||
-- this is a "illegal" hack for windows. E.g. If the git command returns `/` rather than `\` as delimiter,
|
||||
-- vim.fn.expand might just end up returning an empty string. Weird
|
||||
-- Because empty string is not allowed in libuv the job will not spawn. Solution is we just set it to opts.cwd
|
||||
if self.uv_opts.cwd == "" then
|
||||
self.uv_opts.cwd = opts.cwd
|
||||
end
|
||||
end
|
||||
|
||||
self.uv_opts.stdio = {
|
||||
self.stdin.handle,
|
||||
self.stdout.handle,
|
||||
self.stderr.handle,
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AsyncJob:_for_each_pipe(f, ...)
|
||||
for _, pipe in ipairs { self.stdin, self.stdout, self.stderr } do
|
||||
f(pipe, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function AsyncJob:close(force)
|
||||
if force == nil then
|
||||
force = true
|
||||
end
|
||||
|
||||
self:_for_each_pipe(function(p)
|
||||
p:close(force)
|
||||
end)
|
||||
|
||||
uv.process_kill(self.handle, "sigterm")
|
||||
|
||||
log.debug "[async_job] closed"
|
||||
end
|
||||
|
||||
M.spawn = function(opts)
|
||||
local self = AsyncJob.new(opts)
|
||||
self.handle, self.pid = uv.spawn(
|
||||
self.command,
|
||||
self.uv_opts,
|
||||
async.void(function()
|
||||
self:close(false)
|
||||
if not self.handle:is_closing() then
|
||||
self.handle:close()
|
||||
end
|
||||
end)
|
||||
)
|
||||
|
||||
if not self.handle then
|
||||
error(debug.traceback("Failed to spawn process: " .. vim.inspect(self)))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@class uv_pipe_t
|
||||
--- A pipe handle from libuv
|
||||
---@field read_start function: Start reading
|
||||
---@field read_stop function: Stop reading
|
||||
---@field close function: Close the handle
|
||||
---@field is_closing function: Whether handle is currently closing
|
||||
---@field is_active function: Whether the handle is currently reading
|
||||
|
||||
---@class BasePipe
|
||||
---@field super Object: Always available
|
||||
---@field handle uv_pipe_t: A pipe handle
|
||||
---@field extend function: Extend
|
||||
local BasePipe = Object:extend()
|
||||
|
||||
function BasePipe:new()
|
||||
self.eof_tx, self.eof_rx = channel.oneshot()
|
||||
end
|
||||
|
||||
function BasePipe:close(force)
|
||||
if force == nil then
|
||||
force = true
|
||||
end
|
||||
|
||||
assert(self.handle, "Must have a pipe to close. Otherwise it's weird!")
|
||||
|
||||
if self.handle:is_closing() then
|
||||
return
|
||||
end
|
||||
|
||||
-- If we're not forcing the stop, allow waiting for eof
|
||||
-- This ensures that we don't end up with weird race conditions
|
||||
if not force then
|
||||
self.eof_rx()
|
||||
end
|
||||
|
||||
self.handle:read_stop()
|
||||
if not self.handle:is_closing() then
|
||||
self.handle:close()
|
||||
end
|
||||
|
||||
self._closed = true
|
||||
end
|
||||
|
||||
---@class LinesPipe : BasePipe
|
||||
local LinesPipe = BasePipe:extend()
|
||||
|
||||
function LinesPipe:new()
|
||||
LinesPipe.super.new(self)
|
||||
self.handle = uv.new_pipe(false)
|
||||
end
|
||||
|
||||
function LinesPipe:read()
|
||||
local read_tx, read_rx = channel.oneshot()
|
||||
|
||||
self.handle:read_start(function(err, data)
|
||||
assert(not err, err)
|
||||
self.handle:read_stop()
|
||||
|
||||
read_tx(data)
|
||||
if data == nil then
|
||||
self.eof_tx()
|
||||
end
|
||||
end)
|
||||
|
||||
return read_rx()
|
||||
end
|
||||
|
||||
function LinesPipe:iter(schedule)
|
||||
if schedule == nil then
|
||||
schedule = true
|
||||
end
|
||||
|
||||
local text = nil
|
||||
local index = nil
|
||||
|
||||
local get_next_text = function(previous)
|
||||
index = nil
|
||||
|
||||
local read = self:read()
|
||||
if previous == nil and read == nil then
|
||||
return
|
||||
end
|
||||
|
||||
read = string.gsub(read or "", "\r", "")
|
||||
return (previous or "") .. read
|
||||
end
|
||||
|
||||
local next_value = nil
|
||||
next_value = function()
|
||||
if schedule then
|
||||
async.util.scheduler()
|
||||
end
|
||||
|
||||
if text == nil or (text == "" and index == nil) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local start = index
|
||||
index = string.find(text, "\n", index, true)
|
||||
|
||||
if index == nil then
|
||||
text = get_next_text(string.sub(text, start or 1))
|
||||
return next_value()
|
||||
end
|
||||
|
||||
index = index + 1
|
||||
|
||||
return string.sub(text, start or 1, index - 2)
|
||||
end
|
||||
|
||||
text = get_next_text()
|
||||
|
||||
return function()
|
||||
return next_value()
|
||||
end
|
||||
end
|
||||
|
||||
---@class NullPipe : BasePipe
|
||||
local NullPipe = BasePipe:extend()
|
||||
|
||||
function NullPipe:new()
|
||||
NullPipe.super.new(self)
|
||||
self.start = function() end
|
||||
self.read_start = function() end
|
||||
self.close = function() end
|
||||
|
||||
-- This always has eof tx done, so can just call it now
|
||||
self.eof_tx()
|
||||
end
|
||||
|
||||
---@class ChunkPipe : BasePipe
|
||||
local ChunkPipe = BasePipe:extend()
|
||||
|
||||
function ChunkPipe:new()
|
||||
ChunkPipe.super.new(self)
|
||||
self.handle = uv.new_pipe(false)
|
||||
end
|
||||
|
||||
function ChunkPipe:read()
|
||||
local read_tx, read_rx = channel.oneshot()
|
||||
|
||||
self.handle:read_start(function(err, data)
|
||||
assert(not err, err)
|
||||
self.handle:read_stop()
|
||||
|
||||
read_tx(data)
|
||||
if data == nil then
|
||||
self.eof_tx()
|
||||
end
|
||||
end)
|
||||
|
||||
return read_rx()
|
||||
end
|
||||
|
||||
function ChunkPipe:iter()
|
||||
return function()
|
||||
if self._closed then
|
||||
return nil
|
||||
end
|
||||
|
||||
return self:read()
|
||||
end
|
||||
end
|
||||
|
||||
---@class ErrorPipe : BasePipe
|
||||
local ErrorPipe = BasePipe:extend()
|
||||
|
||||
function ErrorPipe:new()
|
||||
ErrorPipe.super.new(self)
|
||||
self.handle = uv.new_pipe(false)
|
||||
end
|
||||
|
||||
function ErrorPipe:start()
|
||||
self.handle:read_start(function(err, data)
|
||||
if not err and not data then
|
||||
return
|
||||
end
|
||||
|
||||
self.handle:read_stop()
|
||||
self.handle:close()
|
||||
|
||||
error(string.format("Err: %s, Data: '%s'", err, data))
|
||||
end)
|
||||
end
|
||||
|
||||
M.NullPipe = NullPipe
|
||||
M.LinesPipe = LinesPipe
|
||||
M.ChunkPipe = ChunkPipe
|
||||
M.ErrorPipe = ErrorPipe
|
||||
|
||||
M.convert_opts = function(o)
|
||||
if not o then
|
||||
error(debug.traceback "Options are required for Job:new")
|
||||
end
|
||||
|
||||
local command = o.command
|
||||
if not command then
|
||||
if o[1] then
|
||||
command = o[1]
|
||||
else
|
||||
error(debug.traceback "'command' is required for Job:new")
|
||||
end
|
||||
elseif o[1] then
|
||||
error(debug.traceback "Cannot pass both 'command' and array args")
|
||||
end
|
||||
|
||||
local args = o.args
|
||||
if not args then
|
||||
if #o > 1 then
|
||||
args = { select(2, unpack(o)) }
|
||||
end
|
||||
end
|
||||
|
||||
local ok, is_exe = pcall(vim.fn.executable, command)
|
||||
if not o.skip_validation and ok and 1 ~= is_exe then
|
||||
error(debug.traceback(command .. ": Executable not found"))
|
||||
end
|
||||
|
||||
local obj = {}
|
||||
|
||||
obj.args = args
|
||||
|
||||
if o.env then
|
||||
if type(o.env) ~= "table" then
|
||||
error(debug.traceback "'env' has to be a table")
|
||||
end
|
||||
|
||||
local transform = {}
|
||||
for k, v in pairs(o.env) do
|
||||
if type(k) == "number" then
|
||||
table.insert(transform, v)
|
||||
elseif type(k) == "string" then
|
||||
table.insert(transform, k .. "=" .. tostring(v))
|
||||
end
|
||||
end
|
||||
obj.env = transform
|
||||
end
|
||||
|
||||
return command, obj
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,73 @@
|
||||
local extensions = {}
|
||||
|
||||
extensions._loaded = {}
|
||||
extensions._config = {}
|
||||
extensions._health = {}
|
||||
|
||||
local load_extension = function(name)
|
||||
local ok, ext = pcall(require, "telescope._extensions." .. name)
|
||||
if not ok then
|
||||
error(string.format("'%s' extension doesn't exist or isn't installed: %s", name, ext))
|
||||
end
|
||||
return ext
|
||||
end
|
||||
|
||||
extensions.manager = setmetatable({}, {
|
||||
__index = function(t, k)
|
||||
local ext = load_extension(k)
|
||||
t[k] = ext.exports or {}
|
||||
if ext.setup then
|
||||
ext.setup(extensions._config[k] or {}, require("telescope.config").values)
|
||||
end
|
||||
extensions._health[k] = ext.health
|
||||
|
||||
return t[k]
|
||||
end,
|
||||
})
|
||||
|
||||
--- Register an extension module.
|
||||
---
|
||||
--- Extensions have several important keys.
|
||||
--- - setup:
|
||||
--- function(ext_config, config) -> nil
|
||||
---
|
||||
--- Called when first loading the extension.
|
||||
--- The first parameter is the config passed by the user
|
||||
--- in telescope setup. The second parameter is the resulting
|
||||
--- config.values after applying the users setup defaults.
|
||||
---
|
||||
--- It is acceptable for a plugin to override values in config,
|
||||
--- as some plugins will be installed simply to manage some setup,
|
||||
--- install some sorter, etc.
|
||||
---
|
||||
--- - exports:
|
||||
--- table
|
||||
---
|
||||
--- Only the items in `exports` will be exposed on the resulting
|
||||
--- module that users can access via require('telescope').extensions.foo
|
||||
--- Also, any top-level key-value pairs in exports where the value is a function and the
|
||||
--- key doesn't start with an underscore will be included when calling the `builtin` picker
|
||||
--- with the `include_extensions` option enabled.
|
||||
---
|
||||
--- Other things in the module will not be accessible. This is the public API
|
||||
--- for your extension. Consider not breaking it a lot :laugh:
|
||||
---
|
||||
--- TODO:
|
||||
--- - actions
|
||||
extensions.register = function(mod)
|
||||
return mod
|
||||
end
|
||||
|
||||
extensions.load = function(name)
|
||||
local ext = load_extension(name)
|
||||
if ext.setup then
|
||||
ext.setup(extensions._config[name] or {}, require("telescope.config").values)
|
||||
end
|
||||
return extensions.manager[name]
|
||||
end
|
||||
|
||||
extensions.set_config = function(extensions_config)
|
||||
extensions._config = extensions_config or {}
|
||||
end
|
||||
|
||||
return extensions
|
||||
@ -0,0 +1,119 @@
|
||||
---@tag telescope.actions.generate
|
||||
---@config { ["module"] = "telescope.actions.generate", ["name"] = "ACTIONS_GENERATE" }
|
||||
|
||||
---@brief [[
|
||||
--- Module for convenience to override defaults of corresponding |telescope.actions| at |telescope.setup()|.
|
||||
---
|
||||
--- General usage:
|
||||
--- <code>
|
||||
--- require("telescope").setup {
|
||||
--- defaults = {
|
||||
--- mappings = {
|
||||
--- n = {
|
||||
--- ["?"] = action_generate.which_key {
|
||||
--- name_width = 20, -- typically leads to smaller floats
|
||||
--- max_height = 0.5, -- increase potential maximum height
|
||||
--- separator = " > ", -- change sep between mode, keybind, and name
|
||||
--- close_with_action = false, -- do not close float on action
|
||||
--- },
|
||||
--- },
|
||||
--- },
|
||||
--- },
|
||||
--- }
|
||||
--- </code>
|
||||
---@brief ]]
|
||||
|
||||
local actions = require "telescope.actions"
|
||||
local config = require "telescope.config"
|
||||
local action_state = require "telescope.actions.state"
|
||||
local finders = require "telescope.finders"
|
||||
|
||||
local action_generate = {}
|
||||
|
||||
--- Display the keymaps of registered actions similar to which-key.nvim.<br>
|
||||
--- - Floating window:
|
||||
--- - Appears on the opposite side of the prompt.
|
||||
--- - Resolves to minimum required number of lines to show hints with `opts` or truncates entries at `max_height`.
|
||||
--- - Closes automatically on action call and can be disabled with by setting `close_with_action` to false.
|
||||
---@param opts table: options to pass to toggling registered actions
|
||||
---@field max_height number: % of max. height or no. of rows for hints (default: 0.4), see |resolver.resolve_height()|
|
||||
---@field only_show_current_mode boolean: only show keymaps for the current mode (default: true)
|
||||
---@field mode_width number: fixed width of mode to be shown (default: 1)
|
||||
---@field keybind_width number: fixed width of keybind to be shown (default: 7)
|
||||
---@field name_width number: fixed width of action name to be shown (default: 30)
|
||||
---@field column_padding string: string to split; can be used for vertical separator (default: " ")
|
||||
---@field mode_hl string: hl group of mode (default: TelescopeResultsConstant)
|
||||
---@field keybind_hl string: hl group of keybind (default: TelescopeResultsVariable)
|
||||
---@field name_hl string: hl group of action name (default: TelescopeResultsFunction)
|
||||
---@field column_indent number: number of left-most spaces before keybinds are shown (default: 4)
|
||||
---@field line_padding number: row padding in top and bottom of float (default: 1)
|
||||
---@field separator string: separator string between mode, key bindings, and action (default: " -> ")
|
||||
---@field close_with_action boolean: registered action will close keymap float (default: true)
|
||||
---@field normal_hl string: winhl of "Normal" for keymap hints floating window (default: "TelescopePrompt")
|
||||
---@field border_hl string: winhl of "Normal" for keymap borders (default: "TelescopePromptBorder")
|
||||
---@field winblend number: pseudo-transparency of keymap hints floating window
|
||||
---@field zindex number: z-index of keymap hints floating window (default: 100)
|
||||
action_generate.which_key = function(opts)
|
||||
local which_key = function(prompt_bufnr)
|
||||
actions.which_key(prompt_bufnr, opts)
|
||||
end
|
||||
return which_key
|
||||
end
|
||||
|
||||
action_generate.refine = function(prompt_bufnr, opts)
|
||||
opts = opts or {}
|
||||
opts.prompt_to_prefix = vim.F.if_nil(opts.prompt_to_prefix, false)
|
||||
opts.prefix_hl_group = vim.F.if_nil(opts.prompt_hl_group, "TelescopePromptPrefix")
|
||||
opts.prompt_prefix = vim.F.if_nil(opts.prompt_prefix, config.values.prompt_prefix)
|
||||
opts.reset_multi_selection = vim.F.if_nil(opts.reset_multi_selection, false)
|
||||
opts.reset_prompt = vim.F.if_nil(opts.reset_prompt, true)
|
||||
opts.sorter = vim.F.if_nil(opts.sorter, config.values.generic_sorter {})
|
||||
local push_history = vim.F.if_nil(opts.push_history, true)
|
||||
|
||||
local current_picker = action_state.get_current_picker(prompt_bufnr)
|
||||
local current_line = action_state.get_current_line()
|
||||
if push_history then
|
||||
action_state.get_current_history():append(current_line, current_picker)
|
||||
end
|
||||
|
||||
-- title
|
||||
if opts.prompt_title and current_picker.layout.prompt.border then
|
||||
current_picker.layout.prompt.border:change_title(opts.prompt_title)
|
||||
end
|
||||
|
||||
if opts.results_title and current_picker.layout.results.border then
|
||||
current_picker.layout.results.border:change_title(opts.results_title)
|
||||
end
|
||||
|
||||
local results = {}
|
||||
for entry in current_picker.manager:iter() do
|
||||
table.insert(results, entry)
|
||||
end
|
||||
|
||||
-- if opts.sorter == false, keep older sorter
|
||||
if opts.sorter then
|
||||
current_picker.sorter:_destroy()
|
||||
current_picker.sorter = opts.sorter
|
||||
current_picker.sorter:_init()
|
||||
end
|
||||
|
||||
local new_finder = finders.new_table {
|
||||
results = results,
|
||||
entry_maker = function(x)
|
||||
return x
|
||||
end,
|
||||
}
|
||||
|
||||
if not opts.reset_multi_selection and current_line ~= "" then
|
||||
opts.multi = current_picker._multi
|
||||
end
|
||||
|
||||
if opts.prompt_to_prefix then
|
||||
local current_prefix = current_picker.prompt_prefix
|
||||
local suffix = current_prefix ~= opts.prompt_prefix and current_prefix or ""
|
||||
opts.new_prefix = suffix .. current_line .. " " .. opts.prompt_prefix
|
||||
end
|
||||
current_picker:refresh(new_finder, opts)
|
||||
end
|
||||
|
||||
return action_generate
|
||||
@ -0,0 +1,217 @@
|
||||
local conf = require("telescope.config").values
|
||||
local Path = require "plenary.path"
|
||||
local utils = require "telescope.utils"
|
||||
|
||||
local uv = vim.loop
|
||||
|
||||
---@tag telescope.actions.history
|
||||
---@config { ["module"] = "telescope.actions.history" }
|
||||
|
||||
---@brief [[
|
||||
--- A base implementation of a prompt history that provides a simple history
|
||||
--- and can be replaced with a custom implementation.
|
||||
---
|
||||
--- For example: We provide an extension for a smart history that uses sql.nvim
|
||||
--- to map histories to metadata, like the calling picker or cwd.
|
||||
---
|
||||
--- So you have a history for:
|
||||
--- - find_files project_1
|
||||
--- - grep_string project_1
|
||||
--- - live_grep project_1
|
||||
--- - find_files project_2
|
||||
--- - grep_string project_2
|
||||
--- - live_grep project_2
|
||||
--- - etc
|
||||
---
|
||||
--- See https://github.com/nvim-telescope/telescope-smart-history.nvim
|
||||
---@brief ]]
|
||||
|
||||
-- TODO(conni2461): currently not present in plenary path only sync.
|
||||
-- But sync is just unnecessary here
|
||||
local write_async = function(path, txt, flag)
|
||||
uv.fs_open(path, flag, 438, function(open_err, fd)
|
||||
assert(not open_err, open_err)
|
||||
uv.fs_write(fd, txt, -1, function(write_err)
|
||||
assert(not write_err, write_err)
|
||||
uv.fs_close(fd, function(close_err)
|
||||
assert(not close_err, close_err)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
local append_async = function(path, txt)
|
||||
write_async(path, txt, "a")
|
||||
end
|
||||
|
||||
local histories = {}
|
||||
|
||||
--- Manages prompt history
|
||||
---@class History @Manages prompt history
|
||||
---@field enabled boolean: Will indicate if History is enabled or disabled
|
||||
---@field path string: Will point to the location of the history file
|
||||
---@field limit string: Will have the limit of the history. Can be nil, if limit is disabled.
|
||||
---@field content table: History table. Needs to be filled by your own History implementation
|
||||
---@field index number: Used to keep track of the next or previous index. Default is #content + 1
|
||||
---@field cycle_wrap boolean: Controls if history will wrap on reaching beginning or end
|
||||
histories.History = {}
|
||||
histories.History.__index = histories.History
|
||||
|
||||
--- Create a new History
|
||||
---@param opts table: Defines the behavior of History
|
||||
---@field init function: Will be called after handling configuration (required)
|
||||
---@field append function: How to append a new prompt item (required)
|
||||
---@field reset function: What happens on reset. Will be called when telescope closes (required)
|
||||
---@field pre_get function: Will be called before a next or previous item will be returned (optional)
|
||||
function histories.History:new(opts)
|
||||
local obj = {}
|
||||
if conf.history == false or type(conf.history) ~= "table" then
|
||||
obj.enabled = false
|
||||
return setmetatable(obj, self)
|
||||
end
|
||||
obj.enabled = true
|
||||
if conf.history.limit then
|
||||
obj.limit = conf.history.limit
|
||||
end
|
||||
obj.path = utils.path_expand(conf.history.path)
|
||||
obj.content = {}
|
||||
obj.index = 1
|
||||
obj.cycle_wrap = conf.history.cycle_wrap
|
||||
|
||||
opts.init(obj)
|
||||
obj._reset = opts.reset
|
||||
obj._append = opts.append
|
||||
obj._pre_get = opts.pre_get
|
||||
|
||||
return setmetatable(obj, self)
|
||||
end
|
||||
|
||||
--- Shorthand to create a new history
|
||||
function histories.new(...)
|
||||
return histories.History:new(...)
|
||||
end
|
||||
|
||||
--- Will reset the history index to the default initial state. Will happen after the picker closed
|
||||
function histories.History:reset()
|
||||
if not self.enabled then
|
||||
return
|
||||
end
|
||||
self._reset(self)
|
||||
end
|
||||
|
||||
--- Append a new line to the history
|
||||
---@param line string: current line that will be appended
|
||||
---@param picker table: the current picker object
|
||||
---@param no_reset boolean: On default it will reset the state at the end. If you don't want to do this set to true
|
||||
function histories.History:append(line, picker, no_reset)
|
||||
if not self.enabled then
|
||||
return
|
||||
end
|
||||
self._append(self, line, picker, no_reset)
|
||||
end
|
||||
|
||||
--- Will return the next history item. Can be nil if there are no next items
|
||||
---@param line string: the current line
|
||||
---@param picker table: the current picker object
|
||||
---@return string: the next history item
|
||||
function histories.History:get_next(line, picker)
|
||||
if not self.enabled then
|
||||
utils.notify("History:get_next", {
|
||||
msg = "You are cycling to next the history item but history is disabled. Read ':help telescope.defaults.history'",
|
||||
level = "WARN",
|
||||
})
|
||||
return false
|
||||
end
|
||||
if self._pre_get then
|
||||
self._pre_get(self, line, picker)
|
||||
end
|
||||
|
||||
local next_idx = self.index + 1
|
||||
if next_idx > #self.content and self.cycle_wrap then
|
||||
next_idx = 1
|
||||
end
|
||||
|
||||
if next_idx <= #self.content then
|
||||
self.index = next_idx
|
||||
return self.content[next_idx]
|
||||
end
|
||||
self.index = #self.content + 1
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Will return the previous history item. Can be nil if there are no previous items
|
||||
---@param line string: the current line
|
||||
---@param picker table: the current picker object
|
||||
---@return string: the previous history item
|
||||
function histories.History:get_prev(line, picker)
|
||||
if not self.enabled then
|
||||
utils.notify("History:get_prev", {
|
||||
msg = "You are cycling to next the history item but history is disabled. Read ':help telescope.defaults.history'",
|
||||
level = "WARN",
|
||||
})
|
||||
return false
|
||||
end
|
||||
if self._pre_get then
|
||||
self._pre_get(self, line, picker)
|
||||
end
|
||||
|
||||
local next_idx = self.index - 1
|
||||
if next_idx < 1 and self.cycle_wrap then
|
||||
next_idx = #self.content
|
||||
end
|
||||
|
||||
if self.index == #self.content + 1 then
|
||||
if line ~= "" then
|
||||
self:append(line, picker, true)
|
||||
end
|
||||
end
|
||||
if next_idx >= 1 then
|
||||
self.index = next_idx
|
||||
return self.content[next_idx]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- A simple implementation of history.
|
||||
---
|
||||
--- It will keep one unified history across all pickers.
|
||||
histories.get_simple_history = function()
|
||||
return histories.new {
|
||||
init = function(obj)
|
||||
local p = Path:new(obj.path)
|
||||
if not p:exists() then
|
||||
p:touch { parents = true }
|
||||
end
|
||||
|
||||
obj.content = Path:new(obj.path):readlines()
|
||||
obj.index = #obj.content
|
||||
table.remove(obj.content, obj.index)
|
||||
end,
|
||||
reset = function(self)
|
||||
self.index = #self.content + 1
|
||||
end,
|
||||
append = function(self, line, _, no_reset)
|
||||
if line ~= "" then
|
||||
if self.content[#self.content] ~= line then
|
||||
table.insert(self.content, line)
|
||||
|
||||
local len = #self.content
|
||||
if self.limit and len > self.limit then
|
||||
local diff = len - self.limit
|
||||
for i = diff, 1, -1 do
|
||||
table.remove(self.content, i)
|
||||
end
|
||||
write_async(self.path, table.concat(self.content, "\n") .. "\n", "w")
|
||||
else
|
||||
append_async(self.path, line .. "\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
if not no_reset then
|
||||
self:reset()
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return histories
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,152 @@
|
||||
---@tag telescope.actions.layout
|
||||
---@config { ["module"] = "telescope.actions.layout", ["name"] = "ACTIONS_LAYOUT" }
|
||||
|
||||
---@brief [[
|
||||
--- The layout actions are actions to be used to change the layout of a picker.
|
||||
---@brief ]]
|
||||
|
||||
local action_state = require "telescope.actions.state"
|
||||
local state = require "telescope.state"
|
||||
local layout_strats = require "telescope.pickers.layout_strategies"
|
||||
|
||||
local transform_mod = require("telescope.actions.mt").transform_mod
|
||||
|
||||
local action_layout = setmetatable({}, {
|
||||
__index = function(_, k)
|
||||
error("'telescope.actions.layout' does not have a value: " .. tostring(k))
|
||||
end,
|
||||
})
|
||||
|
||||
--- Toggle preview window.
|
||||
--- - Note: preview window can be toggled even if preview is set to false.
|
||||
---
|
||||
--- This action is not mapped by default.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
action_layout.toggle_preview = function(prompt_bufnr)
|
||||
local picker = action_state.get_current_picker(prompt_bufnr)
|
||||
local status = state.get_status(picker.prompt_bufnr)
|
||||
|
||||
local preview_winid = status.layout.preview and status.layout.preview.winid
|
||||
if picker.previewer and preview_winid then
|
||||
picker.hidden_previewer = picker.previewer
|
||||
picker.previewer = nil
|
||||
elseif picker.hidden_previewer and not preview_winid then
|
||||
picker.previewer = picker.hidden_previewer
|
||||
picker.hidden_previewer = nil
|
||||
else
|
||||
return
|
||||
end
|
||||
picker:full_layout_update()
|
||||
end
|
||||
|
||||
-- TODO IMPLEMENT (mentored project available, contact @l-kershaw)
|
||||
action_layout.toggle_padding = function(prompt_bufnr)
|
||||
local picker = action_state.get_current_picker(prompt_bufnr)
|
||||
-- if padding ~= 0
|
||||
-- 1. Save `height` and `width` of picker
|
||||
-- 2. Set both to `{padding = 0}`
|
||||
-- else
|
||||
-- 1. Lookup previous `height` and `width` of picker
|
||||
-- 2. Set both to previous values
|
||||
picker:full_layout_update()
|
||||
end
|
||||
|
||||
--- Toggles the `prompt_position` option between "top" and "bottom".
|
||||
--- Checks if `prompt_position` is an option for the current layout.
|
||||
---
|
||||
--- This action is not mapped by default.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
action_layout.toggle_prompt_position = function(prompt_bufnr)
|
||||
local picker = action_state.get_current_picker(prompt_bufnr)
|
||||
picker.layout_config = picker.layout_config or {}
|
||||
picker.layout_config[picker.layout_strategy] = picker.layout_config[picker.layout_strategy] or {}
|
||||
-- flex layout is weird and needs handling separately
|
||||
if picker.layout_strategy == "flex" then
|
||||
picker.layout_config.flex.horizontal = picker.layout_config.flex.horizontal or {}
|
||||
picker.layout_config.flex.vertical = picker.layout_config.flex.vertical or {}
|
||||
local old_pos = vim.F.if_nil(
|
||||
picker.layout_config.flex[picker.__flex_strategy].prompt_position,
|
||||
picker.layout_config[picker.__flex_strategy].prompt_position
|
||||
)
|
||||
local new_pos = old_pos == "top" and "bottom" or "top"
|
||||
picker.layout_config[picker.__flex_strategy].prompt_position = new_pos
|
||||
picker.layout_config.flex[picker.__flex_strategy].prompt_position = new_pos
|
||||
picker:full_layout_update()
|
||||
elseif layout_strats._configurations[picker.layout_strategy].prompt_position then
|
||||
if picker.layout_config.prompt_position == "top" then
|
||||
picker.layout_config.prompt_position = "bottom"
|
||||
picker.layout_config[picker.layout_strategy].prompt_position = "bottom"
|
||||
else
|
||||
picker.layout_config.prompt_position = "top"
|
||||
picker.layout_config[picker.layout_strategy].prompt_position = "top"
|
||||
end
|
||||
picker:full_layout_update()
|
||||
end
|
||||
end
|
||||
|
||||
--- Toggles the `mirror` option between `true` and `false`.
|
||||
--- Checks if `mirror` is an option for the current layout.
|
||||
---
|
||||
--- This action is not mapped by default.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
action_layout.toggle_mirror = function(prompt_bufnr)
|
||||
local picker = action_state.get_current_picker(prompt_bufnr)
|
||||
-- flex layout is weird and needs handling separately
|
||||
if picker.layout_strategy == "flex" then
|
||||
picker.layout_config.flex.horizontal = picker.layout_config.flex.horizontal or {}
|
||||
picker.layout_config.flex.vertical = picker.layout_config.flex.vertical or {}
|
||||
local new_mirror = not picker.layout_config.flex[picker.__flex_strategy].mirror
|
||||
picker.layout_config[picker.__flex_strategy].mirror = new_mirror
|
||||
picker.layout_config.flex[picker.__flex_strategy].mirror = new_mirror
|
||||
picker:full_layout_update()
|
||||
elseif layout_strats._configurations[picker.layout_strategy].mirror then
|
||||
picker.layout_config = picker.layout_config or {}
|
||||
local new_mirror = not picker.layout_config.mirror
|
||||
picker.layout_config.mirror = new_mirror
|
||||
picker.layout_config[picker.layout_strategy] = picker.layout_config[picker.layout_strategy] or {}
|
||||
picker.layout_config[picker.layout_strategy].mirror = new_mirror
|
||||
picker:full_layout_update()
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper function for `cycle_layout_next` and `cycle_layout_prev`.
|
||||
local get_cycle_layout = function(dir)
|
||||
return function(prompt_bufnr)
|
||||
local picker = action_state.get_current_picker(prompt_bufnr)
|
||||
if picker.__layout_index then
|
||||
picker.__layout_index = ((picker.__layout_index + dir - 1) % #picker.__cycle_layout_list) + 1
|
||||
else
|
||||
picker.__layout_index = 1
|
||||
end
|
||||
local new_layout = picker.__cycle_layout_list[picker.__layout_index]
|
||||
if type(new_layout) == "string" then
|
||||
picker.layout_strategy = new_layout
|
||||
picker.layout_config = {}
|
||||
picker.previewer = picker.all_previewers and picker.all_previewers[1] or nil
|
||||
elseif type(new_layout) == "table" then
|
||||
picker.layout_strategy = new_layout.layout_strategy
|
||||
picker.layout_config = new_layout.layout_config or {}
|
||||
picker.previewer = (new_layout.previewer == nil and picker.all_previewers[picker.current_previewer_index])
|
||||
or new_layout.previewer
|
||||
else
|
||||
error("Not a valid layout setup: " .. vim.inspect(new_layout) .. "\nShould be a string or a table")
|
||||
end
|
||||
|
||||
picker:full_layout_update()
|
||||
end
|
||||
end
|
||||
|
||||
--- Cycles to the next layout in `cycle_layout_list`.
|
||||
---
|
||||
--- This action is not mapped by default.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
action_layout.cycle_layout_next = get_cycle_layout(1)
|
||||
|
||||
--- Cycles to the previous layout in `cycle_layout_list`.
|
||||
---
|
||||
--- This action is not mapped by default.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
action_layout.cycle_layout_prev = get_cycle_layout(-1)
|
||||
|
||||
action_layout = transform_mod(action_layout)
|
||||
return action_layout
|
||||
@ -0,0 +1,210 @@
|
||||
local action_mt = {}
|
||||
|
||||
--- Checks all replacement combinations to determine which function to run.
|
||||
--- If no replacement can be found, then it will run the original function
|
||||
local run_replace_or_original = function(replacements, original_func, ...)
|
||||
for _, replacement_map in ipairs(replacements or {}) do
|
||||
for condition, replacement in pairs(replacement_map) do
|
||||
if condition == true or condition(...) then
|
||||
return replacement(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return original_func(...)
|
||||
end
|
||||
|
||||
local append_action_copy = function(new, v, old)
|
||||
table.insert(new, v)
|
||||
new._func[v] = old._func[v]
|
||||
new._static_pre[v] = old._static_pre[v]
|
||||
new._pre[v] = old._pre[v]
|
||||
new._replacements[v] = old._replacements[v]
|
||||
new._static_post[v] = old._static_post[v]
|
||||
new._post[v] = old._post[v]
|
||||
end
|
||||
|
||||
-- TODO(conni2461): Not a fan of this solution/hack. Needs to be addressed
|
||||
local all_mts = {}
|
||||
|
||||
--TODO(conni2461): It gets worse. This is so bad but because we have now n mts for n actions
|
||||
-- We have to check all actions for relevant mts to set replace and before, after
|
||||
-- Its not bad for performance because its being called on startup when we attach mappings.
|
||||
-- Its just a bad solution
|
||||
local find_all_relevant_mts = function(action_name, f)
|
||||
for _, mt in ipairs(all_mts) do
|
||||
for fun, _ in pairs(mt._func) do
|
||||
if fun == action_name then
|
||||
f(mt)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- an action is metatable which allows replacement(prepend or append) of the function
|
||||
---@class Action
|
||||
---@field _func table<string, function>: the original action function
|
||||
---@field _static_pre table<string, function>: will allways run before the function even if its replaced
|
||||
---@field _pre table<string, function>: the functions that will run before the action
|
||||
---@field _replacements table<string, function>: the function that replaces this action
|
||||
---@field _static_post table<string, function>: will allways run after the function even if its replaced
|
||||
---@field _post table<string, function>: the functions that will run after the action
|
||||
action_mt.create = function()
|
||||
local mt = {
|
||||
__call = function(t, ...)
|
||||
local values = {}
|
||||
for _, action_name in ipairs(t) do
|
||||
if t._static_pre[action_name] then
|
||||
t._static_pre[action_name](...)
|
||||
end
|
||||
if vim.tbl_isempty(t._replacements) and t._pre[action_name] then
|
||||
t._pre[action_name](...)
|
||||
end
|
||||
|
||||
local result = {
|
||||
run_replace_or_original(t._replacements[action_name], t._func[action_name], ...),
|
||||
}
|
||||
for _, res in ipairs(result) do
|
||||
table.insert(values, res)
|
||||
end
|
||||
|
||||
if t._static_post[action_name] then
|
||||
t._static_post[action_name](...)
|
||||
end
|
||||
if vim.tbl_isempty(t._replacements) and t._post[action_name] then
|
||||
t._post[action_name](...)
|
||||
end
|
||||
end
|
||||
|
||||
return unpack(values)
|
||||
end,
|
||||
|
||||
__add = function(lhs, rhs)
|
||||
local new_action = setmetatable({}, action_mt.create())
|
||||
for _, v in ipairs(lhs) do
|
||||
append_action_copy(new_action, v, lhs)
|
||||
end
|
||||
|
||||
for _, v in ipairs(rhs) do
|
||||
append_action_copy(new_action, v, rhs)
|
||||
end
|
||||
new_action.clear = function()
|
||||
lhs.clear()
|
||||
rhs.clear()
|
||||
end
|
||||
|
||||
return new_action
|
||||
end,
|
||||
|
||||
_func = {},
|
||||
_static_pre = {},
|
||||
_pre = {},
|
||||
_replacements = {},
|
||||
_static_post = {},
|
||||
_post = {},
|
||||
}
|
||||
|
||||
mt.__index = mt
|
||||
|
||||
mt.clear = function()
|
||||
mt._pre = {}
|
||||
mt._replacements = {}
|
||||
mt._post = {}
|
||||
end
|
||||
|
||||
--- Replace the reference to the function with a new one temporarily
|
||||
function mt:replace(v)
|
||||
assert(#self == 1, "Cannot replace an already combined action")
|
||||
|
||||
return self:replace_map { [true] = v }
|
||||
end
|
||||
|
||||
function mt:replace_if(condition, replacement)
|
||||
assert(#self == 1, "Cannot replace an already combined action")
|
||||
|
||||
return self:replace_map { [condition] = replacement }
|
||||
end
|
||||
|
||||
--- Replace table with
|
||||
-- Example:
|
||||
--
|
||||
-- actions.select:replace_map {
|
||||
-- [function() return filetype == 'lua' end] = actions.file_split,
|
||||
-- [function() return filetype == 'other' end] = actions.file_split_edit,
|
||||
-- }
|
||||
function mt:replace_map(tbl)
|
||||
assert(#self == 1, "Cannot replace an already combined action")
|
||||
|
||||
local action_name = self[1]
|
||||
find_all_relevant_mts(action_name, function(another)
|
||||
if not another._replacements[action_name] then
|
||||
another._replacements[action_name] = {}
|
||||
end
|
||||
|
||||
table.insert(another._replacements[action_name], 1, tbl)
|
||||
end)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function mt:enhance(opts)
|
||||
assert(#self == 1, "Cannot enhance already combined actions")
|
||||
|
||||
local action_name = self[1]
|
||||
find_all_relevant_mts(action_name, function(another)
|
||||
if opts.pre then
|
||||
another._pre[action_name] = opts.pre
|
||||
end
|
||||
|
||||
if opts.post then
|
||||
another._post[action_name] = opts.post
|
||||
end
|
||||
end)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
table.insert(all_mts, mt)
|
||||
return mt
|
||||
end
|
||||
|
||||
action_mt.transform = function(k, mt, _, v)
|
||||
local res = setmetatable({ k }, mt)
|
||||
if type(v) == "table" then
|
||||
res._static_pre[k] = v.pre
|
||||
res._static_post[k] = v.post
|
||||
res._func[k] = v.action
|
||||
else
|
||||
res._func[k] = v
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
action_mt.transform_mod = function(mod)
|
||||
-- Pass the metatable of the module if applicable.
|
||||
-- This allows for custom errors, lookups, etc.
|
||||
local redirect = setmetatable({}, getmetatable(mod) or {})
|
||||
|
||||
for k, v in pairs(mod) do
|
||||
local mt = action_mt.create()
|
||||
redirect[k] = action_mt.transform(k, mt, _, v)
|
||||
end
|
||||
|
||||
redirect._clear = function()
|
||||
for k, v in pairs(redirect) do
|
||||
if k ~= "_clear" then
|
||||
pcall(v.clear)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return redirect
|
||||
end
|
||||
|
||||
action_mt.clear_all = function()
|
||||
for _, v in ipairs(all_mts) do
|
||||
pcall(v.clear)
|
||||
end
|
||||
end
|
||||
|
||||
return action_mt
|
||||
@ -0,0 +1,316 @@
|
||||
---@tag telescope.actions.set
|
||||
---@config { ["module"] = "telescope.actions.set", ["name"] = "ACTIONS_SET" }
|
||||
|
||||
---@brief [[
|
||||
--- Telescope action sets are used to provide an interface for managing
|
||||
--- actions that all primarily do the same thing, but with slight tweaks.
|
||||
---
|
||||
--- For example, when editing files you may want it in the current split,
|
||||
--- a vertical split, etc. Instead of making users have to overwrite EACH
|
||||
--- of those every time they want to change this behavior, they can instead
|
||||
--- replace the `set` itself and then it will work great and they're done.
|
||||
---@brief ]]
|
||||
|
||||
local a = vim.api
|
||||
|
||||
local log = require "telescope.log"
|
||||
local Path = require "plenary.path"
|
||||
local state = require "telescope.state"
|
||||
local utils = require "telescope.utils"
|
||||
|
||||
local action_state = require "telescope.actions.state"
|
||||
|
||||
local transform_mod = require("telescope.actions.mt").transform_mod
|
||||
|
||||
local action_set = setmetatable({}, {
|
||||
__index = function(_, k)
|
||||
error("'telescope.actions.set' does not have a value: " .. tostring(k))
|
||||
end,
|
||||
})
|
||||
|
||||
--- Move the current selection of a picker {change} rows.
|
||||
--- Handles not overflowing / underflowing the list.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
---@param change number: The amount to shift the selection by
|
||||
action_set.shift_selection = function(prompt_bufnr, change)
|
||||
local count = vim.v.count
|
||||
count = count == 0 and 1 or count
|
||||
count = a.nvim_get_mode().mode == "n" and count or 1
|
||||
action_state.get_current_picker(prompt_bufnr):move_selection(change * count)
|
||||
end
|
||||
|
||||
--- Select the current entry. This is the action set to overwrite common
|
||||
--- actions by the user.
|
||||
---
|
||||
--- By default maps to editing a file.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
---@param type string: The type of selection to make
|
||||
-- Valid types include: "default", "horizontal", "vertical", "tabedit"
|
||||
action_set.select = function(prompt_bufnr, type)
|
||||
return action_set.edit(prompt_bufnr, action_state.select_key_to_edit_key(type))
|
||||
end
|
||||
|
||||
-- goal: currently we have a workaround in actions/init.lua where we do this for all files
|
||||
-- action_set.select = {
|
||||
-- -- Will not be called if `select_default` is replaced rather than `action_set.select` because we never get here
|
||||
-- pre = function(prompt_bufnr)
|
||||
-- action_state.get_current_history():append(
|
||||
-- action_state.get_current_line(),
|
||||
-- action_state.get_current_picker(prompt_bufnr)
|
||||
-- )
|
||||
-- end,
|
||||
-- action = function(prompt_bufnr, type)
|
||||
-- return action_set.edit(prompt_bufnr, action_state.select_key_to_edit_key(type))
|
||||
-- end
|
||||
-- }
|
||||
|
||||
local edit_buffer
|
||||
do
|
||||
local map = {
|
||||
drop = "drop",
|
||||
["tab drop"] = "tab drop",
|
||||
edit = "buffer",
|
||||
new = "sbuffer",
|
||||
vnew = "vert sbuffer",
|
||||
["leftabove new"] = "leftabove sbuffer",
|
||||
["leftabove vnew"] = "leftabove vert sbuffer",
|
||||
["rightbelow new"] = "rightbelow sbuffer",
|
||||
["rightbelow vnew"] = "rightbelow vert sbuffer",
|
||||
["topleft new"] = "topleft sbuffer",
|
||||
["topleft vnew"] = "topleft vert sbuffer",
|
||||
["botright new"] = "botright sbuffer",
|
||||
["botright vnew"] = "botright vert sbuffer",
|
||||
tabedit = "tab sb",
|
||||
}
|
||||
|
||||
edit_buffer = function(command, bufnr)
|
||||
local buf_command = map[command]
|
||||
if buf_command == nil then
|
||||
local valid_commands = vim.tbl_map(function(cmd)
|
||||
return string.format("%q", cmd)
|
||||
end, vim.tbl_keys(map))
|
||||
table.sort(valid_commands)
|
||||
error(
|
||||
string.format(
|
||||
"There was no associated buffer command for %q.\nValid commands are: %s.",
|
||||
command,
|
||||
table.concat(valid_commands, ", ")
|
||||
)
|
||||
)
|
||||
end
|
||||
if buf_command ~= "drop" and buf_command ~= "tab drop" then
|
||||
vim.cmd(string.format("%s %d", buf_command, bufnr))
|
||||
else
|
||||
vim.cmd(string.format("%s %s", buf_command, vim.fn.fnameescape(vim.api.nvim_buf_get_name(bufnr))))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Edit a file based on the current selection.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
---@param command string: The command to use to open the file.
|
||||
-- Valid commands are:
|
||||
-- - "edit"
|
||||
-- - "new"
|
||||
-- - "vedit"
|
||||
-- - "tabedit"
|
||||
-- - "drop"
|
||||
-- - "tab drop"
|
||||
-- - "leftabove new"
|
||||
-- - "leftabove vnew"
|
||||
-- - "rightbelow new"
|
||||
-- - "rightbelow vnew"
|
||||
-- - "topleft new"
|
||||
-- - "topleft vnew"
|
||||
-- - "botright new"
|
||||
-- - "botright vnew"
|
||||
action_set.edit = function(prompt_bufnr, command)
|
||||
local entry = action_state.get_selected_entry()
|
||||
|
||||
if not entry then
|
||||
utils.notify("actions.set.edit", {
|
||||
msg = "Nothing currently selected",
|
||||
level = "WARN",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local filename, row, col
|
||||
|
||||
if entry.path or entry.filename then
|
||||
filename = entry.path or entry.filename
|
||||
|
||||
-- TODO: Check for off-by-one
|
||||
row = entry.row or entry.lnum
|
||||
col = entry.col
|
||||
elseif not entry.bufnr then
|
||||
-- TODO: Might want to remove this and force people
|
||||
-- to put stuff into `filename`
|
||||
local value = entry.value
|
||||
if not value then
|
||||
utils.notify("actions.set.edit", {
|
||||
msg = "Could not do anything with blank line...",
|
||||
level = "WARN",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if type(value) == "table" then
|
||||
value = entry.display
|
||||
end
|
||||
|
||||
local sections = vim.split(value, ":")
|
||||
|
||||
filename = sections[1]
|
||||
row = tonumber(sections[2])
|
||||
col = tonumber(sections[3])
|
||||
end
|
||||
|
||||
local entry_bufnr = entry.bufnr
|
||||
|
||||
local picker = action_state.get_current_picker(prompt_bufnr)
|
||||
require("telescope.pickers").on_close_prompt(prompt_bufnr)
|
||||
pcall(vim.api.nvim_set_current_win, picker.original_win_id)
|
||||
local win_id = picker.get_selection_window(picker, entry)
|
||||
|
||||
if picker.push_cursor_on_edit then
|
||||
vim.cmd "normal! m'"
|
||||
end
|
||||
|
||||
if picker.push_tagstack_on_edit then
|
||||
local from = { vim.fn.bufnr "%", vim.fn.line ".", vim.fn.col ".", 0 }
|
||||
local items = { { tagname = vim.fn.expand "<cword>", from = from } }
|
||||
vim.fn.settagstack(vim.fn.win_getid(), { items = items }, "t")
|
||||
end
|
||||
|
||||
if win_id ~= 0 and a.nvim_get_current_win() ~= win_id then
|
||||
vim.api.nvim_set_current_win(win_id)
|
||||
end
|
||||
|
||||
if entry_bufnr then
|
||||
if not vim.api.nvim_buf_get_option(entry_bufnr, "buflisted") then
|
||||
vim.api.nvim_buf_set_option(entry_bufnr, "buflisted", true)
|
||||
end
|
||||
edit_buffer(command, entry_bufnr)
|
||||
else
|
||||
-- check if we didn't pick a different buffer
|
||||
-- prevents restarting lsp server
|
||||
if vim.api.nvim_buf_get_name(0) ~= filename or command ~= "edit" then
|
||||
filename = Path:new(filename):normalize(vim.loop.cwd())
|
||||
pcall(vim.cmd, string.format("%s %s", command, vim.fn.fnameescape(filename)))
|
||||
end
|
||||
end
|
||||
|
||||
-- HACK: fixes folding: https://github.com/nvim-telescope/telescope.nvim/issues/699
|
||||
if vim.wo.foldmethod == "expr" then
|
||||
vim.schedule(function()
|
||||
vim.opt.foldmethod = "expr"
|
||||
end)
|
||||
end
|
||||
|
||||
local pos = vim.api.nvim_win_get_cursor(0)
|
||||
if col == nil then
|
||||
if row == pos[1] then
|
||||
col = pos[2] + 1
|
||||
elseif row == nil then
|
||||
row, col = pos[1], pos[2] + 1
|
||||
else
|
||||
col = 1
|
||||
end
|
||||
end
|
||||
|
||||
if row and col then
|
||||
local ok, err_msg = pcall(a.nvim_win_set_cursor, 0, { row, col })
|
||||
if not ok then
|
||||
log.debug("Failed to move to cursor:", err_msg, row, col)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param prompt_bufnr integer
|
||||
---@return table? previewer
|
||||
---@return number? speed
|
||||
local __scroll_previewer = function(prompt_bufnr)
|
||||
local previewer = action_state.get_current_picker(prompt_bufnr).previewer
|
||||
local status = state.get_status(prompt_bufnr)
|
||||
local preview_winid = status.layout.preview and status.layout.preview.winid
|
||||
|
||||
-- Check if we actually have a previewer and a preview window
|
||||
if type(previewer) ~= "table" or not preview_winid then
|
||||
return
|
||||
end
|
||||
|
||||
local default_speed = vim.api.nvim_win_get_height(preview_winid) / 2
|
||||
local speed = status.picker.layout_config.scroll_speed or default_speed
|
||||
return previewer, speed
|
||||
end
|
||||
|
||||
--- Scrolls the previewer up or down.
|
||||
--- Defaults to a half page scroll, but can be overridden using the `scroll_speed`
|
||||
--- option in `layout_config`. See |telescope.layout| for more details.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
---@param direction number: The direction of the scrolling
|
||||
-- Valid directions include: "1", "-1"
|
||||
action_set.scroll_previewer = function(prompt_bufnr, direction)
|
||||
local previewer, speed = __scroll_previewer(prompt_bufnr)
|
||||
if previewer and previewer.scroll_fn then
|
||||
previewer:scroll_fn(math.floor(speed * direction))
|
||||
end
|
||||
end
|
||||
|
||||
--- Scrolls the previewer to the left or right.
|
||||
--- Defaults to a half page scroll, but can be overridden using the `scroll_speed`
|
||||
--- option in `layout_config`. See |telescope.layout| for more details.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
---@param direction number: The direction of the scrolling
|
||||
-- Valid directions include: "1", "-1"
|
||||
action_set.scroll_horizontal_previewer = function(prompt_bufnr, direction)
|
||||
local previewer, speed = __scroll_previewer(prompt_bufnr)
|
||||
if previewer and previewer.scroll_horizontal_fn then
|
||||
previewer:scroll_horizontal_fn(math.floor(speed * direction))
|
||||
end
|
||||
end
|
||||
|
||||
--- Scrolls the results up or down.
|
||||
--- Defaults to a half page scroll, but can be overridden using the `scroll_speed`
|
||||
--- option in `layout_config`. See |telescope.layout| for more details.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
---@param direction number: The direction of the scrolling
|
||||
-- Valid directions include: "1", "-1"
|
||||
action_set.scroll_results = function(prompt_bufnr, direction)
|
||||
local status = state.get_status(prompt_bufnr)
|
||||
local default_speed = vim.api.nvim_win_get_height(status.layout.results.winid) / 2
|
||||
local speed = status.picker.layout_config.scroll_speed or default_speed
|
||||
|
||||
local input = direction > 0 and [[]] or [[]]
|
||||
|
||||
vim.api.nvim_win_call(status.layout.results.winid, function()
|
||||
vim.cmd([[normal! ]] .. math.floor(speed) .. input)
|
||||
end)
|
||||
|
||||
action_set.shift_selection(prompt_bufnr, math.floor(speed) * direction)
|
||||
end
|
||||
|
||||
--- Scrolls the results to the left or right.
|
||||
--- Defaults to a half page scroll, but can be overridden using the `scroll_speed`
|
||||
--- option in `layout_config`. See |telescope.layout| for more details.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
---@param direction number: The direction of the scrolling
|
||||
-- Valid directions include: "1", "-1"
|
||||
action_set.scroll_horizontal_results = function(prompt_bufnr, direction)
|
||||
local status = state.get_status(prompt_bufnr)
|
||||
local default_speed = vim.api.nvim_win_get_height(status.results_win) / 2
|
||||
local speed = status.picker.layout_config.scroll_speed or default_speed
|
||||
|
||||
local input = direction > 0 and [[zl]] or [[zh]]
|
||||
|
||||
vim.api.nvim_win_call(status.results_win, function()
|
||||
vim.cmd([[normal! ]] .. math.floor(speed) .. input)
|
||||
end)
|
||||
end
|
||||
|
||||
-- ==================================================
|
||||
-- Transforms modules and sets the corect metatables.
|
||||
-- ==================================================
|
||||
action_set = transform_mod(action_set)
|
||||
return action_set
|
||||
@ -0,0 +1,58 @@
|
||||
---@tag telescope.actions.state
|
||||
---@config { ["module"] = "telescope.actions.state", ["name"] = "ACTIONS_STATE" }
|
||||
|
||||
---@brief [[
|
||||
--- Functions to be used to determine the current state of telescope.
|
||||
---
|
||||
--- Generally used from within other |telescope.actions|
|
||||
---@brief ]]
|
||||
|
||||
local global_state = require "telescope.state"
|
||||
local conf = require("telescope.config").values
|
||||
|
||||
local action_state = {}
|
||||
|
||||
--- Get the current entry
|
||||
function action_state.get_selected_entry()
|
||||
return global_state.get_global_key "selected_entry"
|
||||
end
|
||||
|
||||
--- Gets the current line in the search prompt
|
||||
function action_state.get_current_line()
|
||||
return global_state.get_global_key "current_line" or ""
|
||||
end
|
||||
|
||||
--- Gets the current picker
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
function action_state.get_current_picker(prompt_bufnr)
|
||||
return global_state.get_status(prompt_bufnr).picker
|
||||
end
|
||||
|
||||
local select_to_edit_map = {
|
||||
default = "edit",
|
||||
horizontal = "new",
|
||||
vertical = "vnew",
|
||||
tab = "tabedit",
|
||||
drop = "drop",
|
||||
["tab drop"] = "tab drop",
|
||||
}
|
||||
function action_state.select_key_to_edit_key(type)
|
||||
return select_to_edit_map[type]
|
||||
end
|
||||
|
||||
function action_state.get_current_history()
|
||||
local history = global_state.get_global_key "history"
|
||||
if not history then
|
||||
if conf.history == false or type(conf.history) ~= "table" then
|
||||
history = require("telescope.actions.history").get_simple_history()
|
||||
global_state.set_global_key("history", history)
|
||||
else
|
||||
history = conf.history.handler()
|
||||
global_state.set_global_key("history", history)
|
||||
end
|
||||
end
|
||||
|
||||
return history
|
||||
end
|
||||
|
||||
return action_state
|
||||
@ -0,0 +1,150 @@
|
||||
---@tag telescope.actions.utils
|
||||
---@config { ["module"] = "telescope.actions.utils", ["name"] = "ACTIONS_UTILS" }
|
||||
|
||||
---@brief [[
|
||||
--- Utilities to wrap functions around picker selections and entries.
|
||||
---
|
||||
--- Generally used from within other |telescope.actions|
|
||||
---@brief ]]
|
||||
|
||||
local action_state = require "telescope.actions.state"
|
||||
|
||||
local utils = {}
|
||||
|
||||
--- Apply `f` to the entries of the current picker.
|
||||
--- - Notes:
|
||||
--- - Mapped entries include all currently filtered results, not just the visible ones.
|
||||
--- - Indices are 1-indexed, whereas rows are 0-indexed.
|
||||
--- - Warning: `map_entries` has no return value.
|
||||
--- - The below example showcases how to collect results
|
||||
---
|
||||
--- Usage:
|
||||
--- <code>
|
||||
--- local action_state = require "telescope.actions.state"
|
||||
--- local action_utils = require "telescope.actions.utils"
|
||||
--- function entry_value_by_row()
|
||||
--- local prompt_bufnr = vim.api.nvim_get_current_buf()
|
||||
--- local current_picker = action_state.get_current_picker(prompt_bufnr)
|
||||
--- local results = {}
|
||||
--- action_utils.map_entries(prompt_bufnr, function(entry, index, row)
|
||||
--- results[row] = entry.value
|
||||
--- end)
|
||||
--- return results
|
||||
--- end
|
||||
--- </code>
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
---@param f function: Function to map onto entries of picker that takes (entry, index, row) as viable arguments
|
||||
function utils.map_entries(prompt_bufnr, f)
|
||||
vim.validate {
|
||||
f = { f, "function" },
|
||||
}
|
||||
local current_picker = action_state.get_current_picker(prompt_bufnr)
|
||||
local index = 1
|
||||
-- indices are 1-indexed, rows are 0-indexed
|
||||
for entry in current_picker.manager:iter() do
|
||||
local row = current_picker:get_row(index)
|
||||
f(entry, index, row)
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
|
||||
--- Apply `f` to the multi selections of the current picker and return a table of mapped selections.
|
||||
--- - Notes:
|
||||
--- - Mapped selections may include results not visible in the results pop up.
|
||||
--- - Selected entries are returned in order of their selection.
|
||||
--- - Warning: `map_selections` has no return value.
|
||||
--- - The below example showcases how to collect results
|
||||
---
|
||||
--- Usage:
|
||||
--- <code>
|
||||
--- local action_state = require "telescope.actions.state"
|
||||
--- local action_utils = require "telescope.actions.utils"
|
||||
--- function selection_by_index()
|
||||
--- local prompt_bufnr = vim.api.nvim_get_current_buf()
|
||||
--- local current_picker = action_state.get_current_picker(prompt_bufnr)
|
||||
--- local results = {}
|
||||
--- action_utils.map_selections(prompt_bufnr, function(entry, index)
|
||||
--- results[index] = entry.value
|
||||
--- end)
|
||||
--- return results
|
||||
--- end
|
||||
--- </code>
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
---@param f function: Function to map onto selection of picker that takes (selection) as a viable argument
|
||||
function utils.map_selections(prompt_bufnr, f)
|
||||
vim.validate {
|
||||
f = { f, "function" },
|
||||
}
|
||||
local current_picker = action_state.get_current_picker(prompt_bufnr)
|
||||
for _, selection in ipairs(current_picker:get_multi_selection()) do
|
||||
f(selection)
|
||||
end
|
||||
end
|
||||
|
||||
--- Utility to collect mappings of prompt buffer in array of `{mode, keybind, name}`.
|
||||
---@param prompt_bufnr number: The prompt bufnr
|
||||
function utils.get_registered_mappings(prompt_bufnr)
|
||||
local ret = {}
|
||||
for _, mode in ipairs { "n", "i" } do
|
||||
for _, mapping in ipairs(vim.api.nvim_buf_get_keymap(prompt_bufnr, mode)) do
|
||||
-- ensure only telescope mappings
|
||||
if mapping.desc then
|
||||
if mapping.desc:sub(1, 10) == "telescope|" then
|
||||
table.insert(ret, { mode = mode, keybind = mapping.lhs, desc = mapping.desc:sub(11) })
|
||||
elseif mapping.desc:sub(1, 11) == "telescopej|" then
|
||||
local fname = utils._get_anon_function_name(vim.json.decode(mapping.desc:sub(12)))
|
||||
fname = fname:lower() == mapping.lhs:lower() and "<anonymous>" or fname
|
||||
table.insert(ret, {
|
||||
mode = mode,
|
||||
keybind = mapping.lhs,
|
||||
desc = fname,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
-- Best effort to infer function names for actions.which_key
|
||||
function utils._get_anon_function_name(info)
|
||||
local Path = require "plenary.path"
|
||||
local fname
|
||||
-- if fn defined in string (ie loadstring) source is string
|
||||
-- if fn defined in file, source is file name prefixed with a `@´
|
||||
local path = Path:new((info.source:gsub("@", "")))
|
||||
if not path:exists() then
|
||||
return "<anonymous>"
|
||||
end
|
||||
for i, line in ipairs(path:readlines()) do
|
||||
if i == info.linedefined then
|
||||
fname = line
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- test if assignment or named function, otherwise anon
|
||||
if (fname:match "=" == nil) and (fname:match "function %S+%(" == nil) then
|
||||
return "<anonymous>"
|
||||
else
|
||||
local patterns = {
|
||||
{ "function", "" }, -- remove function
|
||||
{ "local", "" }, -- remove local
|
||||
{ "[%s=]", "" }, -- remove whitespace and =
|
||||
{ [=[%[["']]=], "" }, -- remove left-hand bracket of table assignment
|
||||
{ [=[["']%]]=], "" }, -- remove right-ahnd bracket of table assignment
|
||||
{ "%((.+)%)", "" }, -- remove function arguments
|
||||
{ "(.+)%.", "" }, -- remove TABLE. prefix if available
|
||||
}
|
||||
for _, tbl in ipairs(patterns) do
|
||||
fname = (fname:gsub(tbl[1], tbl[2])) -- make sure only string is returned
|
||||
end
|
||||
-- not sure if this can happen, catch all just in case
|
||||
if fname == nil or fname == "" then
|
||||
return "<anonymous>"
|
||||
end
|
||||
return fname
|
||||
end
|
||||
end
|
||||
|
||||
return utils
|
||||
@ -0,0 +1,197 @@
|
||||
-- The fzy matching algorithm
|
||||
--
|
||||
-- by Seth Warn <https://github.com/swarn>
|
||||
-- a lua port of John Hawthorn's fzy <https://github.com/jhawthorn/fzy>
|
||||
--
|
||||
-- > fzy tries to find the result the user intended. It does this by favouring
|
||||
-- > matches on consecutive letters and starts of words. This allows matching
|
||||
-- > using acronyms or different parts of the path." - J Hawthorn
|
||||
|
||||
local has_path, Path = pcall(require, "plenary.path")
|
||||
if not has_path then
|
||||
Path = {
|
||||
path = {
|
||||
separator = "/",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
local SCORE_GAP_LEADING = -0.005
|
||||
local SCORE_GAP_TRAILING = -0.005
|
||||
local SCORE_GAP_INNER = -0.01
|
||||
local SCORE_MATCH_CONSECUTIVE = 1.0
|
||||
local SCORE_MATCH_SLASH = 0.9
|
||||
local SCORE_MATCH_WORD = 0.8
|
||||
local SCORE_MATCH_CAPITAL = 0.7
|
||||
local SCORE_MATCH_DOT = 0.6
|
||||
local SCORE_MAX = math.huge
|
||||
local SCORE_MIN = -math.huge
|
||||
local MATCH_MAX_LENGTH = 1024
|
||||
|
||||
local fzy = {}
|
||||
|
||||
function fzy.has_match(needle, haystack)
|
||||
needle = string.lower(needle)
|
||||
haystack = string.lower(haystack)
|
||||
|
||||
local j = 1
|
||||
for i = 1, string.len(needle) do
|
||||
j = string.find(haystack, needle:sub(i, i), j, true)
|
||||
if not j then
|
||||
return false
|
||||
else
|
||||
j = j + 1
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function is_lower(c)
|
||||
return c:match "%l"
|
||||
end
|
||||
|
||||
local function is_upper(c)
|
||||
return c:match "%u"
|
||||
end
|
||||
|
||||
local function precompute_bonus(haystack)
|
||||
local match_bonus = {}
|
||||
|
||||
local last_char = Path.path.sep
|
||||
for i = 1, string.len(haystack) do
|
||||
local this_char = haystack:sub(i, i)
|
||||
if last_char == Path.path.sep then
|
||||
match_bonus[i] = SCORE_MATCH_SLASH
|
||||
elseif last_char == "-" or last_char == "_" or last_char == " " then
|
||||
match_bonus[i] = SCORE_MATCH_WORD
|
||||
elseif last_char == "." then
|
||||
match_bonus[i] = SCORE_MATCH_DOT
|
||||
elseif is_lower(last_char) and is_upper(this_char) then
|
||||
match_bonus[i] = SCORE_MATCH_CAPITAL
|
||||
else
|
||||
match_bonus[i] = 0
|
||||
end
|
||||
|
||||
last_char = this_char
|
||||
end
|
||||
|
||||
return match_bonus
|
||||
end
|
||||
|
||||
local function compute(needle, haystack, D, M)
|
||||
local match_bonus = precompute_bonus(haystack)
|
||||
local n = string.len(needle)
|
||||
local m = string.len(haystack)
|
||||
local lower_needle = string.lower(needle)
|
||||
local lower_haystack = string.lower(haystack)
|
||||
|
||||
-- Because lua only grants access to chars through substring extraction,
|
||||
-- get all the characters from the haystack once now, to reuse below.
|
||||
local haystack_chars = {}
|
||||
for i = 1, m do
|
||||
haystack_chars[i] = lower_haystack:sub(i, i)
|
||||
end
|
||||
|
||||
for i = 1, n do
|
||||
D[i] = {}
|
||||
M[i] = {}
|
||||
|
||||
local prev_score = SCORE_MIN
|
||||
local gap_score = i == n and SCORE_GAP_TRAILING or SCORE_GAP_INNER
|
||||
local needle_char = lower_needle:sub(i, i)
|
||||
|
||||
for j = 1, m do
|
||||
if needle_char == haystack_chars[j] then
|
||||
local score = SCORE_MIN
|
||||
if i == 1 then
|
||||
score = ((j - 1) * SCORE_GAP_LEADING) + match_bonus[j]
|
||||
elseif j > 1 then
|
||||
local a = M[i - 1][j - 1] + match_bonus[j]
|
||||
local b = D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE
|
||||
score = math.max(a, b)
|
||||
end
|
||||
D[i][j] = score
|
||||
prev_score = math.max(score, prev_score + gap_score)
|
||||
M[i][j] = prev_score
|
||||
else
|
||||
D[i][j] = SCORE_MIN
|
||||
prev_score = prev_score + gap_score
|
||||
M[i][j] = prev_score
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function fzy.score(needle, haystack)
|
||||
local n = string.len(needle)
|
||||
local m = string.len(haystack)
|
||||
|
||||
if n == 0 or m == 0 or m > MATCH_MAX_LENGTH or n > MATCH_MAX_LENGTH then
|
||||
return SCORE_MIN
|
||||
elseif n == m then
|
||||
return SCORE_MAX
|
||||
else
|
||||
local D = {}
|
||||
local M = {}
|
||||
compute(needle, haystack, D, M)
|
||||
return M[n][m]
|
||||
end
|
||||
end
|
||||
|
||||
function fzy.positions(needle, haystack)
|
||||
local n = string.len(needle)
|
||||
local m = string.len(haystack)
|
||||
|
||||
if n == 0 or m == 0 or m > MATCH_MAX_LENGTH or n > MATCH_MAX_LENGTH then
|
||||
return {}
|
||||
elseif n == m then
|
||||
local consecutive = {}
|
||||
for i = 1, n do
|
||||
consecutive[i] = i
|
||||
end
|
||||
return consecutive
|
||||
end
|
||||
|
||||
local D = {}
|
||||
local M = {}
|
||||
compute(needle, haystack, D, M)
|
||||
|
||||
local positions = {}
|
||||
local match_required = false
|
||||
local j = m
|
||||
for i = n, 1, -1 do
|
||||
while j >= 1 do
|
||||
if D[i][j] ~= SCORE_MIN and (match_required or D[i][j] == M[i][j]) then
|
||||
match_required = (i ~= 1) and (j ~= 1) and (M[i][j] == D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE)
|
||||
positions[i] = j
|
||||
j = j - 1
|
||||
break
|
||||
else
|
||||
j = j - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return positions
|
||||
end
|
||||
|
||||
-- If strings a or b are empty or too long, `fzy.score(a, b) == fzy.get_score_min()`.
|
||||
function fzy.get_score_min()
|
||||
return SCORE_MIN
|
||||
end
|
||||
|
||||
-- For exact matches, `fzy.score(s, s) == fzy.get_score_max()`.
|
||||
function fzy.get_score_max()
|
||||
return SCORE_MAX
|
||||
end
|
||||
|
||||
-- For all strings a and b that
|
||||
-- - are not covered by either `fzy.get_score_min()` or fzy.get_score_max()`, and
|
||||
-- - are matched, such that `fzy.has_match(a, b) == true`,
|
||||
-- then `fzy.score(a, b) > fzy.get_score_floor()` will be true.
|
||||
function fzy.get_score_floor()
|
||||
return (MATCH_MAX_LENGTH + 1) * SCORE_GAP_INNER
|
||||
end
|
||||
|
||||
return fzy
|
||||
@ -0,0 +1,255 @@
|
||||
local LinkedList = {}
|
||||
LinkedList.__index = LinkedList
|
||||
|
||||
function LinkedList:new(opts)
|
||||
opts = opts or {}
|
||||
local track_at = opts.track_at
|
||||
|
||||
return setmetatable({
|
||||
size = 0,
|
||||
head = false,
|
||||
tail = false,
|
||||
|
||||
-- track_at: Track at can track a particular node
|
||||
-- Use to keep a node tracked at a particular index
|
||||
-- This greatly decreases looping for checking values at this location.
|
||||
track_at = track_at,
|
||||
_tracked_node = nil,
|
||||
tracked = nil,
|
||||
}, self)
|
||||
end
|
||||
|
||||
function LinkedList:_increment()
|
||||
self.size = self.size + 1
|
||||
return self.size
|
||||
end
|
||||
|
||||
local create_node = function(item)
|
||||
return {
|
||||
item = item,
|
||||
}
|
||||
end
|
||||
|
||||
function LinkedList:append(item)
|
||||
local final_size = self:_increment()
|
||||
|
||||
local node = create_node(item)
|
||||
|
||||
if not self.head then
|
||||
self.head = node
|
||||
end
|
||||
|
||||
if self.tail then
|
||||
self.tail.next = node
|
||||
node.prev = self.tail
|
||||
end
|
||||
|
||||
self.tail = node
|
||||
|
||||
if self.track_at then
|
||||
if final_size == self.track_at then
|
||||
self.tracked = item
|
||||
self._tracked_node = node
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function LinkedList:prepend(item)
|
||||
local final_size = self:_increment()
|
||||
local node = create_node(item)
|
||||
|
||||
if not self.tail then
|
||||
self.tail = node
|
||||
end
|
||||
|
||||
if self.head then
|
||||
self.head.prev = node
|
||||
node.next = self.head
|
||||
end
|
||||
|
||||
self.head = node
|
||||
|
||||
if self.track_at then
|
||||
if final_size == self.track_at then
|
||||
self._tracked_node = self.tail
|
||||
elseif final_size > self.track_at then
|
||||
self._tracked_node = self._tracked_node.prev
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
self.tracked = self._tracked_node.item
|
||||
end
|
||||
end
|
||||
|
||||
-- [a, b, c]
|
||||
-- b.prev = a
|
||||
-- b.next = c
|
||||
--
|
||||
-- a.next = b
|
||||
-- c.prev = c
|
||||
--
|
||||
-- insert d after b
|
||||
-- [a, b, d, c]
|
||||
--
|
||||
-- b.next = d
|
||||
-- b.prev = a
|
||||
--
|
||||
-- Place "item" after "node" (which is at index `index`)
|
||||
function LinkedList:place_after(index, node, item)
|
||||
local new_node = create_node(item)
|
||||
|
||||
assert(node.prev ~= node)
|
||||
assert(node.next ~= node)
|
||||
local final_size = self:_increment()
|
||||
|
||||
-- Update tail to be the next node.
|
||||
if self.tail == node then
|
||||
self.tail = new_node
|
||||
end
|
||||
|
||||
new_node.prev = node
|
||||
new_node.next = node.next
|
||||
|
||||
node.next = new_node
|
||||
|
||||
if new_node.prev then
|
||||
new_node.prev.next = new_node
|
||||
end
|
||||
|
||||
if new_node.next then
|
||||
new_node.next.prev = new_node
|
||||
end
|
||||
|
||||
if self.track_at then
|
||||
if index == self.track_at then
|
||||
self._tracked_node = new_node
|
||||
elseif index < self.track_at then
|
||||
if final_size == self.track_at then
|
||||
self._tracked_node = self.tail
|
||||
elseif final_size > self.track_at then
|
||||
self._tracked_node = self._tracked_node.prev
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self.tracked = self._tracked_node.item
|
||||
end
|
||||
end
|
||||
|
||||
function LinkedList:place_before(index, node, item)
|
||||
local new_node = create_node(item)
|
||||
|
||||
assert(node.prev ~= node)
|
||||
assert(node.next ~= node)
|
||||
local final_size = self:_increment()
|
||||
|
||||
-- Update head to be the node we are inserting.
|
||||
if self.head == node then
|
||||
self.head = new_node
|
||||
end
|
||||
|
||||
new_node.prev = node.prev
|
||||
new_node.next = node
|
||||
|
||||
node.prev = new_node
|
||||
-- node.next = node.next
|
||||
|
||||
if new_node.prev then
|
||||
new_node.prev.next = new_node
|
||||
end
|
||||
|
||||
if new_node.next then
|
||||
new_node.next.prev = new_node
|
||||
end
|
||||
|
||||
if self.track_at then
|
||||
if index == self.track_at - 1 then
|
||||
self._tracked_node = node
|
||||
elseif index < self.track_at then
|
||||
if final_size == self.track_at then
|
||||
self._tracked_node = self.tail
|
||||
elseif final_size > self.track_at then
|
||||
self._tracked_node = self._tracked_node.prev
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self.tracked = self._tracked_node.item
|
||||
end
|
||||
end
|
||||
|
||||
-- Do you even do this in linked lists...?
|
||||
-- function LinkedList:remove(item)
|
||||
-- end
|
||||
|
||||
function LinkedList:iter()
|
||||
local current_node = self.head
|
||||
|
||||
return function()
|
||||
local node = current_node
|
||||
if not node then
|
||||
return nil
|
||||
end
|
||||
|
||||
current_node = current_node.next
|
||||
return node.item
|
||||
end
|
||||
end
|
||||
|
||||
function LinkedList:ipairs()
|
||||
local index = 0
|
||||
local current_node = self.head
|
||||
|
||||
return function()
|
||||
local node = current_node
|
||||
if not node then
|
||||
return nil
|
||||
end
|
||||
|
||||
current_node = current_node.next
|
||||
index = index + 1
|
||||
return index, node.item, node
|
||||
end
|
||||
end
|
||||
|
||||
function LinkedList:truncate(max_results)
|
||||
if max_results >= self.size then
|
||||
return
|
||||
end
|
||||
|
||||
local current_node
|
||||
if max_results < self.size - max_results then
|
||||
local index = 1
|
||||
current_node = self.head
|
||||
while index < max_results do
|
||||
local node = current_node
|
||||
if not node.next then
|
||||
break
|
||||
end
|
||||
current_node = current_node.next
|
||||
index = index + 1
|
||||
end
|
||||
self.size = max_results
|
||||
else
|
||||
current_node = self.tail
|
||||
while self.size > max_results do
|
||||
if current_node.prev == nil then
|
||||
break
|
||||
end
|
||||
current_node = current_node.prev
|
||||
self.size = self.size - 1
|
||||
end
|
||||
end
|
||||
self.tail = current_node
|
||||
self.tail.next = nil
|
||||
if max_results < self.track_at then
|
||||
self.track_at = max_results
|
||||
self.tracked = current_node.item
|
||||
self._tracked_node = current_node
|
||||
end
|
||||
end
|
||||
|
||||
return LinkedList
|
||||
@ -0,0 +1,52 @@
|
||||
local function min(a, b, c)
|
||||
local min_val = a
|
||||
|
||||
if b < min_val then
|
||||
min_val = b
|
||||
end
|
||||
if c < min_val then
|
||||
min_val = c
|
||||
end
|
||||
|
||||
return min_val
|
||||
end
|
||||
|
||||
----------------------------------
|
||||
--- Levenshtein distance function.
|
||||
-- @tparam string s1
|
||||
-- @tparam string s2
|
||||
-- @treturn number the levenshtein distance
|
||||
-- @within Metrics
|
||||
return function(s1, s2)
|
||||
if s1 == s2 then
|
||||
return 0
|
||||
end
|
||||
if s1:len() == 0 then
|
||||
return s2:len()
|
||||
end
|
||||
if s2:len() == 0 then
|
||||
return s1:len()
|
||||
end
|
||||
if s1:len() < s2:len() then
|
||||
s1, s2 = s2, s1
|
||||
end
|
||||
|
||||
local t = {}
|
||||
for i = 1, #s1 + 1 do
|
||||
t[i] = { i - 1 }
|
||||
end
|
||||
|
||||
for i = 1, #s2 + 1 do
|
||||
t[1][i] = i - 1
|
||||
end
|
||||
|
||||
local cost
|
||||
for i = 2, #s1 + 1 do
|
||||
for j = 2, #s2 + 1 do
|
||||
cost = (s1:sub(i - 1, i - 1) == s2:sub(j - 1, j - 1) and 0) or 1
|
||||
t[i][j] = min(t[i - 1][j] + 1, t[i][j - 1] + 1, t[i - 1][j - 1] + cost)
|
||||
end
|
||||
end
|
||||
|
||||
return t[#s1 + 1][#s2 + 1]
|
||||
end
|
||||
@ -0,0 +1,188 @@
|
||||
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 sorting_comparator = function(opts)
|
||||
local current_buf = vim.api.nvim_get_current_buf()
|
||||
local comparators = {
|
||||
-- sort results by bufnr (prioritize cur buf), severity, lnum
|
||||
buffer = 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
|
||||
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,
|
||||
severity = function(a, b)
|
||||
if a.type < b.type then
|
||||
return true
|
||||
elseif a.type > b.type then
|
||||
return false
|
||||
end
|
||||
|
||||
if a.bufnr == b.bufnr then
|
||||
return a.lnum < b.lnum
|
||||
elseif a.bufnr == current_buf then
|
||||
return true
|
||||
elseif b.bufnr == current_buf then
|
||||
return false
|
||||
else
|
||||
return a.bufnr < b.bufnr
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
local sort_by = vim.F.if_nil(opts.sort_by, "buffer")
|
||||
return comparators[sort_by]
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
if vim.version().minor > 9 and vim.tbl_isempty(diagnosis_opts.severity) then
|
||||
diagnosis_opts.severity = nil
|
||||
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
|
||||
|
||||
table.sort(items, sorting_comparator(opts))
|
||||
|
||||
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
|
||||
|
||||
if type(opts.line_width) == "string" and opts.line_width ~= "full" then
|
||||
utils.notify("builtin.diagnostics", {
|
||||
msg = string.format("'%s' is not a valid value for line_width", opts.line_width),
|
||||
level = "ERROR",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
opts.path_display = vim.F.if_nil(opts.path_display, "hidden")
|
||||
pickers
|
||||
.new(opts, {
|
||||
prompt_title = opts.bufnr == nil and "Workspace Diagnostics" or "Document Diagnostics",
|
||||
finder = finders.new_table {
|
||||
results = locations,
|
||||
entry_maker = opts.entry_maker or make_entry.gen_from_diagnostics(opts),
|
||||
},
|
||||
previewer = conf.qflist_previewer(opts),
|
||||
sorter = conf.prefilter_sorter {
|
||||
tag = "type",
|
||||
sorter = conf.generic_sorter(opts),
|
||||
},
|
||||
})
|
||||
:find()
|
||||
end
|
||||
|
||||
local function apply_checks(mod)
|
||||
for k, v in pairs(mod) do
|
||||
mod[k] = function(opts)
|
||||
opts = opts or {}
|
||||
v(opts)
|
||||
end
|
||||
end
|
||||
|
||||
return mod
|
||||
end
|
||||
|
||||
return apply_checks(diagnostics)
|
||||
@ -0,0 +1,648 @@
|
||||
local action_state = require "telescope.actions.state"
|
||||
local action_set = require "telescope.actions.set"
|
||||
local actions = require "telescope.actions"
|
||||
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 = utils.flatten
|
||||
local filter = vim.tbl_filter
|
||||
|
||||
local files = {}
|
||||
|
||||
local escape_chars = function(string)
|
||||
return string.gsub(string, "[%(|%)|\\|%[|%]|%-|%{%}|%?|%+|%*|%^|%$|%.]", {
|
||||
["\\"] = "\\\\",
|
||||
["-"] = "\\-",
|
||||
["("] = "\\(",
|
||||
[")"] = "\\)",
|
||||
["["] = "\\[",
|
||||
["]"] = "\\]",
|
||||
["{"] = "\\{",
|
||||
["}"] = "\\}",
|
||||
["?"] = "\\?",
|
||||
["+"] = "\\+",
|
||||
["*"] = "\\*",
|
||||
["^"] = "\\^",
|
||||
["$"] = "\\$",
|
||||
["."] = "\\.",
|
||||
})
|
||||
end
|
||||
|
||||
local has_rg_program = function(picker_name, program)
|
||||
if vim.fn.executable(program) == 1 then
|
||||
return true
|
||||
end
|
||||
|
||||
utils.notify(picker_name, {
|
||||
msg = string.format(
|
||||
"'ripgrep', or similar alternative, is a required dependency for the %s picker. "
|
||||
.. "Visit https://github.com/BurntSushi/ripgrep#installation for installation instructions.",
|
||||
picker_name
|
||||
),
|
||||
level = "ERROR",
|
||||
})
|
||||
return false
|
||||
end
|
||||
|
||||
local get_open_filelist = function(grep_open_files, cwd)
|
||||
if not grep_open_files then
|
||||
return nil
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
local filelist = {}
|
||||
for _, bufnr in ipairs(bufnrs) do
|
||||
local file = vim.api.nvim_buf_get_name(bufnr)
|
||||
table.insert(filelist, Path:new(file):make_relative(cwd))
|
||||
end
|
||||
return filelist
|
||||
end
|
||||
|
||||
local opts_contain_invert = function(args)
|
||||
local invert = false
|
||||
local files_with_matches = false
|
||||
|
||||
for _, v in ipairs(args) do
|
||||
if v == "--invert-match" then
|
||||
invert = true
|
||||
elseif v == "--files-with-matches" or v == "--files-without-match" then
|
||||
files_with_matches = true
|
||||
end
|
||||
|
||||
if #v >= 2 and v:sub(1, 1) == "-" and v:sub(2, 2) ~= "-" then
|
||||
local non_option = false
|
||||
for i = 2, #v do
|
||||
local vi = v:sub(i, i)
|
||||
if vi == "=" then -- ignore option -g=xxx
|
||||
break
|
||||
elseif vi == "g" or vi == "f" or vi == "m" or vi == "e" or vi == "r" or vi == "t" or vi == "T" then
|
||||
non_option = true
|
||||
elseif non_option == false and vi == "v" then
|
||||
invert = true
|
||||
elseif non_option == false and vi == "l" then
|
||||
files_with_matches = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return invert, files_with_matches
|
||||
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
|
||||
if not has_rg_program("live_grep", vimgrep_arguments[1]) then
|
||||
return
|
||||
end
|
||||
local search_dirs = opts.search_dirs
|
||||
local grep_open_files = opts.grep_open_files
|
||||
opts.cwd = opts.cwd and utils.path_expand(opts.cwd) or vim.loop.cwd()
|
||||
|
||||
local filelist = get_open_filelist(grep_open_files, opts.cwd)
|
||||
if search_dirs then
|
||||
for i, path in ipairs(search_dirs) do
|
||||
search_dirs[i] = utils.path_expand(path)
|
||||
end
|
||||
end
|
||||
|
||||
local additional_args = {}
|
||||
if opts.additional_args ~= nil then
|
||||
if type(opts.additional_args) == "function" then
|
||||
additional_args = opts.additional_args(opts)
|
||||
elseif type(opts.additional_args) == "table" then
|
||||
additional_args = opts.additional_args
|
||||
end
|
||||
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
|
||||
|
||||
if opts.file_encoding then
|
||||
additional_args[#additional_args + 1] = "--encoding=" .. opts.file_encoding
|
||||
end
|
||||
|
||||
local args = flatten { vimgrep_arguments, additional_args }
|
||||
opts.__inverted, opts.__matches = opts_contain_invert(args)
|
||||
|
||||
local live_grepper = finders.new_job(function(prompt)
|
||||
if not prompt or prompt == "" then
|
||||
return nil
|
||||
end
|
||||
|
||||
local search_list = {}
|
||||
|
||||
if grep_open_files then
|
||||
search_list = filelist
|
||||
elseif search_dirs then
|
||||
search_list = search_dirs
|
||||
end
|
||||
|
||||
return flatten { args, "--", prompt, search_list }
|
||||
end, opts.entry_maker or make_entry.gen_from_vimgrep(opts), opts.max_results, opts.cwd)
|
||||
|
||||
pickers
|
||||
.new(opts, {
|
||||
prompt_title = "Live Grep",
|
||||
finder = live_grepper,
|
||||
previewer = conf.grep_previewer(opts),
|
||||
-- TODO: It would be cool to use `--json` output for this
|
||||
-- and then we could get the highlight positions directly.
|
||||
sorter = sorters.highlighter_only(opts),
|
||||
attach_mappings = function(_, map)
|
||||
map("i", "<c-space>", actions.to_fuzzy_refine)
|
||||
return true
|
||||
end,
|
||||
})
|
||||
:find()
|
||||
end
|
||||
|
||||
files.grep_string = function(opts)
|
||||
local vimgrep_arguments = vim.F.if_nil(opts.vimgrep_arguments, conf.vimgrep_arguments)
|
||||
if not has_rg_program("grep_string", vimgrep_arguments[1]) then
|
||||
return
|
||||
end
|
||||
local word
|
||||
local visual = vim.fn.mode() == "v"
|
||||
|
||||
if visual == true then
|
||||
local saved_reg = vim.fn.getreg "v"
|
||||
vim.cmd [[noautocmd sil norm! "vy]]
|
||||
local sele = vim.fn.getreg "v"
|
||||
vim.fn.setreg("v", saved_reg)
|
||||
word = vim.F.if_nil(opts.search, sele)
|
||||
else
|
||||
word = vim.F.if_nil(opts.search, vim.fn.expand "<cword>")
|
||||
end
|
||||
local search = opts.use_regex and word or escape_chars(word)
|
||||
|
||||
local additional_args = {}
|
||||
if opts.additional_args ~= nil then
|
||||
if type(opts.additional_args) == "function" then
|
||||
additional_args = opts.additional_args(opts)
|
||||
elseif type(opts.additional_args) == "table" then
|
||||
additional_args = opts.additional_args
|
||||
end
|
||||
end
|
||||
|
||||
if opts.file_encoding then
|
||||
additional_args[#additional_args + 1] = "--encoding=" .. opts.file_encoding
|
||||
end
|
||||
|
||||
if search == "" then
|
||||
search = { "-v", "--", "^[[:space:]]*$" }
|
||||
else
|
||||
search = { "--", search }
|
||||
end
|
||||
|
||||
local args
|
||||
if visual == true then
|
||||
args = flatten {
|
||||
vimgrep_arguments,
|
||||
additional_args,
|
||||
search,
|
||||
}
|
||||
else
|
||||
args = flatten {
|
||||
vimgrep_arguments,
|
||||
additional_args,
|
||||
opts.word_match,
|
||||
search,
|
||||
}
|
||||
end
|
||||
|
||||
opts.__inverted, opts.__matches = opts_contain_invert(args)
|
||||
|
||||
if opts.grep_open_files then
|
||||
for _, file in ipairs(get_open_filelist(opts.grep_open_files, opts.cwd)) do
|
||||
table.insert(args, file)
|
||||
end
|
||||
elseif opts.search_dirs then
|
||||
for _, path in ipairs(opts.search_dirs) do
|
||||
table.insert(args, utils.path_expand(path))
|
||||
end
|
||||
end
|
||||
|
||||
opts.entry_maker = opts.entry_maker or make_entry.gen_from_vimgrep(opts)
|
||||
pickers
|
||||
.new(opts, {
|
||||
prompt_title = "Find Word (" .. word:gsub("\n", "\\n") .. ")",
|
||||
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 "rg" then
|
||||
return { "rg", "--files", "--color", "never" }
|
||||
elseif 1 == vim.fn.executable "fd" then
|
||||
return { "fd", "--type", "f", "--color", "never" }
|
||||
elseif 1 == vim.fn.executable "fdfind" then
|
||||
return { "fdfind", "--type", "f", "--color", "never" }
|
||||
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] = utils.path_expand(v)
|
||||
end
|
||||
end
|
||||
|
||||
if command == "fd" or command == "fdfind" or command == "rg" then
|
||||
if hidden then
|
||||
find_command[#find_command + 1] = "--hidden"
|
||||
end
|
||||
if no_ignore then
|
||||
find_command[#find_command + 1] = "--no-ignore"
|
||||
end
|
||||
if no_ignore_parent then
|
||||
find_command[#find_command + 1] = "--no-ignore-parent"
|
||||
end
|
||||
if follow then
|
||||
find_command[#find_command + 1] = "-L"
|
||||
end
|
||||
if search_file then
|
||||
if command == "rg" then
|
||||
find_command[#find_command + 1] = "-g"
|
||||
find_command[#find_command + 1] = "*" .. search_file .. "*"
|
||||
else
|
||||
find_command[#find_command + 1] = search_file
|
||||
end
|
||||
end
|
||||
if search_dirs then
|
||||
if command ~= "rg" and not search_file then
|
||||
find_command[#find_command + 1] = "."
|
||||
end
|
||||
vim.list_extend(find_command, search_dirs)
|
||||
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 = utils.path_expand(opts.cwd)
|
||||
end
|
||||
|
||||
opts.entry_maker = opts.entry_maker or make_entry.gen_from_file(opts)
|
||||
|
||||
pickers
|
||||
.new(opts, {
|
||||
prompt_title = "Find Files",
|
||||
__locations_input = true,
|
||||
finder = finders.new_oneshot_job(find_command, opts),
|
||||
previewer = conf.grep_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 = "This picker requires nvim-treesitter",
|
||||
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
|
||||
|
||||
results = utils.filter_symbols(results, opts)
|
||||
if results == nil then
|
||||
-- error message already printed in `utils.filter_symbols`
|
||||
return
|
||||
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.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
|
||||
|
||||
opts.results_ts_highlight = vim.F.if_nil(opts.results_ts_highlight, true)
|
||||
local lang = vim.treesitter.language.get_lang(filetype) or filetype
|
||||
if opts.results_ts_highlight and lang and utils.has_ts_parser(lang) then
|
||||
local parser = vim.treesitter.get_parser(opts.bufnr, lang)
|
||||
local query = vim.treesitter.query.get(lang, "highlights")
|
||||
local root = parser:parse()[1]:root()
|
||||
|
||||
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 = "@" .. query.captures[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()
|
||||
actions.select_default:replace(function(prompt_bufnr)
|
||||
local selection = action_state.get_selected_entry()
|
||||
if not selection then
|
||||
utils.__warn_no_selection "builtin.current_buffer_fuzzy_find"
|
||||
return
|
||||
end
|
||||
local current_picker = action_state.get_current_picker(prompt_bufnr)
|
||||
local searched_for = require("telescope.actions.state").get_current_line()
|
||||
|
||||
---@type number[] | {start:number, end:number?, highlight:string?}[]
|
||||
local highlights = current_picker.sorter:highlighter(searched_for, selection.ordinal) or {}
|
||||
highlights = vim.tbl_map(function(hl)
|
||||
if type(hl) == "table" and hl.start then
|
||||
return hl.start
|
||||
elseif type(hl) == "number" then
|
||||
return hl
|
||||
end
|
||||
error "Invalid higlighter fn"
|
||||
end, highlights)
|
||||
|
||||
local first_col = 0
|
||||
if #highlights > 0 then
|
||||
first_col = math.min(unpack(highlights)) - 1
|
||||
end
|
||||
|
||||
actions.close(prompt_bufnr)
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_win_set_cursor(0, { selection.lnum, first_col })
|
||||
end)
|
||||
end)
|
||||
|
||||
return true
|
||||
end,
|
||||
push_cursor_on_edit = true,
|
||||
})
|
||||
: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 "keepjumps 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)
|
||||
@ -0,0 +1,505 @@
|
||||
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 operators = require "telescope.operators"
|
||||
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_command = utils.__git_command
|
||||
|
||||
local git = {}
|
||||
|
||||
local get_git_command_output = function(args, opts)
|
||||
return utils.get_os_command_output(git_command(args, opts), opts.cwd)
|
||||
end
|
||||
|
||||
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))
|
||||
opts.git_command = vim.F.if_nil(
|
||||
opts.git_command,
|
||||
git_command({ "-c", "core.quotepath=false", "ls-files", "--exclude-standard", "--cached" }, opts)
|
||||
)
|
||||
|
||||
pickers
|
||||
.new(opts, {
|
||||
prompt_title = "Git Files",
|
||||
__locations_input = true,
|
||||
finder = finders.new_oneshot_job(
|
||||
utils.flatten {
|
||||
opts.git_command,
|
||||
show_untracked and "--others" or nil,
|
||||
recurse_submodules and "--recurse-submodules" or nil,
|
||||
},
|
||||
opts
|
||||
),
|
||||
previewer = conf.grep_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))
|
||||
opts.git_command =
|
||||
vim.F.if_nil(opts.git_command, git_command({ "log", "--pretty=oneline", "--abbrev-commit", "--", "." }, opts))
|
||||
|
||||
pickers
|
||||
.new(opts, {
|
||||
prompt_title = "Git Commits",
|
||||
finder = finders.new_oneshot_job(opts.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", "n" }, "<c-r>m", actions.git_reset_mixed)
|
||||
map({ "i", "n" }, "<c-r>s", actions.git_reset_soft)
|
||||
map({ "i", "n" }, "<c-r>h", actions.git_reset_hard)
|
||||
return true
|
||||
end,
|
||||
})
|
||||
:find()
|
||||
end
|
||||
|
||||
git.stash = function(opts)
|
||||
opts.show_branch = vim.F.if_nil(opts.show_branch, true)
|
||||
opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_stash(opts))
|
||||
opts.git_command = vim.F.if_nil(opts.git_command, git_command({ "--no-pager", "stash", "list" }, opts))
|
||||
|
||||
pickers
|
||||
.new(opts, {
|
||||
prompt_title = "Git Stash",
|
||||
finder = finders.new_oneshot_job(opts.git_command, 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
|
||||
|
||||
local bcommits_picker = function(opts, title, finder)
|
||||
return pickers.new(opts, {
|
||||
prompt_title = title,
|
||||
finder = finder,
|
||||
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,
|
||||
})
|
||||
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))
|
||||
opts.git_command =
|
||||
vim.F.if_nil(opts.git_command, git_command({ "log", "--pretty=oneline", "--abbrev-commit", "--follow" }, opts))
|
||||
|
||||
local title = "Git BCommits"
|
||||
local finder = finders.new_oneshot_job(
|
||||
utils.flatten {
|
||||
opts.git_command,
|
||||
opts.current_file,
|
||||
},
|
||||
opts
|
||||
)
|
||||
bcommits_picker(opts, title, finder):find()
|
||||
end
|
||||
|
||||
git.bcommits_range = 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))
|
||||
opts.git_command = vim.F.if_nil(
|
||||
opts.git_command,
|
||||
git_command({ "log", "--pretty=oneline", "--abbrev-commit", "--no-patch", "-L" }, opts)
|
||||
)
|
||||
local visual = string.find(vim.fn.mode(), "[vV]") ~= nil
|
||||
|
||||
local line_number_first = opts.from
|
||||
local line_number_last = vim.F.if_nil(opts.to, line_number_first)
|
||||
if visual then
|
||||
line_number_first = vim.F.if_nil(line_number_first, vim.fn.line "v")
|
||||
line_number_last = vim.F.if_nil(line_number_last, vim.fn.line ".")
|
||||
elseif opts.operator then
|
||||
opts.operator = false
|
||||
opts.operator_callback = true
|
||||
operators.run_operator(git.bcommits_range, opts)
|
||||
return
|
||||
elseif opts.operator_callback then
|
||||
line_number_first = vim.fn.line "'["
|
||||
line_number_last = vim.fn.line "']"
|
||||
elseif line_number_first == nil then
|
||||
line_number_first = vim.F.if_nil(line_number_first, vim.fn.line ".")
|
||||
line_number_last = vim.F.if_nil(line_number_last, vim.fn.line ".")
|
||||
end
|
||||
local line_range =
|
||||
string.format("%d,%d:%s", line_number_first, line_number_last, Path:new(opts.current_file):make_relative(opts.cwd))
|
||||
|
||||
local title = "Git BCommits in range"
|
||||
local finder = finders.new_oneshot_job(
|
||||
utils.flatten {
|
||||
opts.git_command,
|
||||
line_range,
|
||||
},
|
||||
opts
|
||||
)
|
||||
bcommits_picker(opts, title, finder):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 = get_git_command_output(
|
||||
{ "for-each-ref", "--perl", "--format", format, "--sort", "-authordate", opts.pattern },
|
||||
opts
|
||||
)
|
||||
|
||||
local show_remote_tracking_branches = vim.F.if_nil(opts.show_remote_tracking_branches, true)
|
||||
|
||||
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), "''")
|
||||
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
|
||||
if show_remote_tracking_branches then
|
||||
prefix = "refs/remotes/"
|
||||
else
|
||||
return
|
||||
end
|
||||
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", "n" }, "<c-t>", actions.git_track_branch)
|
||||
map({ "i", "n" }, "<c-r>", actions.git_rebase_branch)
|
||||
map({ "i", "n" }, "<c-a>", actions.git_create_branch)
|
||||
map({ "i", "n" }, "<c-s>", actions.git_switch_branch)
|
||||
map({ "i", "n" }, "<c-d>", actions.git_delete_branch)
|
||||
map({ "i", "n" }, "<c-y>", actions.git_merge_branch)
|
||||
return true
|
||||
end,
|
||||
})
|
||||
:find()
|
||||
end
|
||||
|
||||
git.status = function(opts)
|
||||
if opts.is_bare then
|
||||
utils.notify("builtin.git_status", {
|
||||
msg = "This operation must be run in a work tree",
|
||||
level = "ERROR",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local args = { "status", "--porcelain=v1", "--", "." }
|
||||
|
||||
local gen_new_finder = function()
|
||||
if vim.F.if_nil(opts.expand_dir, true) then
|
||||
table.insert(args, #args - 1, "-uall")
|
||||
end
|
||||
local git_cmd = git_command(args, opts)
|
||||
opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_status(opts))
|
||||
return finders.new_oneshot_job(git_cmd, 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),
|
||||
on_complete = {
|
||||
function(self)
|
||||
local lines = self.manager:num_results()
|
||||
local prompt = action_state.get_current_line()
|
||||
if lines == 0 and prompt == "" then
|
||||
utils.notify("builtin.git_status", {
|
||||
msg = "No changes found",
|
||||
level = "ERROR",
|
||||
})
|
||||
actions.close(self.prompt_bufnr)
|
||||
end
|
||||
end,
|
||||
},
|
||||
attach_mappings = function(prompt_bufnr, map)
|
||||
actions.git_staging_toggle:enhance {
|
||||
post = function()
|
||||
local picker = action_state.get_current_picker(prompt_bufnr)
|
||||
|
||||
-- temporarily register a callback which keeps selection on refresh
|
||||
local selection = picker:get_selection_row()
|
||||
local callbacks = { unpack(picker._completion_callbacks) } -- shallow copy
|
||||
picker:register_completion_callback(function(self)
|
||||
self:set_selection(selection)
|
||||
self._completion_callbacks = callbacks
|
||||
end)
|
||||
|
||||
-- refresh
|
||||
picker:refresh(gen_new_finder(), { reset_prompt = false })
|
||||
end,
|
||||
}
|
||||
|
||||
map({ "i", "n" }, "<tab>", actions.git_staging_toggle)
|
||||
return true
|
||||
end,
|
||||
})
|
||||
:find()
|
||||
end
|
||||
|
||||
local try_worktrees = function(opts)
|
||||
local worktrees = conf.git_worktrees
|
||||
|
||||
if utils.islist(worktrees) then
|
||||
for _, wt in ipairs(worktrees) do
|
||||
if vim.startswith(opts.cwd, wt.toplevel) then
|
||||
opts.toplevel = wt.toplevel
|
||||
opts.gitdir = wt.gitdir
|
||||
if opts.use_git_root then
|
||||
opts.cwd = wt.toplevel
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
error(opts.cwd .. " is not a git directory")
|
||||
end
|
||||
|
||||
local current_path_toplevel = function()
|
||||
local gitdir = vim.fn.finddir(".git", vim.fn.expand "%:p" .. ";")
|
||||
if gitdir == "" then
|
||||
return nil
|
||||
end
|
||||
return Path:new(gitdir):parent():absolute()
|
||||
end
|
||||
|
||||
local set_opts_cwd = function(opts)
|
||||
opts.use_git_root = vim.F.if_nil(opts.use_git_root, true)
|
||||
if opts.cwd then
|
||||
opts.cwd = utils.path_expand(opts.cwd)
|
||||
elseif opts.use_file_path then
|
||||
opts.cwd = current_path_toplevel()
|
||||
if not opts.cwd then
|
||||
opts.cwd = vim.fn.expand "%:p:h"
|
||||
try_worktrees(opts)
|
||||
return
|
||||
end
|
||||
else
|
||||
opts.cwd = vim.loop.cwd()
|
||||
end
|
||||
|
||||
local toplevel, ret = utils.get_os_command_output({ "git", "rev-parse", "--show-toplevel" }, opts.cwd)
|
||||
|
||||
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
|
||||
try_worktrees(opts)
|
||||
elseif in_worktree[1] ~= "true" and in_bare[1] == "true" then
|
||||
opts.is_bare = true
|
||||
end
|
||||
else
|
||||
if opts.use_git_root then
|
||||
opts.cwd = toplevel[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)
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,447 @@
|
||||
local channel = require("plenary.async.control").channel
|
||||
local actions = require "telescope.actions"
|
||||
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 = {}
|
||||
|
||||
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.message)
|
||||
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 _, rng in pairs(ch_call.fromRanges) do
|
||||
table.insert(locations, {
|
||||
filename = vim.uri_to_fname(ch_item.uri),
|
||||
text = ch_item.name,
|
||||
lnum = rng.start.line + 1,
|
||||
col = rng.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
|
||||
|
||||
---@type { [string]: fun(results: table, items: table, opts: table): table, table }
|
||||
local action_handlers = {
|
||||
["textDocument/references"] = function(results, items, opts)
|
||||
if not opts.include_current_line then
|
||||
local retresults = {}
|
||||
local retitems = {}
|
||||
|
||||
for i, item in pairs(items) do
|
||||
if
|
||||
not (
|
||||
item.filename == vim.api.nvim_buf_get_name(opts.bufnr)
|
||||
and item.lnum == vim.api.nvim_win_get_cursor(opts.winnr)[1]
|
||||
)
|
||||
then
|
||||
table.insert(retresults, results[i])
|
||||
table.insert(retitems, items[i])
|
||||
end
|
||||
end
|
||||
|
||||
return retresults, retitems
|
||||
end
|
||||
|
||||
return results, items
|
||||
end,
|
||||
}
|
||||
|
||||
---@param action string
|
||||
---@param locations table
|
||||
---@param items table
|
||||
---@param opts table
|
||||
---@return table results, table items
|
||||
local apply_action_handler = function(action, locations, items, opts)
|
||||
local handler = action_handlers[action]
|
||||
if handler then
|
||||
return handler(locations, items, opts)
|
||||
end
|
||||
|
||||
return locations, items
|
||||
end
|
||||
|
||||
---@param action string
|
||||
---@param title string prompt title
|
||||
---@param params lsp.TextDocumentPositionParams
|
||||
---@param opts table
|
||||
local function list_or_jump(action, title, params, opts)
|
||||
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
|
||||
|
||||
if result == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local locations = {}
|
||||
if not utils.islist(result) then
|
||||
locations = { result }
|
||||
end
|
||||
vim.list_extend(locations, result)
|
||||
|
||||
local offset_encoding = vim.lsp.get_client_by_id(ctx.client_id).offset_encoding
|
||||
local items = vim.lsp.util.locations_to_items(locations, offset_encoding)
|
||||
|
||||
locations, items = apply_action_handler(action, locations, items, opts)
|
||||
|
||||
if vim.tbl_isempty(locations) then
|
||||
return
|
||||
end
|
||||
|
||||
if #locations == 1 and opts.jump_type ~= "never" then
|
||||
local current_uri = params.textDocument.uri
|
||||
local target_uri = locations[1].uri or locations[1].targetUri
|
||||
if current_uri ~= target_uri then
|
||||
local cmd
|
||||
local file_path = vim.uri_to_fname(target_uri)
|
||||
if opts.jump_type == "tab" then
|
||||
cmd = "tabedit"
|
||||
elseif opts.jump_type == "split" then
|
||||
cmd = "new"
|
||||
elseif opts.jump_type == "vsplit" then
|
||||
cmd = "vnew"
|
||||
elseif opts.jump_type == "tab drop" then
|
||||
cmd = "tab drop"
|
||||
end
|
||||
|
||||
if cmd then
|
||||
vim.cmd(string.format("%s %s", cmd, file_path))
|
||||
end
|
||||
end
|
||||
|
||||
vim.lsp.util.jump_to_location(locations[1], offset_encoding, opts.reuse_win)
|
||||
else
|
||||
pickers
|
||||
.new(opts, {
|
||||
prompt_title = title,
|
||||
finder = finders.new_table {
|
||||
results = items,
|
||||
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.references = function(opts)
|
||||
local params = vim.lsp.util.make_position_params(opts.winnr)
|
||||
params.context = { includeDeclaration = vim.F.if_nil(opts.include_declaration, true) }
|
||||
return list_or_jump("textDocument/references", "LSP References", params, opts)
|
||||
end
|
||||
|
||||
lsp.definitions = function(opts)
|
||||
local params = vim.lsp.util.make_position_params(opts.winnr)
|
||||
return list_or_jump("textDocument/definition", "LSP Definitions", params, opts)
|
||||
end
|
||||
|
||||
lsp.type_definitions = function(opts)
|
||||
local params = vim.lsp.util.make_position_params(opts.winnr)
|
||||
return list_or_jump("textDocument/typeDefinition", "LSP Type Definitions", params, opts)
|
||||
end
|
||||
|
||||
lsp.implementations = function(opts)
|
||||
local params = vim.lsp.util.make_position_params(opts.winnr)
|
||||
return list_or_jump("textDocument/implementation", "LSP Implementations", params, opts)
|
||||
end
|
||||
|
||||
local symbols_sorter = function(symbols)
|
||||
if vim.tbl_isempty(symbols) then
|
||||
return symbols
|
||||
end
|
||||
|
||||
local current_buf = vim.api.nvim_get_current_buf()
|
||||
|
||||
-- sort adequately for workspace symbols
|
||||
local filename_to_bufnr = {}
|
||||
for _, symbol in ipairs(symbols) do
|
||||
if filename_to_bufnr[symbol.filename] == nil then
|
||||
filename_to_bufnr[symbol.filename] = vim.uri_to_bufnr(vim.uri_from_fname(symbol.filename))
|
||||
end
|
||||
symbol.bufnr = filename_to_bufnr[symbol.filename]
|
||||
end
|
||||
|
||||
table.sort(symbols, function(a, b)
|
||||
if a.bufnr == b.bufnr then
|
||||
return a.lnum < b.lnum
|
||||
end
|
||||
if a.bufnr == current_buf then
|
||||
return true
|
||||
end
|
||||
if b.bufnr == current_buf then
|
||||
return false
|
||||
end
|
||||
return a.bufnr < b.bufnr
|
||||
end)
|
||||
|
||||
return symbols
|
||||
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, symbols_sorter)
|
||||
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, symbols_sorter)
|
||||
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, symbols_sorter) or {}
|
||||
end
|
||||
return locations
|
||||
end
|
||||
end
|
||||
|
||||
lsp.dynamic_workspace_symbols = function(opts)
|
||||
pickers
|
||||
.new(opts, {
|
||||
prompt_title = "LSP Dynamic Workspace Symbols",
|
||||
finder = finders.new_dynamic {
|
||||
entry_maker = opts.entry_maker or make_entry.gen_from_lsp_symbols(opts),
|
||||
fn = get_workspace_symbols_requester(opts.bufnr, opts),
|
||||
},
|
||||
previewer = conf.qflist_previewer(opts),
|
||||
sorter = sorters.highlighter_only(opts),
|
||||
attach_mappings = function(_, map)
|
||||
map("i", "<c-space>", actions.to_fuzzy_refine)
|
||||
return true
|
||||
end,
|
||||
})
|
||||
:find()
|
||||
end
|
||||
|
||||
local function check_capabilities(method, bufnr)
|
||||
--TODO(clason): remove when dropping support for Nvim 0.9
|
||||
local get_clients = vim.fn.has "nvim-0.10" == 1 and vim.lsp.get_clients or vim.lsp.get_active_clients
|
||||
local clients = get_clients { bufnr = bufnr }
|
||||
|
||||
for _, client in pairs(clients) do
|
||||
if client.supports_method(method, { bufnr = bufnr }) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if #clients == 0 then
|
||||
utils.notify("builtin.lsp_*", {
|
||||
msg = "no client attached",
|
||||
level = "INFO",
|
||||
})
|
||||
else
|
||||
utils.notify("builtin.lsp_*", {
|
||||
msg = "server does not support " .. method,
|
||||
level = "INFO",
|
||||
})
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local feature_map = {
|
||||
["document_symbols"] = "textDocument/documentSymbol",
|
||||
["references"] = "textDocument/references",
|
||||
["definitions"] = "textDocument/definition",
|
||||
["type_definitions"] = "textDocument/typeDefinition",
|
||||
["implementations"] = "textDocument/implementation",
|
||||
["workspace_symbols"] = "workspace/symbol",
|
||||
["incoming_calls"] = "callHierarchy/incomingCalls",
|
||||
["outgoing_calls"] = "callHierarchy/outgoingCalls",
|
||||
}
|
||||
|
||||
local function apply_checks(mod)
|
||||
for k, v in pairs(mod) do
|
||||
mod[k] = function(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local method = feature_map[k]
|
||||
if method and not check_capabilities(method, opts.bufnr) then
|
||||
return
|
||||
end
|
||||
v(opts)
|
||||
end
|
||||
end
|
||||
|
||||
return mod
|
||||
end
|
||||
|
||||
return apply_checks(lsp)
|
||||
@ -0,0 +1,590 @@
|
||||
---@tag telescope.builtin
|
||||
|
||||
---@config { ['field_heading'] = "Options", ["module"] = "telescope.builtin" }
|
||||
|
||||
---@brief [[
|
||||
--- Telescope Builtins is a collection of community maintained pickers to support common workflows. It can be used as
|
||||
--- reference when writing PRs, Telescope extensions, your own custom pickers, or just as a discovery tool for all of
|
||||
--- the amazing pickers already shipped with Telescope!
|
||||
---
|
||||
--- Any of these functions can just be called directly by doing:
|
||||
---
|
||||
--- :lua require('telescope.builtin').$NAME_OF_PICKER()
|
||||
---
|
||||
--- To use any of Telescope's default options or any picker-specific options, call your desired picker by passing a lua
|
||||
--- table to the picker with all of the options you want to use. Here's an example with the live_grep picker:
|
||||
---
|
||||
--- <code>
|
||||
--- :lua require('telescope.builtin').live_grep({
|
||||
--- prompt_title = 'find string in open buffers...',
|
||||
--- grep_open_files = true
|
||||
--- })
|
||||
--- -- or with dropdown theme
|
||||
--- :lua require('telescope.builtin').find_files(require('telescope.themes').get_dropdown{
|
||||
--- previewer = false
|
||||
--- })
|
||||
--- </code>
|
||||
---@brief ]]
|
||||
|
||||
local builtin = {}
|
||||
|
||||
-- Ref: https://github.com/tjdevries/lazy.nvim
|
||||
local function require_on_exported_call(mod)
|
||||
return setmetatable({}, {
|
||||
__index = function(_, picker)
|
||||
return function(...)
|
||||
return require(mod)[picker](...)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- File-related Pickers
|
||||
--
|
||||
--
|
||||
|
||||
--- Search for a string and get results live as you type, respects .gitignore
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer)
|
||||
---@field grep_open_files boolean: if true, restrict search to open files only, mutually exclusive with `search_dirs`
|
||||
---@field search_dirs table: directory/directories/files to search, mutually exclusive with `grep_open_files`
|
||||
---@field glob_pattern string|table: argument to be used with `--glob`, e.g. "*.toml", can use the opposite "!*.toml"
|
||||
---@field type_filter string: argument to be used with `--type`, e.g. "rust", see `rg --type-list`
|
||||
---@field additional_args function|table: additional arguments to be passed on. Can be fn(opts) -> tbl
|
||||
---@field max_results number: define a upper result value
|
||||
---@field disable_coordinates boolean: don't show the line & row numbers (default: false)
|
||||
---@field file_encoding string: file encoding for the entry & previewer
|
||||
builtin.live_grep = require_on_exported_call("telescope.builtin.__files").live_grep
|
||||
|
||||
--- Searches for the string under your cursor or the visual selection in your current working directory
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer)
|
||||
---@field search string: the query to search
|
||||
---@field grep_open_files boolean: if true, restrict search to open files only, mutually exclusive with `search_dirs`
|
||||
---@field search_dirs table: directory/directories/files to search, mutually exclusive with `grep_open_files`
|
||||
---@field use_regex boolean: if true, special characters won't be escaped, allows for using regex (default: false)
|
||||
---@field word_match string: can be set to `-w` to enable exact word matches
|
||||
---@field additional_args function|table: additional arguments to be passed on. Can be fn(opts) -> tbl
|
||||
---@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)
|
||||
---@field file_encoding string: file encoding for the entry & previewer
|
||||
builtin.grep_string = require_on_exported_call("telescope.builtin.__files").grep_string
|
||||
|
||||
--- Search for files (respecting .gitignore)
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer)
|
||||
---@field find_command function|table: cmd to use for the search. Can be a fn(opts) -> tbl (default: autodetect)
|
||||
---@field file_entry_encoding string: encoding of output of `find_command`
|
||||
---@field follow boolean: if true, follows symlinks (i.e. uses `-L` flag for the `find` command)
|
||||
---@field hidden boolean: determines whether to show hidden files or not (default: false)
|
||||
---@field no_ignore boolean: show files ignored by .gitignore, .ignore, etc. (default: false)
|
||||
---@field 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
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
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
|
||||
|
||||
--- Lists function names, variables, and other symbols from treesitter queries
|
||||
--- - Default keymaps:
|
||||
--- - `<C-l>`: show autocompletion menu to prefilter your query by kind of ts node you want to see (i.e. `:var:`)
|
||||
---@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_width number: defines the width of the symbol section (default: 25)
|
||||
---@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
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
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 don't display empty lines (default: false)
|
||||
---@field results_ts_highlight boolean: highlight result entries with treesitter (default: true)
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
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)
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer)
|
||||
---@field ctags_file string: specify a particular ctags file to use
|
||||
---@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
|
||||
|
||||
--- Lists all of the tags for the currently open buffer, with a preview
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: root dir to search from (default: cwd, use utils.buffer_dir() to search relative to open buffer)
|
||||
---@field ctags_file string: specify a particular ctags file to use
|
||||
---@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
|
||||
|
||||
--
|
||||
--
|
||||
-- Git-related Pickers
|
||||
--
|
||||
--
|
||||
|
||||
--- Fuzzy search for files tracked by Git. This command lists the output of the `git ls-files` command,
|
||||
--- respects .gitignore
|
||||
--- - Default keymaps:
|
||||
--- - `<cr>`: opens the currently selected file
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: specify the path of the repo
|
||||
---@field use_file_path boolean: if we should use the current buffer git root (default: false)
|
||||
---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true)
|
||||
---@field show_untracked boolean: if true, adds `--others` flag to command and shows untracked files (default: false)
|
||||
---@field recurse_submodules boolean: if true, adds the `--recurse-submodules` flag to command (default: false)
|
||||
---@field git_command table: command that will be executed. {"git","ls-files","--exclude-standard","--cached"}
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.git_files = require_on_exported_call("telescope.builtin.__git").files
|
||||
|
||||
--- Lists commits for current directory with diff preview
|
||||
--- - Default keymaps:
|
||||
--- - `<cr>`: checks out the currently selected commit
|
||||
--- - `<C-r>m`: resets current branch to selected commit using mixed mode
|
||||
--- - `<C-r>s`: resets current branch to selected commit using soft mode
|
||||
--- - `<C-r>h`: resets current branch to selected commit using hard mode
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: specify the path of the repo
|
||||
---@field use_file_path boolean: if we should use the current buffer git root (default: false)
|
||||
---@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 executed. {"git","log","--pretty=oneline","--abbrev-commit","--","."}
|
||||
builtin.git_commits = require_on_exported_call("telescope.builtin.__git").commits
|
||||
|
||||
--- Lists commits for current buffer with diff preview
|
||||
--- - Default keymaps or your overridden `select_` keys:
|
||||
--- - `<cr>`: checks out the currently selected commit
|
||||
--- - `<c-v>`: opens a diff in a vertical split
|
||||
--- - `<c-x>`: opens a diff in a horizontal split
|
||||
--- - `<c-t>`: opens a diff in a new tab
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: specify the path of the repo
|
||||
---@field use_file_path boolean: if we should use the current buffer git root (default: false)
|
||||
---@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 executed. {"git","log","--pretty=oneline","--abbrev-commit"}
|
||||
builtin.git_bcommits = require_on_exported_call("telescope.builtin.__git").bcommits
|
||||
|
||||
--- Lists commits for a range of lines in the current buffer with diff preview
|
||||
--- In visual mode, lists commits for the selected lines
|
||||
--- With operator mode enabled, lists commits inside the text object/motion
|
||||
--- - Default keymaps or your overridden `select_` keys:
|
||||
--- - `<cr>`: checks out the currently selected commit
|
||||
--- - `<c-v>`: opens a diff in a vertical split
|
||||
--- - `<c-x>`: opens a diff in a horizontal split
|
||||
--- - `<c-t>`: opens a diff in a new tab
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: specify the path of the repo
|
||||
---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true)
|
||||
---@field current_file string: specify the current file that should be used for bcommits (default: current buffer)
|
||||
---@field git_command table: command that will be executed. the last element must be "-L". {"git","log","--pretty=oneline","--abbrev-commit","--no-patch","-L"}
|
||||
---@field from number: the first line number in the range (default: current line)
|
||||
---@field to number: the last line number in the range (default: the value of `from`)
|
||||
---@field operator boolean: select lines in operator-pending mode (default: false)
|
||||
builtin.git_bcommits_range = require_on_exported_call("telescope.builtin.__git").bcommits_range
|
||||
|
||||
--- List branches for current directory, with output from `git log --oneline` shown in the preview window
|
||||
--- - Default keymaps:
|
||||
--- - `<cr>`: checks out the currently selected branch
|
||||
--- - `<C-t>`: tracks currently selected branch
|
||||
--- - `<C-r>`: rebases currently selected branch
|
||||
--- - `<C-a>`: creates a new branch, with confirmation prompt before creation
|
||||
--- - `<C-d>`: deletes the currently selected branch, with confirmation prompt before deletion
|
||||
--- - `<C-y>`: merges the currently selected branch, with confirmation prompt before deletion
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: specify the path of the repo
|
||||
---@field use_file_path boolean: if we should use the current buffer git root (default: false)
|
||||
---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true)
|
||||
---@field show_remote_tracking_branches boolean: show remote tracking branches like origin/main (default: true)
|
||||
---@field pattern string: specify the pattern to match all refs
|
||||
builtin.git_branches = require_on_exported_call("telescope.builtin.__git").branches
|
||||
|
||||
--- Lists git status for current directory
|
||||
--- - Default keymaps:
|
||||
--- - `<Tab>`: stages or unstages the currently selected file
|
||||
--- - `<cr>`: opens the currently selected file
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: specify the path of the repo
|
||||
---@field use_file_path boolean: if we should use the current buffer git root (default: false)
|
||||
---@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)
|
||||
---@field expand_dir boolean: pass flag `-uall` to show files in untracked directories (default: true)
|
||||
builtin.git_status = require_on_exported_call("telescope.builtin.__git").status
|
||||
|
||||
--- Lists stash items in current repository
|
||||
--- - Default keymaps:
|
||||
--- - `<cr>`: runs `git apply` for currently selected stash
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: specify the path of the repo
|
||||
---@field use_file_path boolean: if we should use the current buffer git root (default: false)
|
||||
---@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
|
||||
|
||||
--
|
||||
--
|
||||
-- Internal and Vim-related Pickers
|
||||
--
|
||||
--
|
||||
|
||||
--- Lists all of the community maintained pickers built into Telescope
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field include_extensions boolean: if true will show the pickers of the installed extensions (default: false)
|
||||
---@field use_default_opts boolean: if the selected picker should use its default options (default: false)
|
||||
builtin.builtin = require_on_exported_call("telescope.builtin.__internal").builtin
|
||||
|
||||
--- Opens the previous picker in the identical state (incl. multi selections)
|
||||
--- - Notes:
|
||||
--- - Requires `cache_picker` in setup or when having invoked pickers, see |telescope.defaults.cache_picker|
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cache_index number: what picker to resume, where 1 denotes most recent (default: 1)
|
||||
builtin.resume = require_on_exported_call("telescope.builtin.__internal").resume
|
||||
|
||||
--- Opens a picker over previously cached pickers in their preserved states (incl. multi selections)
|
||||
--- - Default keymaps:
|
||||
--- - `<C-x>`: delete the selected cached picker
|
||||
--- - 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
|
||||
|
||||
--- 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)
|
||||
---@field show_moon boolean: we love the Moon (default: false, because its a hidden feature)
|
||||
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.
|
||||
--- We provide a couple of default symbols which can be found in
|
||||
--- https://github.com/nvim-telescope/telescope-symbols.nvim. This repos README also provides more
|
||||
--- information about the format in which the symbols have to be.
|
||||
---@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
|
||||
|
||||
--- Lists available plugin/user commands and runs them on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field show_buf_command boolean: show buf local command (Default: true)
|
||||
builtin.commands = require_on_exported_call("telescope.builtin.__internal").commands
|
||||
|
||||
--- Lists items in the quickfix list, jumps to location on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field show_line boolean: show results text (default: true)
|
||||
---@field trim_text boolean: trim results text (default: false)
|
||||
---@field nr number: specify the quickfix list number
|
||||
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
|
||||
|
||||
--- Lists items from the current window's location list, jumps to location on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field show_line boolean: show results text (default: true)
|
||||
---@field trim_text boolean: trim results text (default: false)
|
||||
builtin.loclist = require_on_exported_call("telescope.builtin.__internal").loclist
|
||||
|
||||
--- Lists previously open files, opens on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: specify a working directory to filter oldfiles by
|
||||
---@field only_cwd boolean: show only files in the cwd (default: false)
|
||||
---@field cwd_only boolean: alias for only_cwd
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.oldfiles = require_on_exported_call("telescope.builtin.__internal").oldfiles
|
||||
|
||||
--- Lists commands that were executed recently, and reruns them on `<cr>`
|
||||
--- - Default keymaps:
|
||||
--- - `<C-e>`: open the command line with the text of the currently selected result populated in it
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field filter_fn function: filter fn(cmd:string). true if the history command should be presented.
|
||||
builtin.command_history = require_on_exported_call("telescope.builtin.__internal").command_history
|
||||
|
||||
--- Lists searches that were executed recently, and reruns them on `<cr>`
|
||||
--- - Default keymaps:
|
||||
--- - `<C-e>`: open a search window with the text of the currently selected search result populated in it
|
||||
---@param opts table: options to pass to the picker
|
||||
builtin.search_history = require_on_exported_call("telescope.builtin.__internal").search_history
|
||||
|
||||
--- Lists vim options, allows you to edit the current value on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
builtin.vim_options = require_on_exported_call("telescope.builtin.__internal").vim_options
|
||||
|
||||
--- Lists available help tags and opens a new window with the relevant help info on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field lang string: specify language (default: vim.o.helplang)
|
||||
---@field fallback boolean: fallback to en if language isn't installed (default: true)
|
||||
builtin.help_tags = require_on_exported_call("telescope.builtin.__internal").help_tags
|
||||
|
||||
--- Lists manpage entries, opens them in a help window on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field sections table: a list of sections to search, use `{ "ALL" }` to search in all sections (default: { "1" })
|
||||
---@field man_cmd function: that returns the man command. (Default: `apropos ""` on linux, `apropos " "` on macos)
|
||||
builtin.man_pages = require_on_exported_call("telescope.builtin.__internal").man_pages
|
||||
|
||||
--- Lists lua modules and reloads them on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field column_len number: define the max column len for the module name (default: dynamic, longest module name)
|
||||
builtin.reloader = require_on_exported_call("telescope.builtin.__internal").reloader
|
||||
|
||||
--- Lists open buffers in current neovim instance, opens selected buffer on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field cwd string: specify a working directory to filter buffers list by
|
||||
---@field show_all_buffers boolean: if true, show all buffers, including unloaded buffers (default: true)
|
||||
---@field ignore_current_buffer boolean: if true, don't show the current buffer in the list (default: false)
|
||||
---@field only_cwd boolean: if true, only show buffers in the current working directory (default: false)
|
||||
---@field cwd_only boolean: alias for only_cwd
|
||||
---@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)
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
---@field sort_buffers function: sort fn(bufnr_a, bufnr_b). true if bufnr_a should go first. Runs after sorting by most recent (if specified)
|
||||
---@field select_current boolean: select current buffer (default: false)
|
||||
builtin.buffers = require_on_exported_call("telescope.builtin.__internal").buffers
|
||||
|
||||
--- Lists available colorschemes and applies them on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field colors table: a list of additional colorschemes to explicitly make available to telescope (default: {})
|
||||
---@field enable_preview boolean: if true, will preview the selected color
|
||||
builtin.colorscheme = require_on_exported_call("telescope.builtin.__internal").colorscheme
|
||||
|
||||
--- Lists vim marks and their value, jumps to the mark on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
---@field mark_type string: filter marks by type (default: "all", options: "all"|"global"|"local")
|
||||
builtin.marks = require_on_exported_call("telescope.builtin.__internal").marks
|
||||
|
||||
--- Lists vim registers, pastes the contents of the register on `<cr>`
|
||||
--- - Default keymaps:
|
||||
--- - `<C-e>`: edit the contents of the currently selected register
|
||||
---@param opts table: options to pass to the picker
|
||||
builtin.registers = require_on_exported_call("telescope.builtin.__internal").registers
|
||||
|
||||
--- Lists normal mode keymappings, runs the selected keymap on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field modes table: a list of short-named keymap modes to search (default: { "n", "i", "c", "x" })
|
||||
---@field show_plug boolean: if true, the keymaps for which the lhs contains "<Plug>" are also shown (default: true)
|
||||
---@field only_buf boolean: if true, only show the buffer-local keymaps (default: false)
|
||||
---@field lhs_filter function: filter(lhs:string) -> boolean. true for keymap.lhs if the keymap should be shown (optional)
|
||||
---@field filter function: filter(km:keymap) -> boolean. true for the keymap if it should be shown (optional)
|
||||
builtin.keymaps = require_on_exported_call("telescope.builtin.__internal").keymaps
|
||||
|
||||
--- Lists all available filetypes, sets currently open buffer's filetype to selected filetype in Telescope on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
builtin.filetypes = require_on_exported_call("telescope.builtin.__internal").filetypes
|
||||
|
||||
--- Lists all available highlights
|
||||
---@param opts table: options to pass to the picker
|
||||
builtin.highlights = require_on_exported_call("telescope.builtin.__internal").highlights
|
||||
|
||||
--- Lists vim autocommands and goes to their declaration on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
builtin.autocommands = require_on_exported_call("telescope.builtin.__internal").autocommands
|
||||
|
||||
--- Lists spelling suggestions for the current word under the cursor, replaces word with selected suggestion on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
builtin.spell_suggest = require_on_exported_call("telescope.builtin.__internal").spell_suggest
|
||||
|
||||
--- Lists the tag stack for the current window, jumps to tag on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field show_line boolean: show results text (default: true)
|
||||
---@field trim_text boolean: trim results text (default: false)
|
||||
builtin.tagstack = require_on_exported_call("telescope.builtin.__internal").tagstack
|
||||
|
||||
--- Lists items from Vim's jumplist, jumps to location on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field show_line boolean: show results text (default: true)
|
||||
---@field trim_text boolean: trim results text (default: false)
|
||||
builtin.jumplist = require_on_exported_call("telescope.builtin.__internal").jumplist
|
||||
|
||||
--
|
||||
--
|
||||
-- LSP-related Pickers
|
||||
--
|
||||
--
|
||||
|
||||
--- Lists LSP references for word under the cursor, jumps to reference on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field include_declaration boolean: include symbol declaration in the lsp references (default: true)
|
||||
---@field include_current_line boolean: include current line (default: false)
|
||||
---@field jump_type string: how to goto reference if there is only one and the definition file is different from the current file, values: "tab", "tab drop", "split", "vsplit", "never"
|
||||
---@field show_line boolean: show results text (default: true)
|
||||
---@field trim_text boolean: trim results text (default: false)
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.lsp_references = require_on_exported_call("telescope.builtin.__lsp").references
|
||||
|
||||
--- Lists LSP incoming calls for word under the cursor, jumps to reference on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field show_line boolean: show results text (default: true)
|
||||
---@field trim_text boolean: trim results text (default: false)
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.lsp_incoming_calls = require_on_exported_call("telescope.builtin.__lsp").incoming_calls
|
||||
|
||||
--- Lists LSP outgoing calls for word under the cursor, jumps to reference on `<cr>`
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field show_line boolean: show results text (default: true)
|
||||
---@field trim_text boolean: trim results text (default: false)
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.lsp_outgoing_calls = require_on_exported_call("telescope.builtin.__lsp").outgoing_calls
|
||||
|
||||
--- Goto the definition of the word under the cursor, if there's only one, otherwise show all options in Telescope
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field jump_type string: how to goto definition if there is only one and the definition file is different from the current file, values: "tab", "tab drop", "split", "vsplit", "never"
|
||||
---@field show_line boolean: show results text (default: true)
|
||||
---@field trim_text boolean: trim results text (default: false)
|
||||
---@field reuse_win boolean: jump to existing window if buffer is already opened (default: false)
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.lsp_definitions = require_on_exported_call("telescope.builtin.__lsp").definitions
|
||||
|
||||
--- Goto the definition of the type of the word under the cursor, if there's only one,
|
||||
--- otherwise show all options in Telescope
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field jump_type string: how to goto definition if there is only one and the definition file is different from the current file, values: "tab", "tab drop", "split", "vsplit", "never"
|
||||
---@field show_line boolean: show results text (default: true)
|
||||
---@field trim_text boolean: trim results text (default: false)
|
||||
---@field reuse_win boolean: jump to existing window if buffer is already opened (default: false)
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.lsp_type_definitions = require_on_exported_call("telescope.builtin.__lsp").type_definitions
|
||||
|
||||
--- Goto the implementation of the word under the cursor if there's only one, otherwise show all options in Telescope
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field jump_type string: how to goto implementation if there is only one and the definition file is different from the current file, values: "tab", "tab drop", "split", "vsplit", "never"
|
||||
---@field show_line boolean: show results text (default: true)
|
||||
---@field trim_text boolean: trim results text (default: false)
|
||||
---@field reuse_win boolean: jump to existing window if buffer is already opened (default: false)
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.lsp_implementations = require_on_exported_call("telescope.builtin.__lsp").implementations
|
||||
|
||||
--- Lists LSP document symbols in the current buffer
|
||||
--- - Default keymaps:
|
||||
--- - `<C-l>`: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`)
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field fname_width number: defines the width of the filename section (default: 30)
|
||||
---@field symbol_width number: defines the width of the symbol section (default: 25)
|
||||
---@field symbol_type_width number: defines the width of the symbol type section (default: 8)
|
||||
---@field show_line boolean: if true, shows the content of the line the tag is found on (default: false)
|
||||
---@field symbols string|table: filter results by symbol kind(s)
|
||||
---@field ignore_symbols string|table: list of symbols to ignore
|
||||
---@field symbol_highlights table: string -> string. Matches symbol with hl_group
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.lsp_document_symbols = require_on_exported_call("telescope.builtin.__lsp").document_symbols
|
||||
|
||||
--- Lists LSP document symbols in the current workspace
|
||||
--- - Default keymaps:
|
||||
--- - `<C-l>`: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`)
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field query string: for what to query the workspace (default: "")
|
||||
---@field fname_width number: defines the width of the filename section (default: 30)
|
||||
---@field symbol_width number: defines the width of the symbol section (default: 25)
|
||||
---@field symbol_type_width number: defines the width of the symbol type section (default: 8)
|
||||
---@field show_line boolean: if true, shows the content of the line the tag is found on (default: false)
|
||||
---@field symbols string|table: filter results by symbol kind(s)
|
||||
---@field ignore_symbols string|table: list of symbols to ignore
|
||||
---@field symbol_highlights table: string -> string. Matches symbol with hl_group
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.lsp_workspace_symbols = require_on_exported_call("telescope.builtin.__lsp").workspace_symbols
|
||||
|
||||
--- Dynamically lists LSP for all workspace symbols
|
||||
--- - Default keymaps:
|
||||
--- - `<C-l>`: show autocompletion menu to prefilter your query by type of symbol you want to see (i.e. `:variable:`), only works after refining to fuzzy search using <C-space>
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field fname_width number: defines the width of the filename section (default: 30)
|
||||
---@field show_line boolean: if true, shows the content of the line the symbol is found on (default: false)
|
||||
---@field symbols string|table: filter results by symbol kind(s)
|
||||
---@field ignore_symbols string|table: list of symbols to ignore
|
||||
---@field symbol_highlights table: string -> string. Matches symbol with hl_group
|
||||
---@field file_encoding string: file encoding for the previewer
|
||||
builtin.lsp_dynamic_workspace_symbols = require_on_exported_call("telescope.builtin.__lsp").dynamic_workspace_symbols
|
||||
|
||||
--
|
||||
--
|
||||
-- Diagnostics Pickers
|
||||
--
|
||||
--
|
||||
|
||||
--- Lists diagnostics
|
||||
--- - Fields:
|
||||
--- - `All severity flags can be passed as `string` or `number` as per `:vim.diagnostic.severity:`
|
||||
--- - Default keymaps:
|
||||
--- - `<C-l>`: show autocompletion menu to prefilter your query with the diagnostic you want to see (i.e. `:warning:`)
|
||||
--- - sort_by option:
|
||||
--- - "buffer": order by bufnr (prioritizing current bufnr), severity, lnum
|
||||
--- - "severity": order by severity, bufnr (prioritizing current bufnr), lnum
|
||||
---@param opts table: options to pass to the picker
|
||||
---@field bufnr number|nil: Buffer number to get diagnostics from. Use 0 for current buffer or nil for all buffers
|
||||
---@field severity string|number: filter diagnostics by severity name (string) or id (number)
|
||||
---@field severity_limit string|number: keep diagnostics equal or more severe wrt severity name (string) or id (number)
|
||||
---@field severity_bound string|number: keep diagnostics equal or less severe wrt severity name (string) or id (number)
|
||||
---@field root_dir string|boolean: if set to string, get diagnostics only for buffers under this dir otherwise cwd
|
||||
---@field no_unlisted boolean: if true, get diagnostics only for listed buffers
|
||||
---@field no_sign boolean: hide DiagnosticSigns from Results (default: false)
|
||||
---@field line_width string|number: set length of diagnostic entry text in Results. Use 'full' for full untruncated text
|
||||
---@field namespace number: limit your diagnostics to a specific namespace
|
||||
---@field disable_coordinates boolean: don't show the line & row numbers (default: false)
|
||||
---@field sort_by string: sort order of the diagnostics results; see above notes (default: "buffer")
|
||||
builtin.diagnostics = require_on_exported_call("telescope.builtin.__diagnostics").get
|
||||
|
||||
local apply_config = function(mod)
|
||||
for k, v in pairs(mod) do
|
||||
mod[k] = function(opts)
|
||||
local pickers_conf = require("telescope.config").pickers
|
||||
|
||||
opts = opts or {}
|
||||
opts.bufnr = opts.bufnr or vim.api.nvim_get_current_buf()
|
||||
opts.winnr = opts.winnr or vim.api.nvim_get_current_win()
|
||||
local pconf = pickers_conf[k] or {}
|
||||
local defaults = (function()
|
||||
if pconf.theme then
|
||||
return require("telescope.themes")["get_" .. pconf.theme](pconf)
|
||||
end
|
||||
return vim.deepcopy(pconf)
|
||||
end)()
|
||||
|
||||
if pconf.mappings then
|
||||
defaults.attach_mappings = function(_, map)
|
||||
for mode, tbl in pairs(pconf.mappings) do
|
||||
for key, action in pairs(tbl) do
|
||||
map(mode, key, action)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if pconf.attach_mappings and opts.attach_mappings then
|
||||
local opts_attach = opts.attach_mappings
|
||||
opts.attach_mappings = function(prompt_bufnr, map)
|
||||
pconf.attach_mappings(prompt_bufnr, map)
|
||||
return opts_attach(prompt_bufnr, map)
|
||||
end
|
||||
end
|
||||
|
||||
if defaults.attach_mappings and opts.attach_mappings then
|
||||
local opts_attach = opts.attach_mappings
|
||||
opts.attach_mappings = function(prompt_bufnr, map)
|
||||
defaults.attach_mappings(prompt_bufnr, map)
|
||||
return opts_attach(prompt_bufnr, map)
|
||||
end
|
||||
end
|
||||
|
||||
v(vim.tbl_extend("force", defaults, opts))
|
||||
end
|
||||
end
|
||||
|
||||
return mod
|
||||
end
|
||||
|
||||
-- We can't do this in one statement because tree-sitter-lua docgen gets confused if we do
|
||||
builtin = apply_config(builtin)
|
||||
return builtin
|
||||
@ -0,0 +1,262 @@
|
||||
---@tag telescope.command
|
||||
---@config { ["module"] = "telescope.command" }
|
||||
|
||||
---@brief [[
|
||||
---
|
||||
--- Telescope commands can be called through two apis,
|
||||
--- the lua api and the viml api.
|
||||
---
|
||||
--- The lua api is the more direct way to interact with Telescope, as you directly call the
|
||||
--- lua functions that Telescope defines.
|
||||
--- It can be called in a lua file using commands like:
|
||||
--- <pre>
|
||||
--- `require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})`
|
||||
--- </pre>
|
||||
--- If you want to use this api from a vim file you should prepend `lua` to the command, as below:
|
||||
--- <pre>
|
||||
--- `lua require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})`
|
||||
--- </pre>
|
||||
--- If you want to use this api from a neovim command line you should prepend `:lua` to
|
||||
--- the command, as below:
|
||||
--- <pre>
|
||||
--- `:lua require("telescope.builtin").find_files({hidden=true, layout_config={prompt_position="top"}})`
|
||||
--- </pre>
|
||||
---
|
||||
--- The viml api is more indirect, as first the command must be parsed to the relevant lua
|
||||
--- equivalent, which brings some limitations.
|
||||
--- The viml api can be called using commands like:
|
||||
--- <pre>
|
||||
--- `:Telescope find_files hidden=true layout_config={"prompt_position":"top"}`
|
||||
--- </pre>
|
||||
--- This involves setting options using an `=` and using viml syntax for lists and
|
||||
--- dictionaries when the corresponding lua function requires a table.
|
||||
---
|
||||
--- One limitation of the viml api is that there can be no spaces in any of the options.
|
||||
--- For example, if you want to use the `cwd` option for `find_files` to specify that you
|
||||
--- only want to search within the folder `/foo bar/subfolder/` you could not do that using the
|
||||
--- viml api, as the path name contains a space.
|
||||
--- Similarly, you could NOT set the `prompt_position` to `"top"` using the following command:
|
||||
--- <pre>
|
||||
--- `:Telescope find_files layout_config={ "prompt_position" : "top" }`
|
||||
--- </pre>
|
||||
--- as there are spaces in the option.
|
||||
---
|
||||
---@brief ]]
|
||||
local themes = require "telescope.themes"
|
||||
local builtin = require "telescope.builtin"
|
||||
local extensions = require("telescope._extensions").manager
|
||||
local config = require "telescope.config"
|
||||
local utils = require "telescope.utils"
|
||||
local command = {}
|
||||
|
||||
local arg_value = {
|
||||
["nil"] = nil,
|
||||
['""'] = "",
|
||||
['"'] = "",
|
||||
}
|
||||
|
||||
local bool_type = {
|
||||
["false"] = false,
|
||||
["true"] = true,
|
||||
}
|
||||
|
||||
local split_keywords = {
|
||||
["find_command"] = true,
|
||||
["vimgrep_arguments"] = true,
|
||||
["sections"] = true,
|
||||
["search_dirs"] = true,
|
||||
["symbols"] = true,
|
||||
["ignore_symbols"] = true,
|
||||
}
|
||||
|
||||
-- convert command line string arguments to
|
||||
-- lua number boolean type and nil value
|
||||
command.convert_user_opts = function(user_opts)
|
||||
local default_opts = config.values
|
||||
|
||||
local _switch = {
|
||||
["boolean"] = function(key, val)
|
||||
if val == "false" then
|
||||
user_opts[key] = false
|
||||
return
|
||||
end
|
||||
user_opts[key] = true
|
||||
end,
|
||||
["number"] = function(key, val)
|
||||
user_opts[key] = tonumber(val)
|
||||
end,
|
||||
["string"] = function(key, val)
|
||||
if arg_value[val] ~= nil then
|
||||
user_opts[key] = arg_value[val]
|
||||
return
|
||||
end
|
||||
|
||||
if bool_type[val] ~= nil then
|
||||
user_opts[key] = bool_type[val]
|
||||
end
|
||||
end,
|
||||
["table"] = function(key, val)
|
||||
local ok, eval = pcall(vim.fn.eval, val)
|
||||
if ok then
|
||||
user_opts[key] = eval
|
||||
else
|
||||
local err
|
||||
eval, err = loadstring("return " .. val)
|
||||
if err ~= nil then
|
||||
-- discard invalid lua expression
|
||||
user_opts[key] = nil
|
||||
elseif eval ~= nil then
|
||||
ok, eval = pcall(eval)
|
||||
if ok and type(eval) == "table" then
|
||||
-- allow if return a table only
|
||||
user_opts[key] = eval
|
||||
else
|
||||
-- otherwise return nil (allows split check later)
|
||||
user_opts[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
local _switch_metatable = {
|
||||
__index = function(_, k)
|
||||
utils.notify("command", {
|
||||
msg = string.format("Type of '%s' does not match", k),
|
||||
level = "WARN",
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
setmetatable(_switch, _switch_metatable)
|
||||
|
||||
for key, val in pairs(user_opts) do
|
||||
if split_keywords[key] then
|
||||
_switch["table"](key, val)
|
||||
if user_opts[key] == nil then
|
||||
user_opts[key] = vim.split(val, ",")
|
||||
end
|
||||
elseif default_opts[key] ~= nil then
|
||||
_switch[type(default_opts[key])](key, val)
|
||||
elseif tonumber(val) ~= nil then
|
||||
_switch["number"](key, val)
|
||||
else
|
||||
_switch["string"](key, val)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- receive the viml command args
|
||||
-- it should be a table value like
|
||||
-- {
|
||||
-- cmd = 'find_files',
|
||||
-- theme = 'dropdown',
|
||||
-- extension_type = 'command'
|
||||
-- opts = {
|
||||
-- cwd = '***',
|
||||
-- }
|
||||
local function run_command(args)
|
||||
local user_opts = args or {}
|
||||
if next(user_opts) == nil and not user_opts.cmd then
|
||||
utils.notify("command", {
|
||||
msg = "Command missing arguments",
|
||||
level = "ERROR",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local cmd = user_opts.cmd
|
||||
local opts = user_opts.opts or {}
|
||||
local extension_type = user_opts.extension_type or ""
|
||||
local theme = user_opts.theme or ""
|
||||
|
||||
if next(opts) ~= nil then
|
||||
command.convert_user_opts(opts)
|
||||
end
|
||||
|
||||
if string.len(theme) > 0 then
|
||||
local func = themes[theme] or themes["get_" .. theme]
|
||||
opts = func(opts)
|
||||
end
|
||||
|
||||
if string.len(extension_type) > 0 and extension_type ~= '"' then
|
||||
extensions[cmd][extension_type](opts)
|
||||
return
|
||||
end
|
||||
|
||||
if builtin[cmd] then
|
||||
builtin[cmd](opts)
|
||||
return
|
||||
end
|
||||
|
||||
if rawget(extensions, cmd) then
|
||||
extensions[cmd][cmd](opts)
|
||||
return
|
||||
end
|
||||
|
||||
local ok = pcall(require("telescope").load_extension, cmd)
|
||||
if ok then
|
||||
extensions[cmd][cmd](opts)
|
||||
return
|
||||
end
|
||||
|
||||
utils.notify("run_command", {
|
||||
msg = "Unknown command",
|
||||
level = "ERROR",
|
||||
})
|
||||
end
|
||||
|
||||
-- @Summary get extensions sub command
|
||||
-- register extensions dap gh etc.
|
||||
-- input in command line `Telescope gh <TAB>`
|
||||
-- Returns a list for each extension.
|
||||
function command.get_extensions_subcommand()
|
||||
local exts = require("telescope._extensions").manager
|
||||
local complete_ext_table = {}
|
||||
for cmd, value in pairs(exts) do
|
||||
if type(value) == "table" then
|
||||
local subcmds = {}
|
||||
for key, _ in pairs(value) do
|
||||
table.insert(subcmds, key)
|
||||
end
|
||||
complete_ext_table[cmd] = subcmds
|
||||
end
|
||||
end
|
||||
return complete_ext_table
|
||||
end
|
||||
|
||||
function command.register_keyword(keyword)
|
||||
split_keywords[keyword] = true
|
||||
end
|
||||
|
||||
function command.load_command(cmd, ...)
|
||||
local args = { ... }
|
||||
if cmd == nil then
|
||||
run_command { cmd = "builtin" }
|
||||
return
|
||||
end
|
||||
|
||||
local user_opts = {
|
||||
cmd = cmd,
|
||||
opts = {},
|
||||
}
|
||||
|
||||
for _, arg in ipairs(args) do
|
||||
if arg:find("=", 1) == nil then
|
||||
user_opts["extension_type"] = arg
|
||||
else
|
||||
local param = vim.split(arg, "=")
|
||||
local key = table.remove(param, 1)
|
||||
param = table.concat(param, "=")
|
||||
if key == "theme" then
|
||||
user_opts["theme"] = param
|
||||
else
|
||||
user_opts.opts[key] = param
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
run_command(user_opts)
|
||||
end
|
||||
|
||||
return command
|
||||
@ -0,0 +1,991 @@
|
||||
local strings = require "plenary.strings"
|
||||
local deprecated = require "telescope.deprecated"
|
||||
local sorters = require "telescope.sorters"
|
||||
local os_sep = require("plenary.path").path.sep
|
||||
local has_win = vim.fn.has "win32" == 1
|
||||
|
||||
-- Keep the values around between reloads
|
||||
_TelescopeConfigurationValues = _TelescopeConfigurationValues or {}
|
||||
_TelescopeConfigurationPickers = _TelescopeConfigurationPickers or {}
|
||||
|
||||
local function first_non_null(...)
|
||||
local n = select("#", ...)
|
||||
for i = 1, n do
|
||||
local value = select(i, ...)
|
||||
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- A function that creates an amended copy of the `base` table,
|
||||
-- by replacing keys at "level 2" that match keys in "level 1" in `priority`,
|
||||
-- and then performs a deep_extend.
|
||||
-- May give unexpected results if used with tables of "depth"
|
||||
-- greater than 2.
|
||||
local smarter_depth_2_extend = function(priority, base)
|
||||
local result = {}
|
||||
for key, val in pairs(base) do
|
||||
if type(val) ~= "table" then
|
||||
result[key] = first_non_null(priority[key], val)
|
||||
else
|
||||
result[key] = {}
|
||||
for k, v in pairs(val) do
|
||||
result[key][k] = first_non_null(priority[k], v)
|
||||
end
|
||||
end
|
||||
end
|
||||
for key, val in pairs(priority) do
|
||||
if type(val) ~= "table" then
|
||||
result[key] = first_non_null(val, result[key])
|
||||
else
|
||||
result[key] = vim.tbl_extend("keep", val, result[key] or {})
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local resolve_table_opts = function(priority, base)
|
||||
if priority == false or (priority == nil and base == false) then
|
||||
return false
|
||||
end
|
||||
if priority == nil and type(base) == "table" then
|
||||
return base
|
||||
end
|
||||
return smarter_depth_2_extend(priority, base)
|
||||
end
|
||||
|
||||
-- TODO: Add other major configuration points here.
|
||||
-- selection_strategy
|
||||
|
||||
local config = {}
|
||||
config.smarter_depth_2_extend = smarter_depth_2_extend
|
||||
config.resolve_table_opts = resolve_table_opts
|
||||
|
||||
config.values = _TelescopeConfigurationValues
|
||||
config.descriptions = {}
|
||||
config.pickers = _TelescopeConfigurationPickers
|
||||
|
||||
function config.set_pickers(pickers)
|
||||
pickers = vim.F.if_nil(pickers, {})
|
||||
|
||||
for k, v in pairs(pickers) do
|
||||
config.pickers[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
local layout_config_defaults = {
|
||||
|
||||
horizontal = {
|
||||
width = 0.8,
|
||||
height = 0.9,
|
||||
prompt_position = "bottom",
|
||||
preview_cutoff = 120,
|
||||
},
|
||||
|
||||
vertical = {
|
||||
width = 0.8,
|
||||
height = 0.9,
|
||||
prompt_position = "bottom",
|
||||
preview_cutoff = 40,
|
||||
},
|
||||
|
||||
center = {
|
||||
width = 0.5,
|
||||
height = 0.4,
|
||||
preview_cutoff = 40,
|
||||
prompt_position = "top",
|
||||
},
|
||||
|
||||
cursor = {
|
||||
width = 0.8,
|
||||
height = 0.9,
|
||||
preview_cutoff = 40,
|
||||
},
|
||||
|
||||
bottom_pane = {
|
||||
height = 25,
|
||||
prompt_position = "top",
|
||||
preview_cutoff = 120,
|
||||
},
|
||||
}
|
||||
|
||||
local layout_config_description = string.format(
|
||||
[[
|
||||
Determines the default configuration values for layout strategies.
|
||||
See |telescope.layout| for details of the configurations options for
|
||||
each strategy.
|
||||
|
||||
Allows setting defaults for all strategies as top level options and
|
||||
for overriding for specific options.
|
||||
For example, the default values below set the default width to 80%% of
|
||||
the screen width for all strategies except 'center', which has width
|
||||
of 50%% of the screen width.
|
||||
|
||||
Default: %s
|
||||
]],
|
||||
vim.inspect(layout_config_defaults, { newline = "\n ", indent = " " })
|
||||
)
|
||||
|
||||
-- A table of all the usual defaults for telescope.
|
||||
-- Keys will be the name of the default,
|
||||
-- values will be a list where:
|
||||
-- - first entry is the value
|
||||
-- - second entry is the description of the option
|
||||
|
||||
local telescope_defaults = {}
|
||||
config.descriptions_order = {}
|
||||
local append = function(name, val, doc)
|
||||
telescope_defaults[name] = { val, doc }
|
||||
table.insert(config.descriptions_order, name)
|
||||
end
|
||||
|
||||
append(
|
||||
"sorting_strategy",
|
||||
"descending",
|
||||
[[
|
||||
Determines the direction "better" results are sorted towards.
|
||||
|
||||
Available options are:
|
||||
- "descending" (default)
|
||||
- "ascending"]]
|
||||
)
|
||||
|
||||
append(
|
||||
"selection_strategy",
|
||||
"reset",
|
||||
[[
|
||||
Determines how the cursor acts after each sort iteration.
|
||||
|
||||
Available options are:
|
||||
- "reset" (default)
|
||||
- "follow"
|
||||
- "row"
|
||||
- "closest"
|
||||
- "none"]]
|
||||
)
|
||||
|
||||
append(
|
||||
"scroll_strategy",
|
||||
"cycle",
|
||||
[[
|
||||
Determines what happens if you try to scroll past the view of the
|
||||
picker.
|
||||
|
||||
Available options are:
|
||||
- "cycle" (default)
|
||||
- "limit"]]
|
||||
)
|
||||
|
||||
append(
|
||||
"layout_strategy",
|
||||
"horizontal",
|
||||
[[
|
||||
Determines the default layout of Telescope pickers.
|
||||
See |telescope.layout| for details of the available strategies.
|
||||
|
||||
Default: 'horizontal']]
|
||||
)
|
||||
|
||||
append(
|
||||
"create_layout",
|
||||
nil,
|
||||
[[
|
||||
Configure the layout of Telescope pickers.
|
||||
See |telescope.pickers.layout| for details.
|
||||
|
||||
Default: 'nil']]
|
||||
)
|
||||
|
||||
append("layout_config", layout_config_defaults, layout_config_description)
|
||||
|
||||
append(
|
||||
"cycle_layout_list",
|
||||
{ "horizontal", "vertical" },
|
||||
[[
|
||||
Determines the layouts to cycle through when using `actions.layout.cycle_layout_next`
|
||||
and `actions.layout.cycle_layout_prev`.
|
||||
Should be a list of "layout setups".
|
||||
Each "layout setup" can take one of two forms:
|
||||
1. string
|
||||
This is interpreted as the name of a `layout_strategy`
|
||||
2. table
|
||||
A table with possible keys `layout_strategy`, `layout_config` and `previewer`
|
||||
|
||||
Default: { "horizontal", "vertical" }
|
||||
]]
|
||||
)
|
||||
|
||||
append(
|
||||
"winblend",
|
||||
function()
|
||||
return vim.o.winblend
|
||||
end,
|
||||
[[
|
||||
Configure winblend for telescope floating windows. See |winblend| for
|
||||
more information. Type can be a number or a function returning a
|
||||
number
|
||||
|
||||
Default: function() return vim.o.winblend end]]
|
||||
)
|
||||
|
||||
append(
|
||||
"wrap_results",
|
||||
false,
|
||||
[[
|
||||
Word wrap the search results
|
||||
|
||||
Default: false]]
|
||||
)
|
||||
|
||||
append(
|
||||
"prompt_prefix",
|
||||
"> ",
|
||||
[[
|
||||
The character(s) that will be shown in front of Telescope's prompt.
|
||||
|
||||
Default: '> ']]
|
||||
)
|
||||
|
||||
append(
|
||||
"selection_caret",
|
||||
"> ",
|
||||
[[
|
||||
The character(s) that will be shown in front of the current selection.
|
||||
|
||||
Default: '> ']]
|
||||
)
|
||||
|
||||
append(
|
||||
"entry_prefix",
|
||||
" ",
|
||||
[[
|
||||
Prefix in front of each result entry. Current selection not included.
|
||||
|
||||
Default: ' ']]
|
||||
)
|
||||
|
||||
append(
|
||||
"multi_icon",
|
||||
"+",
|
||||
[[
|
||||
Symbol to add in front of a multi-selected result entry.
|
||||
Replaces final character of |telescope.defaults.selection_caret| and
|
||||
|telescope.defaults.entry_prefix| as appropriate.
|
||||
To have no icon, set to the empty string.
|
||||
|
||||
Default: '+']]
|
||||
)
|
||||
|
||||
append(
|
||||
"initial_mode",
|
||||
"insert",
|
||||
[[
|
||||
Determines in which mode telescope starts. Valid Keys:
|
||||
`insert` and `normal`.
|
||||
|
||||
Default: "insert"]]
|
||||
)
|
||||
|
||||
append(
|
||||
"border",
|
||||
true,
|
||||
[[
|
||||
Boolean defining if borders are added to Telescope windows.
|
||||
|
||||
Default: true]]
|
||||
)
|
||||
|
||||
append(
|
||||
"path_display",
|
||||
{},
|
||||
[[
|
||||
Determines how file paths are displayed.
|
||||
|
||||
path_display can be set to an array with a combination of:
|
||||
- "hidden" hide file names
|
||||
- "tail" only display the file name, and not the path
|
||||
- "absolute" display absolute paths
|
||||
- "smart" remove as much from the path as possible to only show
|
||||
the difference between the displayed paths.
|
||||
Warning: The nature of the algorithm might have a negative
|
||||
performance impact!
|
||||
- "shorten" only display the first character of each directory in
|
||||
the path
|
||||
- "truncate" truncates the start of the path when the whole path will
|
||||
not fit. To increase the gap between the path and the edge,
|
||||
set truncate to number `truncate = 3`
|
||||
- "filename_first" shows filenames first and then the directories
|
||||
|
||||
You can also specify the number of characters of each directory name
|
||||
to keep by setting `path_display.shorten = num`.
|
||||
e.g. for a path like
|
||||
`alpha/beta/gamma/delta.txt`
|
||||
setting `path_display.shorten = 1` will give a path like:
|
||||
`a/b/g/delta.txt`
|
||||
Similarly, `path_display.shorten = 2` will give a path like:
|
||||
`al/be/ga/delta.txt`
|
||||
|
||||
You can also further customise the shortening behaviour by
|
||||
setting `path_display.shorten = { len = num, exclude = list }`,
|
||||
where `len` acts as above, and `exclude` is a list of positions
|
||||
that are not shortened. Negative numbers in the list are considered
|
||||
relative to the end of the path.
|
||||
e.g. for a path like
|
||||
`alpha/beta/gamma/delta.txt`
|
||||
setting `path_display.shorten = { len = 1, exclude = {1, -1} }`
|
||||
will give a path like:
|
||||
`alpha/b/g/delta.txt`
|
||||
setting `path_display.shorten = { len = 2, exclude = {2, -2} }`
|
||||
will give a path like:
|
||||
`al/beta/gamma/de`
|
||||
|
||||
path_display can also be set to 'filename_first' to put the filename
|
||||
in front.
|
||||
|
||||
path_display = {
|
||||
"filename_first"
|
||||
},
|
||||
|
||||
The directory structure can be reversed as follows:
|
||||
|
||||
path_display = {
|
||||
filename_first = {
|
||||
reverse_directories = true
|
||||
}
|
||||
},
|
||||
|
||||
path_display can also be set to 'hidden' string to hide file names
|
||||
|
||||
path_display can also be set to a function for custom formatting of
|
||||
the path display with the following signature
|
||||
|
||||
Signature: fun(opts: table, path: string): string, table?
|
||||
|
||||
The optional table is an list of positions and highlight groups to
|
||||
set the highlighting of the return path string.
|
||||
|
||||
Example:
|
||||
|
||||
-- Format path as "file.txt (path\to\file\)"
|
||||
path_display = function(opts, path)
|
||||
local tail = require("telescope.utils").path_tail(path)
|
||||
return string.format("%s (%s)", tail, path)
|
||||
end,
|
||||
|
||||
-- Format path and add custom highlighting
|
||||
path_display = function(opts, path)
|
||||
local tail = require("telescope.utils").path_tail(path)
|
||||
path = string.format("%s (%s)", tail, path)
|
||||
|
||||
local highlights = {
|
||||
{
|
||||
{
|
||||
0, -- highlight start position
|
||||
#path, -- highlight end position
|
||||
},
|
||||
"Comment", -- highlight group name
|
||||
},
|
||||
}
|
||||
|
||||
return path, highlights
|
||||
end
|
||||
|
||||
Default: {}]]
|
||||
)
|
||||
|
||||
append(
|
||||
"borderchars",
|
||||
{ "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
|
||||
[[
|
||||
Set the borderchars of telescope floating windows. It has to be a
|
||||
table of 8 string values.
|
||||
|
||||
Default: { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }]]
|
||||
)
|
||||
|
||||
append(
|
||||
"get_status_text",
|
||||
function(self, opts)
|
||||
local multi_select_cnt = #(self:get_multi_selection())
|
||||
local showing_cnt = (self.stats.processed or 0) - (self.stats.filtered or 0)
|
||||
local total_cnt = self.stats.processed or 0
|
||||
|
||||
local status_icon = ""
|
||||
local status_text
|
||||
if opts and not opts.completed then
|
||||
status_icon = "*"
|
||||
end
|
||||
|
||||
if showing_cnt == 0 and total_cnt == 0 then
|
||||
status_text = status_icon
|
||||
elseif multi_select_cnt == 0 then
|
||||
status_text = string.format("%s %s / %s", status_icon, showing_cnt, total_cnt)
|
||||
else
|
||||
status_text = string.format("%s %s / %s / %s", status_icon, multi_select_cnt, showing_cnt, total_cnt)
|
||||
end
|
||||
|
||||
-- quick workaround for extmark right_align side-scrolling limitation
|
||||
-- https://github.com/nvim-telescope/telescope.nvim/issues/2929
|
||||
local prompt_width = vim.api.nvim_win_get_width(self.prompt_win)
|
||||
local cursor_col = vim.api.nvim_win_get_cursor(self.prompt_win)[2]
|
||||
local prefix_display_width = strings.strdisplaywidth(self.prompt_prefix) --[[@as integer]]
|
||||
local prefix_width = #self.prompt_prefix
|
||||
local prefix_shift = 0
|
||||
if prefix_display_width ~= prefix_width then
|
||||
prefix_shift = prefix_display_width
|
||||
end
|
||||
|
||||
local cursor_occluded = (prompt_width - cursor_col - #status_text + prefix_shift) < 0
|
||||
if cursor_occluded then
|
||||
return ""
|
||||
else
|
||||
return status_text
|
||||
end
|
||||
end,
|
||||
[[
|
||||
A function that determines what the virtual text looks like.
|
||||
Signature: function(picker) -> str
|
||||
|
||||
Default: function that shows current count / all]]
|
||||
)
|
||||
|
||||
append(
|
||||
"hl_result_eol",
|
||||
true,
|
||||
[[
|
||||
Changes if the highlight for the selected item in the results
|
||||
window is always the full width of the window
|
||||
|
||||
Default: true]]
|
||||
)
|
||||
|
||||
append(
|
||||
"dynamic_preview_title",
|
||||
false,
|
||||
[[
|
||||
Will change the title of the preview window dynamically, where it
|
||||
is supported. For example, the preview window's title could show up as
|
||||
the full filename.
|
||||
|
||||
Default: false]]
|
||||
)
|
||||
|
||||
append(
|
||||
"results_title",
|
||||
"Results",
|
||||
[[
|
||||
Defines the default title of the results window. A false value
|
||||
can be used to hide the title altogether.
|
||||
|
||||
Default: "Results"]]
|
||||
)
|
||||
|
||||
append(
|
||||
"prompt_title",
|
||||
"Prompt",
|
||||
[[
|
||||
Defines the default title of the prompt window. A false value
|
||||
can be used to hide the title altogether. Most of the times builtins
|
||||
define a prompt_title which will be preferred over this default.
|
||||
|
||||
Default: "Prompt"]]
|
||||
)
|
||||
|
||||
append(
|
||||
"mappings",
|
||||
{},
|
||||
[[
|
||||
Your mappings to override telescope's default mappings.
|
||||
|
||||
See: ~
|
||||
|telescope.mappings|
|
||||
]]
|
||||
)
|
||||
|
||||
append(
|
||||
"default_mappings",
|
||||
nil,
|
||||
[[
|
||||
Not recommended to use except for advanced users.
|
||||
|
||||
Will allow you to completely remove all of telescope's default maps
|
||||
and use your own.
|
||||
|
||||
Default: nil
|
||||
]]
|
||||
)
|
||||
|
||||
append(
|
||||
"history",
|
||||
{
|
||||
path = vim.fn.stdpath "data" .. os_sep .. "telescope_history",
|
||||
limit = 100,
|
||||
handler = function(...)
|
||||
return require("telescope.actions.history").get_simple_history(...)
|
||||
end,
|
||||
cycle_wrap = false,
|
||||
},
|
||||
[[
|
||||
This field handles the configuration for prompt history.
|
||||
By default it is a table, with default values (more below).
|
||||
To disable history, set it to false.
|
||||
|
||||
Currently mappings still need to be added, Example:
|
||||
mappings = {
|
||||
i = {
|
||||
["<C-Down>"] = require('telescope.actions').cycle_history_next,
|
||||
["<C-Up>"] = require('telescope.actions').cycle_history_prev,
|
||||
},
|
||||
},
|
||||
|
||||
Fields:
|
||||
- path: The path to the telescope history as string.
|
||||
Default: stdpath("data")/telescope_history
|
||||
- limit: The amount of entries that will be written in the
|
||||
history.
|
||||
Warning: If limit is set to nil it will grow unbound.
|
||||
Default: 100
|
||||
- handler: A lua function that implements the history.
|
||||
This is meant as a developer setting for extensions to
|
||||
override the history handling, e.g.,
|
||||
https://github.com/nvim-telescope/telescope-smart-history.nvim,
|
||||
which allows context sensitive (cwd + picker) history.
|
||||
|
||||
Default:
|
||||
require('telescope.actions.history').get_simple_history
|
||||
- cycle_wrap: Indicates whether the cycle_history_next and
|
||||
cycle_history_prev functions should wrap around to the
|
||||
beginning or end of the history entries on reaching
|
||||
their respective ends
|
||||
Default: false]]
|
||||
)
|
||||
|
||||
append(
|
||||
"cache_picker",
|
||||
{
|
||||
num_pickers = 1,
|
||||
limit_entries = 1000,
|
||||
ignore_empty_prompt = false,
|
||||
},
|
||||
[[
|
||||
This field handles the configuration for picker caching.
|
||||
By default it is a table, with default values (more below).
|
||||
To disable caching, set it to false.
|
||||
|
||||
Caching preserves all previous multi selections and results and
|
||||
therefore may result in slowdown or increased RAM occupation
|
||||
if too many pickers (`cache_picker.num_pickers`) or entries
|
||||
('cache_picker.limit_entries`) are cached.
|
||||
|
||||
Fields:
|
||||
- num_pickers: The number of pickers to be cached.
|
||||
Set to -1 to preserve all pickers of your
|
||||
session. If passed to a picker, the cached
|
||||
pickers with indices larger than
|
||||
`cache_picker.num_pickers` will be cleared.
|
||||
Default: 1
|
||||
- limit_entries: The amount of entries that will be saved for
|
||||
each picker.
|
||||
Default: 1000
|
||||
- ignore_empty_prompt: If true, the picker will not be cached if
|
||||
the prompt is empty (i.e., no text has been
|
||||
typed at the time of closing the prompt).
|
||||
Default: false
|
||||
]]
|
||||
)
|
||||
|
||||
append(
|
||||
"preview",
|
||||
{
|
||||
check_mime_type = not has_win,
|
||||
filesize_limit = 25,
|
||||
highlight_limit = 1,
|
||||
timeout = 250,
|
||||
treesitter = true,
|
||||
msg_bg_fillchar = "╱",
|
||||
hide_on_startup = false,
|
||||
},
|
||||
[[
|
||||
This field handles the global configuration for previewers.
|
||||
By default it is a table, with default values (more below).
|
||||
To disable previewing, set it to false. If you have disabled previewers
|
||||
globally, but want to opt in to previewing for single pickers, you will have to
|
||||
pass `preview = true` or `preview = {...}` (your config) to the `opts` of
|
||||
your picker.
|
||||
|
||||
Fields:
|
||||
- check_mime_type: Use `file` if available to try to infer whether the
|
||||
file to preview is a binary if filetype
|
||||
detection fails.
|
||||
Windows users get `file` from:
|
||||
https://github.com/julian-r/file-windows
|
||||
Set to false to attempt to preview any mime type.
|
||||
Default: true for all OS excl. Windows
|
||||
- filesize_limit: The maximum file size in MB attempted to be previewed.
|
||||
Set to false to attempt to preview any file size.
|
||||
Default: 25
|
||||
- highlight_limit: The maximum file size in MB attempted to be highlighted.
|
||||
Set to false to attempt to highlight any file size.
|
||||
Default: 1
|
||||
- timeout: Timeout the previewer if the preview did not
|
||||
complete within `timeout` milliseconds.
|
||||
Set to false to not timeout preview.
|
||||
Default: 250
|
||||
- hook(s): Function(s) that takes `(filepath, bufnr, opts)`, where opts
|
||||
exposes winid and ft (filetype).
|
||||
Available hooks (in order of priority):
|
||||
{filetype, mime, filesize, timeout}_hook
|
||||
Important: the filetype_hook must return true or false
|
||||
to indicate whether to continue (true) previewing or not (false),
|
||||
respectively.
|
||||
Two examples:
|
||||
local putils = require("telescope.previewers.utils")
|
||||
... -- preview is called in telescope.setup { ... }
|
||||
preview = {
|
||||
-- 1) Do not show previewer for certain files
|
||||
filetype_hook = function(filepath, bufnr, opts)
|
||||
-- you could analogously check opts.ft for filetypes
|
||||
local excluded = vim.tbl_filter(function(ending)
|
||||
return filepath:match(ending)
|
||||
end, {
|
||||
".*%.csv",
|
||||
".*%.toml",
|
||||
})
|
||||
if not vim.tbl_isempty(excluded) then
|
||||
putils.set_preview_message(
|
||||
bufnr,
|
||||
opts.winid,
|
||||
string.format("I don't like %s files!",
|
||||
excluded[1]:sub(5, -1))
|
||||
)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end,
|
||||
-- 2) Truncate lines to preview window for too large files
|
||||
filesize_hook = function(filepath, bufnr, opts)
|
||||
local path = require("plenary.path"):new(filepath)
|
||||
-- opts exposes winid
|
||||
local height = vim.api.nvim_win_get_height(opts.winid)
|
||||
local lines = vim.split(path:head(height), "[\r]?\n")
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
end,
|
||||
}
|
||||
The configuration recipes for relevant examples.
|
||||
Note: we use vim.filetype filetype detection,
|
||||
so if you have troubles with files not
|
||||
highlighting correctly, please read
|
||||
|vim.filetype|
|
||||
Default: nil
|
||||
- treesitter: Determines whether the previewer performs treesitter
|
||||
highlighting, which falls back to regex-based highlighting.
|
||||
`true`: treesitter highlighting for all available filetypes
|
||||
`false`: regex-based highlighting for all filetypes
|
||||
`table`: may contain the following keys:
|
||||
- enable boolean|table: if boolean, enable ts
|
||||
highlighting for all supported
|
||||
filetypes.
|
||||
if table, ts highlighting is only
|
||||
enabled for given filetypes.
|
||||
- disable table: list of filetypes for which ts highlighting
|
||||
is not used if `enable = true`.
|
||||
Default: true
|
||||
- msg_bg_fillchar: Character to fill background of unpreviewable buffers with
|
||||
Default: "╱"
|
||||
- hide_on_startup: Hide previewer when picker starts. Previewer can be toggled
|
||||
with actions.layout.toggle_preview.
|
||||
Default: false
|
||||
- ls_short: Determines whether to use the `--short` flag for the `ls`
|
||||
command when previewing directories. Otherwise will result
|
||||
to using `--long`.
|
||||
Default: false
|
||||
]]
|
||||
)
|
||||
|
||||
append(
|
||||
"vimgrep_arguments",
|
||||
{ "rg", "--color=never", "--no-heading", "--with-filename", "--line-number", "--column", "--smart-case" },
|
||||
[[
|
||||
Defines the command that will be used for `live_grep` and `grep_string`
|
||||
pickers.
|
||||
Hint: Make sure that color is currently set to `never` because we do
|
||||
not yet interpret color codes
|
||||
Hint 2: Make sure that these options are in your changes arguments:
|
||||
"--no-heading", "--with-filename", "--line-number", "--column"
|
||||
because we need them so the ripgrep output is in the correct format.
|
||||
|
||||
Default: {
|
||||
"rg",
|
||||
"--color=never",
|
||||
"--no-heading",
|
||||
"--with-filename",
|
||||
"--line-number",
|
||||
"--column",
|
||||
"--smart-case"
|
||||
}]]
|
||||
)
|
||||
|
||||
append(
|
||||
"use_less",
|
||||
true,
|
||||
[[
|
||||
Boolean if less should be enabled in term_previewer (deprecated and
|
||||
currently no longer used in the builtin pickers).
|
||||
|
||||
Default: true]]
|
||||
)
|
||||
|
||||
append(
|
||||
"set_env",
|
||||
nil,
|
||||
[[
|
||||
Set an environment for term_previewer. A table of key values:
|
||||
Example: { COLORTERM = "truecolor", ... }
|
||||
Hint: Empty table is not allowed.
|
||||
|
||||
Default: nil]]
|
||||
)
|
||||
|
||||
append(
|
||||
"color_devicons",
|
||||
true,
|
||||
[[
|
||||
Boolean if devicons should be enabled or not. If set to false, the
|
||||
text highlight group is used.
|
||||
Hint: Coloring only works if |termguicolors| is enabled.
|
||||
|
||||
Default: true]]
|
||||
)
|
||||
|
||||
append(
|
||||
"file_sorter",
|
||||
sorters.get_fzy_sorter,
|
||||
[[
|
||||
A function pointer that specifies the file_sorter. This sorter will
|
||||
be used for find_files, git_files and similar.
|
||||
Hint: If you load a native sorter, you don't need to change this value,
|
||||
the native sorter will override it anyway.
|
||||
|
||||
Default: require("telescope.sorters").get_fzy_sorter]]
|
||||
)
|
||||
|
||||
append(
|
||||
"generic_sorter",
|
||||
sorters.get_fzy_sorter,
|
||||
[[
|
||||
A function pointer to the generic sorter. The sorter that should be
|
||||
used for everything that is not a file.
|
||||
Hint: If you load a native sorter, you don't need to change this value,
|
||||
the native sorter will override it anyway.
|
||||
|
||||
Default: require("telescope.sorters").get_fzy_sorter]]
|
||||
)
|
||||
|
||||
--TODO(conni2461): Why is this even configurable???
|
||||
append(
|
||||
"prefilter_sorter",
|
||||
sorters.prefilter,
|
||||
[[
|
||||
This points to a wrapper sorter around the generic_sorter that is able
|
||||
to do prefiltering.
|
||||
It's usually used for lsp_*_symbols and lsp_*_diagnostics
|
||||
|
||||
Default: require("telescope.sorters").prefilter]]
|
||||
)
|
||||
|
||||
append(
|
||||
"tiebreak",
|
||||
function(current_entry, existing_entry, _)
|
||||
return #current_entry.ordinal < #existing_entry.ordinal
|
||||
end,
|
||||
[[
|
||||
A function that determines how to break a tie when two entries have
|
||||
the same score.
|
||||
Having a function that always returns false would keep the entries in
|
||||
the order they are found, so existing_entry before current_entry.
|
||||
Vice versa always returning true would place the current_entry
|
||||
before the existing_entry.
|
||||
|
||||
Signature: function(current_entry, existing_entry, prompt) -> boolean
|
||||
|
||||
Default: function that breaks the tie based on the length of the
|
||||
entry's ordinal]]
|
||||
)
|
||||
|
||||
append(
|
||||
"file_ignore_patterns",
|
||||
nil,
|
||||
[[
|
||||
A table of lua regex that define the files that should be ignored.
|
||||
Example: { "^scratch/" } -- ignore all files in scratch directory
|
||||
Example: { "%.npz" } -- ignore all npz files
|
||||
See: https://www.lua.org/manual/5.1/manual.html#5.4.1 for more
|
||||
information about lua regex
|
||||
Note: `file_ignore_patterns` will be used in all pickers that have a
|
||||
file associated. This might lead to the problem that lsp_ pickers
|
||||
aren't displaying results because they might be ignored by
|
||||
`file_ignore_patterns`. For example, setting up node_modules as ignored
|
||||
will never show node_modules in any results, even if you are
|
||||
interested in lsp_ results.
|
||||
|
||||
If you only want `file_ignore_patterns` for `find_files` and
|
||||
`grep_string`/`live_grep` it is suggested that you setup `gitignore`
|
||||
and have fd and or ripgrep installed because both tools will not show
|
||||
`gitignore`d files on default.
|
||||
|
||||
Default: nil]]
|
||||
)
|
||||
|
||||
append(
|
||||
"get_selection_window",
|
||||
function()
|
||||
return 0
|
||||
end,
|
||||
[[
|
||||
Function that takes function(picker, entry) and returns a window id.
|
||||
The window ID will be used to decide what window the chosen file will
|
||||
be opened in and the cursor placed in upon leaving the picker.
|
||||
|
||||
Default: `function() return 0 end`
|
||||
]]
|
||||
)
|
||||
|
||||
append(
|
||||
"git_worktrees",
|
||||
nil,
|
||||
[[
|
||||
A table of arrays of detached working trees with keys `gitdir` and `toplevel`.
|
||||
Used to pass `--git-dir` and `--work-tree` flags to git commands when telescope fails
|
||||
to infer the top-level directory of a given working tree based on cwd.
|
||||
Example:
|
||||
git_worktrees = {
|
||||
{
|
||||
toplevel = vim.env.HOME,
|
||||
gitdir = vim.env.HOME .. '/.cfg'
|
||||
}
|
||||
}
|
||||
|
||||
Default: nil
|
||||
]]
|
||||
)
|
||||
|
||||
append(
|
||||
"file_previewer",
|
||||
function(...)
|
||||
return require("telescope.previewers").vim_buffer_cat.new(...)
|
||||
end,
|
||||
[[
|
||||
Function pointer to the default file_previewer. It is mostly used
|
||||
for find_files, git_files and similar.
|
||||
You can change this function pointer to either use your own
|
||||
previewer or use the command-line program bat as the previewer:
|
||||
require("telescope.previewers").cat.new
|
||||
|
||||
Default: require("telescope.previewers").vim_buffer_cat.new]]
|
||||
)
|
||||
|
||||
append(
|
||||
"grep_previewer",
|
||||
function(...)
|
||||
return require("telescope.previewers").vim_buffer_vimgrep.new(...)
|
||||
end,
|
||||
[[
|
||||
Function pointer to the default vim_grep previewer. It is mostly
|
||||
used for live_grep, grep_string and similar.
|
||||
You can change this function pointer to either use your own
|
||||
previewer or use the command-line program bat as the previewer:
|
||||
require("telescope.previewers").vimgrep.new
|
||||
|
||||
Default: require("telescope.previewers").vim_buffer_vimgrep.new]]
|
||||
)
|
||||
|
||||
append(
|
||||
"qflist_previewer",
|
||||
function(...)
|
||||
return require("telescope.previewers").vim_buffer_qflist.new(...)
|
||||
end,
|
||||
[[
|
||||
Function pointer to the default qflist previewer. It is mostly
|
||||
used for qflist, loclist and lsp.
|
||||
You can change this function pointer to either use your own
|
||||
previewer or use the command-line program bat as the previewer:
|
||||
require("telescope.previewers").qflist.new
|
||||
|
||||
Default: require("telescope.previewers").vim_buffer_qflist.new]]
|
||||
)
|
||||
|
||||
append(
|
||||
"buffer_previewer_maker",
|
||||
function(...)
|
||||
return require("telescope.previewers").buffer_previewer_maker(...)
|
||||
end,
|
||||
[[
|
||||
Developer option that defines the underlining functionality
|
||||
of the buffer previewer.
|
||||
For interesting configuration examples take a look at
|
||||
https://github.com/nvim-telescope/telescope.nvim/wiki/Configuration-Recipes
|
||||
|
||||
Default: require("telescope.previewers").buffer_previewer_maker]]
|
||||
)
|
||||
|
||||
-- @param user_defaults table: a table where keys are the names of options,
|
||||
-- and values are the ones the user wants
|
||||
-- @param tele_defaults table: (optional) a table containing all of the defaults
|
||||
-- for telescope [defaults to `telescope_defaults`]
|
||||
function config.set_defaults(user_defaults, tele_defaults)
|
||||
user_defaults = vim.F.if_nil(user_defaults, {})
|
||||
tele_defaults = vim.F.if_nil(tele_defaults, telescope_defaults)
|
||||
|
||||
-- Check if using layout keywords outside of `layout_config`
|
||||
deprecated.options(user_defaults)
|
||||
|
||||
local function get(name, default_val)
|
||||
if name == "layout_config" then
|
||||
return smarter_depth_2_extend(
|
||||
vim.F.if_nil(user_defaults[name], {}),
|
||||
vim.tbl_deep_extend("keep", vim.F.if_nil(config.values[name], {}), vim.F.if_nil(default_val, {}))
|
||||
)
|
||||
end
|
||||
if name == "history" or name == "cache_picker" or name == "preview" then
|
||||
if user_defaults[name] == false or config.values[name] == false then
|
||||
return false
|
||||
end
|
||||
if user_defaults[name] == true then
|
||||
return vim.F.if_nil(config.values[name], {})
|
||||
end
|
||||
|
||||
return smarter_depth_2_extend(
|
||||
vim.F.if_nil(user_defaults[name], {}),
|
||||
vim.tbl_deep_extend("keep", vim.F.if_nil(config.values[name], {}), vim.F.if_nil(default_val, {}))
|
||||
)
|
||||
end
|
||||
return first_non_null(user_defaults[name], config.values[name], default_val)
|
||||
end
|
||||
|
||||
local function set(name, default_val, description)
|
||||
assert(description, "Config values must always have a description")
|
||||
|
||||
config.values[name] = get(name, default_val)
|
||||
config.descriptions[name] = strings.dedent(description)
|
||||
end
|
||||
|
||||
for key, info in pairs(tele_defaults) do
|
||||
set(key, info[1], info[2])
|
||||
end
|
||||
|
||||
local M = {}
|
||||
M.get = get
|
||||
return M
|
||||
end
|
||||
|
||||
function config.clear_defaults()
|
||||
for k, _ in pairs(config.values) do
|
||||
config.values[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
config.set_defaults()
|
||||
|
||||
return config
|
||||
@ -0,0 +1,323 @@
|
||||
---@tag telescope.resolve
|
||||
---@config { ["module"] = "telescope.resolve" }
|
||||
|
||||
---@brief [[
|
||||
--- Provides "resolver functions" to allow more customisable inputs for options.
|
||||
---@brief ]]
|
||||
|
||||
--[[
|
||||
|
||||
Ultimately boils down to getting `height` and `width` for:
|
||||
- prompt
|
||||
- preview
|
||||
- results
|
||||
|
||||
No matter what you do, I will not make prompt have more than one line (atm)
|
||||
|
||||
Result of `resolve` should be a table with:
|
||||
|
||||
{
|
||||
preview = {
|
||||
get_width = function(self, max_columns, max_lines) end
|
||||
get_height = function(self, max_columns, max_lines) end
|
||||
},
|
||||
|
||||
result = {
|
||||
get_width = function(self, max_columns, max_lines) end
|
||||
get_height = function(self, max_columns, max_lines) end
|
||||
},
|
||||
|
||||
prompt = {
|
||||
get_width = function(self, max_columns, max_lines) end
|
||||
get_height = function(self, max_columns, max_lines) end
|
||||
},
|
||||
|
||||
total ?
|
||||
}
|
||||
|
||||
!!NOT IMPLEMENTED YET!!
|
||||
|
||||
height =
|
||||
1. 0 <= number < 1
|
||||
This means total height as a percentage
|
||||
|
||||
2. 1 <= number
|
||||
This means total height as a fixed number
|
||||
|
||||
3. function(picker, columns, lines)
|
||||
-> returns one of the above options
|
||||
return math.min(110, max_rows * .5)
|
||||
|
||||
if columns > 120 then
|
||||
return 110
|
||||
else
|
||||
return 0.6
|
||||
end
|
||||
|
||||
3. {
|
||||
previewer = x,
|
||||
results = x,
|
||||
prompt = x,
|
||||
}, this means I do my best guess I can for these, given your options
|
||||
|
||||
width =
|
||||
exactly the same, but switch to width
|
||||
|
||||
|
||||
{
|
||||
height = 0.5,
|
||||
width = {
|
||||
previewer = 0.25,
|
||||
results = 30,
|
||||
}
|
||||
}
|
||||
|
||||
https://github.com/nvim-lua/telescope.nvim/pull/43
|
||||
|
||||
After we get layout, we should try and make top-down sorting work.
|
||||
That's the next step to scrolling.
|
||||
|
||||
{
|
||||
vertical = {
|
||||
},
|
||||
horizontal = {
|
||||
},
|
||||
|
||||
height = ...
|
||||
width = ...
|
||||
}
|
||||
|
||||
|
||||
|
||||
--]]
|
||||
|
||||
local resolver = {}
|
||||
local _resolve_map = {}
|
||||
|
||||
local throw_invalid_config_option = function(key, value)
|
||||
error(string.format("Invalid configuration option for '%s': '%s'", key, tostring(value)), 2)
|
||||
end
|
||||
|
||||
-- Booleans
|
||||
_resolve_map[function(val)
|
||||
return val == false
|
||||
end] = function(_, val)
|
||||
return function(...)
|
||||
return val
|
||||
end
|
||||
end
|
||||
|
||||
-- Percentages
|
||||
_resolve_map[function(val)
|
||||
return type(val) == "number" and val >= 0 and val < 1
|
||||
end] = function(selector, val)
|
||||
return function(...)
|
||||
local selected = select(selector, ...)
|
||||
return math.floor(val * selected)
|
||||
end
|
||||
end
|
||||
|
||||
-- Numbers
|
||||
_resolve_map[function(val)
|
||||
return type(val) == "number" and val >= 1
|
||||
end] = function(selector, val)
|
||||
return function(...)
|
||||
local selected = select(selector, ...)
|
||||
return math.min(val, selected)
|
||||
end
|
||||
end
|
||||
|
||||
-- function:
|
||||
-- Function must have same signature as get_window_layout
|
||||
-- function(self, max_columns, max_lines): number
|
||||
--
|
||||
-- Resulting number is used for this configuration value.
|
||||
_resolve_map[function(val)
|
||||
return type(val) == "function"
|
||||
end] = function(_, val)
|
||||
return val
|
||||
end
|
||||
|
||||
_resolve_map[function(val)
|
||||
return type(val) == "table" and val["max"] ~= nil and val[1] ~= nil and val[1] >= 0 and val[1] < 1
|
||||
end] = function(
|
||||
selector,
|
||||
val
|
||||
)
|
||||
return function(...)
|
||||
local selected = select(selector, ...)
|
||||
return math.min(math.floor(val[1] * selected), val["max"])
|
||||
end
|
||||
end
|
||||
|
||||
_resolve_map[function(val)
|
||||
return type(val) == "table" and val["min"] ~= nil and val[1] ~= nil and val[1] >= 0 and val[1] < 1
|
||||
end] = function(
|
||||
selector,
|
||||
val
|
||||
)
|
||||
return function(...)
|
||||
local selected = select(selector, ...)
|
||||
return math.max(math.floor(val[1] * selected), val["min"])
|
||||
end
|
||||
end
|
||||
|
||||
-- Add padding option
|
||||
_resolve_map[function(val)
|
||||
return type(val) == "table" and val["padding"] ~= nil
|
||||
end] = function(selector, val)
|
||||
local resolve_pad = function(value)
|
||||
for k, v in pairs(_resolve_map) do
|
||||
if k(value) then
|
||||
return v(selector, value)
|
||||
end
|
||||
end
|
||||
throw_invalid_config_option("padding", value)
|
||||
end
|
||||
|
||||
return function(...)
|
||||
local selected = select(selector, ...)
|
||||
local padding = resolve_pad(val["padding"])
|
||||
return math.floor(selected - 2 * padding(...))
|
||||
end
|
||||
end
|
||||
|
||||
--- Converts input to a function that returns the height.
|
||||
--- The input must take one of five forms:
|
||||
--- 1. 0 <= number < 1 <br>
|
||||
--- This means total height as a percentage.
|
||||
--- 2. 1 <= number <br>
|
||||
--- This means total height as a fixed number.
|
||||
--- 3. function <br>
|
||||
--- Must have signature:
|
||||
--- function(self, max_columns, max_lines): number
|
||||
--- 4. table of the form: { val, max = ..., min = ... } <br>
|
||||
--- val has to be in the first form 0 <= val < 1 and only one is given,
|
||||
--- `min` or `max` as fixed number
|
||||
--- 5. table of the form: {padding = `foo`} <br>
|
||||
--- where `foo` has one of the previous three forms. <br>
|
||||
--- The height is then set to be the remaining space after padding.
|
||||
--- For example, if the window has height 50, and the input is {padding = 5},
|
||||
--- the height returned will be `40 = 50 - 2*5`
|
||||
---
|
||||
--- The returned function will have signature:
|
||||
--- function(self, max_columns, max_lines): number
|
||||
resolver.resolve_height = function(val)
|
||||
for k, v in pairs(_resolve_map) do
|
||||
if k(val) then
|
||||
return v(3, val)
|
||||
end
|
||||
end
|
||||
throw_invalid_config_option("height", val)
|
||||
end
|
||||
|
||||
--- Converts input to a function that returns the width.
|
||||
--- The input must take one of five forms:
|
||||
--- 1. 0 <= number < 1 <br>
|
||||
--- This means total width as a percentage.
|
||||
--- 2. 1 <= number <br>
|
||||
--- This means total width as a fixed number.
|
||||
--- 3. function <br>
|
||||
--- Must have signature:
|
||||
--- function(self, max_columns, max_lines): number
|
||||
--- 4. table of the form: { val, max = ..., min = ... } <br>
|
||||
--- val has to be in the first form 0 <= val < 1 and only one is given,
|
||||
--- `min` or `max` as fixed number
|
||||
--- 5. table of the form: {padding = `foo`} <br>
|
||||
--- where `foo` has one of the previous three forms. <br>
|
||||
--- The width is then set to be the remaining space after padding.
|
||||
--- For example, if the window has width 100, and the input is {padding = 5},
|
||||
--- the width returned will be `90 = 100 - 2*5`
|
||||
---
|
||||
--- The returned function will have signature:
|
||||
--- function(self, max_columns, max_lines): number
|
||||
resolver.resolve_width = function(val)
|
||||
for k, v in pairs(_resolve_map) do
|
||||
if k(val) then
|
||||
return v(2, val)
|
||||
end
|
||||
end
|
||||
|
||||
throw_invalid_config_option("width", val)
|
||||
end
|
||||
|
||||
--- Calculates the adjustment required to move the picker from the middle of the screen to
|
||||
--- an edge or corner. <br>
|
||||
--- The `anchor` can be any of the following strings:
|
||||
--- - "", "CENTER", "NW", "N", "NE", "E", "SE", "S", "SW", "W"
|
||||
--- The anchors have the following meanings:
|
||||
--- - "" or "CENTER":<br>
|
||||
--- the picker will remain in the middle of the screen.
|
||||
--- - Compass directions:<br>
|
||||
--- the picker will move to the corresponding edge/corner
|
||||
--- e.g. "NW" -> "top left corner", "E" -> "right edge", "S" -> "bottom edge"
|
||||
resolver.resolve_anchor_pos = function(anchor, p_width, p_height, max_columns, max_lines)
|
||||
anchor = anchor:upper()
|
||||
local pos = { 0, 0 }
|
||||
if anchor == "CENTER" then
|
||||
return pos
|
||||
end
|
||||
if anchor:find "W" then
|
||||
pos[1] = math.ceil((p_width - max_columns) / 2) + 1
|
||||
elseif anchor:find "E" then
|
||||
pos[1] = math.ceil((max_columns - p_width) / 2) - 1
|
||||
end
|
||||
if anchor:find "N" then
|
||||
pos[2] = math.ceil((p_height - max_lines) / 2) + 1
|
||||
elseif anchor:find "S" then
|
||||
pos[2] = math.ceil((max_lines - p_height) / 2) - 1
|
||||
end
|
||||
return pos
|
||||
end
|
||||
|
||||
-- duplicate from utils.lua to keep self-contained
|
||||
-- Win option always returns a table with preview, results, and prompt.
|
||||
-- It handles many different ways. Some examples are as follows:
|
||||
--
|
||||
-- -- Disable
|
||||
-- borderchars = false
|
||||
--
|
||||
-- -- All three windows share the same
|
||||
-- borderchars = { '─', '│', '─', '│', '┌', '┐', '┘', '└'},
|
||||
--
|
||||
-- -- Each window gets it's own configuration
|
||||
-- borderchars = {
|
||||
-- preview = {...},
|
||||
-- results = {...},
|
||||
-- prompt = {...},
|
||||
-- }
|
||||
--
|
||||
-- -- Default to [1] but override with specific items
|
||||
-- borderchars = {
|
||||
-- {...}
|
||||
-- prompt = {...},
|
||||
-- }
|
||||
resolver.win_option = function(val, default)
|
||||
local islist = require("telescope.utils").islist
|
||||
if type(val) ~= "table" or islist(val) then
|
||||
if val == nil then
|
||||
val = default
|
||||
end
|
||||
|
||||
return {
|
||||
preview = val,
|
||||
results = val,
|
||||
prompt = val,
|
||||
}
|
||||
elseif type(val) == "table" then
|
||||
assert(not islist(val))
|
||||
|
||||
local val_to_set = val[1]
|
||||
if val_to_set == nil then
|
||||
val_to_set = default
|
||||
end
|
||||
|
||||
return {
|
||||
preview = vim.F.if_nil(val.preview, val_to_set),
|
||||
results = vim.F.if_nil(val.results, val_to_set),
|
||||
prompt = vim.F.if_nil(val.prompt, val_to_set),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return resolver
|
||||
@ -0,0 +1,171 @@
|
||||
-- Credit: https://gist.github.com/runiq/31aa5c4bf00f8e0843cd267880117201
|
||||
--
|
||||
|
||||
local M = {}
|
||||
|
||||
---Validates args for `throttle()` and `debounce()`.
|
||||
local function td_validate(fn, ms)
|
||||
vim.validate {
|
||||
fn = { fn, "f" },
|
||||
ms = {
|
||||
ms,
|
||||
function(v)
|
||||
return type(v) == "number" and v > 0
|
||||
end,
|
||||
"number > 0",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
--- Throttles a function on the leading edge. Automatically `schedule_wrap()`s.
|
||||
---@param fn fun(...) Function to throttle
|
||||
---@param ms number Timeout in ms
|
||||
---@return fun(...) wrapped_fn Throttled function
|
||||
---@return uv_timer_t timer Remember to call `timer.close()` at the end or you will leak memory!
|
||||
function M.throttle_leading(fn, ms)
|
||||
td_validate(fn, ms)
|
||||
local timer = vim.loop.new_timer()
|
||||
local running = false
|
||||
|
||||
local function wrapped_fn(...)
|
||||
if not running then
|
||||
timer:start(ms, 0, function()
|
||||
running = false
|
||||
end)
|
||||
running = true
|
||||
pcall(vim.schedule_wrap(fn), select(1, ...))
|
||||
end
|
||||
end
|
||||
return wrapped_fn, timer
|
||||
end
|
||||
|
||||
--- Throttles a function on the trailing edge. Automatically `schedule_wrap()`s.
|
||||
---@param fn fun(...) Function to throttle
|
||||
---@param ms number Timeout in ms
|
||||
---@param last? boolean Whether to use the arguments of the last call to `fn` within the timeframe.
|
||||
--- Default: Use arguments of the first call.
|
||||
---@return fun(...) wrapped_fn Throttled function
|
||||
---@return uv_timer_t timer Remember to call `timer.close()` at the end or you will leak memory!
|
||||
function M.throttle_trailing(fn, ms, last)
|
||||
td_validate(fn, ms)
|
||||
local timer = vim.loop.new_timer()
|
||||
local running = false
|
||||
|
||||
local wrapped_fn
|
||||
if not last then
|
||||
function wrapped_fn(...)
|
||||
if not running then
|
||||
local argv = { ... }
|
||||
local argc = select("#", ...)
|
||||
|
||||
timer:start(ms, 0, function()
|
||||
running = false
|
||||
pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc))
|
||||
end)
|
||||
running = true
|
||||
end
|
||||
end
|
||||
else
|
||||
local argv, argc
|
||||
function wrapped_fn(...)
|
||||
argv = { ... }
|
||||
argc = select("#", ...)
|
||||
|
||||
if not running then
|
||||
timer:start(ms, 0, function()
|
||||
running = false
|
||||
pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc))
|
||||
end)
|
||||
running = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return wrapped_fn, timer
|
||||
end
|
||||
|
||||
--- Debounces a function on the leading edge. Automatically `schedule_wrap()`s.
|
||||
---@param fn fun(...) Function to debounce
|
||||
---@param ms number Timeout in ms
|
||||
---@return fun(...) wrapped_fn Debounced function
|
||||
---@return uv_timer_t timer Remember to call `timer.close()` at the end or you will leak memory!
|
||||
function M.debounce_leading(fn, ms)
|
||||
td_validate(fn, ms)
|
||||
local timer = vim.loop.new_timer()
|
||||
local running = false
|
||||
|
||||
local function wrapped_fn(...)
|
||||
timer:start(ms, 0, function()
|
||||
running = false
|
||||
end)
|
||||
|
||||
if not running then
|
||||
running = true
|
||||
pcall(vim.schedule_wrap(fn), select(1, ...))
|
||||
end
|
||||
end
|
||||
return wrapped_fn, timer
|
||||
end
|
||||
|
||||
--- Debounces a function on the trailing edge. Automatically `schedule_wrap()`s.
|
||||
---@param fn fun(...) Function to debounce
|
||||
---@param ms number Timeout in ms
|
||||
---@param first? boolean Whether to use the arguments of the first call to `fn` within the timeframe.
|
||||
--- Default: Use arguments of the last call.
|
||||
---@return fun(...) wrapped_fn Debounced function
|
||||
---@return uv_timer_t timer Remember to call `timer.close()` at the end or you will leak memory!
|
||||
function M.debounce_trailing(fn, ms, first)
|
||||
td_validate(fn, ms)
|
||||
local timer = vim.loop.new_timer()
|
||||
local wrapped_fn
|
||||
|
||||
if not first then
|
||||
function wrapped_fn(...)
|
||||
local argv = { ... }
|
||||
local argc = select("#", ...)
|
||||
|
||||
timer:start(ms, 0, function()
|
||||
pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc))
|
||||
end)
|
||||
end
|
||||
else
|
||||
local argv, argc
|
||||
function wrapped_fn(...)
|
||||
argv = argv or { ... }
|
||||
argc = argc or select("#", ...)
|
||||
|
||||
timer:start(ms, 0, function()
|
||||
pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc))
|
||||
end)
|
||||
end
|
||||
end
|
||||
return wrapped_fn, timer
|
||||
end
|
||||
|
||||
--- Test deferment methods (`{throttle,debounce}_{leading,trailing}()`).
|
||||
---@param bouncer string Bouncer function to test
|
||||
---@param ms? number Timeout in ms, default 2000.
|
||||
---@param firstlast? boolean Whether to use the 'other' fn call strategy.
|
||||
function M.test_defer(bouncer, ms, firstlast)
|
||||
local bouncers = {
|
||||
tl = M.throttle_leading,
|
||||
tt = M.throttle_trailing,
|
||||
dl = M.debounce_leading,
|
||||
dt = M.debounce_trailing,
|
||||
}
|
||||
|
||||
local timeout = ms or 2000
|
||||
|
||||
local bounced = bouncers[bouncer](function(i)
|
||||
vim.cmd('echom "' .. bouncer .. ": " .. i .. '"')
|
||||
end, timeout, firstlast)
|
||||
|
||||
for i, _ in ipairs { 1, 2, 3, 4, 5 } do
|
||||
bounced(i)
|
||||
vim.schedule(function()
|
||||
vim.cmd("echom " .. i)
|
||||
end)
|
||||
vim.fn.call("wait", { 1000, "v:false" })
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,12 @@
|
||||
local deprecated = {}
|
||||
|
||||
deprecated.options = function(opts)
|
||||
local messages = {}
|
||||
|
||||
if #messages > 0 then
|
||||
table.insert(messages, 1, "Deprecated options. Please see ':help telescope.changelog'")
|
||||
vim.api.nvim_err_write(table.concat(messages, "\n \n ") .. "\n \nPress <Enter> to continue\n")
|
||||
end
|
||||
end
|
||||
|
||||
return deprecated
|
||||
@ -0,0 +1,168 @@
|
||||
local log = require "telescope.log"
|
||||
|
||||
local LinkedList = require "telescope.algos.linked_list"
|
||||
|
||||
local EntryManager = {}
|
||||
EntryManager.__index = EntryManager
|
||||
|
||||
function EntryManager:new(max_results, set_entry, info)
|
||||
log.trace "Creating entry_manager..."
|
||||
|
||||
info = info or {}
|
||||
info.looped = 0
|
||||
info.inserted = 0
|
||||
info.find_loop = 0
|
||||
|
||||
-- state contains list of
|
||||
-- { entry, score }
|
||||
-- Stored directly in a table, accessed as [1], [2]
|
||||
set_entry = set_entry or function() end
|
||||
|
||||
return setmetatable({
|
||||
linked_states = LinkedList:new { track_at = max_results },
|
||||
info = info,
|
||||
max_results = max_results,
|
||||
set_entry = set_entry,
|
||||
worst_acceptable_score = math.huge,
|
||||
}, self)
|
||||
end
|
||||
|
||||
function EntryManager:num_results()
|
||||
return self.linked_states.size
|
||||
end
|
||||
|
||||
function EntryManager:get_container(index)
|
||||
local count = 0
|
||||
for val in self.linked_states:iter() do
|
||||
count = count + 1
|
||||
|
||||
if count == index then
|
||||
return val
|
||||
end
|
||||
end
|
||||
|
||||
return {}
|
||||
end
|
||||
|
||||
function EntryManager:get_entry(index)
|
||||
return self:get_container(index)[1]
|
||||
end
|
||||
|
||||
function EntryManager:get_score(index)
|
||||
return self:get_container(index)[2]
|
||||
end
|
||||
|
||||
function EntryManager:get_ordinal(index)
|
||||
return self:get_entry(index).ordinal
|
||||
end
|
||||
|
||||
function EntryManager:find_entry(entry)
|
||||
local info = self.info
|
||||
|
||||
local count = 0
|
||||
for container in self.linked_states:iter() do
|
||||
count = count + 1
|
||||
|
||||
if container[1] == entry then
|
||||
info.find_loop = info.find_loop + count
|
||||
|
||||
return count
|
||||
end
|
||||
end
|
||||
|
||||
info.find_loop = info.find_loop + count
|
||||
return nil
|
||||
end
|
||||
|
||||
function EntryManager:_update_score_from_tracked()
|
||||
local linked = self.linked_states
|
||||
|
||||
if linked.tracked then
|
||||
self.worst_acceptable_score = math.min(self.worst_acceptable_score, linked.tracked[2])
|
||||
end
|
||||
end
|
||||
|
||||
function EntryManager:_insert_container_before(picker, index, linked_node, new_container)
|
||||
self.linked_states:place_before(index, linked_node, new_container)
|
||||
self.set_entry(picker, index, new_container[1], new_container[2], true)
|
||||
|
||||
self:_update_score_from_tracked()
|
||||
end
|
||||
|
||||
function EntryManager:_insert_container_after(picker, index, linked_node, new_container)
|
||||
self.linked_states:place_after(index, linked_node, new_container)
|
||||
self.set_entry(picker, index, new_container[1], new_container[2], true)
|
||||
|
||||
self:_update_score_from_tracked()
|
||||
end
|
||||
|
||||
function EntryManager:_append_container(picker, new_container, should_update)
|
||||
self.linked_states:append(new_container)
|
||||
self.worst_acceptable_score = math.min(self.worst_acceptable_score, new_container[2])
|
||||
|
||||
if should_update then
|
||||
self.set_entry(picker, self.linked_states.size, new_container[1], new_container[2])
|
||||
end
|
||||
end
|
||||
|
||||
function EntryManager:add_entry(picker, score, entry, prompt)
|
||||
score = score or 0
|
||||
|
||||
local max_res = self.max_results
|
||||
local worst_score = self.worst_acceptable_score
|
||||
local size = self.linked_states.size
|
||||
|
||||
local info = self.info
|
||||
info.maxed = info.maxed or 0
|
||||
|
||||
local new_container = { entry, score }
|
||||
|
||||
-- Short circuit for bad scores -- they never need to be displayed.
|
||||
-- Just save them and we'll deal with them later.
|
||||
if score >= worst_score then
|
||||
return self.linked_states:append(new_container)
|
||||
end
|
||||
|
||||
-- Short circuit for first entry.
|
||||
if size == 0 then
|
||||
self.linked_states:prepend(new_container)
|
||||
self.set_entry(picker, 1, entry, score)
|
||||
return
|
||||
end
|
||||
|
||||
for index, container, node in self.linked_states:ipairs() do
|
||||
info.looped = info.looped + 1
|
||||
|
||||
if container[2] > score then
|
||||
return self:_insert_container_before(picker, index, node, new_container)
|
||||
end
|
||||
|
||||
if score < 1 and container[2] == score and picker.tiebreak(entry, container[1], prompt) then
|
||||
return self:_insert_container_before(picker, index, node, new_container)
|
||||
end
|
||||
|
||||
-- Don't add results that are too bad.
|
||||
if index >= max_res then
|
||||
info.maxed = info.maxed + 1
|
||||
return self:_append_container(picker, new_container, false)
|
||||
end
|
||||
end
|
||||
|
||||
if self.linked_states.size >= max_res then
|
||||
self.worst_acceptable_score = math.min(self.worst_acceptable_score, score)
|
||||
end
|
||||
|
||||
return self:_insert_container_after(picker, size + 1, self.linked_states.tail, new_container)
|
||||
end
|
||||
|
||||
function EntryManager:iter()
|
||||
local iterator = self.linked_states:iter()
|
||||
return function()
|
||||
local val = iterator()
|
||||
if val then
|
||||
return val[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return EntryManager
|
||||
@ -0,0 +1,231 @@
|
||||
local Job = require "plenary.job"
|
||||
|
||||
local make_entry = require "telescope.make_entry"
|
||||
local log = require "telescope.log"
|
||||
|
||||
local async_static_finder = require "telescope.finders.async_static_finder"
|
||||
local async_oneshot_finder = require "telescope.finders.async_oneshot_finder"
|
||||
local async_job_finder = require "telescope.finders.async_job_finder"
|
||||
|
||||
local finders = {}
|
||||
|
||||
local _callable_obj = function()
|
||||
local obj = {}
|
||||
|
||||
obj.__index = obj
|
||||
obj.__call = function(t, ...)
|
||||
return t:_find(...)
|
||||
end
|
||||
|
||||
obj.close = function() end
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
--[[ =============================================================
|
||||
|
||||
JobFinder
|
||||
|
||||
Uses an external Job to get results. Processes results as they arrive.
|
||||
|
||||
For more information about how Jobs are implemented, checkout 'plenary.job'
|
||||
|
||||
-- ============================================================= ]]
|
||||
local JobFinder = _callable_obj()
|
||||
|
||||
--- Create a new finder command
|
||||
---
|
||||
---@param opts table Keys:
|
||||
-- fn_command function The function to call
|
||||
function JobFinder:new(opts)
|
||||
opts = opts or {}
|
||||
|
||||
assert(not opts.results, "`results` should be used with finder.new_table")
|
||||
assert(not opts.static, "`static` should be used with finder.new_oneshot_job")
|
||||
|
||||
local obj = setmetatable({
|
||||
entry_maker = opts.entry_maker or make_entry.gen_from_string(opts),
|
||||
fn_command = opts.fn_command,
|
||||
cwd = opts.cwd,
|
||||
writer = opts.writer,
|
||||
|
||||
-- Maximum number of results to process.
|
||||
-- Particularly useful for live updating large queries.
|
||||
maximum_results = opts.maximum_results,
|
||||
}, self)
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function JobFinder:_find(prompt, process_result, process_complete)
|
||||
log.trace "Finding..."
|
||||
|
||||
if self.job and not self.job.is_shutdown then
|
||||
log.debug "Shutting down old job"
|
||||
self.job:shutdown()
|
||||
end
|
||||
|
||||
local line_num = 0
|
||||
local on_output = function(_, line, _)
|
||||
line_num = line_num + 1
|
||||
if not line or line == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local entry
|
||||
if self.entry_maker then
|
||||
entry = self.entry_maker(line)
|
||||
if entry then
|
||||
entry.index = line_num
|
||||
end
|
||||
else
|
||||
entry = line
|
||||
end
|
||||
|
||||
process_result(entry)
|
||||
end
|
||||
|
||||
local opts = self:fn_command(prompt)
|
||||
if not opts then
|
||||
process_complete()
|
||||
return
|
||||
end
|
||||
|
||||
local writer = nil
|
||||
if opts.writer and Job.is_job(opts.writer) then
|
||||
writer = opts.writer
|
||||
elseif opts.writer then
|
||||
writer = Job:new(opts.writer)
|
||||
end
|
||||
|
||||
self.job = Job:new {
|
||||
command = opts.command,
|
||||
args = opts.args,
|
||||
cwd = opts.cwd or self.cwd,
|
||||
|
||||
maximum_results = self.maximum_results,
|
||||
|
||||
writer = writer,
|
||||
|
||||
enable_recording = false,
|
||||
|
||||
on_stdout = on_output,
|
||||
-- on_stderr = on_output,
|
||||
|
||||
on_exit = function()
|
||||
process_complete()
|
||||
end,
|
||||
}
|
||||
|
||||
self.job:start()
|
||||
end
|
||||
|
||||
local DynamicFinder = _callable_obj()
|
||||
|
||||
function DynamicFinder:new(opts)
|
||||
opts = opts or {}
|
||||
|
||||
assert(not opts.results, "`results` should be used with finder.new_table")
|
||||
assert(not opts.static, "`static` should be used with finder.new_oneshot_job")
|
||||
|
||||
local obj = setmetatable({
|
||||
curr_buf = opts.curr_buf,
|
||||
fn = opts.fn,
|
||||
entry_maker = opts.entry_maker or make_entry.gen_from_string(opts),
|
||||
}, self)
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function DynamicFinder:_find(prompt, process_result, process_complete)
|
||||
local results = self.fn(prompt)
|
||||
|
||||
local result_num = 0
|
||||
for _, result in ipairs(results) do
|
||||
result_num = result_num + 1
|
||||
local entry = self.entry_maker(result)
|
||||
if entry then
|
||||
entry.index = result_num
|
||||
end
|
||||
if process_result(entry) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
process_complete()
|
||||
end
|
||||
|
||||
--- Return a new Finder
|
||||
--
|
||||
-- Use at your own risk.
|
||||
-- This opts dictionary is likely to change, but you are welcome to use it right now.
|
||||
-- I will try not to change it needlessly, but I will change it sometimes and I won't feel bad.
|
||||
finders._new = function(opts)
|
||||
assert(not opts.results, "finder.new is deprecated with `results`. You should use `finder.new_table`")
|
||||
return JobFinder:new(opts)
|
||||
end
|
||||
|
||||
finders.new_async_job = function(opts)
|
||||
if opts.writer then
|
||||
return finders._new(opts)
|
||||
end
|
||||
|
||||
return async_job_finder(opts)
|
||||
end
|
||||
|
||||
finders.new_job = function(command_generator, entry_maker, _, cwd)
|
||||
return async_job_finder {
|
||||
command_generator = command_generator,
|
||||
entry_maker = entry_maker,
|
||||
cwd = cwd,
|
||||
}
|
||||
end
|
||||
|
||||
--- One shot job
|
||||
---@param command_list string[]: Command list to execute.
|
||||
---@param opts table: stuff
|
||||
-- @key entry_maker function Optional: function(line: string) => table
|
||||
-- @key cwd string
|
||||
finders.new_oneshot_job = function(command_list, opts)
|
||||
opts = opts or {}
|
||||
|
||||
assert(not opts.results, "`results` should be used with finder.new_table")
|
||||
|
||||
command_list = vim.deepcopy(command_list)
|
||||
local command = table.remove(command_list, 1)
|
||||
|
||||
return async_oneshot_finder {
|
||||
entry_maker = opts.entry_maker or make_entry.gen_from_string(opts),
|
||||
|
||||
cwd = opts.cwd,
|
||||
maximum_results = opts.maximum_results,
|
||||
|
||||
fn_command = function()
|
||||
return {
|
||||
command = command,
|
||||
args = command_list,
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
--- Used to create a finder for a Lua table.
|
||||
-- If you only pass a table of results, then it will use that as the entries.
|
||||
--
|
||||
-- If you pass a table, and then a function, it's used as:
|
||||
-- results table, the results to run on
|
||||
-- entry_maker function, the function to convert results to entries.
|
||||
finders.new_table = function(t)
|
||||
return async_static_finder(t)
|
||||
end
|
||||
|
||||
--- Used to create a finder from a function.
|
||||
--
|
||||
---@param opts table: stuff
|
||||
-- @key fn function() => list[string]
|
||||
-- @key entry_maker function Optional: function(line: string) => table
|
||||
finders.new_dynamic = function(opts)
|
||||
return DynamicFinder:new(opts)
|
||||
end
|
||||
|
||||
return finders
|
||||
@ -0,0 +1,84 @@
|
||||
local async_job = require "telescope._"
|
||||
local LinesPipe = require("telescope._").LinesPipe
|
||||
|
||||
local make_entry = require "telescope.make_entry"
|
||||
local log = require "telescope.log"
|
||||
|
||||
return function(opts)
|
||||
log.trace("Creating async_job:", opts)
|
||||
local entry_maker = opts.entry_maker or make_entry.gen_from_string(opts)
|
||||
|
||||
local fn_command = function(prompt)
|
||||
local command_list = opts.command_generator(prompt)
|
||||
if command_list == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local command = table.remove(command_list, 1)
|
||||
|
||||
local res = {
|
||||
command = command,
|
||||
args = command_list,
|
||||
}
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
local job
|
||||
|
||||
local callable = function(_, prompt, process_result, process_complete)
|
||||
if job then
|
||||
job:close(true)
|
||||
end
|
||||
|
||||
local job_opts = fn_command(prompt)
|
||||
if not job_opts then
|
||||
process_complete()
|
||||
return
|
||||
end
|
||||
|
||||
local writer = nil
|
||||
-- if job_opts.writer and Job.is_job(job_opts.writer) then
|
||||
-- writer = job_opts.writer
|
||||
if opts.writer then
|
||||
error "async_job_finder.writer is not yet implemented"
|
||||
writer = async_job.writer(opts.writer)
|
||||
end
|
||||
|
||||
local stdout = LinesPipe()
|
||||
|
||||
job = async_job.spawn {
|
||||
command = job_opts.command,
|
||||
args = job_opts.args,
|
||||
cwd = job_opts.cwd or opts.cwd,
|
||||
env = job_opts.env or opts.env,
|
||||
writer = writer,
|
||||
|
||||
stdout = stdout,
|
||||
}
|
||||
|
||||
local line_num = 0
|
||||
for line in stdout:iter(true) do
|
||||
line_num = line_num + 1
|
||||
local entry = entry_maker(line)
|
||||
if entry then
|
||||
entry.index = line_num
|
||||
end
|
||||
if process_result(entry) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
process_complete()
|
||||
end
|
||||
|
||||
return setmetatable({
|
||||
close = function()
|
||||
if job then
|
||||
job:close(true)
|
||||
end
|
||||
end,
|
||||
}, {
|
||||
__call = callable,
|
||||
})
|
||||
end
|
||||
@ -0,0 +1,104 @@
|
||||
local async = require "plenary.async"
|
||||
local async_job = require "telescope._"
|
||||
local LinesPipe = require("telescope._").LinesPipe
|
||||
|
||||
local make_entry = require "telescope.make_entry"
|
||||
|
||||
local await_count = 1000
|
||||
|
||||
return function(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local entry_maker = opts.entry_maker or make_entry.gen_from_string(opts)
|
||||
local cwd = opts.cwd
|
||||
local env = opts.env
|
||||
local fn_command = assert(opts.fn_command, "Must pass `fn_command`")
|
||||
|
||||
local results = vim.F.if_nil(opts.results, {})
|
||||
local num_results = #results
|
||||
|
||||
local job_started = false
|
||||
local job_completed = false
|
||||
local stdout = nil
|
||||
|
||||
local job
|
||||
|
||||
return setmetatable({
|
||||
close = function()
|
||||
if job then
|
||||
job:close()
|
||||
end
|
||||
end,
|
||||
results = results,
|
||||
entry_maker = entry_maker,
|
||||
}, {
|
||||
__call = function(_, prompt, process_result, process_complete)
|
||||
if not job_started then
|
||||
local job_opts = fn_command()
|
||||
|
||||
-- TODO: Handle writers.
|
||||
-- local writer
|
||||
-- if job_opts.writer and Job.is_job(job_opts.writer) then
|
||||
-- writer = job_opts.writer
|
||||
-- elseif job_opts.writer then
|
||||
-- writer = Job:new(job_opts.writer)
|
||||
-- end
|
||||
|
||||
stdout = LinesPipe()
|
||||
job = async_job.spawn {
|
||||
command = job_opts.command,
|
||||
args = job_opts.args,
|
||||
cwd = cwd,
|
||||
env = env,
|
||||
|
||||
stdout = stdout,
|
||||
}
|
||||
|
||||
job_started = true
|
||||
end
|
||||
|
||||
if not job_completed then
|
||||
if not vim.tbl_isempty(results) then
|
||||
for _, v in ipairs(results) do
|
||||
process_result(v)
|
||||
end
|
||||
end
|
||||
for line in stdout:iter(false) do
|
||||
num_results = num_results + 1
|
||||
|
||||
if num_results % await_count then
|
||||
async.util.scheduler()
|
||||
end
|
||||
|
||||
local entry = entry_maker(line)
|
||||
if entry then
|
||||
entry.index = num_results
|
||||
end
|
||||
results[num_results] = entry
|
||||
process_result(entry)
|
||||
end
|
||||
|
||||
process_complete()
|
||||
job_completed = true
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local current_count = num_results
|
||||
for index = 1, current_count do
|
||||
-- TODO: Figure out scheduling...
|
||||
if index % await_count then
|
||||
async.util.scheduler()
|
||||
end
|
||||
|
||||
if process_result(results[index]) then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if job_completed then
|
||||
process_complete()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
@ -0,0 +1,44 @@
|
||||
local scheduler = require("plenary.async").util.scheduler
|
||||
|
||||
local make_entry = require "telescope.make_entry"
|
||||
|
||||
return function(opts)
|
||||
local input_results
|
||||
if require("telescope.utils").islist(opts) then
|
||||
input_results = opts
|
||||
else
|
||||
input_results = opts.results
|
||||
end
|
||||
|
||||
local entry_maker = opts.entry_maker or make_entry.gen_from_string(opts)
|
||||
|
||||
local results = {}
|
||||
for k, v in ipairs(input_results) do
|
||||
local entry = entry_maker(v)
|
||||
|
||||
if entry then
|
||||
entry.index = k
|
||||
table.insert(results, entry)
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable({
|
||||
results = results,
|
||||
entry_maker = entry_maker,
|
||||
close = function() end,
|
||||
}, {
|
||||
__call = function(_, _, process_result, process_complete)
|
||||
for i, v in ipairs(results) do
|
||||
if process_result(v) then
|
||||
break
|
||||
end
|
||||
|
||||
if i % 1000 == 0 then
|
||||
scheduler()
|
||||
end
|
||||
end
|
||||
|
||||
process_complete()
|
||||
end,
|
||||
})
|
||||
end
|
||||
@ -0,0 +1,45 @@
|
||||
--[[ =============================================================================
|
||||
|
||||
Get metadata from entries.
|
||||
|
||||
This file is still WIP, so expect some changes if you're trying to consume these APIs.
|
||||
|
||||
This will provide standard mechanism for accessing information from an entry.
|
||||
|
||||
--============================================================================= ]]
|
||||
local utils = require "telescope.utils"
|
||||
|
||||
local from_entry = {}
|
||||
|
||||
function from_entry.path(entry, validate, escape)
|
||||
escape = vim.F.if_nil(escape, true)
|
||||
local path = entry.path
|
||||
if path == nil then
|
||||
path = entry.filename
|
||||
end
|
||||
if path == nil then
|
||||
path = entry.value
|
||||
end
|
||||
if path == nil then
|
||||
require("telescope.log").error(string.format("Invalid Entry: '%s'", vim.inspect(entry)))
|
||||
return
|
||||
end
|
||||
|
||||
-- only 0 if neither filereadable nor directory
|
||||
if validate then
|
||||
-- We need to expand for filereadable and isdirectory
|
||||
-- TODO(conni2461): we are not going to return the expanded path because
|
||||
-- this would lead to cache misses in the perviewer.
|
||||
-- Requires overall refactoring in previewer interface
|
||||
local expanded = utils.path_expand(path)
|
||||
if (vim.fn.filereadable(expanded) + vim.fn.isdirectory(expanded)) == 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
if escape then
|
||||
return vim.fn.fnameescape(path)
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
return from_entry
|
||||
@ -0,0 +1,133 @@
|
||||
local health = vim.health or require "health"
|
||||
local start = health.start or health.report_start
|
||||
local ok = health.ok or health.report_ok
|
||||
local warn = health.warn or health.report_warn
|
||||
local error = health.error or health.report_error
|
||||
local info = health.info or health.report_info
|
||||
|
||||
local extension_module = require "telescope._extensions"
|
||||
local extension_info = require("telescope").extensions
|
||||
local is_win = vim.api.nvim_call_function("has", { "win32" }) == 1
|
||||
|
||||
local optional_dependencies = {
|
||||
{
|
||||
finder_name = "live-grep",
|
||||
package = {
|
||||
{
|
||||
name = "rg",
|
||||
url = "[BurntSushi/ripgrep](https://github.com/BurntSushi/ripgrep)",
|
||||
optional = false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
finder_name = "find-files",
|
||||
package = {
|
||||
{
|
||||
name = "fd",
|
||||
binaries = { "fdfind", "fd" },
|
||||
url = "[sharkdp/fd](https://github.com/sharkdp/fd)",
|
||||
optional = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local required_plugins = {
|
||||
{ lib = "plenary", optional = false },
|
||||
{
|
||||
lib = "nvim-treesitter",
|
||||
optional = true,
|
||||
info = "(Required for `:Telescope treesitter`.)",
|
||||
},
|
||||
}
|
||||
|
||||
local check_binary_installed = function(package)
|
||||
local binaries = package.binaries or { package.name }
|
||||
for _, binary in ipairs(binaries) do
|
||||
local found = vim.fn.executable(binary) == 1
|
||||
if not found and is_win then
|
||||
binary = binary .. ".exe"
|
||||
found = vim.fn.executable(binary) == 1
|
||||
end
|
||||
if found then
|
||||
local handle = io.popen(binary .. " --version")
|
||||
local binary_version = handle:read "*a"
|
||||
handle:close()
|
||||
return true, binary_version
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function lualib_installed(lib_name)
|
||||
local res, _ = pcall(require, lib_name)
|
||||
return res
|
||||
end
|
||||
|
||||
local M = {}
|
||||
|
||||
M.check = function()
|
||||
-- Required lua libs
|
||||
start "Checking for required plugins"
|
||||
for _, plugin in ipairs(required_plugins) do
|
||||
if lualib_installed(plugin.lib) then
|
||||
ok(plugin.lib .. " installed.")
|
||||
else
|
||||
local lib_not_installed = plugin.lib .. " not found."
|
||||
if plugin.optional then
|
||||
warn(("%s %s"):format(lib_not_installed, plugin.info))
|
||||
else
|
||||
error(lib_not_installed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- external dependencies
|
||||
-- TODO: only perform checks if user has enabled dependency in their config
|
||||
start "Checking external dependencies"
|
||||
|
||||
for _, opt_dep in pairs(optional_dependencies) do
|
||||
for _, package in ipairs(opt_dep.package) do
|
||||
local installed, version = check_binary_installed(package)
|
||||
if not installed then
|
||||
local err_msg = ("%s: not found."):format(package.name)
|
||||
if package.optional then
|
||||
warn(("%s %s"):format(err_msg, ("Install %s for extended capabilities"):format(package.url)))
|
||||
else
|
||||
error(
|
||||
("%s %s"):format(
|
||||
err_msg,
|
||||
("`%s` finder will not function without %s installed."):format(opt_dep.finder_name, package.url)
|
||||
)
|
||||
)
|
||||
end
|
||||
else
|
||||
local eol = version:find "\n"
|
||||
local ver = eol and version:sub(0, eol - 1) or "(unknown version)"
|
||||
ok(("%s: found %s"):format(package.name, ver))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Extensions
|
||||
start "===== Installed extensions ====="
|
||||
|
||||
local installed = {}
|
||||
for extension_name, _ in pairs(extension_info) do
|
||||
installed[#installed + 1] = extension_name
|
||||
end
|
||||
table.sort(installed)
|
||||
|
||||
for _, installed_ext in ipairs(installed) do
|
||||
local extension_healthcheck = extension_module._health[installed_ext]
|
||||
|
||||
start(string.format("Telescope Extension: `%s`", installed_ext))
|
||||
if extension_healthcheck then
|
||||
extension_healthcheck()
|
||||
else
|
||||
info "No healthcheck provided"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,176 @@
|
||||
local _extensions = require "telescope._extensions"
|
||||
|
||||
local telescope = {}
|
||||
|
||||
-- TODO(conni2461): also table of contents for tree-sitter-lua
|
||||
-- TODO: Add pre to the works
|
||||
-- ---@pre [[
|
||||
-- ---@pre ]]
|
||||
|
||||
---@brief [[
|
||||
--- Telescope.nvim is a plugin for fuzzy finding and neovim. It helps you search,
|
||||
--- filter, find and pick things in Lua.
|
||||
---
|
||||
--- Getting started with telescope:
|
||||
--- 1. Run `:checkhealth telescope` to make sure everything is installed.
|
||||
--- 2. Evaluate it is working with
|
||||
--- `:Telescope find_files` or
|
||||
--- `:lua require("telescope.builtin").find_files()`
|
||||
--- 3. Put a `require("telescope").setup()` call somewhere in your neovim config.
|
||||
--- 4. Read |telescope.setup| to check what config keys are available and what you can put inside the setup call
|
||||
--- 5. Read |telescope.builtin| to check which builtin pickers are offered and what options these implement
|
||||
--- 6. Profit
|
||||
---
|
||||
--- The below flow chart illustrates a simplified telescope architecture:
|
||||
--- <pre>
|
||||
--- ┌───────────────────────────────────────────────────────────┐
|
||||
--- │ ┌────────┐ │
|
||||
--- │ │ Multi │ ┌───────+ │
|
||||
--- │ │ Select │ ┌───────┐ │ Entry │ │
|
||||
--- │ └─────┬──* │ Entry │ ┌────────+ │ Maker │ │
|
||||
--- │ │ ┌───│Manager│────│ Sorter │┐ └───┬───* │
|
||||
--- │ ▼ ▼ └───────* └────────┘│ │ │
|
||||
--- │ 1────────┐ 2───┴──┐ │ │
|
||||
--- │ ┌─────│ Picker │ │Finder│◀────┘ │
|
||||
--- │ ▼ └───┬────┘ └──────* │
|
||||
--- │ ┌────────┐ │ 3────────+ ▲ │
|
||||
--- │ │Selected│ └───────│ Prompt │─────────┘ │
|
||||
--- │ │ Entry │ └───┬────┘ │
|
||||
--- │ └────────* ┌───┴────┐ ┌────────┐ ┌────────┐ │
|
||||
--- │ │ ▲ 4─────────┐│ Prompt │ │(Attach)│ │Actions │ │
|
||||
--- │ ▼ └──▶ │ Results ││ Buffer │◀─┤Mappings│◀─┤User Fn │ │
|
||||
--- │5─────────┐ └─────────┘└────────┘ └────────┘ └────────┘ │
|
||||
--- ││Previewer│ │
|
||||
--- │└─────────┘ telescope.nvim architecture │
|
||||
--- └───────────────────────────────────────────────────────────┘
|
||||
---
|
||||
--- + The `Entry Maker` at least defines
|
||||
--- - value: "raw" result of the finder
|
||||
--- - ordinal: string to be sorted derived from value
|
||||
--- - display: line representation of entry in results buffer
|
||||
---
|
||||
--- * The finder, entry manager, selected entry, and multi selections
|
||||
--- comprises `entries` constructed by the `Entry Maker` from
|
||||
--- raw results of the finder (`value`s)
|
||||
---
|
||||
--- Primary components:
|
||||
--- 1 Picker: central UI dedicated to varying use cases
|
||||
--- (finding files, grepping, diagnostics, etc.)
|
||||
--- see :h telescope.builtin
|
||||
--- 2 Finder: pipe or interactively generates results to pick over
|
||||
--- 3 Prompt: user input that triggers the finder which sorts results
|
||||
--- in order into the entry manager
|
||||
--- 4 Results: listed entries scored by sorter from finder results
|
||||
--- 5 Previewer: preview of context of selected entry
|
||||
--- see :h telescope.previewers
|
||||
--- </pre>
|
||||
---
|
||||
--- A practical introduction into telescope customization is our
|
||||
--- `developers.md` (top-level of repo) and `:h telescope.actions` that
|
||||
--- showcase how to access information about the state of the picker (current
|
||||
--- selection, etc.).
|
||||
--- <pre>
|
||||
--- To find out more:
|
||||
--- https://github.com/nvim-telescope/telescope.nvim
|
||||
---
|
||||
--- :h telescope.setup
|
||||
--- :h telescope.command
|
||||
--- :h telescope.builtin
|
||||
--- :h telescope.themes
|
||||
--- :h telescope.layout
|
||||
--- :h telescope.resolve
|
||||
--- :h telescope.actions
|
||||
--- :h telescope.actions.state
|
||||
--- :h telescope.actions.set
|
||||
--- :h telescope.actions.utils
|
||||
--- :h telescope.actions.generate
|
||||
--- :h telescope.actions.history
|
||||
--- :h telescope.previewers
|
||||
--- </pre>
|
||||
---@brief ]]
|
||||
|
||||
---@tag telescope.nvim
|
||||
---@config { ["name"] = "INTRODUCTION" }
|
||||
|
||||
--- Setup function to be run by user. Configures the defaults, pickers and
|
||||
--- extensions of telescope.
|
||||
---
|
||||
--- Usage:
|
||||
--- <code>
|
||||
--- require('telescope').setup{
|
||||
--- defaults = {
|
||||
--- -- Default configuration for telescope goes here:
|
||||
--- -- config_key = value,
|
||||
--- -- ..
|
||||
--- },
|
||||
--- pickers = {
|
||||
--- -- Default configuration for builtin pickers goes here:
|
||||
--- -- picker_name = {
|
||||
--- -- picker_config_key = value,
|
||||
--- -- ...
|
||||
--- -- }
|
||||
--- -- Now the picker_config_key will be applied every time you call this
|
||||
--- -- builtin picker
|
||||
--- },
|
||||
--- extensions = {
|
||||
--- -- Your extension configuration goes here:
|
||||
--- -- extension_name = {
|
||||
--- -- extension_config_key = value,
|
||||
--- -- }
|
||||
--- -- please take a look at the readme of the extension you want to configure
|
||||
--- }
|
||||
--- }
|
||||
--- </code>
|
||||
---@param opts table: Configuration opts. Keys: defaults, pickers, extensions
|
||||
---@eval { ["description"] = require('telescope').__format_setup_keys() }
|
||||
function telescope.setup(opts)
|
||||
opts = opts or {}
|
||||
|
||||
if opts.default then
|
||||
error "'default' is not a valid value for setup. See 'defaults'"
|
||||
end
|
||||
|
||||
require("telescope.config").set_defaults(opts.defaults)
|
||||
require("telescope.config").set_pickers(opts.pickers)
|
||||
_extensions.set_config(opts.extensions)
|
||||
end
|
||||
|
||||
--- Load an extension.
|
||||
--- - Notes:
|
||||
--- - Loading triggers ext setup via the config passed in |telescope.setup|
|
||||
---@param name string: Name of the extension
|
||||
function telescope.load_extension(name)
|
||||
return _extensions.load(name)
|
||||
end
|
||||
|
||||
--- Register an extension. To be used by plugin authors.
|
||||
---@param mod table: Module
|
||||
function telescope.register_extension(mod)
|
||||
return _extensions.register(mod)
|
||||
end
|
||||
|
||||
--- Use telescope.extensions to reference any extensions within your configuration. <br>
|
||||
--- While the docs currently generate this as a function, it's actually a table. Sorry.
|
||||
telescope.extensions = require("telescope._extensions").manager
|
||||
|
||||
telescope.__format_setup_keys = function()
|
||||
local names = require("telescope.config").descriptions_order
|
||||
local descriptions = require("telescope.config").descriptions
|
||||
|
||||
local result = { "<pre>", "", "Valid keys for {opts.defaults}" }
|
||||
for _, name in ipairs(names) do
|
||||
local desc = descriptions[name]
|
||||
|
||||
table.insert(result, "")
|
||||
table.insert(result, string.format("%s*telescope.defaults.%s*", string.rep(" ", 70 - 20 - #name), name))
|
||||
table.insert(result, string.format("%s: ~", name))
|
||||
for _, line in ipairs(vim.split(desc, "\n")) do
|
||||
table.insert(result, string.format(" %s", line))
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(result, "</pre>")
|
||||
return result
|
||||
end
|
||||
|
||||
return telescope
|
||||
@ -0,0 +1,4 @@
|
||||
return require("plenary.log").new {
|
||||
plugin = "telescope",
|
||||
level = "info",
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,364 @@
|
||||
---@tag telescope.mappings
|
||||
|
||||
---@brief [[
|
||||
--- |telescope.mappings| is used to configure the keybindings within
|
||||
--- a telescope picker. These key binds are only local to the picker window
|
||||
--- and will be cleared once you exit the picker.
|
||||
---
|
||||
--- We provide multiple configuration options to make it easy for you to adjust
|
||||
--- telescope's default key bindings and create your own custom key binds.
|
||||
---
|
||||
--- To see many of the builtin actions that you can use as values for this
|
||||
--- table, see |telescope.actions|
|
||||
---
|
||||
--- Format is:
|
||||
--- <code>
|
||||
--- {
|
||||
--- mode = { ..keys }
|
||||
--- }
|
||||
--- </code>
|
||||
---
|
||||
--- where {mode} is the one character letter for a mode ('i' for insert, 'n' for normal).
|
||||
---
|
||||
--- For example:
|
||||
--- <code>
|
||||
--- mappings = {
|
||||
--- i = {
|
||||
--- ["<esc>"] = require('telescope.actions').close,
|
||||
--- },
|
||||
--- }
|
||||
--- </code>
|
||||
---
|
||||
--- To disable a keymap, put `[map] = false`<br>
|
||||
--- For example:
|
||||
--- <code>
|
||||
--- {
|
||||
--- ...,
|
||||
--- ["<C-n>"] = false,
|
||||
--- ...,
|
||||
--- }
|
||||
--- </code>
|
||||
---
|
||||
--- To override behavior of a key, simply set the value
|
||||
--- to be a function (either by requiring an action or by writing
|
||||
--- your own function)
|
||||
--- <code>
|
||||
--- {
|
||||
--- ...,
|
||||
--- ["<C-i>"] = require('telescope.actions').select_default,
|
||||
--- ...,
|
||||
--- }
|
||||
--- </code>
|
||||
---
|
||||
--- If the function you want is part of `telescope.actions`, then you can
|
||||
--- simply supply the function name as a string.
|
||||
--- For example, the previous option is equivalent to:
|
||||
--- <code>
|
||||
--- {
|
||||
--- ...,
|
||||
--- ["<C-i>"] = "select_default",
|
||||
--- ...,
|
||||
--- }
|
||||
--- </code>
|
||||
---
|
||||
--- You can also add other mappings using tables with `type = "command"`.
|
||||
--- For example:
|
||||
--- <code>
|
||||
--- {
|
||||
--- ...,
|
||||
--- ["jj"] = { "<esc>", type = "command" },
|
||||
--- ["kk"] = { "<cmd>echo \"Hello, World!\"<cr>", type = "command" },)
|
||||
--- ...,
|
||||
--- }
|
||||
--- </code>
|
||||
---
|
||||
--- You can also add additional options for mappings of any type ("action" and "command").
|
||||
--- For example:
|
||||
--- <code>
|
||||
--- {
|
||||
--- ...,
|
||||
--- ["<C-j>"] = {
|
||||
--- actions.move_selection_next, type = "action",
|
||||
--- opts = { nowait = true, silent = true }
|
||||
--- },
|
||||
--- ...,
|
||||
--- }
|
||||
--- </code>
|
||||
---
|
||||
--- There are three main places you can configure |telescope.mappings|. These are
|
||||
--- ordered from the lowest priority to the highest priority.
|
||||
---
|
||||
--- 1. |telescope.defaults.mappings|
|
||||
--- 2. In the |telescope.setup()| table, inside a picker with a given name, use the `mappings` key
|
||||
--- <code>
|
||||
--- require("telescope").setup {
|
||||
--- pickers = {
|
||||
--- find_files = {
|
||||
--- mappings = {
|
||||
--- n = {
|
||||
--- ["kj"] = "close",
|
||||
--- },
|
||||
--- },
|
||||
--- },
|
||||
--- },
|
||||
--- }
|
||||
--- </code>
|
||||
--- 3. `attach_mappings` function for a particular picker.
|
||||
--- <code>
|
||||
--- require("telescope.builtin").find_files {
|
||||
--- attach_mappings = function(_, map)
|
||||
--- map("i", "asdf", function(_prompt_bufnr)
|
||||
--- print "You typed asdf"
|
||||
--- end)
|
||||
---
|
||||
--- map({"i", "n"}, "<C-r>", function(_prompt_bufnr)
|
||||
--- print "You typed <C-r>"
|
||||
--- end, { desc = "desc for which key"})
|
||||
---
|
||||
--- -- needs to return true if you want to map default_mappings and
|
||||
--- -- false if not
|
||||
--- return true
|
||||
--- end,
|
||||
--- }
|
||||
--- </code>
|
||||
---@brief ]]
|
||||
|
||||
local a = vim.api
|
||||
|
||||
local actions = require "telescope.actions"
|
||||
local config = require "telescope.config"
|
||||
|
||||
local mappings = {}
|
||||
|
||||
mappings.default_mappings = config.values.default_mappings
|
||||
or {
|
||||
i = {
|
||||
["<LeftMouse>"] = {
|
||||
actions.mouse_click,
|
||||
type = "action",
|
||||
opts = { expr = true },
|
||||
},
|
||||
["<2-LeftMouse>"] = {
|
||||
actions.double_mouse_click,
|
||||
type = "action",
|
||||
opts = { expr = true },
|
||||
},
|
||||
|
||||
["<C-n>"] = actions.move_selection_next,
|
||||
["<C-p>"] = actions.move_selection_previous,
|
||||
|
||||
["<C-c>"] = actions.close,
|
||||
|
||||
["<Down>"] = actions.move_selection_next,
|
||||
["<Up>"] = actions.move_selection_previous,
|
||||
|
||||
["<CR>"] = actions.select_default,
|
||||
["<C-x>"] = actions.select_horizontal,
|
||||
["<C-v>"] = actions.select_vertical,
|
||||
["<C-t>"] = actions.select_tab,
|
||||
|
||||
["<C-u>"] = actions.preview_scrolling_up,
|
||||
["<C-d>"] = actions.preview_scrolling_down,
|
||||
["<C-f>"] = actions.preview_scrolling_left,
|
||||
["<C-k>"] = actions.preview_scrolling_right,
|
||||
|
||||
["<PageUp>"] = actions.results_scrolling_up,
|
||||
["<PageDown>"] = actions.results_scrolling_down,
|
||||
["<M-f>"] = actions.results_scrolling_left,
|
||||
["<M-k>"] = actions.results_scrolling_right,
|
||||
|
||||
["<Tab>"] = actions.toggle_selection + actions.move_selection_worse,
|
||||
["<S-Tab>"] = actions.toggle_selection + actions.move_selection_better,
|
||||
["<C-q>"] = actions.send_to_qflist + actions.open_qflist,
|
||||
["<M-q>"] = actions.send_selected_to_qflist + actions.open_qflist,
|
||||
["<C-l>"] = actions.complete_tag,
|
||||
["<C-/>"] = actions.which_key,
|
||||
["<C-_>"] = actions.which_key, -- keys from pressing <C-/>
|
||||
["<C-w>"] = { "<c-s-w>", type = "command" },
|
||||
["<C-r><C-w>"] = actions.insert_original_cword,
|
||||
|
||||
-- disable c-j because we dont want to allow new lines #2123
|
||||
["<C-j>"] = actions.nop,
|
||||
},
|
||||
n = {
|
||||
["<LeftMouse>"] = {
|
||||
actions.mouse_click,
|
||||
type = "action",
|
||||
opts = { expr = true },
|
||||
},
|
||||
["<2-LeftMouse>"] = {
|
||||
actions.double_mouse_click,
|
||||
type = "action",
|
||||
opts = { expr = true },
|
||||
},
|
||||
|
||||
["<esc>"] = actions.close,
|
||||
["<CR>"] = actions.select_default,
|
||||
["<C-x>"] = actions.select_horizontal,
|
||||
["<C-v>"] = actions.select_vertical,
|
||||
["<C-t>"] = actions.select_tab,
|
||||
|
||||
["<Tab>"] = actions.toggle_selection + actions.move_selection_worse,
|
||||
["<S-Tab>"] = actions.toggle_selection + actions.move_selection_better,
|
||||
["<C-q>"] = actions.send_to_qflist + actions.open_qflist,
|
||||
["<M-q>"] = actions.send_selected_to_qflist + actions.open_qflist,
|
||||
|
||||
-- TODO: This would be weird if we switch the ordering.
|
||||
["j"] = actions.move_selection_next,
|
||||
["k"] = actions.move_selection_previous,
|
||||
["H"] = actions.move_to_top,
|
||||
["M"] = actions.move_to_middle,
|
||||
["L"] = actions.move_to_bottom,
|
||||
|
||||
["<Down>"] = actions.move_selection_next,
|
||||
["<Up>"] = actions.move_selection_previous,
|
||||
["gg"] = actions.move_to_top,
|
||||
["G"] = actions.move_to_bottom,
|
||||
|
||||
["<C-u>"] = actions.preview_scrolling_up,
|
||||
["<C-d>"] = actions.preview_scrolling_down,
|
||||
["<C-f>"] = actions.preview_scrolling_left,
|
||||
["<C-k>"] = actions.preview_scrolling_right,
|
||||
|
||||
["<PageUp>"] = actions.results_scrolling_up,
|
||||
["<PageDown>"] = actions.results_scrolling_down,
|
||||
["<M-f>"] = actions.results_scrolling_left,
|
||||
["<M-k>"] = actions.results_scrolling_right,
|
||||
|
||||
["?"] = actions.which_key,
|
||||
},
|
||||
}
|
||||
|
||||
-- normal names are prefixed with telescope|
|
||||
-- encoded objects are prefixed with telescopej|
|
||||
---@param key_func table|fun()
|
||||
---@param opts table
|
||||
---@return string?
|
||||
local get_desc_for_keyfunc = function(key_func, opts)
|
||||
if opts and opts.desc then
|
||||
return "telescope|" .. opts.desc
|
||||
end
|
||||
|
||||
if type(key_func) == "table" then
|
||||
local name = ""
|
||||
for _, action in ipairs(key_func) do
|
||||
if type(action) == "string" then
|
||||
name = name == "" and action or name .. " + " .. action
|
||||
end
|
||||
end
|
||||
return "telescope|" .. name
|
||||
elseif type(key_func) == "function" then
|
||||
local info = debug.getinfo(key_func)
|
||||
return "telescopej|" .. vim.json.encode { source = info.source, linedefined = info.linedefined }
|
||||
end
|
||||
end
|
||||
|
||||
local telescope_map = function(prompt_bufnr, mode, key_bind, key_func, opts)
|
||||
if not key_func then
|
||||
return
|
||||
end
|
||||
|
||||
opts = opts or {}
|
||||
if opts.noremap == nil then
|
||||
opts.noremap = true
|
||||
end
|
||||
if opts.silent == nil then
|
||||
opts.silent = true
|
||||
end
|
||||
|
||||
if type(key_func) == "string" then
|
||||
key_func = actions[key_func]
|
||||
elseif type(key_func) == "table" then
|
||||
if key_func.type == "command" then
|
||||
vim.keymap.set(
|
||||
mode,
|
||||
key_bind,
|
||||
key_func[1],
|
||||
vim.tbl_extend("force", opts or {
|
||||
silent = true,
|
||||
}, { buffer = prompt_bufnr })
|
||||
)
|
||||
return
|
||||
elseif key_func.type == "action_key" then
|
||||
key_func = actions[key_func[1]]
|
||||
elseif key_func.type == "action" then
|
||||
key_func = key_func[1]
|
||||
end
|
||||
end
|
||||
|
||||
vim.keymap.set(mode, key_bind, function()
|
||||
local ret = key_func(prompt_bufnr)
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "TelescopeKeymap" })
|
||||
return ret
|
||||
end, vim.tbl_extend("force", opts, { buffer = prompt_bufnr, desc = get_desc_for_keyfunc(key_func, opts) }))
|
||||
end
|
||||
|
||||
local extract_keymap_opts = function(key_func)
|
||||
if type(key_func) == "table" and key_func.opts ~= nil then
|
||||
-- we can't clear this because key_func could be a table from the config.
|
||||
-- If we clear it the table ref would lose opts after the first bind
|
||||
-- We need to copy it so noremap and silent won't be part of the table ref after the first bind
|
||||
return vim.deepcopy(key_func.opts)
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
mappings.apply_keymap = function(prompt_bufnr, attach_mappings, buffer_keymap)
|
||||
local applied_mappings = { n = {}, i = {} }
|
||||
|
||||
local map = function(modes, key_bind, key_func, opts)
|
||||
if type(modes) == "string" then
|
||||
modes = { modes }
|
||||
end
|
||||
|
||||
for _, mode in pairs(modes) do
|
||||
mode = string.lower(mode)
|
||||
local key_bind_internal = a.nvim_replace_termcodes(key_bind, true, true, true)
|
||||
applied_mappings[mode][key_bind_internal] = true
|
||||
|
||||
telescope_map(prompt_bufnr, mode, key_bind, key_func, opts)
|
||||
end
|
||||
end
|
||||
|
||||
if attach_mappings then
|
||||
local attach_results = attach_mappings(prompt_bufnr, map)
|
||||
|
||||
if attach_results == nil then
|
||||
error(
|
||||
"Attach mappings must always return a value. `true` means use default mappings, "
|
||||
.. "`false` means only use attached mappings"
|
||||
)
|
||||
end
|
||||
|
||||
if not attach_results then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
for mode, mode_map in pairs(buffer_keymap or {}) do
|
||||
mode = string.lower(mode)
|
||||
|
||||
for key_bind, key_func in pairs(mode_map) do
|
||||
local key_bind_internal = a.nvim_replace_termcodes(key_bind, true, true, true)
|
||||
if not applied_mappings[mode][key_bind_internal] then
|
||||
applied_mappings[mode][key_bind_internal] = true
|
||||
telescope_map(prompt_bufnr, mode, key_bind, key_func, extract_keymap_opts(key_func))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: Probably should not overwrite any keymaps
|
||||
for mode, mode_map in pairs(mappings.default_mappings) do
|
||||
mode = string.lower(mode)
|
||||
|
||||
for key_bind, key_func in pairs(mode_map) do
|
||||
local key_bind_internal = a.nvim_replace_termcodes(key_bind, true, true, true)
|
||||
if not applied_mappings[mode][key_bind_internal] then
|
||||
applied_mappings[mode][key_bind_internal] = true
|
||||
telescope_map(prompt_bufnr, mode, key_bind, key_func, extract_keymap_opts(key_func))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return mappings
|
||||
@ -0,0 +1,23 @@
|
||||
local operators = {}
|
||||
|
||||
local last_operator = { callback = function(_) end, opts = {} }
|
||||
|
||||
--- Execute the last saved operator callback and options
|
||||
operators.operator_callback = function()
|
||||
last_operator.callback(last_operator.opts)
|
||||
end
|
||||
|
||||
--- Enters operator-pending mode, then executes callback.
|
||||
--- See `:h map-operator`
|
||||
---
|
||||
---@param callback function: the function to call after exiting operator-pending
|
||||
---@param opts table: options to pass to the callback
|
||||
operators.run_operator = function(callback, opts)
|
||||
last_operator = { callback = callback, opts = opts }
|
||||
vim.o.operatorfunc = "v:lua.require'telescope.operators'.operator_callback"
|
||||
-- feed g@ to enter operator-pending mode
|
||||
-- 'i' required for which-key compatibility, etc.
|
||||
vim.api.nvim_feedkeys("g@", "mi", false)
|
||||
end
|
||||
|
||||
return operators
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,169 @@
|
||||
---@tag telescope.pickers.entry_display
|
||||
|
||||
---@brief [[
|
||||
--- Entry Display is used to format each entry shown in the result panel.
|
||||
---
|
||||
--- Entry Display create() will give us a function based on the configuration
|
||||
--- of column widths we pass into it. We then can use this function n times to
|
||||
--- return a string based on structured input.
|
||||
---
|
||||
--- Note that if you call `create()` inside `make_display` it will be called for
|
||||
--- every single entry. So it is suggested to do this outside of `make_display`
|
||||
--- for the best performance.
|
||||
---
|
||||
--- The create function will use the column widths passed to it in
|
||||
--- configuration.items. Each item in that table is the number of characters in
|
||||
--- the column. It's also possible for the final column to not have a fixed
|
||||
--- width, this will be shown in the configuration as 'remaining = true'.
|
||||
---
|
||||
--- An example of this configuration is shown for the buffers picker:
|
||||
--- <code>
|
||||
--- local displayer = entry_display.create {
|
||||
--- separator = " ",
|
||||
--- items = {
|
||||
--- { width = opts.bufnr_width },
|
||||
--- { width = 4 },
|
||||
--- { width = icon_width },
|
||||
--- { remaining = true },
|
||||
--- },
|
||||
--- }
|
||||
--- </code>
|
||||
---
|
||||
--- This shows 4 columns, the first is defined in the opts as the width we'll
|
||||
--- use when display_string is the number of the buffer. The second has a fixed
|
||||
--- width of 4 and the third column's width will be decided by the width of the
|
||||
--- icons we use. The fourth column will use the remaining space. Finally, we
|
||||
--- have also defined the separator between each column will be the space " ".
|
||||
---
|
||||
--- An example of how the display reference will be used is shown, again for
|
||||
--- the buffers picker:
|
||||
--- <code>
|
||||
--- return displayer {
|
||||
--- { entry.bufnr, "TelescopeResultsNumber" },
|
||||
--- { entry.indicator, "TelescopeResultsComment" },
|
||||
--- { icon, hl_group },
|
||||
--- display_bufname .. ":" .. entry.lnum,
|
||||
--- }
|
||||
--- </code>
|
||||
---
|
||||
--- There are two types of values each column can have. Either a simple String
|
||||
--- or a table containing the String as well as the hl_group.
|
||||
---
|
||||
--- The displayer can return values, string and an optional highlights. The string
|
||||
--- is all the text to be displayed for this entry as a single string. If parts of
|
||||
--- the string are to be highlighted they will be described in the highlights
|
||||
--- table.
|
||||
---
|
||||
--- For a better understanding of how create() and displayer are used it's best to look
|
||||
--- at the code in make_entry.lua.
|
||||
---@brief ]]
|
||||
|
||||
local strings = require "plenary.strings"
|
||||
local state = require "telescope.state"
|
||||
local resolve = require "telescope.config.resolve"
|
||||
|
||||
local entry_display = {}
|
||||
entry_display.truncate = strings.truncate
|
||||
|
||||
entry_display.create = function(configuration)
|
||||
local generator = {}
|
||||
for _, v in ipairs(configuration.items) do
|
||||
if v.width then
|
||||
local justify = v.right_justify
|
||||
local width
|
||||
table.insert(generator, function(item)
|
||||
if width == nil then
|
||||
local status = state.get_status(vim.F.if_nil(configuration.prompt_bufnr, vim.api.nvim_get_current_buf()))
|
||||
local s = {}
|
||||
s[1] = vim.api.nvim_win_get_width(status.layout.results.winid) - #status.picker.selection_caret
|
||||
s[2] = vim.api.nvim_win_get_height(status.layout.results.winid)
|
||||
width = resolve.resolve_width(v.width)(nil, s[1], s[2])
|
||||
end
|
||||
if type(item) == "table" then
|
||||
return strings.align_str(entry_display.truncate(item[1], width), width, justify), item[2]
|
||||
else
|
||||
return strings.align_str(entry_display.truncate(item, width), width, justify)
|
||||
end
|
||||
end)
|
||||
else
|
||||
table.insert(generator, function(item)
|
||||
if type(item) == "table" then
|
||||
return item[1], item[2]
|
||||
else
|
||||
return item
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return function(self, picker)
|
||||
local results = {}
|
||||
local highlights = {}
|
||||
for i = 1, #generator do
|
||||
if self[i] ~= nil then
|
||||
local str, hl = generator[i](self[i], picker)
|
||||
if hl then
|
||||
local hl_start = 0
|
||||
for j = 1, (i - 1) do
|
||||
hl_start = hl_start + #results[j] + (#configuration.separator or 1)
|
||||
end
|
||||
local hl_end = hl_start + #str:gsub("%s*$", "")
|
||||
|
||||
if type(hl) == "function" then
|
||||
for _, hl_res in ipairs(hl()) do
|
||||
table.insert(highlights, { { hl_res[1][1] + hl_start, hl_res[1][2] + hl_start }, hl_res[2] })
|
||||
end
|
||||
else
|
||||
table.insert(highlights, { { hl_start, hl_end }, hl })
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(results, str)
|
||||
end
|
||||
end
|
||||
|
||||
if configuration.separator_hl then
|
||||
local width = #configuration.separator or 1
|
||||
|
||||
local hl_start, hl_end
|
||||
for _, v in ipairs(results) do
|
||||
hl_start = (hl_end or 0) + #tostring(v)
|
||||
hl_end = hl_start + width
|
||||
table.insert(highlights, { { hl_start, hl_end }, configuration.separator_hl })
|
||||
end
|
||||
end
|
||||
|
||||
local final_str = table.concat(results, configuration.separator or "│")
|
||||
if configuration.hl_chars then
|
||||
for i = 1, #final_str do
|
||||
local c = final_str:sub(i, i)
|
||||
local hl = configuration.hl_chars[c]
|
||||
if hl then
|
||||
table.insert(highlights, { { i - 1, i }, hl })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return final_str, highlights
|
||||
end
|
||||
end
|
||||
|
||||
entry_display.resolve = function(self, entry)
|
||||
local display, display_highlights
|
||||
if type(entry.display) == "function" then
|
||||
self:_increment "display_fn"
|
||||
display, display_highlights = entry:display(self)
|
||||
|
||||
if type(display) == "string" then
|
||||
return display, display_highlights
|
||||
end
|
||||
else
|
||||
display = entry.display
|
||||
end
|
||||
|
||||
if type(display) == "string" then
|
||||
return display, display_highlights
|
||||
end
|
||||
end
|
||||
|
||||
return entry_display
|
||||
@ -0,0 +1,125 @@
|
||||
local a = vim.api
|
||||
local log = require "telescope.log"
|
||||
local conf = require("telescope.config").values
|
||||
|
||||
local highlights = {}
|
||||
|
||||
local ns_telescope_selection = a.nvim_create_namespace "telescope_selection"
|
||||
local ns_telescope_multiselection = a.nvim_create_namespace "telescope_multiselection"
|
||||
local ns_telescope_entry = a.nvim_create_namespace "telescope_entry"
|
||||
|
||||
local Highlighter = {}
|
||||
Highlighter.__index = Highlighter
|
||||
|
||||
function Highlighter:new(picker)
|
||||
return setmetatable({
|
||||
picker = picker,
|
||||
}, self)
|
||||
end
|
||||
|
||||
function Highlighter:hi_display(row, prefix, display_highlights)
|
||||
-- This is the bug that made my highlight fixes not work.
|
||||
-- We will leave the solution commented, so the test fails.
|
||||
if not display_highlights or vim.tbl_isempty(display_highlights) then
|
||||
return
|
||||
end
|
||||
|
||||
local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr")
|
||||
|
||||
a.nvim_buf_clear_namespace(results_bufnr, ns_telescope_entry, row, row + 1)
|
||||
local len_prefix = #prefix
|
||||
|
||||
for _, hl_block in ipairs(display_highlights) do
|
||||
a.nvim_buf_add_highlight(
|
||||
results_bufnr,
|
||||
ns_telescope_entry,
|
||||
hl_block[2],
|
||||
row,
|
||||
len_prefix + hl_block[1][1],
|
||||
len_prefix + hl_block[1][2]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function Highlighter:clear_display()
|
||||
if
|
||||
not self
|
||||
or not self.picker
|
||||
or not self.picker.results_bufnr
|
||||
or not vim.api.nvim_buf_is_valid(self.picker.results_bufnr)
|
||||
then
|
||||
return
|
||||
end
|
||||
|
||||
a.nvim_buf_clear_namespace(self.picker.results_bufnr, ns_telescope_entry, 0, -1)
|
||||
end
|
||||
|
||||
function Highlighter:hi_sorter(row, prompt, display)
|
||||
local picker = self.picker
|
||||
if not picker.sorter or not picker.sorter.highlighter then
|
||||
return
|
||||
end
|
||||
|
||||
local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr")
|
||||
picker:highlight_one_row(results_bufnr, prompt, display, row)
|
||||
end
|
||||
|
||||
function Highlighter:hi_selection(row, caret)
|
||||
caret = vim.F.if_nil(caret, "")
|
||||
local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr")
|
||||
|
||||
a.nvim_buf_clear_namespace(results_bufnr, ns_telescope_selection, 0, -1)
|
||||
a.nvim_buf_add_highlight(results_bufnr, ns_telescope_selection, "TelescopeSelectionCaret", row, 0, #caret)
|
||||
|
||||
a.nvim_buf_set_extmark(
|
||||
results_bufnr,
|
||||
ns_telescope_selection,
|
||||
row,
|
||||
#caret,
|
||||
{ end_line = row + 1, hl_eol = conf.hl_result_eol, hl_group = "TelescopeSelection" }
|
||||
)
|
||||
end
|
||||
|
||||
function Highlighter:hi_multiselect(row, is_selected)
|
||||
local results_bufnr = assert(self.picker.results_bufnr, "Must have a results bufnr")
|
||||
|
||||
if is_selected then
|
||||
vim.api.nvim_buf_add_highlight(results_bufnr, ns_telescope_multiselection, "TelescopeMultiSelection", row, 0, -1)
|
||||
if self.picker.multi_icon then
|
||||
local line = vim.api.nvim_buf_get_lines(results_bufnr, row, row + 1, false)[1]
|
||||
local pos = line:find(self.picker.multi_icon)
|
||||
if pos and pos <= math.max(#self.picker.selection_caret, #self.picker.entry_prefix) then
|
||||
vim.api.nvim_buf_add_highlight(
|
||||
results_bufnr,
|
||||
ns_telescope_multiselection,
|
||||
"TelescopeMultiIcon",
|
||||
row,
|
||||
pos - 1,
|
||||
pos - 1 + #self.picker.multi_icon
|
||||
)
|
||||
end
|
||||
end
|
||||
else
|
||||
local existing_marks = vim.api.nvim_buf_get_extmarks(
|
||||
results_bufnr,
|
||||
ns_telescope_multiselection,
|
||||
{ row, 0 },
|
||||
{ row, -1 },
|
||||
{}
|
||||
)
|
||||
|
||||
-- This is still kind of weird to me, since it seems like I'm erasing stuff
|
||||
-- when I shouldn't... Perhaps it's about the gravity of the extmark?
|
||||
if #existing_marks > 0 then
|
||||
log.trace("Clearing highlight multi select row: ", row)
|
||||
|
||||
vim.api.nvim_buf_clear_namespace(results_bufnr, ns_telescope_multiselection, row, row + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
highlights.new = function(...)
|
||||
return Highlighter:new(...)
|
||||
end
|
||||
|
||||
return highlights
|
||||
@ -0,0 +1,193 @@
|
||||
---@tag telescope.pickers.layout
|
||||
---@config { ["module"] = "telescope.pickers.layout" }
|
||||
|
||||
---@brief [[
|
||||
--- The telescope pickers layout can be configured using the
|
||||
--- |telescope.defaults.create_layout| option.
|
||||
---
|
||||
--- Parameters: ~
|
||||
--- - picker : A Picker object.
|
||||
---
|
||||
--- Return: ~
|
||||
--- - layout : instance of `TelescopeLayout` class.
|
||||
---
|
||||
--- Example: ~
|
||||
--- <code>
|
||||
--- local Layout = require "telescope.pickers.layout"
|
||||
---
|
||||
--- require("telescope").setup {
|
||||
--- create_layout = function(picker)
|
||||
--- local function create_window(enter, width, height, row, col, title)
|
||||
--- local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
--- local winid = vim.api.nvim_open_win(bufnr, enter, {
|
||||
--- style = "minimal",
|
||||
--- relative = "editor",
|
||||
--- width = width,
|
||||
--- height = height,
|
||||
--- row = row,
|
||||
--- col = col,
|
||||
--- border = "single",
|
||||
--- title = title,
|
||||
--- })
|
||||
---
|
||||
--- vim.wo[winid].winhighlight = "Normal:Normal"
|
||||
---
|
||||
--- return Layout.Window {
|
||||
--- bufnr = bufnr,
|
||||
--- winid = winid,
|
||||
--- }
|
||||
--- end
|
||||
---
|
||||
--- local function destory_window(window)
|
||||
--- if window then
|
||||
--- if vim.api.nvim_win_is_valid(window.winid) then
|
||||
--- vim.api.nvim_win_close(window.winid, true)
|
||||
--- end
|
||||
--- if vim.api.nvim_buf_is_valid(window.bufnr) then
|
||||
--- vim.api.nvim_buf_delete(window.bufnr, { force = true })
|
||||
--- end
|
||||
--- end
|
||||
--- end
|
||||
---
|
||||
--- local layout = Layout {
|
||||
--- picker = picker,
|
||||
--- mount = function(self)
|
||||
--- self.results = create_window(false, 40, 20, 0, 0, "Results")
|
||||
--- self.preview = create_window(false, 40, 23, 0, 42, "Preview")
|
||||
--- self.prompt = create_window(true, 40, 1, 22, 0, "Prompt")
|
||||
--- end,
|
||||
--- unmount = function(self)
|
||||
--- destory_window(self.results)
|
||||
--- destory_window(self.preview)
|
||||
--- destory_window(self.prompt)
|
||||
--- end,
|
||||
--- update = function(self) end,
|
||||
--- }
|
||||
---
|
||||
--- return layout
|
||||
--- end,
|
||||
--- }
|
||||
--- </code>
|
||||
---@brief ]]
|
||||
|
||||
local function wrap_instance(class, instance)
|
||||
local self = instance
|
||||
if not getmetatable(instance) then
|
||||
self = setmetatable(instance, { __index = class })
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---@class TelescopeWindowBorder.config
|
||||
---@field bufnr integer
|
||||
---@field winid integer|nil
|
||||
---@field change_title nil|function: (self: TelescopeWindowBorder, title: string, pos?: "NW"|"N"|"NE"|"SW"|"S"|"SE"):nil
|
||||
|
||||
---@param class TelescopeWindowBorder
|
||||
---@param config TelescopeWindowBorder.config
|
||||
---@return TelescopeWindowBorder
|
||||
local function init_border(class, config)
|
||||
config = config or {}
|
||||
|
||||
---@type TelescopeWindowBorder
|
||||
local self = wrap_instance(class, config)
|
||||
if not self.change_title then
|
||||
self.change_title = class.change_title
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@class TelescopeWindowBorder
|
||||
---@field bufnr integer|nil
|
||||
---@field winid integer|nil
|
||||
local Border = setmetatable({}, {
|
||||
__call = init_border,
|
||||
__name = "TelescopeWindowBorder",
|
||||
})
|
||||
|
||||
---@param title string
|
||||
---@param pos "NW"|"N"|"NE"|"SW"|"S"|"SE"|nil
|
||||
function Border:change_title(title, pos) end
|
||||
|
||||
---@class TelescopeWindow.config
|
||||
---@field bufnr integer
|
||||
---@field winid integer|nil
|
||||
---@field border TelescopeWindowBorder.config|nil
|
||||
|
||||
---@param class TelescopeWindow
|
||||
---@param config TelescopeWindow.config
|
||||
---@return TelescopeWindow
|
||||
local function init_window(class, config)
|
||||
config = config or {}
|
||||
|
||||
---@type TelescopeWindow
|
||||
local self = wrap_instance(class, config)
|
||||
self.border = Border(config.border)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@class TelescopeWindow
|
||||
---@field border TelescopeWindowBorder
|
||||
---@field bufnr integer
|
||||
---@field winid integer
|
||||
local Window = setmetatable({}, {
|
||||
__call = init_window,
|
||||
__name = "TelescopeWindow",
|
||||
})
|
||||
|
||||
---@class TelescopeLayout.config
|
||||
---@field mount function: (self: TelescopeLayout):nil
|
||||
---@field unmount function: (self: TelescopeLayout):nil
|
||||
---@field update function: (self: TelescopeLayout):nil
|
||||
---@field prompt TelescopeWindow|nil
|
||||
---@field results TelescopeWindow|nil
|
||||
---@field preview TelescopeWindow|nil
|
||||
|
||||
---@param class TelescopeLayout
|
||||
---@param config TelescopeLayout.config
|
||||
---@return TelescopeLayout
|
||||
local function init_layout(class, config)
|
||||
config = config or {}
|
||||
|
||||
---@type TelescopeLayout
|
||||
local self = wrap_instance(class, config)
|
||||
|
||||
assert(config.mount, "missing layout:mount")
|
||||
assert(config.unmount, "missing layout:unmount")
|
||||
assert(config.update, "missing layout:update")
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@class TelescopeLayout
|
||||
---@field prompt TelescopeWindow
|
||||
---@field results TelescopeWindow
|
||||
---@field preview TelescopeWindow|nil
|
||||
local Layout = setmetatable({
|
||||
Window = Window,
|
||||
}, {
|
||||
__call = init_layout,
|
||||
__name = "TelescopeLayout",
|
||||
})
|
||||
|
||||
--- Create the layout.
|
||||
--- This needs to ensure the required properties are populated.
|
||||
function Layout:mount() end
|
||||
|
||||
--- Destroy the layout.
|
||||
--- This is responsible for performing clean-up, for example:
|
||||
--- - deleting buffers
|
||||
--- - closing windows
|
||||
--- - clearing autocmds
|
||||
function Layout:unmount() end
|
||||
|
||||
--- Refresh the layout.
|
||||
--- This is called when, for example, vim is resized.
|
||||
function Layout:update() end
|
||||
|
||||
---@alias TelescopeWindow.constructor fun(config: TelescopeWindow.config): TelescopeWindow
|
||||
---@alias TelescopeLayout.constructor fun(config: TelescopeLayout.config): TelescopeLayout
|
||||
|
||||
return Layout --[[@as TelescopeLayout.constructor|{ Window: TelescopeWindow.constructor }]]
|
||||
@ -0,0 +1,949 @@
|
||||
---@tag telescope.layout
|
||||
---@config { ["module"] = "telescope.layout" }
|
||||
|
||||
---@brief [[
|
||||
--- The layout of telescope pickers can be adjusted using the
|
||||
--- |telescope.defaults.layout_strategy| and |telescope.defaults.layout_config| options.
|
||||
--- For example, the following configuration changes the default layout strategy and the
|
||||
--- default size of the picker:
|
||||
--- <code>
|
||||
--- require('telescope').setup{
|
||||
--- defaults = {
|
||||
--- layout_strategy = 'vertical',
|
||||
--- layout_config = { height = 0.95 },
|
||||
--- },
|
||||
--- }
|
||||
--- </code>
|
||||
---
|
||||
--- ────────────────────────────────────────────────────────────────────────────────
|
||||
---
|
||||
--- Layout strategies are different functions to position telescope.
|
||||
---
|
||||
--- All layout strategies are functions with the following signature:
|
||||
---
|
||||
--- <code>
|
||||
--- function(picker, columns, lines, layout_config)
|
||||
--- -- Do some calculations here...
|
||||
--- return {
|
||||
--- preview = preview_configuration
|
||||
--- results = results_configuration,
|
||||
--- prompt = prompt_configuration,
|
||||
--- }
|
||||
--- end
|
||||
--- </code>
|
||||
---
|
||||
--- <pre>
|
||||
--- Parameters: ~
|
||||
--- - picker : A Picker object. (docs coming soon)
|
||||
--- - columns : (number) Columns in the vim window
|
||||
--- - lines : (number) Lines in the vim window
|
||||
--- - layout_config : (table) The configuration values specific to the picker.
|
||||
--- </pre>
|
||||
---
|
||||
--- This means you can create your own layout strategy if you want! Just be aware
|
||||
--- for now that we may change some APIs or interfaces, so they may break if you create
|
||||
--- your own.
|
||||
---
|
||||
--- A good method for creating your own would be to copy one of the strategies that most
|
||||
--- resembles what you want from "./lua/telescope/pickers/layout_strategies.lua" in the
|
||||
--- telescope repo.
|
||||
---
|
||||
---@brief ]]
|
||||
|
||||
local resolve = require "telescope.config.resolve"
|
||||
local p_window = require "telescope.pickers.window"
|
||||
|
||||
local get_border_size = function(opts)
|
||||
if opts.window.border == false then
|
||||
return 0
|
||||
end
|
||||
|
||||
return 1
|
||||
end
|
||||
|
||||
local calc_tabline = function(max_lines)
|
||||
local tbln = (vim.o.showtabline == 2) or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1)
|
||||
if tbln then
|
||||
max_lines = max_lines - 1
|
||||
end
|
||||
return max_lines, tbln
|
||||
end
|
||||
|
||||
-- Helper function for capping over/undersized width/height, and calculating spacing
|
||||
--@param cur_size number: size to be capped
|
||||
--@param max_size any: the maximum size, e.g. max_lines or max_columns
|
||||
--@param bs number: the size of the border
|
||||
--@param w_num number: the maximum number of windows of the picker in the given direction
|
||||
--@param b_num number: the number of border rows/column in the given direction (when border enabled)
|
||||
--@param s_num number: the number of gaps in the given direction (when border disabled)
|
||||
local calc_size_and_spacing = function(cur_size, max_size, bs, w_num, b_num, s_num)
|
||||
local spacing = s_num * (1 - bs) + b_num * bs
|
||||
cur_size = math.min(cur_size, max_size)
|
||||
cur_size = math.max(cur_size, w_num + spacing)
|
||||
return cur_size, spacing
|
||||
end
|
||||
|
||||
local layout_strategies = {}
|
||||
layout_strategies._configurations = {}
|
||||
|
||||
--@param strategy_config table: table with keys for each option for a strategy
|
||||
--@return table: table with keys for each option (for this strategy) and with keys for each layout_strategy
|
||||
local get_valid_configuration_keys = function(strategy_config)
|
||||
local valid_configuration_keys = {
|
||||
-- TEMP: There are a few keys we should say are valid to start with.
|
||||
preview_cutoff = true,
|
||||
prompt_position = true,
|
||||
}
|
||||
|
||||
for key in pairs(strategy_config) do
|
||||
valid_configuration_keys[key] = true
|
||||
end
|
||||
|
||||
for name in pairs(layout_strategies) do
|
||||
valid_configuration_keys[name] = true
|
||||
end
|
||||
|
||||
return valid_configuration_keys
|
||||
end
|
||||
|
||||
local adjust_pos = function(pos, ...)
|
||||
for _, opts in ipairs { ... } do
|
||||
opts.col = opts.col and opts.col + pos[1]
|
||||
opts.line = opts.line and opts.line + pos[2]
|
||||
end
|
||||
end
|
||||
|
||||
--@param strategy_name string: the name of the layout_strategy we are validating for
|
||||
--@param configuration table: table with keys for each option available
|
||||
--@param values table: table containing all of the non-default options we want to set
|
||||
--@param default_layout_config table: table with the default values to configure layouts
|
||||
--@return table: table containing the combined options (defaults and non-defaults)
|
||||
local function validate_layout_config(strategy_name, configuration, values, default_layout_config)
|
||||
assert(strategy_name, "It is required to have a strategy name for validation.")
|
||||
local valid_configuration_keys = get_valid_configuration_keys(configuration)
|
||||
|
||||
-- If no default_layout_config provided, check Telescope's config values
|
||||
default_layout_config = vim.F.if_nil(default_layout_config, require("telescope.config").values.layout_config)
|
||||
|
||||
local result = {}
|
||||
local get_value = function(k)
|
||||
-- skip "private" items
|
||||
if string.sub(k, 1, 1) == "_" then
|
||||
return
|
||||
end
|
||||
|
||||
local val
|
||||
-- Prioritise options that are specific to this strategy
|
||||
if values[strategy_name] ~= nil and values[strategy_name][k] ~= nil then
|
||||
val = values[strategy_name][k]
|
||||
end
|
||||
|
||||
-- Handle nested layout config values
|
||||
if layout_strategies[k] and strategy_name ~= k and type(val) == "table" then
|
||||
val = vim.tbl_deep_extend("force", default_layout_config[k], val)
|
||||
end
|
||||
|
||||
if val == nil and values[k] ~= nil then
|
||||
val = values[k]
|
||||
end
|
||||
|
||||
if val == nil then
|
||||
if default_layout_config[strategy_name] ~= nil and default_layout_config[strategy_name][k] ~= nil then
|
||||
val = default_layout_config[strategy_name][k]
|
||||
else
|
||||
val = default_layout_config[k]
|
||||
end
|
||||
end
|
||||
|
||||
return val
|
||||
end
|
||||
|
||||
-- Always set the values passed first.
|
||||
for k in pairs(values) do
|
||||
if not valid_configuration_keys[k] then
|
||||
-- TODO: At some point we'll move to error here,
|
||||
-- but it's a bit annoying to just straight up crash everyone's stuff.
|
||||
vim.api.nvim_err_writeln(
|
||||
string.format(
|
||||
"Unsupported layout_config key for the %s strategy: %s\n%s",
|
||||
strategy_name,
|
||||
k,
|
||||
vim.inspect(values)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
result[k] = get_value(k)
|
||||
end
|
||||
|
||||
-- And then set other valid keys via "inheritance" style extension
|
||||
for k in pairs(valid_configuration_keys) do
|
||||
if result[k] == nil then
|
||||
result[k] = get_value(k)
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- List of options that are shared by more than one layout.
|
||||
local shared_options = {
|
||||
width = { "How wide to make Telescope's entire layout", "See |resolver.resolve_width()|" },
|
||||
height = { "How tall to make Telescope's entire layout", "See |resolver.resolve_height()|" },
|
||||
mirror = "Flip the location of the results/prompt and preview windows",
|
||||
scroll_speed = "The number of lines to scroll through the previewer",
|
||||
prompt_position = { "Where to place prompt window.", "Available Values: 'bottom', 'top'" },
|
||||
anchor = { "Which edge/corner to pin the picker to", "See |resolver.resolve_anchor_pos()|" },
|
||||
}
|
||||
|
||||
-- Used for generating vim help documentation.
|
||||
layout_strategies._format = function(name)
|
||||
local strategy_config = layout_strategies._configurations[name]
|
||||
if vim.tbl_isempty(strategy_config) then
|
||||
return {}
|
||||
end
|
||||
|
||||
local results = { "<pre>", "`picker.layout_config` shared options:" }
|
||||
|
||||
local strategy_keys = vim.tbl_keys(strategy_config)
|
||||
table.sort(strategy_keys, function(a, b)
|
||||
return a < b
|
||||
end)
|
||||
|
||||
local add_value = function(k, val)
|
||||
if type(val) == "string" then
|
||||
table.insert(results, string.format(" - %s: %s", k, val))
|
||||
elseif type(val) == "table" then
|
||||
table.insert(results, string.format(" - %s:", k))
|
||||
for _, line in ipairs(val) do
|
||||
table.insert(results, string.format(" - %s", line))
|
||||
end
|
||||
else
|
||||
error(string.format("expected string or table but found '%s'", type(val)))
|
||||
end
|
||||
end
|
||||
|
||||
for _, k in ipairs(strategy_keys) do
|
||||
if shared_options[k] then
|
||||
add_value(k, strategy_config[k])
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(results, "")
|
||||
table.insert(results, "`picker.layout_config` unique options:")
|
||||
|
||||
for _, k in ipairs(strategy_keys) do
|
||||
if not shared_options[k] then
|
||||
add_value(k, strategy_config[k])
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(results, "</pre>")
|
||||
return results
|
||||
end
|
||||
|
||||
--@param name string: the name to be assigned to the layout
|
||||
--@param layout_config table: table where keys are the available options for the layout
|
||||
--@param layout function: function with signature
|
||||
-- function(self, max_columns, max_lines, layout_config): table
|
||||
-- the returned table is the sizing and location information for the parts of the picker
|
||||
--@retun function: wrapped function that inputs a validated layout_config into the `layout` function
|
||||
local function make_documented_layout(name, layout_config, layout)
|
||||
-- Save configuration data to be used by documentation
|
||||
layout_strategies._configurations[name] = layout_config
|
||||
|
||||
-- Return new function that always validates configuration
|
||||
return function(self, max_columns, max_lines, override_layout)
|
||||
return layout(
|
||||
self,
|
||||
max_columns,
|
||||
max_lines,
|
||||
validate_layout_config(
|
||||
name,
|
||||
layout_config,
|
||||
vim.tbl_deep_extend("keep", vim.F.if_nil(override_layout, {}), vim.F.if_nil(self.layout_config, {}))
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
--- Horizontal layout has two columns, one for the preview
|
||||
--- and one for the prompt and results.
|
||||
---
|
||||
--- <pre>
|
||||
--- ┌──────────────────────────────────────────────────┐
|
||||
--- │ │
|
||||
--- │ ┌───────────────────┐┌───────────────────┐ │
|
||||
--- │ │ ││ │ │
|
||||
--- │ │ ││ │ │
|
||||
--- │ │ ││ │ │
|
||||
--- │ │ Results ││ │ │
|
||||
--- │ │ ││ Preview │ │
|
||||
--- │ │ ││ │ │
|
||||
--- │ │ ││ │ │
|
||||
--- │ └───────────────────┘│ │ │
|
||||
--- │ ┌───────────────────┐│ │ │
|
||||
--- │ │ Prompt ││ │ │
|
||||
--- │ └───────────────────┘└───────────────────┘ │
|
||||
--- │ │
|
||||
--- └──────────────────────────────────────────────────┘
|
||||
--- </pre>
|
||||
---@eval { ["description"] = require('telescope.pickers.layout_strategies')._format("horizontal") }
|
||||
---
|
||||
layout_strategies.horizontal = make_documented_layout(
|
||||
"horizontal",
|
||||
vim.tbl_extend("error", shared_options, {
|
||||
preview_width = { "Change the width of Telescope's preview window", "See |resolver.resolve_width()|" },
|
||||
preview_cutoff = "When columns are less than this value, the preview will be disabled",
|
||||
}),
|
||||
function(self, max_columns, max_lines, layout_config)
|
||||
local initial_options = p_window.get_initial_window_options(self)
|
||||
local preview = initial_options.preview
|
||||
local results = initial_options.results
|
||||
local prompt = initial_options.prompt
|
||||
|
||||
local tbln
|
||||
max_lines, tbln = calc_tabline(max_lines)
|
||||
|
||||
local width_opt = layout_config.width
|
||||
local width = resolve.resolve_width(width_opt)(self, max_columns, max_lines)
|
||||
|
||||
local height_opt = layout_config.height
|
||||
local height = resolve.resolve_height(height_opt)(self, max_columns, max_lines)
|
||||
|
||||
local bs = get_border_size(self)
|
||||
|
||||
local w_space
|
||||
if self.previewer and max_columns >= layout_config.preview_cutoff then
|
||||
-- Cap over/undersized width (with previewer)
|
||||
width, w_space = calc_size_and_spacing(width, max_columns, bs, 2, 4, 1)
|
||||
|
||||
preview.width = resolve.resolve_width(vim.F.if_nil(layout_config.preview_width, function(_, cols)
|
||||
if cols < 150 then
|
||||
return math.floor(cols * 0.4)
|
||||
elseif cols < 200 then
|
||||
return 80
|
||||
else
|
||||
return 120
|
||||
end
|
||||
end))(self, width, max_lines)
|
||||
|
||||
results.width = width - preview.width - w_space
|
||||
prompt.width = results.width
|
||||
else
|
||||
-- Cap over/undersized width (without previewer)
|
||||
width, w_space = calc_size_and_spacing(width, max_columns, bs, 1, 2, 0)
|
||||
|
||||
preview.width = 0
|
||||
results.width = width - preview.width - w_space
|
||||
prompt.width = results.width
|
||||
end
|
||||
|
||||
local h_space
|
||||
-- Cap over/undersized height
|
||||
height, h_space = calc_size_and_spacing(height, max_lines, bs, 2, 4, 1)
|
||||
|
||||
prompt.height = 1
|
||||
results.height = height - prompt.height - h_space
|
||||
|
||||
if self.previewer then
|
||||
preview.height = height - 2 * bs
|
||||
else
|
||||
preview.height = 0
|
||||
end
|
||||
|
||||
local width_padding = math.floor((max_columns - width) / 2)
|
||||
-- Default value is false, to use the normal horizontal layout
|
||||
if not layout_config.mirror then
|
||||
results.col = width_padding + bs + 1
|
||||
prompt.col = results.col
|
||||
preview.col = results.col + results.width + 1 + bs
|
||||
else
|
||||
preview.col = width_padding + bs + 1
|
||||
prompt.col = preview.col + preview.width + 1 + bs
|
||||
results.col = preview.col + preview.width + 1 + bs
|
||||
end
|
||||
|
||||
preview.line = math.floor((max_lines - height) / 2) + bs + 1
|
||||
if layout_config.prompt_position == "top" then
|
||||
prompt.line = preview.line
|
||||
results.line = prompt.line + prompt.height + 1 + bs
|
||||
elseif layout_config.prompt_position == "bottom" then
|
||||
results.line = preview.line
|
||||
prompt.line = results.line + results.height + 1 + bs
|
||||
else
|
||||
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
|
||||
end
|
||||
|
||||
local anchor_pos = resolve.resolve_anchor_pos(layout_config.anchor or "", width, height, max_columns, max_lines)
|
||||
adjust_pos(anchor_pos, prompt, results, preview)
|
||||
|
||||
if tbln then
|
||||
prompt.line = prompt.line + 1
|
||||
results.line = results.line + 1
|
||||
preview.line = preview.line + 1
|
||||
end
|
||||
|
||||
return {
|
||||
preview = self.previewer and preview.width > 0 and preview,
|
||||
results = results,
|
||||
prompt = prompt,
|
||||
}
|
||||
end
|
||||
)
|
||||
|
||||
--- Centered layout with a combined block of the prompt
|
||||
--- and results aligned to the middle of the screen.
|
||||
--- The preview window is then placed in the remaining
|
||||
--- space above or below, according to `anchor` or `mirror`.
|
||||
--- Particularly useful for creating dropdown menus
|
||||
--- (see |telescope.themes| and |themes.get_dropdown()|).
|
||||
---
|
||||
--- Note that vertical anchoring, i.e. `anchor` containing
|
||||
--- `"N"` or `"S"`, will override `mirror` config. For `"N"`
|
||||
--- anchoring preview will be placed below prompt/result
|
||||
--- block. For `"S"` anchoring preview will be placed above
|
||||
--- prompt/result block. For horizontal only anchoring preview
|
||||
--- will be placed according to `mirror` config, default is
|
||||
--- above the prompt/result block.
|
||||
---
|
||||
--- <pre>
|
||||
--- ┌──────────────────────────────────────────────────┐
|
||||
--- │ ┌────────────────────────────────────────┐ │
|
||||
--- │ │ Preview │ │
|
||||
--- │ │ Preview │ │
|
||||
--- │ └────────────────────────────────────────┘ │
|
||||
--- │ ┌────────────────────────────────────────┐ │
|
||||
--- │ │ Prompt │ │
|
||||
--- │ ├────────────────────────────────────────┤ │
|
||||
--- │ │ Result │ │
|
||||
--- │ │ Result │ │
|
||||
--- │ └────────────────────────────────────────┘ │
|
||||
--- │ │
|
||||
--- │ │
|
||||
--- │ │
|
||||
--- │ │
|
||||
--- └──────────────────────────────────────────────────┘
|
||||
--- </pre>
|
||||
---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("center") }
|
||||
---
|
||||
layout_strategies.center = make_documented_layout(
|
||||
"center",
|
||||
vim.tbl_extend("error", shared_options, {
|
||||
preview_cutoff = "When lines are less than this value, the preview will be disabled",
|
||||
}),
|
||||
function(self, max_columns, max_lines, layout_config)
|
||||
local initial_options = p_window.get_initial_window_options(self)
|
||||
local preview = initial_options.preview
|
||||
local results = initial_options.results
|
||||
local prompt = initial_options.prompt
|
||||
|
||||
local tbln
|
||||
max_lines, tbln = calc_tabline(max_lines)
|
||||
|
||||
-- This sets the width for the whole layout
|
||||
local width_opt = layout_config.width
|
||||
local width = resolve.resolve_width(width_opt)(self, max_columns, max_lines)
|
||||
|
||||
-- This sets the height for the whole layout
|
||||
local height_opt = layout_config.height
|
||||
local height = resolve.resolve_height(height_opt)(self, max_columns, max_lines)
|
||||
|
||||
local bs = get_border_size(self)
|
||||
|
||||
local w_space
|
||||
-- Cap over/undersized width
|
||||
width, w_space = calc_size_and_spacing(width, max_columns, bs, 1, 2, 0)
|
||||
|
||||
prompt.width = width - w_space
|
||||
results.width = width - w_space
|
||||
preview.width = width - w_space
|
||||
|
||||
local h_space
|
||||
-- Cap over/undersized height
|
||||
height, h_space = calc_size_and_spacing(height, max_lines, bs, 2, 3, 0)
|
||||
|
||||
prompt.height = 1
|
||||
results.height = height - prompt.height - h_space
|
||||
|
||||
local topline = math.floor((max_lines / 2) - ((results.height + (2 * bs)) / 2) + 1)
|
||||
-- Align the prompt and results so halfway up the screen is
|
||||
-- in the middle of this combined block
|
||||
if layout_config.prompt_position == "top" then
|
||||
prompt.line = topline
|
||||
results.line = prompt.line + 1 + bs
|
||||
elseif layout_config.prompt_position == "bottom" then
|
||||
results.line = topline
|
||||
prompt.line = results.line + results.height + bs
|
||||
if type(prompt.title) == "string" then
|
||||
prompt.title = { { pos = "S", text = prompt.title } }
|
||||
end
|
||||
else
|
||||
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
|
||||
end
|
||||
|
||||
local width_padding = math.floor((max_columns - width) / 2) + bs + 1
|
||||
results.col, preview.col, prompt.col = width_padding, width_padding, width_padding
|
||||
|
||||
local anchor = layout_config.anchor or ""
|
||||
local anchor_pos = resolve.resolve_anchor_pos(anchor, width, height, max_columns, max_lines)
|
||||
adjust_pos(anchor_pos, prompt, results, preview)
|
||||
|
||||
-- Vertical anchoring (S or N variations) ignores layout_config.mirror
|
||||
anchor = anchor:upper()
|
||||
local mirror
|
||||
if anchor:find "S" then
|
||||
mirror = false
|
||||
elseif anchor:find "N" then
|
||||
mirror = true
|
||||
else
|
||||
mirror = layout_config.mirror
|
||||
end
|
||||
|
||||
-- Set preview position
|
||||
local block_line = math.min(results.line, prompt.line)
|
||||
if not mirror then -- Preview at top
|
||||
preview.line = 1 + bs
|
||||
preview.height = block_line - (2 + 2 * bs)
|
||||
else -- Preview at bottom
|
||||
preview.line = block_line + results.height + 2 + 2 * bs
|
||||
preview.height = max_lines - preview.line - bs + 1
|
||||
end
|
||||
|
||||
if not (self.previewer and max_lines >= layout_config.preview_cutoff) then
|
||||
preview.height = 0
|
||||
end
|
||||
|
||||
if tbln then
|
||||
prompt.line = prompt.line + 1
|
||||
results.line = results.line + 1
|
||||
preview.line = preview.line + 1
|
||||
end
|
||||
|
||||
return {
|
||||
preview = self.previewer and preview.height > 0 and preview,
|
||||
results = results,
|
||||
prompt = prompt,
|
||||
}
|
||||
end
|
||||
)
|
||||
|
||||
--- Cursor layout dynamically positioned below the cursor if possible.
|
||||
--- If there is no place below the cursor it will be placed above.
|
||||
---
|
||||
--- <pre>
|
||||
--- ┌──────────────────────────────────────────────────┐
|
||||
--- │ │
|
||||
--- │ █ │
|
||||
--- │ ┌──────────────┐┌─────────────────────┐ │
|
||||
--- │ │ Prompt ││ Preview │ │
|
||||
--- │ ├──────────────┤│ Preview │ │
|
||||
--- │ │ Result ││ Preview │ │
|
||||
--- │ │ Result ││ Preview │ │
|
||||
--- │ └──────────────┘└─────────────────────┘ │
|
||||
--- │ █ │
|
||||
--- │ │
|
||||
--- │ │
|
||||
--- │ │
|
||||
--- │ │
|
||||
--- │ │
|
||||
--- └──────────────────────────────────────────────────┘
|
||||
--- </pre>
|
||||
---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("cursor") }
|
||||
layout_strategies.cursor = make_documented_layout(
|
||||
"cursor",
|
||||
vim.tbl_extend("error", {
|
||||
width = shared_options.width,
|
||||
height = shared_options.height,
|
||||
scroll_speed = shared_options.scroll_speed,
|
||||
}, {
|
||||
preview_width = { "Change the width of Telescope's preview window", "See |resolver.resolve_width()|" },
|
||||
preview_cutoff = "When columns are less than this value, the preview will be disabled",
|
||||
}),
|
||||
function(self, max_columns, max_lines, layout_config)
|
||||
local initial_options = p_window.get_initial_window_options(self)
|
||||
local preview = initial_options.preview
|
||||
local results = initial_options.results
|
||||
local prompt = initial_options.prompt
|
||||
local winid = self.original_win_id
|
||||
|
||||
local height_opt = layout_config.height
|
||||
local height = resolve.resolve_height(height_opt)(self, max_columns, max_lines)
|
||||
|
||||
local width_opt = layout_config.width
|
||||
local width = resolve.resolve_width(width_opt)(self, max_columns, max_lines)
|
||||
|
||||
local bs = get_border_size(self)
|
||||
|
||||
local h_space
|
||||
-- Cap over/undersized height
|
||||
height, h_space = calc_size_and_spacing(height, max_lines, bs, 2, 3, 0)
|
||||
|
||||
prompt.height = 1
|
||||
results.height = height - prompt.height - h_space
|
||||
preview.height = height - 2 * bs
|
||||
|
||||
local w_space
|
||||
if self.previewer and max_columns >= layout_config.preview_cutoff then
|
||||
-- Cap over/undersized width (with preview)
|
||||
width, w_space = calc_size_and_spacing(width, max_columns, bs, 2, 4, 0)
|
||||
|
||||
preview.width = resolve.resolve_width(vim.F.if_nil(layout_config.preview_width, 2 / 3))(self, width, max_lines)
|
||||
prompt.width = width - preview.width - w_space
|
||||
results.width = prompt.width
|
||||
else
|
||||
-- Cap over/undersized width (without preview)
|
||||
width, w_space = calc_size_and_spacing(width, max_columns, bs, 1, 2, 0)
|
||||
|
||||
preview.width = 0
|
||||
prompt.width = width - w_space
|
||||
results.width = prompt.width
|
||||
end
|
||||
|
||||
local position = vim.api.nvim_win_get_position(winid)
|
||||
local winbar = (function()
|
||||
if vim.fn.exists "&winbar" == 1 then
|
||||
return vim.wo[winid].winbar == "" and 0 or 1
|
||||
end
|
||||
return 0
|
||||
end)()
|
||||
local top_left = {
|
||||
line = vim.api.nvim_win_call(winid, vim.fn.winline) + position[1] + bs + winbar,
|
||||
col = vim.api.nvim_win_call(winid, vim.fn.wincol) + position[2],
|
||||
}
|
||||
local bot_right = {
|
||||
line = top_left.line + height - 1,
|
||||
col = top_left.col + width - 1,
|
||||
}
|
||||
|
||||
if bot_right.line > max_lines then
|
||||
-- position above current line
|
||||
top_left.line = top_left.line - height - 1
|
||||
end
|
||||
if bot_right.col >= max_columns then
|
||||
-- cap to the right of the screen
|
||||
top_left.col = max_columns - width
|
||||
end
|
||||
|
||||
prompt.line = top_left.line + 1
|
||||
results.line = prompt.line + bs + 1
|
||||
preview.line = prompt.line
|
||||
|
||||
prompt.col = top_left.col + 1
|
||||
results.col = prompt.col
|
||||
preview.col = results.col + (bs * 2) + results.width
|
||||
|
||||
return {
|
||||
preview = self.previewer and preview.width > 0 and preview,
|
||||
results = results,
|
||||
prompt = prompt,
|
||||
}
|
||||
end
|
||||
)
|
||||
|
||||
--- Vertical layout stacks the items on top of each other.
|
||||
--- Particularly useful with thinner windows.
|
||||
---
|
||||
--- <pre>
|
||||
--- ┌──────────────────────────────────────────────────┐
|
||||
--- │ │
|
||||
--- │ ┌────────────────────────────────────────┐ │
|
||||
--- │ │ Preview │ │
|
||||
--- │ │ Preview │ │
|
||||
--- │ │ Preview │ │
|
||||
--- │ └────────────────────────────────────────┘ │
|
||||
--- │ ┌────────────────────────────────────────┐ │
|
||||
--- │ │ Result │ │
|
||||
--- │ │ Result │ │
|
||||
--- │ └────────────────────────────────────────┘ │
|
||||
--- │ ┌────────────────────────────────────────┐ │
|
||||
--- │ │ Prompt │ │
|
||||
--- │ └────────────────────────────────────────┘ │
|
||||
--- │ │
|
||||
--- └──────────────────────────────────────────────────┘
|
||||
--- </pre>
|
||||
---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("vertical") }
|
||||
---
|
||||
layout_strategies.vertical = make_documented_layout(
|
||||
"vertical",
|
||||
vim.tbl_extend("error", shared_options, {
|
||||
preview_cutoff = "When lines are less than this value, the preview will be disabled",
|
||||
preview_height = { "Change the height of Telescope's preview window", "See |resolver.resolve_height()|" },
|
||||
}),
|
||||
function(self, max_columns, max_lines, layout_config)
|
||||
local initial_options = p_window.get_initial_window_options(self)
|
||||
local preview = initial_options.preview
|
||||
local results = initial_options.results
|
||||
local prompt = initial_options.prompt
|
||||
|
||||
local tbln
|
||||
max_lines, tbln = calc_tabline(max_lines)
|
||||
|
||||
local width_opt = layout_config.width
|
||||
local width = resolve.resolve_width(width_opt)(self, max_columns, max_lines)
|
||||
|
||||
local height_opt = layout_config.height
|
||||
local height = resolve.resolve_height(height_opt)(self, max_columns, max_lines)
|
||||
|
||||
local bs = get_border_size(self)
|
||||
|
||||
local w_space
|
||||
-- Cap over/undersized width
|
||||
width, w_space = calc_size_and_spacing(width, max_columns, bs, 1, 2, 0)
|
||||
|
||||
prompt.width = width - w_space
|
||||
results.width = prompt.width
|
||||
preview.width = prompt.width
|
||||
|
||||
local h_space
|
||||
if self.previewer and max_lines >= layout_config.preview_cutoff then
|
||||
-- Cap over/undersized height (with previewer)
|
||||
height, h_space = calc_size_and_spacing(height, max_lines, bs, 3, 6, 2)
|
||||
|
||||
preview.height =
|
||||
resolve.resolve_height(vim.F.if_nil(layout_config.preview_height, 0.5))(self, max_columns, height)
|
||||
else
|
||||
-- Cap over/undersized height (without previewer)
|
||||
height, h_space = calc_size_and_spacing(height, max_lines, bs, 2, 4, 1)
|
||||
|
||||
preview.height = 0
|
||||
end
|
||||
prompt.height = 1
|
||||
results.height = height - preview.height - prompt.height - h_space
|
||||
|
||||
local width_padding = math.floor((max_columns - width) / 2) + bs + 1
|
||||
results.col, preview.col, prompt.col = width_padding, width_padding, width_padding
|
||||
|
||||
local height_padding = math.floor((max_lines - height) / 2)
|
||||
if not layout_config.mirror then
|
||||
preview.line = height_padding + (1 + bs)
|
||||
if layout_config.prompt_position == "top" then
|
||||
prompt.line = (preview.height == 0) and preview.line or preview.line + preview.height + (1 + bs)
|
||||
results.line = prompt.line + prompt.height + (1 + bs)
|
||||
elseif layout_config.prompt_position == "bottom" then
|
||||
results.line = (preview.height == 0) and preview.line or preview.line + preview.height + (1 + bs)
|
||||
prompt.line = results.line + results.height + (1 + bs)
|
||||
else
|
||||
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
|
||||
end
|
||||
else
|
||||
if layout_config.prompt_position == "top" then
|
||||
prompt.line = height_padding + (1 + bs)
|
||||
results.line = prompt.line + prompt.height + (1 + bs)
|
||||
preview.line = results.line + results.height + (1 + bs)
|
||||
elseif layout_config.prompt_position == "bottom" then
|
||||
results.line = height_padding + (1 + bs)
|
||||
prompt.line = results.line + results.height + (1 + bs)
|
||||
preview.line = prompt.line + prompt.height + (1 + bs)
|
||||
else
|
||||
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
|
||||
end
|
||||
end
|
||||
|
||||
local anchor_pos = resolve.resolve_anchor_pos(layout_config.anchor or "", width, height, max_columns, max_lines)
|
||||
adjust_pos(anchor_pos, prompt, results, preview)
|
||||
|
||||
if tbln then
|
||||
prompt.line = prompt.line + 1
|
||||
results.line = results.line + 1
|
||||
preview.line = preview.line + 1
|
||||
end
|
||||
|
||||
return {
|
||||
preview = self.previewer and preview.height > 0 and preview,
|
||||
results = results,
|
||||
prompt = prompt,
|
||||
}
|
||||
end
|
||||
)
|
||||
|
||||
--- Flex layout swaps between `horizontal` and `vertical` strategies based on the window width
|
||||
--- - Supports |layout_strategies.vertical| or |layout_strategies.horizontal| features
|
||||
---
|
||||
---@eval { ["description"] = require("telescope.pickers.layout_strategies")._format("flex") }
|
||||
---
|
||||
layout_strategies.flex = make_documented_layout(
|
||||
"flex",
|
||||
vim.tbl_extend("error", shared_options, {
|
||||
flip_columns = "The number of columns required to move to horizontal mode",
|
||||
flip_lines = "The number of lines required to move to horizontal mode",
|
||||
vertical = "Options to pass when switching to vertical layout",
|
||||
horizontal = "Options to pass when switching to horizontal layout",
|
||||
}),
|
||||
function(self, max_columns, max_lines, layout_config)
|
||||
local flip_columns = vim.F.if_nil(layout_config.flip_columns, 100)
|
||||
local flip_lines = vim.F.if_nil(layout_config.flip_lines, 20)
|
||||
|
||||
if max_columns < flip_columns and max_lines > flip_lines then
|
||||
self.__flex_strategy = "vertical"
|
||||
self.layout_config.flip_columns = nil
|
||||
self.layout_config.flip_lines = nil
|
||||
return layout_strategies.vertical(self, max_columns, max_lines, layout_config.vertical)
|
||||
else
|
||||
self.__flex_strategy = "horizontal"
|
||||
self.layout_config.flip_columns = nil
|
||||
self.layout_config.flip_lines = nil
|
||||
return layout_strategies.horizontal(self, max_columns, max_lines, layout_config.horizontal)
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
layout_strategies.current_buffer = make_documented_layout("current_buffer", {
|
||||
-- No custom options.
|
||||
-- height, width ignored
|
||||
}, function(self, _, _, _)
|
||||
local initial_options = p_window.get_initial_window_options(self)
|
||||
|
||||
local window_width = vim.api.nvim_win_get_width(0)
|
||||
local window_height = vim.api.nvim_win_get_height(0)
|
||||
|
||||
local preview = initial_options.preview
|
||||
local results = initial_options.results
|
||||
local prompt = initial_options.prompt
|
||||
|
||||
local bs = get_border_size(self)
|
||||
|
||||
-- Width
|
||||
local width_padding = (1 + bs) -- TODO(l-kershaw): make this configurable
|
||||
|
||||
prompt.width = window_width - 2 * width_padding
|
||||
results.width = prompt.width
|
||||
preview.width = prompt.width
|
||||
|
||||
-- Height
|
||||
local height_padding = (1 + bs) -- TODO(l-kershaw): make this configurable
|
||||
|
||||
prompt.height = 1
|
||||
if self.previewer then
|
||||
results.height = 10 -- TODO(l-kershaw): make this configurable
|
||||
preview.height = window_height - results.height - prompt.height - 2 * (1 + bs) - 2 * height_padding
|
||||
else
|
||||
results.height = window_height - prompt.height - (1 + bs) - 2 * height_padding
|
||||
preview.height = 0
|
||||
end
|
||||
|
||||
local win_position = vim.api.nvim_win_get_position(0)
|
||||
|
||||
local line = win_position[1]
|
||||
if self.previewer then
|
||||
preview.line = height_padding + line + 1
|
||||
results.line = preview.line + preview.height + (1 + bs)
|
||||
prompt.line = results.line + results.height + (1 + bs)
|
||||
else
|
||||
results.line = height_padding + line + 1
|
||||
prompt.line = results.line + results.height + (1 + bs)
|
||||
end
|
||||
|
||||
local col = win_position[2] + width_padding + 1
|
||||
preview.col, results.col, prompt.col = col, col, col
|
||||
|
||||
return {
|
||||
preview = preview.height > 0 and preview,
|
||||
results = results,
|
||||
prompt = prompt,
|
||||
}
|
||||
end)
|
||||
|
||||
--- Bottom pane can be used to create layouts similar to "ivy".
|
||||
---
|
||||
--- For an easy ivy configuration, see |themes.get_ivy()|
|
||||
layout_strategies.bottom_pane = make_documented_layout(
|
||||
"bottom_pane",
|
||||
vim.tbl_extend("error", shared_options, {
|
||||
preview_width = { "Change the width of Telescope's preview window", "See |resolver.resolve_width()|" },
|
||||
preview_cutoff = "When columns are less than this value, the preview will be disabled",
|
||||
}),
|
||||
function(self, max_columns, max_lines, layout_config)
|
||||
local initial_options = p_window.get_initial_window_options(self)
|
||||
local results = initial_options.results
|
||||
local prompt = initial_options.prompt
|
||||
local preview = initial_options.preview
|
||||
|
||||
local tbln
|
||||
max_lines, tbln = calc_tabline(max_lines)
|
||||
|
||||
local height = vim.F.if_nil(resolve.resolve_height(layout_config.height)(self, max_columns, max_lines), 25)
|
||||
if type(layout_config.height) == "table" and type(layout_config.height.padding) == "number" then
|
||||
-- Since bottom_pane only has padding at the top, we only need half as much padding in total
|
||||
-- This doesn't match the vim help for `resolve.resolve_height`, but it matches expectations
|
||||
height = math.floor((max_lines + height) / 2)
|
||||
end
|
||||
|
||||
local bs = get_border_size(self)
|
||||
|
||||
-- Cap over/undersized height
|
||||
height, _ = calc_size_and_spacing(height, max_lines, bs, 2, 3, 0)
|
||||
|
||||
-- Height
|
||||
prompt.height = 1
|
||||
results.height = height - prompt.height - (2 * bs)
|
||||
preview.height = results.height - bs
|
||||
|
||||
-- Width
|
||||
prompt.width = max_columns - 2 * bs
|
||||
if self.previewer and max_columns >= layout_config.preview_cutoff then
|
||||
-- Cap over/undersized width (with preview)
|
||||
local width, w_space = calc_size_and_spacing(max_columns, max_columns, bs, 2, 4, 0)
|
||||
|
||||
preview.width = resolve.resolve_width(vim.F.if_nil(layout_config.preview_width, 0.5))(self, width, max_lines)
|
||||
results.width = width - preview.width - w_space
|
||||
else
|
||||
results.width = prompt.width
|
||||
preview.width = 0
|
||||
end
|
||||
|
||||
-- Line
|
||||
if layout_config.prompt_position == "top" then
|
||||
prompt.line = max_lines - results.height - (1 + bs) + 1
|
||||
results.line = prompt.line + 1
|
||||
preview.line = results.line + bs
|
||||
if results.border == true then
|
||||
results.border = { 0, 1, 1, 1 }
|
||||
end
|
||||
if type(results.title) == "string" then
|
||||
results.title = { { pos = "S", text = results.title } }
|
||||
end
|
||||
if type(preview.title) == "string" then
|
||||
preview.title = { { pos = "S", text = preview.title } }
|
||||
end
|
||||
elseif layout_config.prompt_position == "bottom" then
|
||||
results.line = max_lines - results.height - (1 + bs) + 1
|
||||
preview.line = results.line
|
||||
prompt.line = max_lines - bs
|
||||
if type(prompt.title) == "string" then
|
||||
prompt.title = { { pos = "S", text = prompt.title } }
|
||||
end
|
||||
if results.border == true then
|
||||
results.border = { 1, 1, 0, 1 }
|
||||
end
|
||||
else
|
||||
error(string.format("Unknown prompt_position: %s\n%s", self.window.prompt_position, vim.inspect(layout_config)))
|
||||
end
|
||||
|
||||
-- Col
|
||||
prompt.col = 0 -- centered
|
||||
if layout_config.mirror and preview.width > 0 then
|
||||
results.col = preview.width + (3 * bs) + 1
|
||||
preview.col = bs + 1
|
||||
else
|
||||
results.col = bs + 1
|
||||
preview.col = results.width + (3 * bs) + 1
|
||||
end
|
||||
|
||||
if tbln then
|
||||
prompt.line = prompt.line + 1
|
||||
results.line = results.line + 1
|
||||
preview.line = preview.line + 1
|
||||
end
|
||||
|
||||
return {
|
||||
preview = self.previewer and preview.width > 0 and preview,
|
||||
prompt = prompt,
|
||||
results = results,
|
||||
}
|
||||
end
|
||||
)
|
||||
|
||||
layout_strategies._validate_layout_config = validate_layout_config
|
||||
|
||||
return layout_strategies
|
||||
@ -0,0 +1,50 @@
|
||||
local MultiSelect = {}
|
||||
MultiSelect.__index = MultiSelect
|
||||
|
||||
function MultiSelect:new()
|
||||
return setmetatable({
|
||||
_entries = {},
|
||||
}, MultiSelect)
|
||||
end
|
||||
|
||||
function MultiSelect:get()
|
||||
local marked_entries = {}
|
||||
for entry, count in pairs(self._entries) do
|
||||
table.insert(marked_entries, { count, entry })
|
||||
end
|
||||
|
||||
table.sort(marked_entries, function(left, right)
|
||||
return left[1] < right[1]
|
||||
end)
|
||||
|
||||
local selections = {}
|
||||
for _, entry in ipairs(marked_entries) do
|
||||
table.insert(selections, entry[2])
|
||||
end
|
||||
|
||||
return selections
|
||||
end
|
||||
|
||||
function MultiSelect:is_selected(entry)
|
||||
return self._entries[entry]
|
||||
end
|
||||
|
||||
local multi_select_count = 0
|
||||
function MultiSelect:add(entry)
|
||||
multi_select_count = multi_select_count + 1
|
||||
self._entries[entry] = multi_select_count
|
||||
end
|
||||
|
||||
function MultiSelect:drop(entry)
|
||||
self._entries[entry] = nil
|
||||
end
|
||||
|
||||
function MultiSelect:toggle(entry)
|
||||
if self:is_selected(entry) then
|
||||
self:drop(entry)
|
||||
else
|
||||
self:add(entry)
|
||||
end
|
||||
end
|
||||
|
||||
return MultiSelect
|
||||
@ -0,0 +1,124 @@
|
||||
local scroller = {}
|
||||
|
||||
local range_calculators = {
|
||||
ascending = function(max_results, num_results)
|
||||
return 0, math.min(max_results, num_results)
|
||||
end,
|
||||
|
||||
descending = function(max_results, num_results)
|
||||
return math.max(max_results - num_results, 0), max_results
|
||||
end,
|
||||
}
|
||||
|
||||
local scroll_calculators = {
|
||||
cycle = function(range_fn)
|
||||
return function(max_results, num_results, row)
|
||||
local start, finish = range_fn(max_results, num_results)
|
||||
|
||||
if row >= finish then
|
||||
return start
|
||||
elseif row < start then
|
||||
return (finish - 1 < 0) and finish or finish - 1
|
||||
end
|
||||
|
||||
return row
|
||||
end
|
||||
end,
|
||||
|
||||
limit = function(range_fn)
|
||||
return function(max_results, num_results, row)
|
||||
local start, finish = range_fn(max_results, num_results)
|
||||
|
||||
if row >= finish and finish > 0 then
|
||||
return finish - 1
|
||||
elseif row < start then
|
||||
return start
|
||||
end
|
||||
|
||||
return row
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
scroller.create = function(scroll_strategy, sorting_strategy)
|
||||
local range_fn = range_calculators[sorting_strategy]
|
||||
if not range_fn then
|
||||
error(debug.traceback("Unknown sorting strategy: " .. sorting_strategy))
|
||||
end
|
||||
|
||||
local scroll_fn = scroll_calculators[scroll_strategy]
|
||||
if not scroll_fn then
|
||||
error(debug.traceback("Unknown scroll strategy: " .. (scroll_strategy or "")))
|
||||
end
|
||||
|
||||
local calculator = scroll_fn(range_fn)
|
||||
return function(max_results, num_results, row)
|
||||
local result = calculator(max_results, num_results, row)
|
||||
|
||||
if result < 0 then
|
||||
error(
|
||||
string.format(
|
||||
"Must never return a negative row: { result = %s, args = { %s %s %s } }",
|
||||
result,
|
||||
max_results,
|
||||
num_results,
|
||||
row
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
if result > max_results then
|
||||
error(
|
||||
string.format(
|
||||
"Must never exceed max results: { result = %s, args = { %s %s %s } }",
|
||||
result,
|
||||
max_results,
|
||||
num_results,
|
||||
row
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
scroller.top = function(sorting_strategy, max_results, num_results)
|
||||
if sorting_strategy == "ascending" then
|
||||
return 0
|
||||
end
|
||||
return (num_results > max_results) and 0 or (max_results - num_results)
|
||||
end
|
||||
|
||||
scroller.middle = function(sorting_strategy, max_results, num_results)
|
||||
local mid_pos
|
||||
|
||||
if sorting_strategy == "ascending" then
|
||||
mid_pos = math.floor(num_results / 2)
|
||||
else
|
||||
mid_pos = math.floor(max_results - num_results / 2)
|
||||
end
|
||||
|
||||
return (num_results < max_results) and mid_pos or math.floor(max_results / 2)
|
||||
end
|
||||
|
||||
scroller.bottom = function(sorting_strategy, max_results, num_results)
|
||||
if sorting_strategy == "ascending" then
|
||||
return math.min(max_results, num_results) - 1
|
||||
end
|
||||
return max_results - 1
|
||||
end
|
||||
|
||||
scroller.better = function(sorting_strategy)
|
||||
if sorting_strategy == "ascending" then
|
||||
return -1
|
||||
else
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
scroller.worse = function(sorting_strategy)
|
||||
return -(scroller.better(sorting_strategy))
|
||||
end
|
||||
|
||||
return scroller
|
||||
@ -0,0 +1,49 @@
|
||||
local resolve = require "telescope.config.resolve"
|
||||
|
||||
local p_window = {}
|
||||
|
||||
function p_window.get_window_options(picker, max_columns, max_lines)
|
||||
local layout_strategy = picker.layout_strategy
|
||||
local getter = require("telescope.pickers.layout_strategies")[layout_strategy]
|
||||
|
||||
if not getter then
|
||||
error(string.format("'%s' is not a valid layout strategy", layout_strategy))
|
||||
end
|
||||
|
||||
return getter(picker, max_columns, max_lines)
|
||||
end
|
||||
|
||||
function p_window.get_initial_window_options(picker)
|
||||
local popup_border = resolve.win_option(picker.window.border)
|
||||
local popup_borderchars = resolve.win_option(picker.window.borderchars)
|
||||
|
||||
local preview = {
|
||||
title = picker.preview_title,
|
||||
border = popup_border.preview,
|
||||
borderchars = popup_borderchars.preview,
|
||||
enter = false,
|
||||
highlight = false,
|
||||
}
|
||||
|
||||
local results = {
|
||||
title = picker.results_title,
|
||||
border = popup_border.results,
|
||||
borderchars = popup_borderchars.results,
|
||||
enter = false,
|
||||
}
|
||||
|
||||
local prompt = {
|
||||
title = picker.prompt_title,
|
||||
border = popup_border.prompt,
|
||||
borderchars = popup_borderchars.prompt,
|
||||
enter = true,
|
||||
}
|
||||
|
||||
return {
|
||||
preview = preview,
|
||||
results = results,
|
||||
prompt = prompt,
|
||||
}
|
||||
end
|
||||
|
||||
return p_window
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,319 @@
|
||||
---@tag telescope.previewers
|
||||
---@config { ["module"] = "telescope.previewers" }
|
||||
|
||||
---@brief [[
|
||||
--- Provides a Previewer table that has to be implemented by each previewer.
|
||||
--- To achieve this, this module also provides two wrappers that abstract most
|
||||
--- of the work and make it really easy to create new previewers.
|
||||
--- - `previewers.new_termopen_previewer`
|
||||
--- - `previewers.new_buffer_previewer`
|
||||
---
|
||||
--- Furthermore, there are a collection of previewers already defined which
|
||||
--- can be used for every picker, as long as the entries of the picker provide
|
||||
--- the necessary fields. The more important ones are
|
||||
--- - `previewers.cat`
|
||||
--- - `previewers.vimgrep`
|
||||
--- - `previewers.qflist`
|
||||
--- - `previewers.vim_buffer_cat`
|
||||
--- - `previewers.vim_buffer_vimgrep`
|
||||
--- - `previewers.vim_buffer_qflist`
|
||||
---
|
||||
--- Previewers can be disabled for any builtin or custom picker by doing
|
||||
--- :Telescope find_files previewer=false
|
||||
---@brief ]]
|
||||
|
||||
local Previewer = require "telescope.previewers.previewer"
|
||||
local term_previewer = require "telescope.previewers.term_previewer"
|
||||
local buffer_previewer = require "telescope.previewers.buffer_previewer"
|
||||
|
||||
local previewers = {}
|
||||
|
||||
--- This is the base table all previewers have to implement. It's possible to
|
||||
--- write a wrapper for this because most previewers need to have the same
|
||||
--- keys set.
|
||||
--- Examples of wrappers are:
|
||||
--- - `new_buffer_previewer`
|
||||
--- - `new_termopen_previewer`
|
||||
---
|
||||
--- To create a new table do following:
|
||||
--- - `local new_previewer = Previewer:new(opts)`
|
||||
---
|
||||
--- What `:new` expects is listed below
|
||||
---
|
||||
--- The interface provides the following set of functions. All of them, besides
|
||||
--- `new`, will be handled by telescope pickers.
|
||||
--- - `:new(opts)`
|
||||
--- - `:preview(entry, status)`
|
||||
--- - `:teardown()`
|
||||
--- - `:send_input(input)`
|
||||
--- - `:scroll_fn(direction)`
|
||||
--- - `:scroll_horizontal_fn(direction)`
|
||||
---
|
||||
--- `Previewer:new()` expects a table as input with following keys:
|
||||
--- - `setup` function(self): Will be called the first time preview will be
|
||||
--- called.
|
||||
--- - `teardown` function(self): Will be called on clean up.
|
||||
--- - `preview_fn` function(self, entry, status): Will be called each time
|
||||
--- a new entry was selected.
|
||||
--- - `title` function(self): Will return the static title of the previewer.
|
||||
--- - `dynamic_title` function(self, entry): Will return the dynamic title of
|
||||
--- the previewer. Will only be called
|
||||
--- when config value dynamic_preview_title
|
||||
--- is true.
|
||||
--- - `send_input` function(self, input): This is meant for
|
||||
--- `termopen_previewer` and it can be
|
||||
--- used to send input to the terminal
|
||||
--- application, like less.
|
||||
--- - `scroll_fn` function(self, direction): Used to make scrolling work.
|
||||
--- - `scroll_horizontal_fn` function(self, direction): Used to make
|
||||
--- horizontal scrolling work.
|
||||
previewers.Previewer = Previewer
|
||||
|
||||
--- A shorthand for creating a new Previewer.
|
||||
--- The provided table will be forwarded to `Previewer:new(...)`
|
||||
previewers.new = function(...)
|
||||
return Previewer:new(...)
|
||||
end
|
||||
|
||||
--- Is a wrapper around Previewer and helps with creating a new
|
||||
--- `termopen_previewer`.
|
||||
---
|
||||
--- It requires you to specify one table entry `get_command(entry, status)`.
|
||||
--- This `get_command` function has to return the terminal command that will be
|
||||
--- executed for each entry. Example:
|
||||
--- <code>
|
||||
--- get_command = function(entry, status)
|
||||
--- return { 'bat', entry.path }
|
||||
--- end
|
||||
--- </code>
|
||||
---
|
||||
--- Additionally you can define:
|
||||
--- - `title` a static title for example "File Preview"
|
||||
--- - `dyn_title(self, entry)` a dynamic title function which gets called
|
||||
--- when config value `dynamic_preview_title = true`
|
||||
---
|
||||
--- It's an easy way to get your first previewer going and it integrates well
|
||||
--- with `bat` and `less`. Providing out of the box scrolling if the command
|
||||
--- uses less.
|
||||
---
|
||||
--- Furthermore, it will forward all `config.set_env` environment variables to
|
||||
--- that terminal process.
|
||||
previewers.new_termopen_previewer = term_previewer.new_termopen_previewer
|
||||
|
||||
--- Provides a `termopen_previewer` which has the ability to display files.
|
||||
--- It will always show the top of the file and has support for
|
||||
--- `bat`(prioritized) and `cat`. Each entry has to provide either the field
|
||||
--- `path` or `filename` in order to make this previewer work.
|
||||
---
|
||||
--- The preferred way of using this previewer is like this
|
||||
--- `require('telescope.config').values.cat_previewer`
|
||||
--- This will respect user configuration and will use `buffer_previewers` in
|
||||
--- case it's configured that way.
|
||||
previewers.cat = term_previewer.cat
|
||||
|
||||
--- Provides a `termopen_previewer` which has the ability to display files at
|
||||
--- the provided line. It has support for `bat`(prioritized) and `cat`.
|
||||
--- Each entry has to provide either the field `path` or `filename` and
|
||||
--- a `lnum` field in order to make this previewer work.
|
||||
---
|
||||
--- The preferred way of using this previewer is like this
|
||||
--- `require('telescope.config').values.grep_previewer`
|
||||
--- This will respect user configuration and will use `buffer_previewers` in
|
||||
--- case it's configured that way.
|
||||
previewers.vimgrep = term_previewer.vimgrep
|
||||
|
||||
--- Provides a `termopen_previewer` which has the ability to display files at
|
||||
--- the provided line or range. It has support for `bat`(prioritized) and
|
||||
--- `cat`. Each entry has to provide either the field `path` or `filename`,
|
||||
--- `lnum` and a `start` and `finish` range in order to make this previewer
|
||||
--- work.
|
||||
---
|
||||
--- The preferred way of using this previewer is like this
|
||||
--- `require('telescope.config').values.qflist_previewer`
|
||||
--- This will respect user configuration and will use buffer previewers in
|
||||
--- case it's configured that way.
|
||||
previewers.qflist = term_previewer.qflist
|
||||
|
||||
--- An interface to instantiate a new `buffer_previewer`.
|
||||
--- That means that the content actually lives inside a vim buffer which
|
||||
--- enables us more control over the actual content. For example, we can
|
||||
--- use `vim.fn.search` to jump to a specific line or reuse buffers/already
|
||||
--- opened files more easily.
|
||||
--- This interface is more complex than `termopen_previewer` but offers more
|
||||
--- flexibility over your content.
|
||||
--- It was designed to display files but was extended to also display the
|
||||
--- output of terminal commands.
|
||||
---
|
||||
--- In the following options, state table and general tips are mentioned to
|
||||
--- make your experience with this previewer more seamless.
|
||||
---
|
||||
---
|
||||
--- options:
|
||||
--- - `define_preview = function(self, entry, status)` (required)
|
||||
--- Is called for each selected entry, after each selection_move
|
||||
--- (up or down) and is meant to handle things like reading file,
|
||||
--- jump to line or attach a highlighter.
|
||||
--- - `setup = function(self)` (optional)
|
||||
--- Is called once at the beginning, before the preview for the first
|
||||
--- entry is displayed. You can return a table of vars that will be
|
||||
--- available in `self.state` in each `define_preview` call.
|
||||
--- - `teardown = function(self)` (optional)
|
||||
--- Will be called at the end, when the picker is being closed and is
|
||||
--- meant to clean up everything that was allocated by the previewer.
|
||||
--- The `buffer_previewer` will automatically clean up all created buffers.
|
||||
--- So you only need to handle things that were introduced by you.
|
||||
--- - `keep_last_buf = true` (optional)
|
||||
--- Will not delete the last selected buffer. This would allow you to
|
||||
--- reuse that buffer in the select action. For example, that buffer can
|
||||
--- be opened in a new split, rather than recreating that buffer in
|
||||
--- an action. To access the last buffer number:
|
||||
--- `require('telescope.state').get_global_key("last_preview_bufnr")`
|
||||
--- - `get_buffer_by_name = function(self, entry)`
|
||||
--- Allows you to set a unique name for each buffer. This is used for
|
||||
--- caching purposes. `self.state.bufname` will be nil if the entry was
|
||||
--- never loaded or the unique name when it was loaded once. For example,
|
||||
--- useful if you have one file but multiple entries. This happens for grep
|
||||
--- and lsp builtins. So to make the cache work only load content if
|
||||
--- `self.state.bufname ~= entry.your_unique_key`
|
||||
--- - `title` a static title for example "File Preview"
|
||||
--- - `dyn_title(self, entry)` a dynamic title function which gets called
|
||||
--- when config value `dynamic_preview_title = true`
|
||||
---
|
||||
--- `self.state` table:
|
||||
--- - `self.state.bufnr`
|
||||
--- Is the current buffer number, in which you have to write the loaded
|
||||
--- content.
|
||||
--- Don't create a buffer yourself, otherwise it's not managed by the
|
||||
--- buffer_previewer interface and you will probably be better off
|
||||
--- writing your own interface.
|
||||
--- - self.state.winid
|
||||
--- Current window id. Useful if you want to set the cursor to a provided
|
||||
--- line number.
|
||||
--- - self.state.bufname
|
||||
--- Will return the current buffer name, if `get_buffer_by_name` is
|
||||
--- defined. nil will be returned if the entry was never loaded or when
|
||||
--- `get_buffer_by_name` is not set.
|
||||
---
|
||||
--- Tips:
|
||||
--- - If you want to display content of a terminal job, use:
|
||||
--- `require('telescope.previewers.utils').job_maker(cmd, bufnr, opts)`
|
||||
--- - `cmd` table: for example { 'git', 'diff', entry.value }
|
||||
--- - `bufnr` number: in which the content will be written
|
||||
--- - `opts` table: with following keys
|
||||
--- - `bufname` string: used for cache
|
||||
--- - `value` string: used for cache
|
||||
--- - `mode` string: either "insert" or "append". "insert" is default
|
||||
--- - `env` table: define environment variables. Example:
|
||||
--- - `{ ['PAGER'] = '', ['MANWIDTH'] = 50 }`
|
||||
--- - `cwd` string: define current working directory for job
|
||||
--- - `callback` function(bufnr, content): will be called when job
|
||||
--- is done. Content will be nil if job is already loaded.
|
||||
--- So you can do highlighting only the first time the previewer
|
||||
--- is created for that entry.
|
||||
--- Use the returned `bufnr` and not `self.state.bufnr` in callback,
|
||||
--- because state can already be changed at this point in time.
|
||||
--- - If you want to attach a highlighter use:
|
||||
--- - `require('telescope.previewers.utils').highlighter(bufnr, ft)`
|
||||
--- - This will prioritize tree sitter highlighting if available for
|
||||
--- environment and language.
|
||||
--- - `require('telescope.previewers.utils').regex_highlighter(bufnr, ft)`
|
||||
--- - `require('telescope.previewers.utils').ts_highlighter(bufnr, ft)`
|
||||
--- - If you want to use `vim.fn.search` or similar you need to run it in
|
||||
--- that specific buffer context. Do
|
||||
--- <code>
|
||||
--- vim.api.nvim_buf_call(bufnr, function()
|
||||
--- -- for example `search` and `matchadd`
|
||||
--- end)
|
||||
--- </code>
|
||||
--- to achieve that.
|
||||
--- - If you want to read a file into the buffer it's best to use
|
||||
--- `buffer_previewer_maker`. But access this function with
|
||||
--- `require('telescope.config').values.buffer_previewer_maker`
|
||||
--- because it can be redefined by users.
|
||||
previewers.new_buffer_previewer = buffer_previewer.new_buffer_previewer
|
||||
|
||||
--- A universal way of reading a file into a buffer previewer.
|
||||
--- It handles async reading, cache, highlighting, displaying directories
|
||||
--- and provides a callback which can be used, to jump to a line in the buffer.
|
||||
---@param filepath string: String to the filepath, will be expanded
|
||||
---@param bufnr number: Where the content will be written
|
||||
---@param opts table: keys: `use_ft_detect`, `bufname` and `callback`
|
||||
previewers.buffer_previewer_maker = buffer_previewer.file_maker
|
||||
|
||||
--- A previewer that is used to display a file. It uses the `buffer_previewer`
|
||||
--- interface and won't jump to the line. To integrate this one into your
|
||||
--- own picker make sure that the field `path` or `filename` is set for
|
||||
--- each entry.
|
||||
--- The preferred way of using this previewer is like this
|
||||
--- `require('telescope.config').values.file_previewer`
|
||||
--- This will respect user configuration and will use `termopen_previewer` in
|
||||
--- case it's configured that way.
|
||||
previewers.vim_buffer_cat = buffer_previewer.cat
|
||||
|
||||
--- A previewer that is used to display a file and jump to the provided line.
|
||||
--- It uses the `buffer_previewer` interface. To integrate this one into your
|
||||
--- own picker make sure that the field `path` or `filename` and `lnum` is set
|
||||
--- in each entry. If the latter is not present, it will default to the first
|
||||
--- line. Additionally, `lnend`, `col` and `colend` can be set to highlight a
|
||||
--- text range instead of a single line. All line/column values are 1-indexed.
|
||||
--- The preferred way of using this previewer is like this
|
||||
--- `require('telescope.config').values.grep_previewer`
|
||||
--- This will respect user configuration and will use `termopen_previewer` in
|
||||
--- case it's configured that way.
|
||||
previewers.vim_buffer_vimgrep = buffer_previewer.vimgrep
|
||||
|
||||
--- Is the same as `vim_buffer_vimgrep` and only exists for consistency with
|
||||
--- `term_previewers`.
|
||||
---
|
||||
--- The preferred way of using this previewer is like this
|
||||
--- `require('telescope.config').values.qflist_previewer`
|
||||
--- This will respect user configuration and will use `termopen_previewer` in
|
||||
--- case it's configured that way.
|
||||
previewers.vim_buffer_qflist = buffer_previewer.qflist
|
||||
|
||||
--- A previewer that shows a log of a branch as graph
|
||||
previewers.git_branch_log = buffer_previewer.git_branch_log
|
||||
|
||||
--- A previewer that shows a diff of a stash
|
||||
previewers.git_stash_diff = buffer_previewer.git_stash_diff
|
||||
|
||||
--- A previewer that shows a diff of a commit to a parent commit.<br>
|
||||
--- The run command is `git --no-pager diff SHA^! -- $CURRENT_FILE`
|
||||
---
|
||||
--- The current file part is optional. So is only uses it with bcommits.
|
||||
previewers.git_commit_diff_to_parent = buffer_previewer.git_commit_diff_to_parent
|
||||
|
||||
--- A previewer that shows a diff of a commit to head.<br>
|
||||
--- The run command is `git --no-pager diff --cached $SHA -- $CURRENT_FILE`
|
||||
---
|
||||
--- The current file part is optional. So is only uses it with bcommits.
|
||||
previewers.git_commit_diff_to_head = buffer_previewer.git_commit_diff_to_head
|
||||
|
||||
--- A previewer that shows a diff of a commit as it was.<br>
|
||||
--- The run command is `git --no-pager show $SHA:$CURRENT_FILE` or `git --no-pager show $SHA`
|
||||
previewers.git_commit_diff_as_was = buffer_previewer.git_commit_diff_as_was
|
||||
|
||||
--- A previewer that shows the commit message of a diff.<br>
|
||||
--- The run command is `git --no-pager log -n 1 $SHA`
|
||||
previewers.git_commit_message = buffer_previewer.git_commit_message
|
||||
|
||||
--- A previewer that shows the current diff of a file. Used in git_status.<br>
|
||||
--- The run command is `git --no-pager diff $FILE`
|
||||
previewers.git_file_diff = buffer_previewer.git_file_diff
|
||||
|
||||
previewers.ctags = buffer_previewer.ctags
|
||||
previewers.builtin = buffer_previewer.builtin
|
||||
previewers.help = buffer_previewer.help
|
||||
previewers.man = buffer_previewer.man
|
||||
previewers.autocommands = buffer_previewer.autocommands
|
||||
previewers.highlights = buffer_previewer.highlights
|
||||
previewers.pickers = buffer_previewer.pickers
|
||||
|
||||
--- A deprecated way of displaying content more easily. Was written at a time,
|
||||
--- where the buffer_previewer interface wasn't present. Nowadays it's easier
|
||||
--- to just use this. We will keep it around for backwards compatibility
|
||||
--- because some extensions use it.
|
||||
--- It doesn't use cache or some other clever tricks.
|
||||
previewers.display_content = buffer_previewer.display_content
|
||||
|
||||
return previewers
|
||||
@ -0,0 +1,107 @@
|
||||
local utils = require "telescope.utils"
|
||||
|
||||
local Previewer = {}
|
||||
Previewer.__index = Previewer
|
||||
|
||||
local force_function_wrap = function(value)
|
||||
if value ~= nil then
|
||||
if type(value) ~= "function" then
|
||||
return function()
|
||||
return tostring(value)
|
||||
end
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer:new(opts)
|
||||
opts = opts or {}
|
||||
|
||||
return setmetatable({
|
||||
state = nil,
|
||||
_title_fn = force_function_wrap(opts.title),
|
||||
_dyn_title_fn = force_function_wrap(opts.dyn_title),
|
||||
_setup_func = opts.setup,
|
||||
_teardown_func = opts.teardown,
|
||||
_send_input = opts.send_input,
|
||||
_scroll_fn = opts.scroll_fn,
|
||||
_scroll_horizontal_fn = opts.scroll_horizontal_fn,
|
||||
preview_fn = opts.preview_fn,
|
||||
_empty_bufnr = nil,
|
||||
}, Previewer)
|
||||
end
|
||||
|
||||
function Previewer:preview(entry, status)
|
||||
if not entry then
|
||||
if not self._empty_bufnr then
|
||||
self._empty_bufnr = vim.api.nvim_create_buf(false, true)
|
||||
end
|
||||
|
||||
if vim.api.nvim_buf_is_valid(self._empty_bufnr) then
|
||||
vim.api.nvim_win_set_buf(status.layout.preview.winid, self._empty_bufnr)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if not self.state then
|
||||
if self._setup_func then
|
||||
self.state = self:_setup_func(status)
|
||||
else
|
||||
self.state = {}
|
||||
end
|
||||
end
|
||||
|
||||
return self:preview_fn(entry, status)
|
||||
end
|
||||
|
||||
function Previewer:title(entry, dynamic)
|
||||
if dynamic == true and self._dyn_title_fn ~= nil then
|
||||
if entry == nil then
|
||||
if self._title_fn ~= nil then
|
||||
return self:_title_fn()
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
return self:_dyn_title_fn(entry)
|
||||
end
|
||||
if self._title_fn ~= nil then
|
||||
return self:_title_fn()
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer:teardown()
|
||||
if self._empty_bufnr then
|
||||
utils.buf_delete(self._empty_bufnr)
|
||||
end
|
||||
if self._teardown_func then
|
||||
self:_teardown_func()
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer:send_input(input)
|
||||
if self._send_input then
|
||||
self:_send_input(input)
|
||||
else
|
||||
vim.api.nvim_err_writeln "send_input is not defined for this previewer"
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer:scroll_fn(direction)
|
||||
if self._scroll_fn then
|
||||
self:_scroll_fn(direction)
|
||||
else
|
||||
vim.api.nvim_err_writeln "scroll_fn is not defined for this previewer"
|
||||
end
|
||||
end
|
||||
|
||||
function Previewer:scroll_horizontal_fn(direction)
|
||||
if self._scroll_horizontal_fn then
|
||||
self:_scroll_horizontal_fn(direction)
|
||||
else
|
||||
vim.api.nvim_err_writeln "scroll_horizontal_fn is not defined for this previewer"
|
||||
end
|
||||
end
|
||||
|
||||
return Previewer
|
||||
@ -0,0 +1,344 @@
|
||||
local conf = require("telescope.config").values
|
||||
local utils = require "telescope.utils"
|
||||
local Path = require "plenary.path"
|
||||
local from_entry = require "telescope.from_entry"
|
||||
local Previewer = require "telescope.previewers.previewer"
|
||||
local putil = require "telescope.previewers.utils"
|
||||
|
||||
local defaulter = utils.make_default_callable
|
||||
|
||||
local previewers = {}
|
||||
|
||||
-- TODO: Should play with these some more, ty @clason
|
||||
local bat_options = { "--style=plain", "--color=always", "--paging=always" }
|
||||
local has_less = (vim.fn.executable "less" == 1) and conf.use_less
|
||||
|
||||
local get_file_stat = function(filename)
|
||||
return vim.loop.fs_stat(utils.path_expand(filename)) or {}
|
||||
end
|
||||
|
||||
local list_dir = (function()
|
||||
if vim.fn.has "win32" == 1 then
|
||||
return function(dirname)
|
||||
return { "cmd.exe", "/c", "dir", utils.path_expand(dirname) }
|
||||
end
|
||||
else
|
||||
return function(dirname)
|
||||
return { "ls", "-la", utils.path_expand(dirname) }
|
||||
end
|
||||
end
|
||||
end)()
|
||||
|
||||
local bat_maker = function(filename, lnum, start, finish)
|
||||
if get_file_stat(filename).type == "directory" then
|
||||
return list_dir(filename)
|
||||
end
|
||||
|
||||
local command = { "bat" }
|
||||
|
||||
if lnum then
|
||||
table.insert(command, { "--highlight-line", lnum })
|
||||
end
|
||||
|
||||
if has_less then
|
||||
if start then
|
||||
table.insert(command, { "--pager", string.format("less -RS +%s", start) })
|
||||
else
|
||||
table.insert(command, { "--pager", "less -RS" })
|
||||
end
|
||||
else
|
||||
if start and finish then
|
||||
table.insert(command, { "-r", string.format("%s:%s", start, finish) })
|
||||
end
|
||||
end
|
||||
|
||||
return utils.flatten {
|
||||
command,
|
||||
bat_options,
|
||||
"--",
|
||||
utils.path_expand(filename),
|
||||
}
|
||||
end
|
||||
|
||||
local cat_maker = function(filename, _, start, _)
|
||||
if get_file_stat(filename).type == "directory" then
|
||||
return list_dir(filename)
|
||||
end
|
||||
|
||||
if 1 == vim.fn.executable "file" then
|
||||
local mime_type = utils.get_os_command_output({ "file", "--mime-type", "-b", filename })[1]
|
||||
if putil.binary_mime_type(mime_type) then
|
||||
return { "echo", "Binary file found. These files cannot be displayed!" }
|
||||
end
|
||||
end
|
||||
|
||||
if has_less then
|
||||
if start then
|
||||
return { "less", "-RS", string.format("+%s", start), utils.path_expand(filename) }
|
||||
else
|
||||
return { "less", "-RS", utils.path_expand(filename) }
|
||||
end
|
||||
else
|
||||
return {
|
||||
"cat",
|
||||
"--",
|
||||
utils.path_expand(filename),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local get_maker = function(opts)
|
||||
local maker = opts.maker
|
||||
if not maker and 1 == vim.fn.executable "bat" then
|
||||
maker = bat_maker
|
||||
elseif not maker and 1 == vim.fn.executable "cat" then
|
||||
maker = cat_maker
|
||||
end
|
||||
|
||||
if not maker then
|
||||
error "Needs maker"
|
||||
end
|
||||
|
||||
return maker
|
||||
end
|
||||
|
||||
-- TODO: We shoudl make sure that all our terminals close all the way.
|
||||
-- Otherwise it could be bad if they're just sitting around, waiting to be closed.
|
||||
-- I don't think that's the problem, but it could be?
|
||||
previewers.new_termopen_previewer = function(opts)
|
||||
opts = opts or {}
|
||||
|
||||
assert(opts.get_command, "get_command is a required function")
|
||||
assert(not opts.preview_fn, "preview_fn not allowed")
|
||||
|
||||
local opt_setup = opts.setup
|
||||
local opt_teardown = opts.teardown
|
||||
|
||||
local old_bufs = {}
|
||||
local bufentry_table = {}
|
||||
local term_ids = {}
|
||||
|
||||
local function get_term_id(self)
|
||||
if self.state then
|
||||
return self.state.termopen_id
|
||||
end
|
||||
end
|
||||
|
||||
local function get_bufnr(self)
|
||||
if self.state then
|
||||
return self.state.termopen_bufnr
|
||||
end
|
||||
end
|
||||
|
||||
local function set_term_id(self, value)
|
||||
if self.state and term_ids[self.state.termopen_bufnr] == nil then
|
||||
term_ids[self.state.termopen_bufnr] = value
|
||||
self.state.termopen_id = value
|
||||
end
|
||||
end
|
||||
|
||||
local function set_bufnr(self, value)
|
||||
if get_bufnr(self) then
|
||||
table.insert(old_bufs, get_bufnr(self))
|
||||
end
|
||||
if self.state then
|
||||
self.state.termopen_bufnr = value
|
||||
end
|
||||
end
|
||||
|
||||
local function get_bufnr_by_bufentry(self, value)
|
||||
if self.state then
|
||||
return bufentry_table[value]
|
||||
end
|
||||
end
|
||||
|
||||
local function set_bufentry(self, value)
|
||||
if self.state and value then
|
||||
bufentry_table[value] = get_bufnr(self)
|
||||
end
|
||||
end
|
||||
|
||||
function opts.setup(self)
|
||||
local state = {}
|
||||
if opt_setup then
|
||||
vim.tbl_deep_extend("force", state, opt_setup(self))
|
||||
end
|
||||
return state
|
||||
end
|
||||
|
||||
function opts.teardown(self)
|
||||
if opt_teardown then
|
||||
opt_teardown(self)
|
||||
end
|
||||
|
||||
set_bufnr(self, nil)
|
||||
set_bufentry(self, nil)
|
||||
|
||||
for _, bufnr in ipairs(old_bufs) do
|
||||
local term_id = term_ids[bufnr]
|
||||
if term_id and utils.job_is_running(term_id) then
|
||||
vim.fn.jobstop(term_id)
|
||||
end
|
||||
utils.buf_delete(bufnr)
|
||||
end
|
||||
bufentry_table = {}
|
||||
end
|
||||
|
||||
function opts.preview_fn(self, entry, status)
|
||||
local preview_winid = status.layout.preview and status.layout.preview.winid
|
||||
if get_bufnr(self) == nil then
|
||||
set_bufnr(self, vim.api.nvim_win_get_buf(preview_winid))
|
||||
end
|
||||
|
||||
local prev_bufnr = get_bufnr_by_bufentry(self, entry)
|
||||
if prev_bufnr then
|
||||
self.state.termopen_bufnr = prev_bufnr
|
||||
utils.win_set_buf_noautocmd(preview_winid, self.state.termopen_bufnr)
|
||||
self.state.termopen_id = term_ids[self.state.termopen_bufnr]
|
||||
else
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
set_bufnr(self, bufnr)
|
||||
utils.win_set_buf_noautocmd(preview_winid, bufnr)
|
||||
|
||||
local term_opts = {
|
||||
cwd = opts.cwd or vim.loop.cwd(),
|
||||
env = conf.set_env,
|
||||
}
|
||||
|
||||
local cmd = opts.get_command(entry, status)
|
||||
if cmd then
|
||||
vim.api.nvim_buf_call(bufnr, function()
|
||||
set_term_id(self, vim.fn.termopen(cmd, term_opts))
|
||||
end)
|
||||
end
|
||||
set_bufentry(self, entry)
|
||||
end
|
||||
end
|
||||
|
||||
if not opts.send_input then
|
||||
function opts.send_input(self, input)
|
||||
local termcode = vim.api.nvim_replace_termcodes(input, true, false, true)
|
||||
|
||||
local term_id = get_term_id(self)
|
||||
if term_id then
|
||||
if not utils.job_is_running(term_id) then
|
||||
return
|
||||
end
|
||||
|
||||
vim.fn.chansend(term_id, termcode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not opts.scroll_fn then
|
||||
function opts.scroll_fn(self, direction)
|
||||
if not self.state then
|
||||
return
|
||||
end
|
||||
|
||||
local input = direction > 0 and "d" or "u"
|
||||
local count = math.abs(direction)
|
||||
|
||||
self:send_input(count .. input)
|
||||
end
|
||||
end
|
||||
|
||||
return Previewer:new(opts)
|
||||
end
|
||||
|
||||
previewers.cat = defaulter(function(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local maker = get_maker(opts)
|
||||
local cwd = opts.cwd or vim.loop.cwd()
|
||||
|
||||
return previewers.new_termopen_previewer {
|
||||
title = "File Preview",
|
||||
dyn_title = function(_, entry)
|
||||
return Path:new(from_entry.path(entry, false, false)):normalize(cwd)
|
||||
end,
|
||||
|
||||
get_command = function(entry)
|
||||
local p = from_entry.path(entry, true, false)
|
||||
if p == nil or p == "" then
|
||||
return
|
||||
end
|
||||
|
||||
return maker(p)
|
||||
end,
|
||||
}
|
||||
end, {})
|
||||
|
||||
previewers.vimgrep = defaulter(function(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local maker = get_maker(opts)
|
||||
local cwd = opts.cwd or vim.loop.cwd()
|
||||
|
||||
return previewers.new_termopen_previewer {
|
||||
title = "Grep Preview",
|
||||
dyn_title = function(_, entry)
|
||||
return Path:new(from_entry.path(entry, false, false)):normalize(cwd)
|
||||
end,
|
||||
|
||||
get_command = function(entry, status)
|
||||
local win_id = status.layout.preview and status.layout.preview.winid
|
||||
local height = vim.api.nvim_win_get_height(win_id)
|
||||
|
||||
local p = from_entry.path(entry, true, false)
|
||||
if p == nil or p == "" then
|
||||
return
|
||||
end
|
||||
if entry.bufnr and (p == "[No Name]" or vim.api.nvim_buf_get_option(entry.bufnr, "buftype") ~= "") then
|
||||
return
|
||||
end
|
||||
|
||||
local lnum = entry.lnum or 0
|
||||
|
||||
local context = math.floor(height / 2)
|
||||
local start = math.max(0, lnum - context)
|
||||
local finish = lnum + context
|
||||
|
||||
return maker(p, lnum, start, finish)
|
||||
end,
|
||||
}
|
||||
end, {})
|
||||
|
||||
previewers.qflist = defaulter(function(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local maker = get_maker(opts)
|
||||
local cwd = opts.cwd or vim.loop.cwd()
|
||||
|
||||
return previewers.new_termopen_previewer {
|
||||
title = "Grep Preview",
|
||||
dyn_title = function(_, entry)
|
||||
return Path:new(from_entry.path(entry, false, false)):normalize(cwd)
|
||||
end,
|
||||
|
||||
get_command = function(entry, status)
|
||||
local win_id = status.layout.preview and status.layout.preview.winid
|
||||
local height = vim.api.nvim_win_get_height(win_id)
|
||||
|
||||
local p = from_entry.path(entry, true, false)
|
||||
if p == nil or p == "" then
|
||||
return
|
||||
end
|
||||
local lnum = entry.lnum
|
||||
|
||||
local start, finish
|
||||
if entry.start and entry.finish then
|
||||
start = entry.start
|
||||
finish = entry.finish
|
||||
else
|
||||
local context = math.floor(height / 2)
|
||||
start = math.max(0, lnum - context)
|
||||
finish = lnum + context
|
||||
end
|
||||
|
||||
return maker(p, lnum, start, finish)
|
||||
end,
|
||||
}
|
||||
end, {})
|
||||
|
||||
return previewers
|
||||
@ -0,0 +1,242 @@
|
||||
local ts_utils = require "telescope.utils"
|
||||
local strings = require "plenary.strings"
|
||||
local conf = require("telescope.config").values
|
||||
|
||||
local Job = require "plenary.job"
|
||||
local Path = require "plenary.path"
|
||||
|
||||
local utils = {}
|
||||
|
||||
local detect_from_shebang = function(p)
|
||||
local s = p:readbyterange(0, 256)
|
||||
if s then
|
||||
local lines = vim.split(s, "\n")
|
||||
return vim.filetype.match { contents = lines }
|
||||
end
|
||||
end
|
||||
|
||||
local parse_modeline = function(tail)
|
||||
if tail:find "vim:" then
|
||||
return tail:match ".*:ft=([^: ]*):.*$" or ""
|
||||
end
|
||||
end
|
||||
|
||||
local detect_from_modeline = function(p)
|
||||
local s = p:readbyterange(-256, 256)
|
||||
if s then
|
||||
local lines = vim.split(s, "\n")
|
||||
local idx = lines[#lines] ~= "" and #lines or #lines - 1
|
||||
if idx >= 1 then
|
||||
return parse_modeline(lines[idx])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
utils.filetype_detect = function(filepath)
|
||||
if type(filepath) ~= string then
|
||||
filepath = tostring(filepath)
|
||||
end
|
||||
|
||||
local match = vim.filetype.match { filename = filepath }
|
||||
if match and match ~= "" then
|
||||
return match
|
||||
end
|
||||
|
||||
local p = Path:new(filepath)
|
||||
if p and p:is_file() then
|
||||
match = detect_from_shebang(p)
|
||||
if match and match ~= "" then
|
||||
return match
|
||||
end
|
||||
|
||||
match = detect_from_modeline(p)
|
||||
if match and match ~= "" then
|
||||
return match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- API helper functions for buffer previewer
|
||||
--- Job maker for buffer previewer
|
||||
utils.job_maker = function(cmd, bufnr, opts)
|
||||
opts = opts or {}
|
||||
opts.mode = opts.mode or "insert"
|
||||
-- bufname and value are optional
|
||||
-- if passed, they will be use as the cache key
|
||||
-- if any of them are missing, cache will be skipped
|
||||
if opts.bufname ~= opts.value or not opts.bufname or not opts.value then
|
||||
local command = table.remove(cmd, 1)
|
||||
local writer = (function()
|
||||
if opts.writer ~= nil then
|
||||
local wcommand = table.remove(opts.writer, 1)
|
||||
return Job:new {
|
||||
command = wcommand,
|
||||
args = opts.writer,
|
||||
env = opts.env,
|
||||
cwd = opts.cwd,
|
||||
}
|
||||
end
|
||||
end)()
|
||||
|
||||
Job:new({
|
||||
command = command,
|
||||
args = cmd,
|
||||
env = opts.env,
|
||||
cwd = opts.cwd,
|
||||
writer = writer,
|
||||
on_exit = vim.schedule_wrap(function(j)
|
||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
if opts.mode == "append" then
|
||||
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, j:result())
|
||||
elseif opts.mode == "insert" then
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, j:result())
|
||||
end
|
||||
if opts.callback then
|
||||
opts.callback(bufnr, j:result())
|
||||
end
|
||||
end),
|
||||
}):start()
|
||||
else
|
||||
if opts.callback then
|
||||
opts.callback(bufnr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function has_filetype(ft)
|
||||
return ft and ft ~= ""
|
||||
end
|
||||
|
||||
--- Attach default highlighter which will choose between regex and ts
|
||||
utils.highlighter = function(bufnr, ft, opts)
|
||||
opts = vim.F.if_nil(opts, {})
|
||||
opts.preview = vim.F.if_nil(opts.preview, {})
|
||||
opts.preview.treesitter = (function()
|
||||
if type(opts.preview) == "table" and opts.preview.treesitter then
|
||||
return opts.preview.treesitter
|
||||
end
|
||||
if type(conf.preview) == "table" and conf.preview.treesitter then
|
||||
return conf.preview.treesitter
|
||||
end
|
||||
if type(conf.preview) == "boolean" then
|
||||
return conf.preview
|
||||
end
|
||||
-- We should never get here
|
||||
return false
|
||||
end)()
|
||||
|
||||
if type(opts.preview.treesitter) == "boolean" then
|
||||
local temp = { enable = opts.preview.treesitter }
|
||||
opts.preview.treesitter = temp
|
||||
end
|
||||
|
||||
local ts_highlighting = (function()
|
||||
if type(opts.preview.treesitter.enable) == "table" then
|
||||
if vim.tbl_contains(opts.preview.treesitter.enable, ft) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if vim.tbl_contains(vim.F.if_nil(opts.preview.treesitter.disable, {}), ft) then
|
||||
return false
|
||||
end
|
||||
|
||||
return opts.preview.treesitter.enable == nil or opts.preview.treesitter.enable == true
|
||||
end)()
|
||||
|
||||
local ts_success
|
||||
if ts_highlighting then
|
||||
ts_success = utils.ts_highlighter(bufnr, ft)
|
||||
end
|
||||
if not ts_highlighting or ts_success == false then
|
||||
utils.regex_highlighter(bufnr, ft)
|
||||
end
|
||||
end
|
||||
|
||||
--- Attach regex highlighter
|
||||
utils.regex_highlighter = function(bufnr, ft)
|
||||
if has_filetype(ft) then
|
||||
return pcall(vim.api.nvim_buf_set_option, bufnr, "syntax", ft)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Attach ts highlighter
|
||||
utils.ts_highlighter = function(bufnr, ft)
|
||||
if has_filetype(ft) then
|
||||
local lang = vim.treesitter.language.get_lang(ft) or ft
|
||||
if lang and ts_utils.has_ts_parser(lang) then
|
||||
return vim.treesitter.start(bufnr, lang)
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
utils.set_preview_message = function(bufnr, winid, message, fillchar)
|
||||
fillchar = vim.F.if_nil(fillchar, "╱")
|
||||
local height = vim.api.nvim_win_get_height(winid)
|
||||
local width = vim.api.nvim_win_get_width(winid)
|
||||
vim.api.nvim_buf_set_lines(
|
||||
bufnr,
|
||||
0,
|
||||
-1,
|
||||
false,
|
||||
ts_utils.repeated_table(height, table.concat(ts_utils.repeated_table(width, fillchar), ""))
|
||||
)
|
||||
local anon_ns = vim.api.nvim_create_namespace ""
|
||||
local padding = table.concat(ts_utils.repeated_table(#message + 4, " "), "")
|
||||
local formatted_message = " " .. message .. " "
|
||||
-- Populate lines table based on height
|
||||
local lines = {}
|
||||
if height == 1 then
|
||||
lines[1] = formatted_message
|
||||
else
|
||||
for i = 1, math.min(height, 3), 1 do
|
||||
if i % 2 == 0 then
|
||||
lines[i] = formatted_message
|
||||
else
|
||||
lines[i] = padding
|
||||
end
|
||||
end
|
||||
end
|
||||
vim.api.nvim_buf_set_extmark(
|
||||
bufnr,
|
||||
anon_ns,
|
||||
0,
|
||||
0,
|
||||
{ end_line = height, hl_group = "TelescopePreviewMessageFillchar" }
|
||||
)
|
||||
local col = math.floor((width - strings.strdisplaywidth(formatted_message)) / 2)
|
||||
for i, line in ipairs(lines) do
|
||||
local line_pos = math.floor(height / 2) - 2 + i
|
||||
vim.api.nvim_buf_set_extmark(
|
||||
bufnr,
|
||||
anon_ns,
|
||||
math.max(line_pos, 0),
|
||||
0,
|
||||
{ virt_text = { { line, "TelescopePreviewMessage" } }, virt_text_pos = "overlay", virt_text_win_col = col }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if mime type is binary.
|
||||
--- NOT an exhaustive check, may get false negatives. Ideally should check
|
||||
--- filetype with `vim.filetype.match` or `filetype_detect` first for filetype
|
||||
--- info.
|
||||
---@param mime_type string
|
||||
---@return boolean
|
||||
utils.binary_mime_type = function(mime_type)
|
||||
local type_, subtype = unpack(vim.split(mime_type, "/"))
|
||||
if vim.tbl_contains({ "text", "inode" }, type_) then
|
||||
return false
|
||||
end
|
||||
if vim.tbl_contains({ "json", "javascript" }, subtype) then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return utils
|
||||
@ -0,0 +1,624 @@
|
||||
local log = require "telescope.log"
|
||||
local util = require "telescope.utils"
|
||||
|
||||
local sorters = {}
|
||||
|
||||
local ngram_highlighter = function(ngram_len, prompt, display)
|
||||
local highlights = {}
|
||||
display = display:lower()
|
||||
|
||||
for disp_index = 1, #display do
|
||||
local char = display:sub(disp_index, disp_index + ngram_len - 1)
|
||||
if prompt:find(char, 1, true) then
|
||||
table.insert(highlights, {
|
||||
start = disp_index,
|
||||
finish = disp_index + ngram_len - 1,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return highlights
|
||||
end
|
||||
|
||||
local FILTERED = -1
|
||||
|
||||
local Sorter = {}
|
||||
Sorter.__index = Sorter
|
||||
|
||||
---@class Sorter
|
||||
--- Sorter sorts a list of results by return a single integer for a line,
|
||||
--- given a prompt
|
||||
---
|
||||
--- Lower number is better (because it's like a closer match)
|
||||
--- But, any number below 0 means you want that line filtered out.
|
||||
---@field scoring_function function: Function that has the interface: (sorter, prompt, line): number
|
||||
---@field tags table: Unique tags collected at filtering for tag completion
|
||||
---@field filter_function function: Function that can filter results
|
||||
---@field highlighter function: Highlights results to display them pretty
|
||||
---@field discard boolean: Whether this is a discardable style sorter or not.
|
||||
---@field score function: Override the score function if desired.
|
||||
---@field init function: Function to run when creating sorter
|
||||
---@field start function: Function to run on every new prompt
|
||||
---@field finish function: Function to run after every new prompt
|
||||
---@field destroy function: Functo to run when destroying sorter
|
||||
function Sorter:new(opts)
|
||||
opts = opts or {}
|
||||
|
||||
return setmetatable({
|
||||
score = opts.score,
|
||||
state = {},
|
||||
tags = opts.tags,
|
||||
|
||||
-- State management
|
||||
init = opts.init,
|
||||
start = opts.start,
|
||||
finish = opts.finish,
|
||||
destroy = opts.destroy,
|
||||
_status = nil,
|
||||
|
||||
filter_function = opts.filter_function,
|
||||
scoring_function = opts.scoring_function,
|
||||
highlighter = opts.highlighter,
|
||||
discard = opts.discard,
|
||||
_discard_state = {
|
||||
filtered = {},
|
||||
prompt = "",
|
||||
},
|
||||
}, Sorter)
|
||||
end
|
||||
|
||||
function Sorter:_init()
|
||||
self._status = "init"
|
||||
if self.init then
|
||||
self:init()
|
||||
end
|
||||
end
|
||||
|
||||
function Sorter:_destroy()
|
||||
self._status = "destroy"
|
||||
if self.destroy then
|
||||
self:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: We could make this a bit smarter and cache results "as we go" and where they got filtered.
|
||||
-- Then when we hit backspace, we don't have to re-caculate everything.
|
||||
-- Prime did a lot of the hard work already, but I don't want to copy as much memory around
|
||||
-- as he did in his example.
|
||||
-- Example can be found in ./scratch/prime_prompt_cache.lua
|
||||
function Sorter:_start(prompt)
|
||||
self._status = "start"
|
||||
if self.start then
|
||||
self:start(prompt)
|
||||
end
|
||||
|
||||
if not self.discard then
|
||||
return
|
||||
end
|
||||
|
||||
local previous = self._discard_state.prompt
|
||||
local len_previous = #previous
|
||||
|
||||
if #prompt < len_previous then
|
||||
log.trace "Reset discard because shorter prompt"
|
||||
self._discard_state.filtered = {}
|
||||
elseif string.sub(prompt, 1, len_previous) ~= previous then
|
||||
log.trace "Reset discard no match"
|
||||
self._discard_state.filtered = {}
|
||||
end
|
||||
|
||||
self._discard_state.prompt = prompt
|
||||
end
|
||||
|
||||
function Sorter:_finish(prompt)
|
||||
self._status = "finish"
|
||||
if self.finish then
|
||||
self:finish(prompt)
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: Consider doing something that makes it so we can skip the filter checks
|
||||
-- if we're not discarding. Also, that means we don't have to check otherwise as well :)
|
||||
function Sorter:score(prompt, entry, cb_add, cb_filter)
|
||||
if not entry or not entry.ordinal then
|
||||
return
|
||||
end
|
||||
|
||||
if self._status and self._status ~= "start" then
|
||||
return
|
||||
end
|
||||
|
||||
local ordinal = entry.ordinal
|
||||
if self:_was_discarded(prompt, ordinal) then
|
||||
return cb_filter(entry)
|
||||
end
|
||||
|
||||
local filter_score
|
||||
if self.filter_function ~= nil then
|
||||
if self.tags then
|
||||
self.tags:insert(entry)
|
||||
end
|
||||
filter_score, prompt = self:filter_function(prompt, entry, cb_add, cb_filter)
|
||||
end
|
||||
|
||||
if filter_score == FILTERED then
|
||||
return cb_filter(entry)
|
||||
end
|
||||
|
||||
local score = self:scoring_function(prompt or "", ordinal, entry, cb_add, cb_filter)
|
||||
if score == FILTERED then
|
||||
self:_mark_discarded(prompt, ordinal)
|
||||
return cb_filter(entry)
|
||||
end
|
||||
|
||||
if cb_add then
|
||||
return cb_add(score, entry)
|
||||
else
|
||||
return score
|
||||
end
|
||||
end
|
||||
|
||||
function Sorter:_was_discarded(prompt, ordinal)
|
||||
return self.discard and self._discard_state.filtered[ordinal]
|
||||
end
|
||||
|
||||
function Sorter:_mark_discarded(prompt, ordinal)
|
||||
if not self.discard then
|
||||
return
|
||||
end
|
||||
|
||||
self._discard_state.filtered[ordinal] = true
|
||||
end
|
||||
|
||||
function sorters.new(...)
|
||||
return Sorter:new(...)
|
||||
end
|
||||
|
||||
sorters.Sorter = Sorter
|
||||
|
||||
local make_cached_tail = function()
|
||||
local os_sep = util.get_separator()
|
||||
local match_string = "[^" .. os_sep .. "]*$"
|
||||
return setmetatable({}, {
|
||||
__index = function(t, k)
|
||||
local tail = string.match(k, match_string)
|
||||
|
||||
rawset(t, k, tail)
|
||||
return tail
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local make_cached_uppers = function()
|
||||
return setmetatable({}, {
|
||||
__index = function(t, k)
|
||||
local obj = {}
|
||||
for i = 1, #k do
|
||||
local s_byte = k:byte(i, i)
|
||||
if s_byte <= 90 and s_byte >= 65 then
|
||||
obj[s_byte] = true
|
||||
end
|
||||
end
|
||||
|
||||
rawset(t, k, obj)
|
||||
return obj
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
-- TODO: Match on upper case words
|
||||
-- TODO: Match on last match
|
||||
sorters.get_fuzzy_file = function(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local ngram_len = opts.ngram_len or 2
|
||||
|
||||
local cached_ngrams = {}
|
||||
|
||||
local function overlapping_ngrams(s, n)
|
||||
if cached_ngrams[s] and cached_ngrams[s][n] then
|
||||
return cached_ngrams[s][n]
|
||||
end
|
||||
|
||||
local R = {}
|
||||
for i = 1, s:len() - n + 1 do
|
||||
R[#R + 1] = s:sub(i, i + n - 1)
|
||||
end
|
||||
|
||||
if not cached_ngrams[s] then
|
||||
cached_ngrams[s] = {}
|
||||
end
|
||||
|
||||
cached_ngrams[s][n] = R
|
||||
|
||||
return R
|
||||
end
|
||||
|
||||
local cached_tails = make_cached_tail()
|
||||
local cached_uppers = make_cached_uppers()
|
||||
|
||||
return Sorter:new {
|
||||
scoring_function = function(_, prompt, line)
|
||||
local N = #prompt
|
||||
|
||||
if N == 0 or N < ngram_len then
|
||||
-- TODO: If the character is in the line,
|
||||
-- then it should get a point or somethin.
|
||||
return 1
|
||||
end
|
||||
|
||||
local prompt_lower = prompt:lower()
|
||||
local line_lower = line:lower()
|
||||
|
||||
local prompt_lower_ngrams = overlapping_ngrams(prompt_lower, ngram_len)
|
||||
|
||||
-- Contains the original string
|
||||
local contains_string = line_lower:find(prompt_lower, 1, true)
|
||||
|
||||
local prompt_uppers = cached_uppers[prompt]
|
||||
local line_uppers = cached_uppers[line]
|
||||
|
||||
local uppers_matching = 0
|
||||
for k, _ in pairs(prompt_uppers) do
|
||||
if line_uppers[k] then
|
||||
uppers_matching = uppers_matching + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: Consider case senstivity
|
||||
local tail = cached_tails[line_lower]
|
||||
local contains_tail = tail:find(prompt, 1, true)
|
||||
|
||||
local consecutive_matches = 0
|
||||
local previous_match_index = 0
|
||||
local match_count = 0
|
||||
|
||||
for i = 1, #prompt_lower_ngrams do
|
||||
local match_start = line_lower:find(prompt_lower_ngrams[i], 1, true)
|
||||
if match_start then
|
||||
match_count = match_count + 1
|
||||
if match_start > previous_match_index then
|
||||
consecutive_matches = consecutive_matches + 1
|
||||
end
|
||||
|
||||
previous_match_index = match_start
|
||||
end
|
||||
end
|
||||
|
||||
local tail_modifier = 1
|
||||
if contains_tail then
|
||||
tail_modifier = 2
|
||||
end
|
||||
|
||||
local denominator = (
|
||||
(10 * match_count / #prompt_lower_ngrams)
|
||||
-- biases for shorter strings
|
||||
+ 3 * match_count * ngram_len / #line
|
||||
+ consecutive_matches
|
||||
+ N / (contains_string or (2 * #line))
|
||||
-- + 30/(c1 or 2*N)
|
||||
-- TODO: It might be possible that this too strongly correlates,
|
||||
-- but it's unlikely for people to type capital letters without actually
|
||||
-- wanting to do something with a capital letter in it.
|
||||
+ uppers_matching
|
||||
) * tail_modifier
|
||||
|
||||
if denominator == 0 or denominator ~= denominator then
|
||||
return -1
|
||||
end
|
||||
|
||||
if #prompt > 2 and denominator < 0.5 then
|
||||
return -1
|
||||
end
|
||||
|
||||
return 1 / denominator
|
||||
end,
|
||||
|
||||
highlighter = opts.highlighter or function(_, prompt, display)
|
||||
return ngram_highlighter(ngram_len, prompt, display)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
sorters.get_generic_fuzzy_sorter = function(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local ngram_len = opts.ngram_len or 2
|
||||
|
||||
local cached_ngrams = {}
|
||||
local function overlapping_ngrams(s, n)
|
||||
if cached_ngrams[s] and cached_ngrams[s][n] then
|
||||
return cached_ngrams[s][n]
|
||||
end
|
||||
|
||||
local R = {}
|
||||
for i = 1, s:len() - n + 1 do
|
||||
R[#R + 1] = s:sub(i, i + n - 1)
|
||||
end
|
||||
|
||||
if not cached_ngrams[s] then
|
||||
cached_ngrams[s] = {}
|
||||
end
|
||||
|
||||
cached_ngrams[s][n] = R
|
||||
|
||||
return R
|
||||
end
|
||||
|
||||
return Sorter:new {
|
||||
-- self
|
||||
-- prompt (which is the text on the line)
|
||||
-- line (entry.ordinal)
|
||||
-- entry (the whole entry)
|
||||
scoring_function = function(_, prompt, line, _)
|
||||
if prompt == 0 or #prompt < ngram_len then
|
||||
return 1
|
||||
end
|
||||
|
||||
local prompt_lower = prompt:lower()
|
||||
local line_lower = line:lower()
|
||||
|
||||
local prompt_ngrams = overlapping_ngrams(prompt_lower, ngram_len)
|
||||
|
||||
local N = #prompt
|
||||
|
||||
local contains_string = line_lower:find(prompt_lower, 1, true)
|
||||
|
||||
local consecutive_matches = 0
|
||||
local previous_match_index = 0
|
||||
local match_count = 0
|
||||
|
||||
for i = 1, #prompt_ngrams do
|
||||
local match_start = line_lower:find(prompt_ngrams[i], 1, true)
|
||||
if match_start then
|
||||
match_count = match_count + 1
|
||||
if match_start > previous_match_index then
|
||||
consecutive_matches = consecutive_matches + 1
|
||||
end
|
||||
|
||||
previous_match_index = match_start
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: Copied from ashkan.
|
||||
local denominator = (
|
||||
(10 * match_count / #prompt_ngrams)
|
||||
-- biases for shorter strings
|
||||
-- TODO(ashkan): this can bias towards repeated finds of the same
|
||||
-- subpattern with overlapping_ngrams
|
||||
+ 3 * match_count * ngram_len / #line
|
||||
+ consecutive_matches
|
||||
+ N / (contains_string or (2 * #line)) -- + 30/(c1 or 2*N)
|
||||
|
||||
)
|
||||
|
||||
if denominator == 0 or denominator ~= denominator then
|
||||
return -1
|
||||
end
|
||||
|
||||
if #prompt > 2 and denominator < 0.5 then
|
||||
return -1
|
||||
end
|
||||
|
||||
return 1 / denominator
|
||||
end,
|
||||
|
||||
highlighter = opts.highlighter or function(_, prompt, display)
|
||||
return ngram_highlighter(ngram_len, prompt, display)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
sorters.fuzzy_with_index_bias = function(opts)
|
||||
opts = opts or {}
|
||||
opts.ngram_len = 2
|
||||
|
||||
-- TODO: Probably could use a better sorter here.
|
||||
local fuzzy_sorter = sorters.get_generic_fuzzy_sorter(opts)
|
||||
|
||||
return Sorter:new {
|
||||
scoring_function = function(_, prompt, line, entry, cb_add, cb_filter)
|
||||
local base_score = fuzzy_sorter:scoring_function(prompt, line, cb_add, cb_filter)
|
||||
|
||||
if base_score == FILTERED then
|
||||
return FILTERED
|
||||
end
|
||||
|
||||
if not base_score or base_score == 0 then
|
||||
return entry.index
|
||||
else
|
||||
return math.min(math.pow(entry.index, 0.25), 2) * base_score
|
||||
end
|
||||
end,
|
||||
highlighter = fuzzy_sorter.highlighter,
|
||||
}
|
||||
end
|
||||
|
||||
-- Sorter using the fzy algorithm
|
||||
sorters.get_fzy_sorter = function(opts)
|
||||
opts = opts or {}
|
||||
local fzy = opts.fzy_mod or require "telescope.algos.fzy"
|
||||
local OFFSET = -fzy.get_score_floor()
|
||||
|
||||
return sorters.Sorter:new {
|
||||
discard = true,
|
||||
|
||||
scoring_function = function(_, prompt, line)
|
||||
-- Check for actual matches before running the scoring alogrithm.
|
||||
if not fzy.has_match(prompt, line) then
|
||||
return -1
|
||||
end
|
||||
|
||||
local fzy_score = fzy.score(prompt, line)
|
||||
|
||||
-- The fzy score is -inf for empty queries and overlong strings. Since
|
||||
-- this function converts all scores into the range (0, 1), we can
|
||||
-- convert these to 1 as a suitable "worst score" value.
|
||||
if fzy_score == fzy.get_score_min() then
|
||||
return 1
|
||||
end
|
||||
|
||||
-- Poor non-empty matches can also have negative values. Offset the score
|
||||
-- so that all values are positive, then invert to match the
|
||||
-- telescope.Sorter "smaller is better" convention. Note that for exact
|
||||
-- matches, fzy returns +inf, which when inverted becomes 0.
|
||||
return 1 / (fzy_score + OFFSET)
|
||||
end,
|
||||
|
||||
-- The fzy.positions function, which returns an array of string indices, is
|
||||
-- compatible with telescope's conventions. It's moderately wasteful to
|
||||
-- call call fzy.score(x,y) followed by fzy.positions(x,y): both call the
|
||||
-- fzy.compute function, which does all the work. But, this doesn't affect
|
||||
-- perceived performance.
|
||||
highlighter = function(_, prompt, display)
|
||||
return fzy.positions(prompt, display)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
-- TODO: Could probably do something nice where we check their conf
|
||||
-- and choose their default for this.
|
||||
-- But I think `fzy` is good default for now.
|
||||
sorters.highlighter_only = function(opts)
|
||||
opts = opts or {}
|
||||
local fzy = opts.fzy_mod or require "telescope.algos.fzy"
|
||||
|
||||
return Sorter:new {
|
||||
scoring_function = function()
|
||||
return 1
|
||||
end,
|
||||
|
||||
highlighter = function(_, prompt, display)
|
||||
return fzy.positions(prompt, display)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
sorters.empty = function()
|
||||
return Sorter:new {
|
||||
scoring_function = function()
|
||||
return 1
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
-- Bad & Dumb Sorter
|
||||
sorters.get_levenshtein_sorter = function()
|
||||
return Sorter:new {
|
||||
scoring_function = function(_, prompt, line)
|
||||
return require "telescope.algos.string_distance"(prompt, line)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local substr_highlighter = function(make_display)
|
||||
return function(_, prompt, display)
|
||||
local highlights = {}
|
||||
display = make_display(prompt, display)
|
||||
|
||||
local search_terms = util.max_split(prompt, "%s")
|
||||
local hl_start, hl_end
|
||||
|
||||
for _, word in pairs(search_terms) do
|
||||
hl_start, hl_end = display:find(word, 1, true)
|
||||
if hl_start then
|
||||
table.insert(highlights, { start = hl_start, finish = hl_end })
|
||||
end
|
||||
end
|
||||
|
||||
return highlights
|
||||
end
|
||||
end
|
||||
|
||||
sorters.get_substr_matcher = function()
|
||||
local make_display = vim.o.smartcase
|
||||
and function(prompt, display)
|
||||
local has_upper_case = not not prompt:match "%u"
|
||||
return has_upper_case and display or display:lower()
|
||||
end
|
||||
or function(_, display)
|
||||
return display:lower()
|
||||
end
|
||||
|
||||
return Sorter:new {
|
||||
highlighter = substr_highlighter(make_display),
|
||||
scoring_function = function(_, prompt, _, entry)
|
||||
if #prompt == 0 then
|
||||
return 1
|
||||
end
|
||||
|
||||
local display = make_display(prompt, entry.ordinal)
|
||||
|
||||
local search_terms = util.max_split(prompt, "%s")
|
||||
for _, word in pairs(search_terms) do
|
||||
if not display:find(word, 1, true) then
|
||||
return -1
|
||||
end
|
||||
end
|
||||
|
||||
return entry.index
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local substr_matcher = function(_, prompt, line, _)
|
||||
local display = line:lower()
|
||||
local search_terms = util.max_split(prompt:lower(), "%s")
|
||||
local matched = 0
|
||||
local total_search_terms = 0
|
||||
for _, word in pairs(search_terms) do
|
||||
total_search_terms = total_search_terms + 1
|
||||
if display:find(word, 1, true) then
|
||||
matched = matched + 1
|
||||
end
|
||||
end
|
||||
|
||||
return matched == total_search_terms and 0 or FILTERED
|
||||
end
|
||||
|
||||
local filter_function = function(opts)
|
||||
local scoring_function = vim.F.if_nil(opts.filter_function, substr_matcher)
|
||||
local tag = vim.F.if_nil(opts.tag, "ordinal")
|
||||
|
||||
return function(_, prompt, entry)
|
||||
local filter = "^(" .. opts.delimiter .. "(%S+)" .. "[" .. opts.delimiter .. "%s]" .. ")"
|
||||
local matched = prompt:match(filter)
|
||||
|
||||
if matched == nil then
|
||||
return 0, prompt
|
||||
end
|
||||
-- clear prompt of tag
|
||||
prompt = prompt:sub(#matched + 1, -1)
|
||||
local query = vim.trim(matched:gsub(opts.delimiter, ""))
|
||||
return scoring_function(_, query, entry[tag], _), prompt
|
||||
end
|
||||
end
|
||||
|
||||
local function create_tag_set(tag)
|
||||
tag = vim.F.if_nil(tag, "ordinal")
|
||||
local set = {}
|
||||
return setmetatable(set, {
|
||||
__index = {
|
||||
insert = function(set_, entry)
|
||||
local value = entry[tag]
|
||||
if not set_[value] then
|
||||
set_[value] = true
|
||||
end
|
||||
end,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
sorters.prefilter = function(opts)
|
||||
local sorter = opts.sorter
|
||||
opts.delimiter = vim.F.if_nil(opts.delimiter, ":")
|
||||
sorter._delimiter = opts.delimiter
|
||||
sorter.tags = create_tag_set(opts.tag)
|
||||
sorter.filter_function = filter_function(opts)
|
||||
sorter._was_discarded = function()
|
||||
return false
|
||||
end
|
||||
return sorter
|
||||
end
|
||||
|
||||
return sorters
|
||||
@ -0,0 +1,39 @@
|
||||
local state = {}
|
||||
|
||||
TelescopeGlobalState = TelescopeGlobalState or {}
|
||||
TelescopeGlobalState.global = TelescopeGlobalState.global or {}
|
||||
|
||||
--- Set the status for a particular prompt bufnr
|
||||
function state.set_status(prompt_bufnr, status)
|
||||
TelescopeGlobalState[prompt_bufnr] = status
|
||||
end
|
||||
|
||||
function state.set_global_key(key, value)
|
||||
TelescopeGlobalState.global[key] = value
|
||||
end
|
||||
|
||||
function state.get_global_key(key)
|
||||
return TelescopeGlobalState.global[key]
|
||||
end
|
||||
|
||||
function state.get_status(prompt_bufnr)
|
||||
return TelescopeGlobalState[prompt_bufnr] or {}
|
||||
end
|
||||
|
||||
function state.clear_status(prompt_bufnr)
|
||||
state.set_status(prompt_bufnr, nil)
|
||||
end
|
||||
|
||||
function state.get_existing_prompt_bufnrs()
|
||||
local prompt_bufnrs = {}
|
||||
|
||||
for key, _ in pairs(TelescopeGlobalState) do
|
||||
if type(key) == "number" then
|
||||
table.insert(prompt_bufnrs, key)
|
||||
end
|
||||
end
|
||||
|
||||
return prompt_bufnrs
|
||||
end
|
||||
|
||||
return state
|
||||
@ -0,0 +1,56 @@
|
||||
local test_helpers = {}
|
||||
|
||||
test_helpers.get_picker = function()
|
||||
local state = require "telescope.state"
|
||||
return state.get_status(vim.api.nvim_get_current_buf()).picker
|
||||
end
|
||||
|
||||
test_helpers.get_results_bufnr = function()
|
||||
local state = require "telescope.state"
|
||||
return state.get_status(vim.api.nvim_get_current_buf()).layout.results.bufnr
|
||||
end
|
||||
|
||||
test_helpers.get_file = function()
|
||||
return vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ":t")
|
||||
end
|
||||
|
||||
test_helpers.get_prompt = function()
|
||||
return vim.api.nvim_buf_get_lines(0, 0, -1, false)[1]
|
||||
end
|
||||
|
||||
test_helpers.get_results = function()
|
||||
return vim.api.nvim_buf_get_lines(test_helpers.get_results_bufnr(), 0, -1, false)
|
||||
end
|
||||
|
||||
test_helpers.get_best_result = function()
|
||||
local results = test_helpers.get_results()
|
||||
local picker = test_helpers.get_picker()
|
||||
|
||||
if picker.sorting_strategy == "ascending" then
|
||||
return results[1]
|
||||
else
|
||||
return results[#results]
|
||||
end
|
||||
end
|
||||
|
||||
test_helpers.get_selection = function()
|
||||
local state = require "telescope.state"
|
||||
return state.get_global_key "selected_entry"
|
||||
end
|
||||
|
||||
test_helpers.get_selection_value = function()
|
||||
return test_helpers.get_selection().value
|
||||
end
|
||||
|
||||
test_helpers.make_globals = function()
|
||||
GetFile = test_helpers.get_file -- luacheck: globals GetFile
|
||||
GetPrompt = test_helpers.get_prompt -- luacheck: globals GetPrompt
|
||||
|
||||
GetResults = test_helpers.get_results -- luacheck: globals GetResults
|
||||
GetBestResult = test_helpers.get_best_result -- luacheck: globals GetBestResult
|
||||
|
||||
GetSelection = test_helpers.get_selection -- luacheck: globals GetSelection
|
||||
GetSelectionValue = test_helpers.get_selection_value -- luacheck: globals GetSelectionValue
|
||||
end
|
||||
|
||||
return test_helpers
|
||||
@ -0,0 +1,112 @@
|
||||
local assert = require "luassert"
|
||||
|
||||
local Path = require "plenary.path"
|
||||
|
||||
local tester = {}
|
||||
tester.debug = false
|
||||
|
||||
local get_results_from_contents = function(content)
|
||||
local nvim = vim.fn.jobstart(
|
||||
{ "nvim", "--noplugin", "-u", "scripts/minimal_init.vim", "--headless", "--embed" },
|
||||
{ rpc = true }
|
||||
)
|
||||
|
||||
local result = vim.fn.rpcrequest(nvim, "nvim_exec_lua", content, {})
|
||||
assert.are.same(true, result[1], vim.inspect(result))
|
||||
|
||||
local count = 0
|
||||
while
|
||||
vim.fn.rpcrequest(nvim, "nvim_exec_lua", "return require('telescope.testharness.runner').state.done", {}) ~= true
|
||||
do
|
||||
count = count + 1
|
||||
vim.wait(100)
|
||||
|
||||
-- TODO: Could maybe wait longer, but it's annoying to wait if the test is going to timeout.
|
||||
if count > 100 then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local state = vim.fn.rpcrequest(nvim, "nvim_exec_lua", "return require('telescope.testharness.runner').state", {})
|
||||
vim.fn.jobstop(nvim)
|
||||
|
||||
assert.are.same(true, state.done, vim.inspect(state))
|
||||
|
||||
local result_table = {}
|
||||
for _, v in ipairs(state.results) do
|
||||
table.insert(result_table, v)
|
||||
end
|
||||
|
||||
return result_table, state
|
||||
end
|
||||
|
||||
local check_results = function(results, state)
|
||||
assert(state, "Must pass state")
|
||||
|
||||
for _, v in ipairs(results) do
|
||||
local assertion
|
||||
if not v._type or v._type == "are" or v._type == "_default" then
|
||||
assertion = assert.are.same
|
||||
else
|
||||
assertion = assert.are_not.same
|
||||
end
|
||||
|
||||
-- TODO: I think it would be nice to be able to see the state,
|
||||
-- but it clutters up the test output so much here.
|
||||
--
|
||||
-- So we would have to consider how to do that I think.
|
||||
assertion(v.expected, v.actual, string.format("Test Case: %s // %s", v.location, v.case))
|
||||
end
|
||||
end
|
||||
|
||||
tester.run_string = function(contents)
|
||||
contents = [[
|
||||
return (function()
|
||||
local tester = require('telescope.testharness')
|
||||
local runner = require('telescope.testharness.runner')
|
||||
local helper = require('telescope.testharness.helpers')
|
||||
helper.make_globals()
|
||||
local ok, msg = pcall(function()
|
||||
runner.log("Loading Test")
|
||||
]] .. contents .. [[
|
||||
end)
|
||||
return {ok, msg or runner.state}
|
||||
end)()
|
||||
]]
|
||||
|
||||
check_results(get_results_from_contents(contents))
|
||||
end
|
||||
|
||||
tester.run_file = function(filename)
|
||||
local file = "./lua/tests/pickers/" .. filename .. ".lua"
|
||||
local path = Path:new(file)
|
||||
|
||||
if not path:exists() then
|
||||
assert.are.same("<An existing file>", file)
|
||||
end
|
||||
|
||||
local contents = string.format(
|
||||
[[
|
||||
return (function()
|
||||
local runner = require('telescope.testharness.runner')
|
||||
local helper = require('telescope.testharness.helpers')
|
||||
helper.make_globals()
|
||||
local ok, msg = pcall(function()
|
||||
runner.log("Loading Test")
|
||||
return loadfile("%s")()
|
||||
end)
|
||||
return {ok, msg or runner.state}
|
||||
end)()
|
||||
]],
|
||||
path:absolute()
|
||||
)
|
||||
|
||||
check_results(get_results_from_contents(contents))
|
||||
end
|
||||
|
||||
tester.not_ = function(val)
|
||||
val._type = "are_not"
|
||||
return val
|
||||
end
|
||||
|
||||
return tester
|
||||
@ -0,0 +1,156 @@
|
||||
local builtin = require "telescope.builtin"
|
||||
|
||||
local DELAY = vim.g.telescope_test_delay or 50
|
||||
local runner = {}
|
||||
|
||||
-- State is test variable
|
||||
runner.state = {
|
||||
done = false,
|
||||
results = {},
|
||||
msgs = {},
|
||||
}
|
||||
|
||||
local writer = function(val)
|
||||
table.insert(runner.state.results, val)
|
||||
end
|
||||
|
||||
local invalid_test_case = function(k)
|
||||
error { case = k, expected = "<a valid key>", actual = k }
|
||||
end
|
||||
|
||||
local _VALID_KEYS = {
|
||||
post_typed = true,
|
||||
post_close = true,
|
||||
}
|
||||
|
||||
local replace_terms = function(input)
|
||||
return vim.api.nvim_replace_termcodes(input, true, false, true)
|
||||
end
|
||||
|
||||
runner.nvim_feed = function(text, feed_opts)
|
||||
feed_opts = feed_opts or "m"
|
||||
|
||||
vim.api.nvim_feedkeys(text, feed_opts, true)
|
||||
end
|
||||
|
||||
local end_test_cases = function()
|
||||
runner.state.done = true
|
||||
end
|
||||
|
||||
local execute_test_case = function(location, key, spec)
|
||||
local ok, actual = pcall(spec[2])
|
||||
|
||||
if not ok then
|
||||
writer {
|
||||
location = "Error: " .. location,
|
||||
case = key,
|
||||
expected = "To succeed and return: " .. tostring(spec[1]),
|
||||
actual = actual,
|
||||
|
||||
_type = spec._type,
|
||||
}
|
||||
|
||||
end_test_cases()
|
||||
else
|
||||
writer {
|
||||
location = location,
|
||||
case = key,
|
||||
expected = spec[1],
|
||||
actual = actual,
|
||||
|
||||
_type = spec._type,
|
||||
}
|
||||
end
|
||||
|
||||
return ok
|
||||
end
|
||||
|
||||
runner.log = function(msg)
|
||||
table.insert(runner.state.msgs, msg)
|
||||
end
|
||||
|
||||
runner.picker = function(picker_name, input, test_cases, opts)
|
||||
opts = opts or {}
|
||||
|
||||
for k, _ in pairs(test_cases) do
|
||||
if not _VALID_KEYS[k] then
|
||||
return invalid_test_case(k)
|
||||
end
|
||||
end
|
||||
|
||||
opts.on_complete = {
|
||||
runner.create_on_complete(input, test_cases),
|
||||
}
|
||||
|
||||
opts._on_error = function(self, msg)
|
||||
runner.state.done = true
|
||||
writer {
|
||||
location = "Error while running on complete",
|
||||
expected = "To Work",
|
||||
actual = msg,
|
||||
}
|
||||
end
|
||||
|
||||
runner.log "Starting picker"
|
||||
builtin[picker_name](opts)
|
||||
runner.log "Called picker"
|
||||
end
|
||||
|
||||
runner.create_on_complete = function(input, test_cases)
|
||||
input = replace_terms(input)
|
||||
|
||||
local actions = {}
|
||||
for i = 1, #input do
|
||||
local char = input:sub(i, i)
|
||||
table.insert(actions, {
|
||||
cb = function()
|
||||
runner.log("Inserting char: " .. char)
|
||||
runner.nvim_feed(char, "")
|
||||
end,
|
||||
char = char,
|
||||
})
|
||||
end
|
||||
|
||||
return function()
|
||||
local action
|
||||
|
||||
repeat
|
||||
action = table.remove(actions, 1)
|
||||
if action then
|
||||
action.cb()
|
||||
end
|
||||
until not action or string.match(action.char, "%g")
|
||||
|
||||
if #actions > 0 then
|
||||
return
|
||||
end
|
||||
|
||||
vim.defer_fn(function()
|
||||
if test_cases.post_typed then
|
||||
for k, v in ipairs(test_cases.post_typed) do
|
||||
if not execute_test_case("post_typed", k, v) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vim.defer_fn(function()
|
||||
runner.nvim_feed(replace_terms "<CR>", "")
|
||||
|
||||
vim.defer_fn(function()
|
||||
if test_cases.post_close then
|
||||
for k, v in ipairs(test_cases.post_close) do
|
||||
if not execute_test_case("post_close", k, v) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vim.defer_fn(end_test_cases, DELAY)
|
||||
end, DELAY)
|
||||
end, DELAY)
|
||||
end, DELAY)
|
||||
end
|
||||
end
|
||||
|
||||
return runner
|
||||
@ -0,0 +1,139 @@
|
||||
-- Prototype Theme System (WIP)
|
||||
-- Currently certain designs need a number of parameters.
|
||||
--
|
||||
-- local opts = themes.get_dropdown { winblend = 3 }
|
||||
|
||||
---@tag telescope.themes
|
||||
---@config { ["module"] = "telescope.themes" }
|
||||
|
||||
---@brief [[
|
||||
--- Themes are ways to combine several elements of styling together.
|
||||
---
|
||||
--- They are helpful for managing the several different UI aspects for telescope and provide
|
||||
--- a simple interface for users to get a particular "style" of picker.
|
||||
---@brief ]]
|
||||
|
||||
local themes = {}
|
||||
|
||||
--- Dropdown style theme.
|
||||
---
|
||||
--- Usage:
|
||||
--- <code>
|
||||
--- local opts = {...} -- picker options
|
||||
--- local builtin = require('telescope.builtin')
|
||||
--- local themes = require('telescope.themes')
|
||||
--- builtin.find_files(themes.get_dropdown(opts))
|
||||
--- </code>
|
||||
function themes.get_dropdown(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local theme_opts = {
|
||||
theme = "dropdown",
|
||||
|
||||
results_title = false,
|
||||
|
||||
sorting_strategy = "ascending",
|
||||
layout_strategy = "center",
|
||||
layout_config = {
|
||||
preview_cutoff = 1, -- Preview should always show (unless previewer = false)
|
||||
|
||||
width = function(_, max_columns, _)
|
||||
return math.min(max_columns, 80)
|
||||
end,
|
||||
|
||||
height = function(_, _, max_lines)
|
||||
return math.min(max_lines, 15)
|
||||
end,
|
||||
},
|
||||
|
||||
border = true,
|
||||
borderchars = {
|
||||
prompt = { "─", "│", " ", "│", "╭", "╮", "│", "│" },
|
||||
results = { "─", "│", "─", "│", "├", "┤", "╯", "╰" },
|
||||
preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
|
||||
},
|
||||
}
|
||||
if opts.layout_config and opts.layout_config.prompt_position == "bottom" then
|
||||
theme_opts.borderchars = {
|
||||
prompt = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
|
||||
results = { "─", "│", "─", "│", "╭", "╮", "┤", "├" },
|
||||
preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
|
||||
}
|
||||
end
|
||||
|
||||
return vim.tbl_deep_extend("force", theme_opts, opts)
|
||||
end
|
||||
|
||||
--- Cursor style theme.
|
||||
---
|
||||
--- Usage:
|
||||
--- <code>
|
||||
--- local opts = {...} -- picker options
|
||||
--- local builtin = require('telescope.builtin')
|
||||
--- local themes = require('telescope.themes')
|
||||
--- builtin.find_files(themes.get_cursor(opts))
|
||||
--- </code>
|
||||
function themes.get_cursor(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local theme_opts = {
|
||||
theme = "cursor",
|
||||
|
||||
sorting_strategy = "ascending",
|
||||
results_title = false,
|
||||
layout_strategy = "cursor",
|
||||
layout_config = {
|
||||
width = 80,
|
||||
height = 9,
|
||||
},
|
||||
borderchars = {
|
||||
prompt = { "─", "│", " ", "│", "╭", "╮", "│", "│" },
|
||||
results = { "─", "│", "─", "│", "├", "┤", "╯", "╰" },
|
||||
preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
|
||||
},
|
||||
}
|
||||
|
||||
return vim.tbl_deep_extend("force", theme_opts, opts)
|
||||
end
|
||||
|
||||
--- Ivy style theme.
|
||||
---
|
||||
--- Usage:
|
||||
--- <code>
|
||||
--- local opts = {...} -- picker options
|
||||
--- local builtin = require('telescope.builtin')
|
||||
--- local themes = require('telescope.themes')
|
||||
--- builtin.find_files(themes.get_ivy(opts))
|
||||
--- </code>
|
||||
function themes.get_ivy(opts)
|
||||
opts = opts or {}
|
||||
|
||||
local theme_opts = {
|
||||
theme = "ivy",
|
||||
|
||||
sorting_strategy = "ascending",
|
||||
|
||||
layout_strategy = "bottom_pane",
|
||||
layout_config = {
|
||||
height = 25,
|
||||
},
|
||||
|
||||
border = true,
|
||||
borderchars = {
|
||||
prompt = { "─", " ", " ", " ", "─", "─", " ", " " },
|
||||
results = { " " },
|
||||
preview = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" },
|
||||
},
|
||||
}
|
||||
if opts.layout_config and opts.layout_config.prompt_position == "bottom" then
|
||||
theme_opts.borderchars = {
|
||||
prompt = { " ", " ", "─", " ", " ", " ", "─", "─" },
|
||||
results = { "─", " ", " ", " ", "─", "─", " ", " " },
|
||||
preview = { "─", " ", "─", "│", "┬", "─", "─", "╰" },
|
||||
}
|
||||
end
|
||||
|
||||
return vim.tbl_deep_extend("force", theme_opts, opts)
|
||||
end
|
||||
|
||||
return themes
|
||||
@ -0,0 +1,730 @@
|
||||
---@tag telescope.utils
|
||||
---@config { ["module"] = "telescope.utils" }
|
||||
|
||||
---@brief [[
|
||||
--- Utilities for writing telescope pickers
|
||||
---@brief ]]
|
||||
|
||||
local Path = require "plenary.path"
|
||||
local Job = require "plenary.job"
|
||||
|
||||
local log = require "telescope.log"
|
||||
|
||||
local truncate = require("plenary.strings").truncate
|
||||
local get_status = require("telescope.state").get_status
|
||||
|
||||
local utils = {}
|
||||
|
||||
utils.iswin = vim.loop.os_uname().sysname == "Windows_NT"
|
||||
|
||||
--TODO(clason): Remove when dropping support for Nvim 0.9
|
||||
utils.islist = vim.fn.has "nvim-0.10" == 1 and vim.islist or vim.tbl_islist
|
||||
local flatten = function(t)
|
||||
return vim.iter(t):flatten():totable()
|
||||
end
|
||||
utils.flatten = vim.fn.has "nvim-0.11" == 1 and flatten or vim.tbl_flatten
|
||||
|
||||
--- Hybrid of `vim.fn.expand()` and custom `vim.fs.normalize()`
|
||||
---
|
||||
--- Paths starting with '%', '#' or '<' are expanded with `vim.fn.expand()`.
|
||||
--- Otherwise avoids using `vim.fn.expand()` due to its overly aggressive
|
||||
--- expansion behavior which can sometimes lead to errors or the creation of
|
||||
--- non-existent paths when dealing with valid absolute paths.
|
||||
---
|
||||
--- Other paths will have '~' and environment variables expanded.
|
||||
--- Unlike `vim.fs.normalize()`, backslashes are preserved. This has better
|
||||
--- compatibility with `plenary.path` and also avoids mangling valid Unix paths
|
||||
--- with literal backslashes.
|
||||
---
|
||||
--- Trailing slashes are trimmed. With the exception of root paths.
|
||||
--- eg. `/` on Unix or `C:\` on Windows
|
||||
---
|
||||
---@param path string
|
||||
---@return string
|
||||
utils.path_expand = function(path)
|
||||
vim.validate {
|
||||
path = { path, { "string" } },
|
||||
}
|
||||
|
||||
if utils.is_uri(path) then
|
||||
return path
|
||||
end
|
||||
|
||||
if path:match "^[%%#<]" then
|
||||
path = vim.fn.expand(path)
|
||||
end
|
||||
|
||||
if path:sub(1, 1) == "~" then
|
||||
local home = vim.loop.os_homedir() or "~"
|
||||
if home:sub(-1) == "\\" or home:sub(-1) == "/" then
|
||||
home = home:sub(1, -2)
|
||||
end
|
||||
path = home .. path:sub(2)
|
||||
end
|
||||
|
||||
path = path:gsub("%$([%w_]+)", vim.loop.os_getenv)
|
||||
path = path:gsub("/+", "/")
|
||||
if utils.iswin then
|
||||
path = path:gsub("\\+", "\\")
|
||||
if path:match "^%w:\\$" then
|
||||
return path
|
||||
else
|
||||
return (path:gsub("(.)\\$", "%1"))
|
||||
end
|
||||
end
|
||||
return (path:gsub("(.)/$", "%1"))
|
||||
end
|
||||
|
||||
utils.get_separator = function()
|
||||
return Path.path.sep
|
||||
end
|
||||
|
||||
utils.cycle = function(i, n)
|
||||
return i % n == 0 and n or i % n
|
||||
end
|
||||
|
||||
utils.get_lazy_default = function(x, defaulter, ...)
|
||||
if x == nil then
|
||||
return defaulter(...)
|
||||
else
|
||||
return x
|
||||
end
|
||||
end
|
||||
|
||||
utils.repeated_table = function(n, val)
|
||||
local empty_lines = {}
|
||||
for _ = 1, n do
|
||||
table.insert(empty_lines, val)
|
||||
end
|
||||
return empty_lines
|
||||
end
|
||||
|
||||
utils.filter_symbols = function(results, opts, post_filter)
|
||||
local has_ignore = opts.ignore_symbols ~= nil
|
||||
local has_symbols = opts.symbols ~= nil
|
||||
local filtered_symbols
|
||||
|
||||
if has_symbols and has_ignore then
|
||||
utils.notify("filter_symbols", {
|
||||
msg = "Either opts.symbols or opts.ignore_symbols, can't process opposing options at the same time!",
|
||||
level = "ERROR",
|
||||
})
|
||||
return
|
||||
elseif not (has_ignore or has_symbols) then
|
||||
return results
|
||||
elseif has_ignore then
|
||||
if type(opts.ignore_symbols) == "string" then
|
||||
opts.ignore_symbols = { opts.ignore_symbols }
|
||||
end
|
||||
if type(opts.ignore_symbols) ~= "table" then
|
||||
utils.notify("filter_symbols", {
|
||||
msg = "Please pass ignore_symbols as either a string or a list of strings",
|
||||
level = "ERROR",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
opts.ignore_symbols = vim.tbl_map(string.lower, opts.ignore_symbols)
|
||||
filtered_symbols = vim.tbl_filter(function(item)
|
||||
return not vim.tbl_contains(opts.ignore_symbols, string.lower(item.kind))
|
||||
end, results)
|
||||
elseif has_symbols then
|
||||
if type(opts.symbols) == "string" then
|
||||
opts.symbols = { opts.symbols }
|
||||
end
|
||||
if type(opts.symbols) ~= "table" then
|
||||
utils.notify("filter_symbols", {
|
||||
msg = "Please pass filtering symbols as either a string or a list of strings",
|
||||
level = "ERROR",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
opts.symbols = vim.tbl_map(string.lower, opts.symbols)
|
||||
filtered_symbols = vim.tbl_filter(function(item)
|
||||
return vim.tbl_contains(opts.symbols, string.lower(item.kind))
|
||||
end, results)
|
||||
end
|
||||
|
||||
if type(post_filter) == "function" then
|
||||
filtered_symbols = post_filter(filtered_symbols)
|
||||
end
|
||||
|
||||
if not vim.tbl_isempty(filtered_symbols) then
|
||||
return filtered_symbols
|
||||
end
|
||||
|
||||
-- print message that filtered_symbols is now empty
|
||||
if has_symbols then
|
||||
local symbols = table.concat(opts.symbols, ", ")
|
||||
utils.notify("filter_symbols", {
|
||||
msg = string.format("%s symbol(s) were not part of the query results", symbols),
|
||||
level = "WARN",
|
||||
})
|
||||
elseif has_ignore then
|
||||
local symbols = table.concat(opts.ignore_symbols, ", ")
|
||||
utils.notify("filter_symbols", {
|
||||
msg = string.format("%s ignore_symbol(s) have removed everything from the query result", symbols),
|
||||
level = "WARN",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
utils.path_smart = (function()
|
||||
local paths = {}
|
||||
local os_sep = utils.get_separator()
|
||||
return function(filepath)
|
||||
local final = filepath
|
||||
if #paths ~= 0 then
|
||||
local dirs = vim.split(filepath, os_sep)
|
||||
local max = 1
|
||||
for _, p in pairs(paths) do
|
||||
if #p > 0 and p ~= filepath then
|
||||
local _dirs = vim.split(p, os_sep)
|
||||
for i = 1, math.min(#dirs, #_dirs) do
|
||||
if (dirs[i] ~= _dirs[i]) and i > max then
|
||||
max = i
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if #dirs ~= 0 then
|
||||
if max == 1 and #dirs >= 2 then
|
||||
max = #dirs - 2
|
||||
end
|
||||
final = ""
|
||||
for k, v in pairs(dirs) do
|
||||
if k >= max - 1 then
|
||||
final = final .. (#final > 0 and os_sep or "") .. v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not paths[filepath] then
|
||||
paths[filepath] = ""
|
||||
table.insert(paths, filepath)
|
||||
end
|
||||
if final and final ~= filepath then
|
||||
return ".." .. os_sep .. final
|
||||
else
|
||||
return filepath
|
||||
end
|
||||
end
|
||||
end)()
|
||||
|
||||
utils.path_tail = (function()
|
||||
local os_sep = utils.get_separator()
|
||||
|
||||
return function(path)
|
||||
for i = #path, 1, -1 do
|
||||
if path:sub(i, i) == os_sep then
|
||||
return path:sub(i + 1, -1)
|
||||
end
|
||||
end
|
||||
return path
|
||||
end
|
||||
end)()
|
||||
|
||||
utils.is_path_hidden = function(opts, path_display)
|
||||
path_display = path_display or vim.F.if_nil(opts.path_display, require("telescope.config").values.path_display)
|
||||
|
||||
return path_display == nil
|
||||
or path_display == "hidden"
|
||||
or type(path_display) == "table" and (vim.tbl_contains(path_display, "hidden") or path_display.hidden)
|
||||
end
|
||||
|
||||
utils.is_uri = function(filename)
|
||||
local char = string.byte(filename, 1) or 0
|
||||
|
||||
-- is alpha?
|
||||
if char < 65 or (char > 90 and char < 97) or char > 122 then
|
||||
return false
|
||||
end
|
||||
|
||||
for i = 2, #filename do
|
||||
char = string.byte(filename, i)
|
||||
if char == 58 then -- `:`
|
||||
return i < #filename and string.byte(filename, i + 1) ~= 92 -- `\`
|
||||
elseif
|
||||
not (
|
||||
(char >= 48 and char <= 57) -- 0-9
|
||||
or (char >= 65 and char <= 90) -- A-Z
|
||||
or (char >= 97 and char <= 122) -- a-z
|
||||
or char == 43 -- `+`
|
||||
or char == 46 -- `.`
|
||||
or char == 45 -- `-`
|
||||
)
|
||||
then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local calc_result_length = function(truncate_len)
|
||||
local status = get_status(vim.api.nvim_get_current_buf())
|
||||
local len = vim.api.nvim_win_get_width(status.layout.results.winid) - status.picker.selection_caret:len() - 2
|
||||
return type(truncate_len) == "number" and len - truncate_len or len
|
||||
end
|
||||
|
||||
--- Transform path is a util function that formats a path based on path_display
|
||||
--- found in `opts` or the default value from config.
|
||||
--- It is meant to be used in make_entry to have a uniform interface for
|
||||
--- builtins as well as extensions utilizing the same user configuration
|
||||
--- Note: It is only supported inside `make_entry`/`make_display` the use of
|
||||
--- this function outside of telescope might yield to undefined behavior and will
|
||||
--- not be addressed by us
|
||||
---@param opts table: The opts the users passed into the picker. Might contains a path_display key
|
||||
---@param path string|nil: The path that should be formatted
|
||||
---@return string: path to be displayed
|
||||
---@return table: The transformed path ready to be displayed with the styling
|
||||
utils.transform_path = function(opts, path)
|
||||
if path == nil then
|
||||
return "", {}
|
||||
end
|
||||
if utils.is_uri(path) then
|
||||
return path, {}
|
||||
end
|
||||
|
||||
---@type fun(opts:table, path: string): string, table?
|
||||
local path_display = vim.F.if_nil(opts.path_display, require("telescope.config").values.path_display)
|
||||
|
||||
local transformed_path = path
|
||||
local path_style = {}
|
||||
|
||||
if type(path_display) == "function" then
|
||||
local custom_transformed_path, custom_path_style = path_display(opts, transformed_path)
|
||||
return custom_transformed_path, custom_path_style or path_style
|
||||
elseif utils.is_path_hidden(nil, path_display) then
|
||||
return "", path_style
|
||||
elseif type(path_display) == "table" then
|
||||
if vim.tbl_contains(path_display, "tail") or path_display.tail then
|
||||
transformed_path = utils.path_tail(transformed_path)
|
||||
elseif vim.tbl_contains(path_display, "smart") or path_display.smart then
|
||||
transformed_path = utils.path_smart(transformed_path)
|
||||
else
|
||||
if not vim.tbl_contains(path_display, "absolute") and not path_display.absolute then
|
||||
local cwd
|
||||
if opts.cwd then
|
||||
cwd = opts.cwd
|
||||
if not vim.in_fast_event() then
|
||||
cwd = utils.path_expand(opts.cwd)
|
||||
end
|
||||
else
|
||||
cwd = vim.loop.cwd()
|
||||
end
|
||||
transformed_path = Path:new(transformed_path):make_relative(cwd)
|
||||
end
|
||||
|
||||
if vim.tbl_contains(path_display, "shorten") or path_display["shorten"] ~= nil then
|
||||
if type(path_display["shorten"]) == "table" then
|
||||
local shorten = path_display["shorten"]
|
||||
transformed_path = Path:new(transformed_path):shorten(shorten.len, shorten.exclude)
|
||||
else
|
||||
local length = type(path_display["shorten"]) == "number" and path_display["shorten"]
|
||||
transformed_path = Path:new(transformed_path):shorten(length)
|
||||
end
|
||||
end
|
||||
|
||||
if vim.tbl_contains(path_display, "truncate") or path_display.truncate then
|
||||
if opts.__length == nil then
|
||||
opts.__length = calc_result_length(path_display.truncate)
|
||||
end
|
||||
if opts.__prefix == nil then
|
||||
opts.__prefix = 0
|
||||
end
|
||||
transformed_path = truncate(transformed_path, opts.__length - opts.__prefix, nil, -1)
|
||||
end
|
||||
|
||||
-- IMPORTANT: filename_first needs to be the last option. Otherwise the
|
||||
-- other options will not be displayed correctly.
|
||||
if vim.tbl_contains(path_display, "filename_first") or path_display["filename_first"] ~= nil then
|
||||
local reverse_directories = false
|
||||
|
||||
if type(path_display["filename_first"]) == "table" then
|
||||
local filename_first_opts = path_display["filename_first"]
|
||||
|
||||
if filename_first_opts.reverse_directories == nil or filename_first_opts.reverse_directories == false then
|
||||
reverse_directories = false
|
||||
else
|
||||
reverse_directories = filename_first_opts.reverse_directories
|
||||
end
|
||||
end
|
||||
|
||||
local dirs = vim.split(transformed_path, utils.get_separator())
|
||||
local filename
|
||||
|
||||
if reverse_directories then
|
||||
dirs = utils.reverse_table(dirs)
|
||||
filename = table.remove(dirs, 1)
|
||||
else
|
||||
filename = table.remove(dirs, #dirs)
|
||||
end
|
||||
|
||||
local tail = table.concat(dirs, utils.get_separator())
|
||||
|
||||
-- Prevents a toplevel filename to have a trailing whitespace
|
||||
transformed_path = vim.trim(filename .. " " .. tail)
|
||||
|
||||
path_style = { { { #filename, #transformed_path }, "TelescopeResultsComment" } }
|
||||
end
|
||||
end
|
||||
|
||||
return transformed_path, path_style
|
||||
else
|
||||
log.warn("`path_display` must be either a function or a table.", "See `:help telescope.defaults.path_display.")
|
||||
return transformed_path, path_style
|
||||
end
|
||||
end
|
||||
|
||||
-- local x = utils.make_default_callable(function(opts)
|
||||
-- return function()
|
||||
-- print(opts.example, opts.another)
|
||||
-- end
|
||||
-- end, { example = 7, another = 5 })
|
||||
|
||||
-- x()
|
||||
-- x.new { example = 3 }()
|
||||
function utils.make_default_callable(f, default_opts)
|
||||
default_opts = default_opts or {}
|
||||
|
||||
return setmetatable({
|
||||
new = function(opts)
|
||||
opts = vim.tbl_extend("keep", opts, default_opts)
|
||||
return f(opts)
|
||||
end,
|
||||
}, {
|
||||
__call = function()
|
||||
local ok, err = pcall(f(default_opts))
|
||||
if not ok then
|
||||
error(debug.traceback(err))
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function utils.job_is_running(job_id)
|
||||
if job_id == nil then
|
||||
return false
|
||||
end
|
||||
return vim.fn.jobwait({ job_id }, 0)[1] == -1
|
||||
end
|
||||
|
||||
function utils.buf_delete(bufnr)
|
||||
if bufnr == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Suppress the buffer deleted message for those with &report<2
|
||||
local start_report = vim.o.report
|
||||
if start_report < 2 then
|
||||
vim.o.report = 2
|
||||
end
|
||||
|
||||
if vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr) then
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end
|
||||
|
||||
if start_report < 2 then
|
||||
vim.o.report = start_report
|
||||
end
|
||||
end
|
||||
|
||||
function utils.win_delete(name, win_id, force, bdelete)
|
||||
if win_id == nil or not vim.api.nvim_win_is_valid(win_id) then
|
||||
return
|
||||
end
|
||||
|
||||
local bufnr = vim.api.nvim_win_get_buf(win_id)
|
||||
if bdelete then
|
||||
utils.buf_delete(bufnr)
|
||||
end
|
||||
|
||||
if not vim.api.nvim_win_is_valid(win_id) then
|
||||
return
|
||||
end
|
||||
|
||||
if not pcall(vim.api.nvim_win_close, win_id, force) then
|
||||
log.trace("Unable to close window: ", name, "/", win_id)
|
||||
end
|
||||
end
|
||||
|
||||
function utils.max_split(s, pattern, maxsplit)
|
||||
pattern = pattern or " "
|
||||
maxsplit = maxsplit or -1
|
||||
|
||||
local t = {}
|
||||
|
||||
local curpos = 0
|
||||
while maxsplit ~= 0 and curpos < #s do
|
||||
local found, final = string.find(s, pattern, curpos, false)
|
||||
if found ~= nil then
|
||||
local val = string.sub(s, curpos, found - 1)
|
||||
|
||||
if #val > 0 then
|
||||
maxsplit = maxsplit - 1
|
||||
table.insert(t, val)
|
||||
end
|
||||
|
||||
curpos = final + 1
|
||||
else
|
||||
table.insert(t, string.sub(s, curpos))
|
||||
break
|
||||
-- curpos = curpos + 1
|
||||
end
|
||||
|
||||
if maxsplit == 0 then
|
||||
table.insert(t, string.sub(s, curpos))
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
function utils.data_directory()
|
||||
local sourced_file = require("plenary.debug_utils").sourced_filepath()
|
||||
local base_directory = vim.fn.fnamemodify(sourced_file, ":h:h:h")
|
||||
|
||||
return Path:new({ base_directory, "data" }):absolute() .. Path.path.sep
|
||||
end
|
||||
|
||||
function utils.buffer_dir()
|
||||
return vim.fn.expand "%:p:h"
|
||||
end
|
||||
|
||||
function utils.display_termcodes(str)
|
||||
return str:gsub(string.char(9), "<TAB>"):gsub("", "<C-F>"):gsub(" ", "<Space>")
|
||||
end
|
||||
|
||||
function utils.get_os_command_output(cmd, cwd)
|
||||
if type(cmd) ~= "table" then
|
||||
utils.notify("get_os_command_output", {
|
||||
msg = "cmd has to be a table",
|
||||
level = "ERROR",
|
||||
})
|
||||
return {}
|
||||
end
|
||||
local command = table.remove(cmd, 1)
|
||||
local stderr = {}
|
||||
local stdout, ret = Job:new({
|
||||
command = command,
|
||||
args = cmd,
|
||||
cwd = cwd,
|
||||
on_stderr = function(_, data)
|
||||
table.insert(stderr, data)
|
||||
end,
|
||||
}):sync()
|
||||
return stdout, ret, stderr
|
||||
end
|
||||
|
||||
function utils.win_set_buf_noautocmd(win, buf)
|
||||
local save_ei = vim.o.eventignore
|
||||
vim.o.eventignore = "all"
|
||||
vim.api.nvim_win_set_buf(win, buf)
|
||||
vim.o.eventignore = save_ei
|
||||
end
|
||||
|
||||
local load_once = function(f)
|
||||
local resolved = nil
|
||||
return function(...)
|
||||
if resolved == nil then
|
||||
resolved = f()
|
||||
end
|
||||
|
||||
return resolved(...)
|
||||
end
|
||||
end
|
||||
|
||||
utils.file_extension = function(filename)
|
||||
local parts = vim.split(filename, "%.")
|
||||
-- this check enables us to get multi-part extensions, like *.test.js for example
|
||||
if #parts > 2 then
|
||||
return table.concat(vim.list_slice(parts, #parts - 1), ".")
|
||||
else
|
||||
return table.concat(vim.list_slice(parts, #parts), ".")
|
||||
end
|
||||
end
|
||||
|
||||
utils.transform_devicons = load_once(function()
|
||||
local has_devicons, devicons = pcall(require, "nvim-web-devicons")
|
||||
|
||||
if has_devicons then
|
||||
if not devicons.has_loaded() then
|
||||
devicons.setup()
|
||||
end
|
||||
|
||||
return function(filename, display, disable_devicons)
|
||||
local conf = require("telescope.config").values
|
||||
if disable_devicons or not filename then
|
||||
return display
|
||||
end
|
||||
|
||||
local basename = utils.path_tail(filename)
|
||||
local icon, icon_highlight = devicons.get_icon(basename, utils.file_extension(basename), { default = false })
|
||||
if not icon then
|
||||
icon, icon_highlight = devicons.get_icon(basename, nil, { default = true })
|
||||
icon = icon or " "
|
||||
end
|
||||
local icon_display = icon .. " " .. (display or "")
|
||||
|
||||
if conf.color_devicons then
|
||||
return icon_display, icon_highlight, icon
|
||||
else
|
||||
return icon_display, nil, icon
|
||||
end
|
||||
end
|
||||
else
|
||||
return function(_, display, _)
|
||||
return display
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
utils.get_devicons = load_once(function()
|
||||
local has_devicons, devicons = pcall(require, "nvim-web-devicons")
|
||||
|
||||
if has_devicons then
|
||||
if not devicons.has_loaded() then
|
||||
devicons.setup()
|
||||
end
|
||||
|
||||
return function(filename, disable_devicons)
|
||||
local conf = require("telescope.config").values
|
||||
if disable_devicons or not filename then
|
||||
return ""
|
||||
end
|
||||
|
||||
local basename = utils.path_tail(filename)
|
||||
local icon, icon_highlight = devicons.get_icon(basename, utils.file_extension(basename), { default = false })
|
||||
if not icon then
|
||||
icon, icon_highlight = devicons.get_icon(basename, nil, { default = true })
|
||||
end
|
||||
if conf.color_devicons then
|
||||
return icon, icon_highlight
|
||||
else
|
||||
return icon, nil
|
||||
end
|
||||
end
|
||||
else
|
||||
return function(_, _)
|
||||
return ""
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
--- Checks if treesitter parser for language is installed
|
||||
---@param lang string
|
||||
utils.has_ts_parser = function(lang)
|
||||
return pcall(vim.treesitter.language.add, lang)
|
||||
end
|
||||
|
||||
--- Telescope Wrapper around vim.notify
|
||||
---@param funname string: name of the function that will be
|
||||
---@param opts table: opts.level string, opts.msg string, opts.once bool
|
||||
utils.notify = function(funname, opts)
|
||||
opts.once = vim.F.if_nil(opts.once, false)
|
||||
local level = vim.log.levels[opts.level]
|
||||
if not level then
|
||||
error("Invalid error level", 2)
|
||||
end
|
||||
local notify_fn = opts.once and vim.notify_once or vim.notify
|
||||
notify_fn(string.format("[telescope.%s]: %s", funname, opts.msg), level, {
|
||||
title = "telescope.nvim",
|
||||
})
|
||||
end
|
||||
|
||||
utils.__warn_no_selection = function(name)
|
||||
utils.notify(name, {
|
||||
msg = "Nothing currently selected",
|
||||
level = "WARN",
|
||||
})
|
||||
end
|
||||
|
||||
--- Generate git command optionally with git env variables
|
||||
---@param args string[]
|
||||
---@param opts? table
|
||||
---@return string[]
|
||||
utils.__git_command = function(args, opts)
|
||||
opts = opts or {}
|
||||
|
||||
local _args = { "git" }
|
||||
if opts.gitdir then
|
||||
vim.list_extend(_args, { "--git-dir", opts.gitdir })
|
||||
end
|
||||
if opts.toplevel then
|
||||
vim.list_extend(_args, { "--work-tree", opts.toplevel })
|
||||
end
|
||||
|
||||
return vim.list_extend(_args, args)
|
||||
end
|
||||
|
||||
utils.list_find = function(func, list)
|
||||
for i, v in ipairs(list) do
|
||||
if func(v, i, list) then
|
||||
return i, v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Takes the path and parses optional cursor location `$file:$line:$column`
|
||||
--- If line or column not present `0` returned.
|
||||
---@param path string
|
||||
---@return string path
|
||||
---@return integer? lnum
|
||||
---@return integer? col
|
||||
utils.__separate_file_path_location = function(path)
|
||||
local location_numbers = {}
|
||||
for i = #path, 1, -1 do
|
||||
if path:sub(i, i) == ":" then
|
||||
if i == #path then
|
||||
path = path:sub(1, i - 1)
|
||||
else
|
||||
local location_value = tonumber(path:sub(i + 1))
|
||||
if location_value then
|
||||
table.insert(location_numbers, location_value)
|
||||
path = path:sub(1, i - 1)
|
||||
|
||||
if #location_numbers == 2 then
|
||||
-- There couldn't be more than 2 : separated number
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #location_numbers == 2 then
|
||||
-- because of the reverse the line number will be second
|
||||
return path, location_numbers[2], location_numbers[1]
|
||||
end
|
||||
|
||||
if #location_numbers == 1 then
|
||||
return path, location_numbers[1], 0
|
||||
end
|
||||
|
||||
return path, nil, nil
|
||||
end
|
||||
|
||||
utils.merge_styles = function(style1, style2, offset)
|
||||
local function addOffset(i, obj)
|
||||
return { obj[1] + i, obj[2] + i }
|
||||
end
|
||||
|
||||
for _, item in ipairs(style2) do
|
||||
item[1] = addOffset(offset, item[1])
|
||||
table.insert(style1, item)
|
||||
end
|
||||
|
||||
return style1
|
||||
end
|
||||
|
||||
utils.reverse_table = function(input_table)
|
||||
local temp_table = {}
|
||||
for index = 0, #input_table do
|
||||
temp_table[#input_table - index] = input_table[index + 1] -- Reverses the order
|
||||
end
|
||||
return temp_table
|
||||
end
|
||||
|
||||
return utils
|
||||
@ -0,0 +1,510 @@
|
||||
local actions = require "telescope.actions"
|
||||
local action_set = require "telescope.actions.set"
|
||||
|
||||
local transform_mod = require("telescope.actions.mt").transform_mod
|
||||
|
||||
local eq = assert.are.same
|
||||
|
||||
describe("actions", function()
|
||||
it("should allow creating custom actions", function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return 5
|
||||
end,
|
||||
}
|
||||
|
||||
eq(5, a.x())
|
||||
end)
|
||||
|
||||
it("allows adding actions", function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "x"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
local x_plus_y = a.x + a.y
|
||||
|
||||
eq({ "x", "y" }, { x_plus_y() })
|
||||
end)
|
||||
|
||||
it("ignores nils from added actions", function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "x"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
nil_maker = function()
|
||||
return nil
|
||||
end,
|
||||
}
|
||||
|
||||
local x_plus_y = a.x + a.nil_maker + a.y
|
||||
|
||||
eq({ "x", "y" }, { x_plus_y() })
|
||||
end)
|
||||
|
||||
it("allows overriding an action", function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "x"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
-- actions.file_goto_selection_edit:replace(...)
|
||||
a.x:replace(function()
|
||||
return "foo"
|
||||
end)
|
||||
eq("foo", a.x())
|
||||
|
||||
a._clear()
|
||||
eq("x", a.x())
|
||||
end)
|
||||
|
||||
it("allows overriding an action only in specific cases with if", function()
|
||||
local a = transform_mod {
|
||||
x = function(e)
|
||||
return e * 10
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
-- actions.file_goto_selection_edit:replace(...)
|
||||
a.x:replace_if(function(e)
|
||||
return e > 0
|
||||
end, function(e)
|
||||
return (e / 10)
|
||||
end)
|
||||
eq(-100, a.x(-10))
|
||||
eq(10, a.x(100))
|
||||
eq(1, a.x(10))
|
||||
|
||||
a._clear()
|
||||
eq(100, a.x(10))
|
||||
end)
|
||||
|
||||
it("allows overriding an action only in specific cases with mod", function()
|
||||
local a = transform_mod {
|
||||
x = function(e)
|
||||
return e * 10
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
-- actions.file_goto_selection_edit:replace(...)
|
||||
a.x:replace_map {
|
||||
[function(e)
|
||||
return e > 0
|
||||
end] = function(e)
|
||||
return (e / 10)
|
||||
end,
|
||||
[function(e)
|
||||
return e == 0
|
||||
end] = function(e)
|
||||
return (e + 10)
|
||||
end,
|
||||
}
|
||||
|
||||
eq(-100, a.x(-10))
|
||||
eq(10, a.x(100))
|
||||
eq(1, a.x(10))
|
||||
eq(10, a.x(0))
|
||||
|
||||
a._clear()
|
||||
eq(100, a.x(10))
|
||||
end)
|
||||
|
||||
it("continuous replacement", function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "cleared"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
-- Replace original, which becomes new fallback
|
||||
a.x:replace(function()
|
||||
return "negative"
|
||||
end)
|
||||
|
||||
-- actions.file_goto_selection_edit:replace(...)
|
||||
a.x:replace_map {
|
||||
[function(e)
|
||||
return e > 0
|
||||
end] = function(e)
|
||||
return "positive"
|
||||
end,
|
||||
[function(e)
|
||||
return e == 0
|
||||
end] = function(e)
|
||||
return "zero"
|
||||
end,
|
||||
}
|
||||
|
||||
eq("positive", a.x(10))
|
||||
eq("zero", a.x(0))
|
||||
eq("negative", a.x(-10))
|
||||
|
||||
a._clear()
|
||||
eq("cleared", a.x(10))
|
||||
end)
|
||||
|
||||
it("enhance.pre", function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "x"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
local called_pre = false
|
||||
|
||||
a.y:enhance {
|
||||
pre = function()
|
||||
called_pre = true
|
||||
end,
|
||||
}
|
||||
eq("y", a.y())
|
||||
eq(true, called_pre)
|
||||
end)
|
||||
|
||||
it("enhance.post", function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "x"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
local called_post = false
|
||||
|
||||
a.y:enhance {
|
||||
post = function()
|
||||
called_post = true
|
||||
end,
|
||||
}
|
||||
eq("y", a.y())
|
||||
eq(true, called_post)
|
||||
end)
|
||||
|
||||
it("static_pre static_post", function()
|
||||
local called_pre = false
|
||||
local called_post = false
|
||||
local static_post = 0
|
||||
local a = transform_mod {
|
||||
x = {
|
||||
pre = function()
|
||||
called_pre = true
|
||||
end,
|
||||
action = function()
|
||||
return "x"
|
||||
end,
|
||||
post = function()
|
||||
called_post = true
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
eq("x", a.x())
|
||||
eq(true, called_pre)
|
||||
eq(true, called_post)
|
||||
end)
|
||||
|
||||
it("can call both", function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "x"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
local called_count = 0
|
||||
local count_inc = function()
|
||||
called_count = called_count + 1
|
||||
end
|
||||
|
||||
a.y:enhance {
|
||||
pre = count_inc,
|
||||
post = count_inc,
|
||||
}
|
||||
|
||||
eq("y", a.y())
|
||||
eq(2, called_count)
|
||||
end)
|
||||
|
||||
it("can call both even when combined", function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "x"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
local called_count = 0
|
||||
local count_inc = function()
|
||||
called_count = called_count + 1
|
||||
end
|
||||
|
||||
a.y:enhance {
|
||||
pre = count_inc,
|
||||
post = count_inc,
|
||||
}
|
||||
|
||||
a.x:enhance {
|
||||
post = count_inc,
|
||||
}
|
||||
|
||||
local x_plus_y = a.x + a.y
|
||||
x_plus_y()
|
||||
|
||||
eq(3, called_count)
|
||||
end)
|
||||
|
||||
it(
|
||||
"can call replace fn even when combined before replace registered the fn (because that happens with mappings)",
|
||||
function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "x"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
local called_count = 0
|
||||
local count_inc = function()
|
||||
called_count = called_count + 1
|
||||
end
|
||||
|
||||
local x_plus_y = a.x + a.y
|
||||
a.x:replace(function()
|
||||
count_inc()
|
||||
end)
|
||||
a.y:replace(function()
|
||||
count_inc()
|
||||
end)
|
||||
|
||||
x_plus_y()
|
||||
|
||||
eq(2, called_count)
|
||||
end
|
||||
)
|
||||
|
||||
it(
|
||||
"can call enhance fn even when combined before enhance registed fns (because that happens with mappings)",
|
||||
function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "x"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
local called_count = 0
|
||||
local count_inc = function()
|
||||
called_count = called_count + 1
|
||||
end
|
||||
|
||||
local x_plus_y = a.x + a.y
|
||||
a.y:enhance {
|
||||
pre = count_inc,
|
||||
post = count_inc,
|
||||
}
|
||||
|
||||
a.x:enhance {
|
||||
post = count_inc,
|
||||
}
|
||||
|
||||
x_plus_y()
|
||||
|
||||
eq(3, called_count)
|
||||
end
|
||||
)
|
||||
|
||||
it("clears enhance", function()
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
return "x"
|
||||
end,
|
||||
y = function()
|
||||
return "y"
|
||||
end,
|
||||
}
|
||||
|
||||
local called_post = false
|
||||
|
||||
a.y:enhance {
|
||||
post = function()
|
||||
called_post = true
|
||||
end,
|
||||
}
|
||||
|
||||
a._clear()
|
||||
|
||||
eq("y", a.y())
|
||||
eq(false, called_post)
|
||||
end)
|
||||
|
||||
it("handles passing arguments", function()
|
||||
local a = transform_mod {
|
||||
x = function(bufnr)
|
||||
return string.format "bufnr: %s"
|
||||
end,
|
||||
}
|
||||
|
||||
a.x:replace(function(bufnr)
|
||||
return string.format("modified: %s", bufnr)
|
||||
end)
|
||||
eq("modified: 5", a.x(5))
|
||||
end)
|
||||
|
||||
it("handles add with two different tables", function()
|
||||
local count_a = 0
|
||||
local count_b = 0
|
||||
local a = transform_mod {
|
||||
x = function()
|
||||
count_a = count_a + 1
|
||||
end,
|
||||
}
|
||||
local b = transform_mod {
|
||||
y = function()
|
||||
count_b = count_b + 1
|
||||
end,
|
||||
}
|
||||
|
||||
local called_count = 0
|
||||
local count_inc = function()
|
||||
called_count = called_count + 1
|
||||
end
|
||||
|
||||
a.x:enhance {
|
||||
post = count_inc,
|
||||
}
|
||||
b.y:enhance {
|
||||
post = count_inc,
|
||||
}
|
||||
|
||||
local x_plus_y = a.x + b.y
|
||||
x_plus_y()
|
||||
|
||||
eq(2, called_count)
|
||||
eq(1, count_a)
|
||||
eq(1, count_b)
|
||||
end)
|
||||
|
||||
it("handles tripple concat with static pre post", function()
|
||||
local count_a = 0
|
||||
local count_b = 0
|
||||
local count_c = 0
|
||||
local static_pre = 0
|
||||
local static_post = 0
|
||||
local a = transform_mod {
|
||||
x = {
|
||||
pre = function()
|
||||
static_pre = static_pre + 1
|
||||
end,
|
||||
action = function()
|
||||
count_a = count_a + 1
|
||||
end,
|
||||
post = function()
|
||||
static_post = static_post + 1
|
||||
end,
|
||||
},
|
||||
}
|
||||
local b = transform_mod {
|
||||
y = {
|
||||
pre = function()
|
||||
static_pre = static_pre + 1
|
||||
end,
|
||||
action = function()
|
||||
count_b = count_b + 1
|
||||
end,
|
||||
post = function()
|
||||
static_post = static_post + 1
|
||||
end,
|
||||
},
|
||||
}
|
||||
local c = transform_mod {
|
||||
z = {
|
||||
pre = function()
|
||||
static_pre = static_pre + 1
|
||||
end,
|
||||
action = function()
|
||||
count_c = count_c + 1
|
||||
end,
|
||||
post = function()
|
||||
static_post = static_post + 1
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
local replace_count = 0
|
||||
a.x:replace(function()
|
||||
replace_count = replace_count + 1
|
||||
end)
|
||||
|
||||
local x_plus_y_plus_z = a.x + b.y + c.z
|
||||
x_plus_y_plus_z()
|
||||
|
||||
eq(0, count_a)
|
||||
eq(1, count_b)
|
||||
eq(1, count_c)
|
||||
eq(1, replace_count)
|
||||
eq(3, static_pre)
|
||||
eq(3, static_post)
|
||||
end)
|
||||
|
||||
describe("action_set", function()
|
||||
it("can replace `action_set.edit`", function()
|
||||
action_set.edit:replace(function(_, arg)
|
||||
return "replaced:" .. arg
|
||||
end)
|
||||
eq("replaced:edit", actions.file_edit())
|
||||
eq("replaced:vnew", actions.file_vsplit())
|
||||
end)
|
||||
|
||||
pending("handles backwards compat with select and edit files", function()
|
||||
-- Reproduce steps:
|
||||
-- In config, we have { ["<CR>"] = actions.select, ... }
|
||||
-- In caller, we have actions._goto:replace(...)
|
||||
-- Person calls `select`, does not see update
|
||||
action_set.edit:replace(function(_, arg)
|
||||
return "default_to_edit:" .. arg
|
||||
end)
|
||||
eq("default_to_edit:edit", actions.select_default())
|
||||
|
||||
action_set.select:replace(function(_, arg)
|
||||
return "override_with_select:" .. arg
|
||||
end)
|
||||
eq("override_with_select:default", actions.select_default())
|
||||
|
||||
-- Sometimes you might want to change the default selection...
|
||||
-- but you don't want to prohibit the ability to edit the code...
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,102 @@
|
||||
local command = require "telescope.command"
|
||||
|
||||
local eq = assert.are.same
|
||||
|
||||
describe("command_parser", function()
|
||||
local test_parse = function(should, input, output)
|
||||
it(should, function()
|
||||
command.convert_user_opts(input)
|
||||
eq(output, input)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Strings
|
||||
test_parse("should handle cwd", { cwd = "string" }, { cwd = "string" })
|
||||
|
||||
-- Find commands
|
||||
test_parse(
|
||||
"should handle find_command 1",
|
||||
{ find_command = "rg,--ignore,--hidden,files" },
|
||||
{ find_command = { "rg", "--ignore", "--hidden", "files" } }
|
||||
)
|
||||
test_parse(
|
||||
"should handle find_command 2",
|
||||
{ find_command = "fd,-t,f,-H" },
|
||||
{ find_command = { "fd", "-t", "f", "-H" } }
|
||||
)
|
||||
test_parse(
|
||||
"should handle find_command 3",
|
||||
{ find_command = "fdfind,--type,f,--no-ignore" },
|
||||
{ find_command = { "fdfind", "--type", "f", "--no-ignore" } }
|
||||
)
|
||||
|
||||
-- Dictionaries/tables
|
||||
test_parse(
|
||||
"should handle layout_config viml 1",
|
||||
{ layout_config = "{'prompt_position':'top'}" },
|
||||
{ layout_config = { prompt_position = "top" } }
|
||||
)
|
||||
test_parse(
|
||||
"should handle layout_config viml 2",
|
||||
{ layout_config = "#{prompt_position:'bottom'}" },
|
||||
{ layout_config = { prompt_position = "bottom" } }
|
||||
)
|
||||
test_parse(
|
||||
"should handle layout_config viml 3",
|
||||
{ layout_config = "{'mirror':v:true}" },
|
||||
{ layout_config = { mirror = true } }
|
||||
)
|
||||
test_parse(
|
||||
"should handle layout_config viml 4",
|
||||
{ layout_config = "#{mirror:v:true}" },
|
||||
{ layout_config = { mirror = true } }
|
||||
)
|
||||
test_parse(
|
||||
"should handle layout_config lua 1",
|
||||
{ layout_config = "{prompt_position='bottom'}" },
|
||||
{ layout_config = { prompt_position = "bottom" } }
|
||||
)
|
||||
test_parse(
|
||||
"should handle layout_config lua 2",
|
||||
{ layout_config = "{mirror=true}" },
|
||||
{ layout_config = { mirror = true } }
|
||||
)
|
||||
|
||||
-- Lists/tables
|
||||
test_parse(
|
||||
"should handle symbols commas list",
|
||||
{ symbols = "alpha,beta,gamma" },
|
||||
{ symbols = { "alpha", "beta", "gamma" } }
|
||||
)
|
||||
test_parse(
|
||||
"should handle symbols viml list",
|
||||
{ symbols = "['alpha','beta','gamma']" },
|
||||
{ symbols = { "alpha", "beta", "gamma" } }
|
||||
)
|
||||
test_parse(
|
||||
"should handle symbols lua list",
|
||||
{ symbols = "{'alpha','beta','gamma'}" },
|
||||
{ symbols = { "alpha", "beta", "gamma" } }
|
||||
)
|
||||
|
||||
-- Booleans
|
||||
test_parse("should handle booleans 1", { hidden = "true" }, { hidden = true })
|
||||
test_parse("should handle booleans 2", { no_ignore = "false" }, { no_ignore = false })
|
||||
|
||||
-- Numbers
|
||||
test_parse("should handle numbers 1", { depth = "2" }, { depth = 2 })
|
||||
test_parse("should handle numbers 2", { bufnr_width = "4" }, { bufnr_width = 4 })
|
||||
test_parse("should handle numbers 3", { severity = "27" }, { severity = 27 })
|
||||
|
||||
-- Multiple options
|
||||
test_parse(
|
||||
"should handle multiple options 1",
|
||||
{ layout_config = '{prompt_position="top"}', cwd = "/foobar", severity = "27" },
|
||||
{ layout_config = { prompt_position = "top" }, cwd = "/foobar", severity = 27 }
|
||||
)
|
||||
test_parse(
|
||||
"should handle multiple options 2",
|
||||
{ symbols = "['alef','bet','gimel']", depth = "2", find_command = "rg,--ignore,files" },
|
||||
{ symbols = { "alef", "bet", "gimel" }, depth = 2, find_command = { "rg", "--ignore", "files" } }
|
||||
)
|
||||
end)
|
||||
@ -0,0 +1,34 @@
|
||||
local entry_display = require "telescope.pickers.entry_display"
|
||||
|
||||
describe("truncate", function()
|
||||
for _, ambiwidth in ipairs { "single", "double" } do
|
||||
for _, case in ipairs {
|
||||
{ args = { "abcde", 6 }, expected = { single = "abcde", double = "abcde" } },
|
||||
{ args = { "abcde", 5 }, expected = { single = "abcde", double = "abcde" } },
|
||||
{ args = { "abcde", 4 }, expected = { single = "abc…", double = "ab…" } },
|
||||
{ args = { "アイウエオ", 11 }, expected = { single = "アイウエオ", double = "アイウエオ" } },
|
||||
{ args = { "アイウエオ", 10 }, expected = { single = "アイウエオ", double = "アイウエオ" } },
|
||||
{ args = { "アイウエオ", 9 }, expected = { single = "アイウエ…", double = "アイウ…" } },
|
||||
{ args = { "アイウエオ", 8 }, expected = { single = "アイウ…", double = "アイウ…" } },
|
||||
{ args = { "├─┤", 7 }, expected = { single = "├─┤", double = "├─┤" } },
|
||||
{ args = { "├─┤", 6 }, expected = { single = "├─┤", double = "├─┤" } },
|
||||
{ args = { "├─┤", 5 }, expected = { single = "├─┤", double = "├…" } },
|
||||
{ args = { "├─┤", 4 }, expected = { single = "├─┤", double = "├…" } },
|
||||
{ args = { "├─┤", 3 }, expected = { single = "├─┤", double = "…" } },
|
||||
{ args = { "├─┤", 2 }, expected = { single = "├…", double = "…" } },
|
||||
} do
|
||||
local msg = ("can truncate: ambiwidth = %s, [%s, %d] -> %s"):format(
|
||||
ambiwidth,
|
||||
case.args[1],
|
||||
case.args[2],
|
||||
case.expected[ambiwidth]
|
||||
)
|
||||
it(msg, function()
|
||||
local original = vim.o.ambiwidth
|
||||
vim.o.ambiwidth = ambiwidth
|
||||
assert.are.same(case.expected[ambiwidth], entry_display.truncate(case.args[1], case.args[2]))
|
||||
vim.o.ambiwidth = original
|
||||
end)
|
||||
end
|
||||
end
|
||||
end)
|
||||
@ -0,0 +1,189 @@
|
||||
local EntryManager = require "telescope.entry_manager"
|
||||
|
||||
local eq = assert.are.same
|
||||
|
||||
describe("process_result", function()
|
||||
it("works with one entry", function()
|
||||
local manager = EntryManager:new(5, nil)
|
||||
|
||||
manager:add_entry(nil, 1, "hello", "")
|
||||
|
||||
eq(1, manager:get_score(1))
|
||||
end)
|
||||
|
||||
it("works with two entries", function()
|
||||
local manager = EntryManager:new(5, nil)
|
||||
|
||||
manager:add_entry(nil, 1, "hello", "")
|
||||
manager:add_entry(nil, 2, "later", "")
|
||||
|
||||
eq(2, manager.linked_states.size)
|
||||
|
||||
eq("hello", manager:get_entry(1))
|
||||
eq("later", manager:get_entry(2))
|
||||
end)
|
||||
|
||||
it("calls functions when inserting", function()
|
||||
local called_count = 0
|
||||
local manager = EntryManager:new(5, function()
|
||||
called_count = called_count + 1
|
||||
end)
|
||||
|
||||
assert(called_count == 0)
|
||||
manager:add_entry(nil, 1, "hello", "")
|
||||
assert(called_count == 1)
|
||||
end)
|
||||
|
||||
it("calls functions when inserting twice", function()
|
||||
local called_count = 0
|
||||
local manager = EntryManager:new(5, function()
|
||||
called_count = called_count + 1
|
||||
end)
|
||||
|
||||
assert(called_count == 0)
|
||||
manager:add_entry(nil, 1, "hello", "")
|
||||
manager:add_entry(nil, 2, "world", "")
|
||||
assert(called_count == 2)
|
||||
end)
|
||||
|
||||
it("correctly sorts lower scores", function()
|
||||
local called_count = 0
|
||||
local manager = EntryManager:new(5, function()
|
||||
called_count = called_count + 1
|
||||
end)
|
||||
manager:add_entry(nil, 5, "worse result", "")
|
||||
manager:add_entry(nil, 2, "better result", "")
|
||||
|
||||
eq("better result", manager:get_entry(1))
|
||||
eq("worse result", manager:get_entry(2))
|
||||
|
||||
eq(2, called_count)
|
||||
end)
|
||||
|
||||
it("respects max results", function()
|
||||
local called_count = 0
|
||||
local manager = EntryManager:new(1, function()
|
||||
called_count = called_count + 1
|
||||
end)
|
||||
manager:add_entry(nil, 2, "better result", "")
|
||||
manager:add_entry(nil, 5, "worse result", "")
|
||||
|
||||
eq("better result", manager:get_entry(1))
|
||||
eq(1, called_count)
|
||||
end)
|
||||
|
||||
it("should allow simple entries", function()
|
||||
local manager = EntryManager:new(5)
|
||||
|
||||
local counts_executed = 0
|
||||
manager:add_entry(
|
||||
nil,
|
||||
1,
|
||||
setmetatable({}, {
|
||||
__index = function(t, k)
|
||||
local val = nil
|
||||
if k == "ordinal" then
|
||||
counts_executed = counts_executed + 1
|
||||
|
||||
-- This could be expensive, only call later
|
||||
val = "wow"
|
||||
end
|
||||
|
||||
rawset(t, k, val)
|
||||
return val
|
||||
end,
|
||||
}),
|
||||
""
|
||||
)
|
||||
|
||||
eq("wow", manager:get_ordinal(1))
|
||||
eq("wow", manager:get_ordinal(1))
|
||||
eq("wow", manager:get_ordinal(1))
|
||||
|
||||
eq(1, counts_executed)
|
||||
end)
|
||||
|
||||
it("should not loop a bunch", function()
|
||||
local info = {}
|
||||
local manager = EntryManager:new(5, nil, info)
|
||||
manager:add_entry(nil, 4, "better result", "")
|
||||
manager:add_entry(nil, 3, "better result", "")
|
||||
manager:add_entry(nil, 2, "better result", "")
|
||||
|
||||
-- Loops once to find 3 < 4
|
||||
-- Loops again to find 2 < 3
|
||||
eq(2, info.looped)
|
||||
end)
|
||||
|
||||
it("should not loop a bunch, part 2", function()
|
||||
local info = {}
|
||||
local manager = EntryManager:new(5, nil, info)
|
||||
manager:add_entry(nil, 4, "better result", "")
|
||||
manager:add_entry(nil, 2, "better result", "")
|
||||
manager:add_entry(nil, 3, "better result", "")
|
||||
|
||||
-- Loops again to find 2 < 4
|
||||
-- Loops once to find 3 > 2
|
||||
-- but less than 4
|
||||
eq(3, info.looped)
|
||||
end)
|
||||
|
||||
it("should update worst score in all append case", function()
|
||||
local manager = EntryManager:new(2, nil)
|
||||
manager:add_entry(nil, 2, "result 2", "")
|
||||
manager:add_entry(nil, 3, "result 3", "")
|
||||
manager:add_entry(nil, 4, "result 4", "")
|
||||
|
||||
eq(3, manager.worst_acceptable_score)
|
||||
end)
|
||||
|
||||
it("should update worst score in all prepend case", function()
|
||||
local called_count = 0
|
||||
local manager = EntryManager:new(2, function()
|
||||
called_count = called_count + 1
|
||||
end)
|
||||
manager:add_entry(nil, 5, "worse result", "")
|
||||
manager:add_entry(nil, 4, "less worse result", "")
|
||||
manager:add_entry(nil, 2, "better result", "")
|
||||
|
||||
-- Once for insert 5
|
||||
-- Once for prepend 4
|
||||
-- Once for prepend 2
|
||||
eq(3, called_count)
|
||||
|
||||
eq("better result", manager:get_entry(1))
|
||||
eq(4, manager.worst_acceptable_score)
|
||||
end)
|
||||
|
||||
it("should call tiebreaker if score is the same, sort length", function()
|
||||
local manager = EntryManager:new(5, nil)
|
||||
local picker = {
|
||||
tiebreak = function(curr, prev, prompt)
|
||||
eq("asdf", prompt)
|
||||
return #curr < #prev
|
||||
end,
|
||||
}
|
||||
|
||||
manager:add_entry(picker, 0.5, "same same", "asdf")
|
||||
manager:add_entry(picker, 0.5, "same", "asdf")
|
||||
|
||||
eq("same", manager:get_entry(1))
|
||||
eq("same same", manager:get_entry(2))
|
||||
end)
|
||||
|
||||
it("should call tiebreaker if score is the same, keep initial", function()
|
||||
local manager = EntryManager:new(5, nil)
|
||||
local picker = {
|
||||
tiebreak = function(_, _, prompt)
|
||||
eq("asdf", prompt)
|
||||
return false
|
||||
end,
|
||||
}
|
||||
|
||||
manager:add_entry(picker, 0.5, "same same", "asdf")
|
||||
manager:add_entry(picker, 0.5, "same", "asdf")
|
||||
|
||||
eq("same", manager:get_entry(2))
|
||||
eq("same same", manager:get_entry(1))
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,161 @@
|
||||
local config = require "telescope.config"
|
||||
local resolve = require "telescope.config.resolve"
|
||||
local layout_strats = require "telescope.pickers.layout_strategies"
|
||||
|
||||
local validate_layout_config = layout_strats._validate_layout_config
|
||||
|
||||
local eq = assert.are.same
|
||||
|
||||
describe("layout_strategies", function()
|
||||
it("should have validator", function()
|
||||
assert(validate_layout_config, "Has validator")
|
||||
end)
|
||||
|
||||
local test_height = function(should, output, input, opts)
|
||||
opts = opts or {}
|
||||
|
||||
local max_columns, max_lines = opts.max_columns or 100, opts.max_lines or 100
|
||||
it(should, function()
|
||||
local layout_config = validate_layout_config("horizontal", { height = true }, { height = input })
|
||||
|
||||
eq(output, resolve.resolve_height(layout_config.height)({}, max_columns, max_lines))
|
||||
end)
|
||||
end
|
||||
|
||||
test_height("should handle numbers", 10, 10)
|
||||
|
||||
test_height("should handle percentage: 100", 10, 0.1, { max_lines = 100 })
|
||||
test_height("should handle percentage: 110", 11, 0.1, { max_lines = 110 })
|
||||
|
||||
test_height("should call functions: simple", 5, function()
|
||||
return 5
|
||||
end)
|
||||
test_height("should call functions: percentage", 15, function(_, _, lines)
|
||||
return 0.1 * lines
|
||||
end, {
|
||||
max_lines = 150,
|
||||
})
|
||||
|
||||
local test_defaults_key = function(should, key, strat, output, ours, theirs, override)
|
||||
ours = ours or {}
|
||||
theirs = theirs or {}
|
||||
override = override or {}
|
||||
|
||||
it(should, function()
|
||||
config.clear_defaults()
|
||||
config.set_defaults({ layout_config = theirs }, { layout_config = { ours, "description" } })
|
||||
local layout_config = validate_layout_config(strat, layout_strats._configurations[strat], override)
|
||||
eq(output, layout_config[key])
|
||||
end)
|
||||
end
|
||||
|
||||
test_defaults_key(
|
||||
"should use ours if theirs and override don't give the key",
|
||||
"height",
|
||||
"horizontal",
|
||||
50,
|
||||
{ height = 50 },
|
||||
{ width = 100 },
|
||||
{ width = 120 }
|
||||
)
|
||||
|
||||
test_defaults_key(
|
||||
"should use ours if theirs and override don't give the key for this strategy",
|
||||
"height",
|
||||
"horizontal",
|
||||
50,
|
||||
{ height = 50 },
|
||||
{ vertical = { height = 100 } },
|
||||
{ vertical = { height = 120 } }
|
||||
)
|
||||
|
||||
test_defaults_key(
|
||||
"should use theirs if override doesn't give the key",
|
||||
"height",
|
||||
"horizontal",
|
||||
100,
|
||||
{ height = 50 },
|
||||
{ height = 100 },
|
||||
{ width = 120 }
|
||||
)
|
||||
|
||||
test_defaults_key(
|
||||
"should use override if key given",
|
||||
"height",
|
||||
"horizontal",
|
||||
120,
|
||||
{ height = 50 },
|
||||
{ height = 100 },
|
||||
{ height = 120 }
|
||||
)
|
||||
|
||||
test_defaults_key(
|
||||
"should use override if key given for this strategy",
|
||||
"height",
|
||||
"horizontal",
|
||||
120,
|
||||
{ height = 50 },
|
||||
{ height = 100 },
|
||||
{ horizontal = { height = 120 } }
|
||||
)
|
||||
|
||||
test_defaults_key(
|
||||
"should use theirs if override doesn't give key (even if ours has strategy specific)",
|
||||
"height",
|
||||
"horizontal",
|
||||
100,
|
||||
{ horizontal = { height = 50 } },
|
||||
{ height = 100 },
|
||||
{ width = 120 }
|
||||
)
|
||||
|
||||
test_defaults_key(
|
||||
"should use override (even if ours has strategy specific)",
|
||||
"height",
|
||||
"horizontal",
|
||||
120,
|
||||
{ horizontal = { height = 50 } },
|
||||
{ height = 100 },
|
||||
{ height = 120 }
|
||||
)
|
||||
|
||||
test_defaults_key(
|
||||
"should use override (even if theirs has strategy specific)",
|
||||
"height",
|
||||
"horizontal",
|
||||
120,
|
||||
{ height = 50 },
|
||||
{ horizontal = { height = 100 } },
|
||||
{ height = 120 }
|
||||
)
|
||||
|
||||
test_defaults_key(
|
||||
"should use override (even if ours and theirs have strategy specific)",
|
||||
"height",
|
||||
"horizontal",
|
||||
120,
|
||||
{ horizontal = { height = 50 } },
|
||||
{ horizontal = { height = 100 } },
|
||||
{ height = 120 }
|
||||
)
|
||||
|
||||
test_defaults_key(
|
||||
"should handle user config overriding a table with a number",
|
||||
"height",
|
||||
"horizontal",
|
||||
120,
|
||||
{ height = { padding = 5 } },
|
||||
{ height = 120 },
|
||||
{}
|
||||
)
|
||||
|
||||
test_defaults_key(
|
||||
"should handle user oneshot overriding a table with a number",
|
||||
"height",
|
||||
"horizontal",
|
||||
120,
|
||||
{},
|
||||
{ height = { padding = 5 } },
|
||||
{ height = 120 }
|
||||
)
|
||||
end)
|
||||
@ -0,0 +1,133 @@
|
||||
local LinkedList = require "telescope.algos.linked_list"
|
||||
|
||||
describe("LinkedList", function()
|
||||
it("can create a list", function()
|
||||
local l = LinkedList:new()
|
||||
|
||||
assert.are.same(0, l.size)
|
||||
end)
|
||||
|
||||
it("can add a single entry to the list", function()
|
||||
local l = LinkedList:new()
|
||||
l:append "hello"
|
||||
|
||||
assert.are.same(1, l.size)
|
||||
end)
|
||||
|
||||
it("can iterate over one item", function()
|
||||
local l = LinkedList:new()
|
||||
l:append "hello"
|
||||
|
||||
for val in l:iter() do
|
||||
assert.are.same("hello", val)
|
||||
end
|
||||
end)
|
||||
|
||||
it("iterates in order", function()
|
||||
local l = LinkedList:new()
|
||||
l:append "hello"
|
||||
l:append "world"
|
||||
|
||||
local x = {}
|
||||
for val in l:iter() do
|
||||
table.insert(x, val)
|
||||
end
|
||||
|
||||
assert.are.same({ "hello", "world" }, x)
|
||||
end)
|
||||
|
||||
it("iterates in order, for prepend", function()
|
||||
local l = LinkedList:new()
|
||||
l:prepend "world"
|
||||
l:prepend "hello"
|
||||
|
||||
local x = {}
|
||||
for val in l:iter() do
|
||||
table.insert(x, val)
|
||||
end
|
||||
|
||||
assert.are.same({ "hello", "world" }, x)
|
||||
end)
|
||||
|
||||
it("iterates in order, for combo", function()
|
||||
local l = LinkedList:new()
|
||||
l:prepend "world"
|
||||
l:prepend "hello"
|
||||
l:append "last"
|
||||
l:prepend "first"
|
||||
|
||||
local x = {}
|
||||
for val in l:iter() do
|
||||
table.insert(x, val)
|
||||
end
|
||||
|
||||
assert.are.same({ "first", "hello", "world", "last" }, x)
|
||||
assert.are.same(#x, l.size)
|
||||
end)
|
||||
|
||||
it("has ipairs", function()
|
||||
local l = LinkedList:new()
|
||||
l:prepend "world"
|
||||
l:prepend "hello"
|
||||
l:append "last"
|
||||
l:prepend "first"
|
||||
|
||||
local x = {}
|
||||
for v in l:iter() do
|
||||
table.insert(x, v)
|
||||
end
|
||||
assert.are.same({ "first", "hello", "world", "last" }, x)
|
||||
|
||||
local expected = {}
|
||||
for i, v in ipairs(x) do
|
||||
table.insert(expected, { i, v })
|
||||
end
|
||||
|
||||
local actual = {}
|
||||
for i, v in l:ipairs() do
|
||||
table.insert(actual, { i, v })
|
||||
end
|
||||
|
||||
assert.are.same(expected, actual)
|
||||
end)
|
||||
|
||||
describe("track_at", function()
|
||||
it("should update tracked when only appending", function()
|
||||
local l = LinkedList:new { track_at = 2 }
|
||||
l:append "first"
|
||||
l:append "second"
|
||||
l:append "third"
|
||||
|
||||
assert.are.same("second", l.tracked)
|
||||
end)
|
||||
|
||||
it("should update tracked when first some prepend and then append", function()
|
||||
local l = LinkedList:new { track_at = 2 }
|
||||
l:prepend "first"
|
||||
l:append "second"
|
||||
l:append "third"
|
||||
|
||||
assert.are.same("second", l.tracked)
|
||||
end)
|
||||
|
||||
it("should update when only prepending", function()
|
||||
local l = LinkedList:new { track_at = 2 }
|
||||
l:prepend "third"
|
||||
l:prepend "second"
|
||||
l:prepend "first"
|
||||
|
||||
assert.are.same("second", l.tracked)
|
||||
end)
|
||||
|
||||
it("should update when lots of prepend and append", function()
|
||||
local l = LinkedList:new { track_at = 2 }
|
||||
l:prepend "third"
|
||||
l:prepend "second"
|
||||
l:prepend "first"
|
||||
l:append "fourth"
|
||||
l:prepend "zeroth"
|
||||
|
||||
assert.are.same("first", l.tracked)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,143 @@
|
||||
-- Just skip on mac, it has flaky CI for some reason
|
||||
if vim.fn.has "mac" == 1 or require("telescope.utils").iswin then
|
||||
return
|
||||
end
|
||||
|
||||
local tester = require "telescope.testharness"
|
||||
|
||||
local disp = function(val)
|
||||
return vim.inspect(val, { newline = " ", indent = "" })
|
||||
end
|
||||
|
||||
describe("builtin.find_files", function()
|
||||
it("should find the readme", function()
|
||||
tester.run_file "find_files__readme"
|
||||
end)
|
||||
|
||||
it("should handle cycling for full list", function()
|
||||
tester.run_file "find_files__scrolling_descending_cycle"
|
||||
end)
|
||||
|
||||
for _, configuration in ipairs {
|
||||
{ sorting_strategy = "descending" },
|
||||
{ sorting_strategy = "ascending" },
|
||||
} do
|
||||
it("should not display devicons when disabled: " .. disp(configuration), function()
|
||||
tester.run_string(string.format(
|
||||
[[
|
||||
local max_results = 5
|
||||
|
||||
runner.picker('find_files', 'README.md', {
|
||||
post_typed = {
|
||||
{ "> README.md", GetPrompt },
|
||||
{ "> README.md", GetBestResult },
|
||||
},
|
||||
post_close = {
|
||||
{ 'README.md', GetFile },
|
||||
{ 'README.md', GetFile },
|
||||
}
|
||||
}, vim.tbl_extend("force", {
|
||||
disable_devicons = true,
|
||||
sorter = require('telescope.sorters').get_fzy_sorter(),
|
||||
layout_strategy = 'center',
|
||||
layout_config = {
|
||||
height = max_results + 1,
|
||||
width = 0.9,
|
||||
},
|
||||
}, vim.json.decode([==[%s]==])))
|
||||
]],
|
||||
vim.json.encode(configuration)
|
||||
))
|
||||
end)
|
||||
|
||||
pending("use devicons, if it has it when enabled", function()
|
||||
if not pcall(require, "nvim-web-devicons") then
|
||||
return
|
||||
end
|
||||
|
||||
local md = require("nvim-web-devicons").get_icon "md"
|
||||
tester.run_string(string.format(
|
||||
[[
|
||||
runner.picker('find_files', 'README.md', {
|
||||
post_typed = {
|
||||
{ "> README.md", GetPrompt },
|
||||
{ "> %s README.md", GetBestResult }
|
||||
},
|
||||
post_close = {
|
||||
{ 'README.md', GetFile },
|
||||
{ 'README.md', GetFile },
|
||||
}
|
||||
}, vim.tbl_extend("force", {
|
||||
disable_devicons = false,
|
||||
sorter = require('telescope.sorters').get_fzy_sorter(),
|
||||
}, vim.json.decode([==[%s]==])))
|
||||
]],
|
||||
md,
|
||||
vim.json.encode(configuration)
|
||||
))
|
||||
end)
|
||||
end
|
||||
|
||||
it("should find the readme, using lowercase", function()
|
||||
tester.run_string [[
|
||||
runner.picker('find_files', 'readme.md', {
|
||||
post_close = {
|
||||
{ 'README.md', GetFile },
|
||||
}
|
||||
})
|
||||
]]
|
||||
end)
|
||||
|
||||
it("should find the pickers.lua, using lowercase", function()
|
||||
tester.run_string [[
|
||||
runner.picker('find_files', 'pickers.lua', {
|
||||
post_close = {
|
||||
{ 'pickers.lua', GetFile },
|
||||
}
|
||||
})
|
||||
]]
|
||||
end)
|
||||
|
||||
it("should find the pickers.lua", function()
|
||||
tester.run_string [[
|
||||
runner.picker('find_files', 'pickers.lua', {
|
||||
post_close = {
|
||||
{ 'pickers.lua', GetFile },
|
||||
{ 'pickers.lua', GetFile },
|
||||
}
|
||||
})
|
||||
]]
|
||||
end)
|
||||
|
||||
it("should be able to c-n the items", function()
|
||||
tester.run_string [[
|
||||
runner.picker('find_files', 'fixtures/find_files/file<c-n>', {
|
||||
post_typed = {
|
||||
{
|
||||
{
|
||||
" lua/tests/fixtures/find_files/file_a.txt",
|
||||
"> lua/tests/fixtures/find_files/file_abc.txt",
|
||||
}, GetResults
|
||||
},
|
||||
},
|
||||
post_close = {
|
||||
{ 'file_abc.txt', GetFile },
|
||||
},
|
||||
}, {
|
||||
sorter = require('telescope.sorters').get_fzy_sorter(),
|
||||
sorting_strategy = "ascending",
|
||||
disable_devicons = true,
|
||||
})
|
||||
]]
|
||||
end)
|
||||
|
||||
it("should be able to get the current selection", function()
|
||||
tester.run_string [[
|
||||
runner.picker('find_files', 'fixtures/find_files/file_abc', {
|
||||
post_typed = {
|
||||
{ 'lua/tests/fixtures/find_files/file_abc.txt', GetSelectionValue },
|
||||
}
|
||||
})
|
||||
]]
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,208 @@
|
||||
local eq = function(a, b)
|
||||
assert.are.same(a, b)
|
||||
end
|
||||
|
||||
local resolve = require "telescope.config.resolve"
|
||||
|
||||
describe("telescope.config.resolve", function()
|
||||
describe("win_option", function()
|
||||
it("should resolve for percentages", function()
|
||||
local height_config = 0.8
|
||||
local opt = resolve.win_option(height_config)
|
||||
|
||||
eq(height_config, opt.preview)
|
||||
eq(height_config, opt.prompt)
|
||||
eq(height_config, opt.results)
|
||||
end)
|
||||
|
||||
it("should resolve for percentages with default", function()
|
||||
local height_config = 0.8
|
||||
local opt = resolve.win_option(nil, height_config)
|
||||
|
||||
eq(height_config, opt.preview)
|
||||
eq(height_config, opt.prompt)
|
||||
eq(height_config, opt.results)
|
||||
end)
|
||||
|
||||
it("should resolve table values", function()
|
||||
local table_val = { "a" }
|
||||
local opt = resolve.win_option(nil, table_val)
|
||||
|
||||
eq(table_val, opt.preview)
|
||||
eq(table_val, opt.prompt)
|
||||
eq(table_val, opt.results)
|
||||
end)
|
||||
|
||||
it("should allow overrides for different wins", function()
|
||||
local prompt_override = { "a", prompt = "b" }
|
||||
local opt = resolve.win_option(prompt_override)
|
||||
eq("a", opt.preview)
|
||||
eq("a", opt.results)
|
||||
eq("b", opt.prompt)
|
||||
end)
|
||||
|
||||
it("should allow overrides for all wins", function()
|
||||
local all_specified = { preview = "a", prompt = "b", results = "c" }
|
||||
local opt = resolve.win_option(all_specified)
|
||||
eq("a", opt.preview)
|
||||
eq("b", opt.prompt)
|
||||
eq("c", opt.results)
|
||||
end)
|
||||
|
||||
it("should allow some specified with a simple default", function()
|
||||
local some_specified = { prompt = "b", results = "c" }
|
||||
local opt = resolve.win_option(some_specified, "a")
|
||||
eq("a", opt.preview)
|
||||
eq("b", opt.prompt)
|
||||
eq("c", opt.results)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("resolve_height/width", function()
|
||||
local test_sizes = {
|
||||
{ 24, 100 },
|
||||
{ 35, 125 },
|
||||
{ 60, 59 },
|
||||
{ 100, 40 },
|
||||
}
|
||||
it("should handle percentages", function()
|
||||
local percentages = { 0.1, 0.33333, 0.5, 0.99 }
|
||||
for _, s in ipairs(test_sizes) do
|
||||
for _, p in ipairs(percentages) do
|
||||
eq(math.floor(s[1] * p), resolve.resolve_width(p)(nil, unpack(s)))
|
||||
eq(math.floor(s[2] * p), resolve.resolve_height(p)(nil, unpack(s)))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
it("should handle percentages with min/max boundary", function()
|
||||
eq(20, resolve.resolve_width { 0.1, min = 20 }(nil, 40, 120))
|
||||
eq(30, resolve.resolve_height { 0.1, min = 20 }(nil, 40, 300))
|
||||
|
||||
eq(24, resolve.resolve_width { 0.4, max = 80 }(nil, 60, 60))
|
||||
eq(80, resolve.resolve_height { 0.4, max = 80 }(nil, 60, 300))
|
||||
end)
|
||||
|
||||
it("should handle fixed size", function()
|
||||
local fixed = { 5, 8, 13, 21, 34 }
|
||||
for _, s in ipairs(test_sizes) do
|
||||
for _, f in ipairs(fixed) do
|
||||
eq(math.min(f, s[1]), resolve.resolve_width(f)(nil, unpack(s)))
|
||||
eq(math.min(f, s[2]), resolve.resolve_height(f)(nil, unpack(s)))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
it("should handle functions", function()
|
||||
local func = function(_, max_columns, max_lines)
|
||||
if max_columns < 45 then
|
||||
return math.min(max_columns, max_lines)
|
||||
elseif max_columns < max_lines then
|
||||
return max_columns * 0.8
|
||||
else
|
||||
return math.min(max_columns, max_lines) * 0.5
|
||||
end
|
||||
end
|
||||
for _, s in ipairs(test_sizes) do
|
||||
eq(func(nil, unpack(s)), resolve.resolve_height(func)(nil, unpack(s)))
|
||||
end
|
||||
end)
|
||||
|
||||
it("should handle padding", function()
|
||||
local func = function(_, max_columns, max_lines)
|
||||
return math.floor(math.min(max_columns * 0.6, max_lines * 0.8))
|
||||
end
|
||||
local pads = { 0.1, 5, func }
|
||||
for _, s in ipairs(test_sizes) do
|
||||
for _, p in ipairs(pads) do
|
||||
eq(s[1] - 2 * resolve.resolve_width(p)(nil, unpack(s)), resolve.resolve_width { padding = p }(nil, unpack(s)))
|
||||
eq(
|
||||
s[2] - 2 * resolve.resolve_height(p)(nil, unpack(s)),
|
||||
resolve.resolve_height { padding = p }(nil, unpack(s))
|
||||
)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("resolve_anchor_pos", function()
|
||||
local test_sizes = {
|
||||
{ 6, 7, 8, 9 },
|
||||
{ 10, 20, 30, 40 },
|
||||
{ 15, 15, 16, 16 },
|
||||
{ 17, 19, 23, 31 },
|
||||
{ 21, 18, 26, 24 },
|
||||
{ 50, 100, 150, 200 },
|
||||
}
|
||||
|
||||
it([[should not adjust when "CENTER" or "" is the anchor]], function()
|
||||
for _, s in ipairs(test_sizes) do
|
||||
eq({ 0, 0 }, resolve.resolve_anchor_pos("", unpack(s)))
|
||||
eq({ 0, 0 }, resolve.resolve_anchor_pos("center", unpack(s)))
|
||||
eq({ 0, 0 }, resolve.resolve_anchor_pos("CENTER", unpack(s)))
|
||||
end
|
||||
end)
|
||||
|
||||
it([[should end up at top when "N" in the anchor]], function()
|
||||
local top_test = function(anchor, p_width, p_height, max_columns, max_lines)
|
||||
local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines)
|
||||
eq(1, pos[2] + math.floor((max_lines - p_height) / 2))
|
||||
end
|
||||
for _, s in ipairs(test_sizes) do
|
||||
top_test("NW", unpack(s))
|
||||
top_test("N", unpack(s))
|
||||
top_test("NE", unpack(s))
|
||||
end
|
||||
end)
|
||||
|
||||
it([[should end up at left when "W" in the anchor]], function()
|
||||
local left_test = function(anchor, p_width, p_height, max_columns, max_lines)
|
||||
local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines)
|
||||
eq(1, pos[1] + math.floor((max_columns - p_width) / 2))
|
||||
end
|
||||
for _, s in ipairs(test_sizes) do
|
||||
left_test("NW", unpack(s))
|
||||
left_test("W", unpack(s))
|
||||
left_test("SW", unpack(s))
|
||||
end
|
||||
end)
|
||||
|
||||
it([[should end up at bottom when "S" in the anchor]], function()
|
||||
local bot_test = function(anchor, p_width, p_height, max_columns, max_lines)
|
||||
local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines)
|
||||
eq(max_lines - 1, pos[2] + p_height + math.floor((max_lines - p_height) / 2))
|
||||
end
|
||||
for _, s in ipairs(test_sizes) do
|
||||
bot_test("SW", unpack(s))
|
||||
bot_test("S", unpack(s))
|
||||
bot_test("SE", unpack(s))
|
||||
end
|
||||
end)
|
||||
|
||||
it([[should end up at right when "E" in the anchor]], function()
|
||||
local right_test = function(anchor, p_width, p_height, max_columns, max_lines)
|
||||
local pos = resolve.resolve_anchor_pos(anchor, p_width, p_height, max_columns, max_lines)
|
||||
eq(max_columns - 1, pos[1] + p_width + math.floor((max_columns - p_width) / 2))
|
||||
end
|
||||
for _, s in ipairs(test_sizes) do
|
||||
right_test("NE", unpack(s))
|
||||
right_test("E", unpack(s))
|
||||
right_test("SE", unpack(s))
|
||||
end
|
||||
end)
|
||||
|
||||
it([[should ignore casing of the anchor]], function()
|
||||
local case_test = function(a1, a2, p_width, p_height, max_columns, max_lines)
|
||||
local pos1 = resolve.resolve_anchor_pos(a1, p_width, p_height, max_columns, max_lines)
|
||||
local pos2 = resolve.resolve_anchor_pos(a2, p_width, p_height, max_columns, max_lines)
|
||||
eq(pos1, pos2)
|
||||
end
|
||||
for _, s in ipairs(test_sizes) do
|
||||
case_test("ne", "NE", unpack(s))
|
||||
case_test("w", "W", unpack(s))
|
||||
case_test("sW", "sw", unpack(s))
|
||||
case_test("cEnTeR", "CeNtEr", unpack(s))
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,143 @@
|
||||
local p_scroller = require "telescope.pickers.scroller"
|
||||
|
||||
local log = require "telescope.log"
|
||||
log.use_console = false
|
||||
|
||||
local eq = assert.are.same
|
||||
|
||||
describe("scroller", function()
|
||||
local max_results = 10
|
||||
|
||||
describe("ascending cycle", function()
|
||||
local cycle_scroller = p_scroller.create("cycle", "ascending")
|
||||
|
||||
it("should return values within the max results", function()
|
||||
eq(5, cycle_scroller(max_results, max_results, 5))
|
||||
end)
|
||||
|
||||
it("should return 0 at 0", function()
|
||||
eq(0, cycle_scroller(max_results, max_results, 0))
|
||||
end)
|
||||
|
||||
it("should cycle you to the top when you go below 0", function()
|
||||
eq(max_results - 1, cycle_scroller(max_results, max_results, -1))
|
||||
end)
|
||||
|
||||
it("should cycle you to 0 when you go past the results", function()
|
||||
eq(0, cycle_scroller(max_results, max_results, max_results + 1))
|
||||
end)
|
||||
|
||||
it("should cycle when current results is less than max_results", function()
|
||||
eq(0, cycle_scroller(max_results, 5, 7))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("ascending limit", function()
|
||||
local limit_scroller = p_scroller.create("limit", "ascending")
|
||||
|
||||
it("should return values within the max results", function()
|
||||
eq(5, limit_scroller(max_results, max_results, 5))
|
||||
end)
|
||||
|
||||
it("should return 0 at 0", function()
|
||||
eq(0, limit_scroller(max_results, max_results, 0))
|
||||
end)
|
||||
|
||||
it("should not cycle", function()
|
||||
eq(0, limit_scroller(max_results, max_results, -1))
|
||||
end)
|
||||
|
||||
it("should not cycle you to 0 when you go past the results", function()
|
||||
eq(max_results - 1, limit_scroller(max_results, max_results, max_results + 1))
|
||||
end)
|
||||
|
||||
it("should stay at current results when current results is less than max_results", function()
|
||||
local current = 5
|
||||
eq(current - 1, limit_scroller(max_results, current, 7))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("descending cycle", function()
|
||||
local cycle_scroller = p_scroller.create("cycle", "descending")
|
||||
|
||||
it("should return values within the max results", function()
|
||||
eq(5, cycle_scroller(max_results, max_results, 5))
|
||||
end)
|
||||
|
||||
it("should return max_results - 1 at 0", function()
|
||||
eq(0, cycle_scroller(max_results, max_results, 0))
|
||||
end)
|
||||
|
||||
it("should cycle you to the bot when you go below 0", function()
|
||||
eq(max_results - 1, cycle_scroller(max_results, max_results, -1))
|
||||
end)
|
||||
|
||||
it("should cycle you to 0 when you go past the results", function()
|
||||
eq(0, cycle_scroller(max_results, max_results, max_results + 1))
|
||||
end)
|
||||
|
||||
it("should cycle when current results is less than max_results", function()
|
||||
eq(9, cycle_scroller(max_results, 5, 4))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("descending limit", function()
|
||||
local limit_scroller = p_scroller.create("limit", "descending")
|
||||
|
||||
it("should return values within the max results", function()
|
||||
eq(5, limit_scroller(max_results, max_results, 5))
|
||||
end)
|
||||
|
||||
it("should return 0 at 0", function()
|
||||
eq(0, limit_scroller(max_results, max_results, 0))
|
||||
end)
|
||||
|
||||
it("should not cycle", function()
|
||||
eq(0, limit_scroller(max_results, max_results, -1))
|
||||
end)
|
||||
|
||||
it("should not cycle you to 0 when you go past the results", function()
|
||||
eq(max_results - 1, limit_scroller(max_results, max_results, max_results + 1))
|
||||
end)
|
||||
|
||||
it("should stay at current results when current results is less than max_results", function()
|
||||
local current = 5
|
||||
eq(max_results - current, limit_scroller(max_results, current, 4))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("https://github.com/nvim-telescope/telescope.nvim/pull/293#issuecomment-751463224", function()
|
||||
it("should handle having many more results than necessary", function()
|
||||
local scroller = p_scroller.create("cycle", "descending")
|
||||
|
||||
-- 23 112 23
|
||||
eq(0, scroller(23, 112, 23))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("should give top, middle and bottom index", function()
|
||||
it("should handle ascending", function()
|
||||
eq(0, p_scroller.top("ascending", 20, 1000))
|
||||
eq(19, p_scroller.bottom("ascending", 20, 1000))
|
||||
|
||||
eq(0, p_scroller.top("ascending", 20, 10))
|
||||
eq(9, p_scroller.bottom("ascending", 20, 10))
|
||||
|
||||
eq(5, p_scroller.middle("ascending", 11, 100))
|
||||
eq(10, p_scroller.middle("ascending", 20, 100))
|
||||
eq(12, p_scroller.middle("ascending", 25, 100))
|
||||
end)
|
||||
|
||||
it("should handle descending", function()
|
||||
eq(0, p_scroller.top("descending", 20, 1000))
|
||||
eq(19, p_scroller.bottom("descending", 20, 1000))
|
||||
|
||||
eq(10, p_scroller.top("descending", 20, 10))
|
||||
eq(19, p_scroller.bottom("descending", 20, 10))
|
||||
|
||||
eq(25, p_scroller.middle("descending", 30, 10))
|
||||
eq(50, p_scroller.middle("descending", 60, 20))
|
||||
eq(105, p_scroller.middle("descending", 120, 30))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,84 @@
|
||||
local sorters = require "telescope.sorters"
|
||||
|
||||
describe("get_substr_matcher", function()
|
||||
local function with_smartcase(smartcase, case)
|
||||
local original = vim.o.smartcase
|
||||
vim.o.smartcase = smartcase
|
||||
|
||||
describe("scoring_function", function()
|
||||
it(case.msg, function()
|
||||
local matcher = sorters.get_substr_matcher()
|
||||
assert.are.same(case.expected_score, matcher.scoring_function(_, case.prompt, _, case.entry))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("highlighter", function()
|
||||
it("returns valid highlights", function()
|
||||
local matcher = sorters.get_substr_matcher()
|
||||
local highlights = matcher.highlighter(_, case.prompt, case.entry.ordinal)
|
||||
table.sort(highlights, function(a, b)
|
||||
return a.start < b.start
|
||||
end)
|
||||
assert.are.same(case.expected_highlights, highlights)
|
||||
end)
|
||||
end)
|
||||
|
||||
vim.o.smartcase = original
|
||||
end
|
||||
|
||||
describe("when smartcase=OFF", function()
|
||||
for _, case in ipairs {
|
||||
{
|
||||
msg = "doesn't match",
|
||||
prompt = "abc def",
|
||||
entry = { index = 3, ordinal = "abc d" },
|
||||
expected_score = -1,
|
||||
expected_highlights = { { start = 1, finish = 3 } },
|
||||
},
|
||||
{
|
||||
msg = "matches with lower case letters only",
|
||||
prompt = "abc def",
|
||||
entry = { index = 3, ordinal = "abc def ghi" },
|
||||
expected_score = 3,
|
||||
expected_highlights = { { start = 1, finish = 3 }, { start = 5, finish = 7 } },
|
||||
},
|
||||
{
|
||||
msg = "doesn't match with upper case letters",
|
||||
prompt = "ABC def",
|
||||
entry = { index = 3, ordinal = "ABC def ghi" },
|
||||
expected_score = -1,
|
||||
expected_highlights = { { start = 5, finish = 7 } },
|
||||
},
|
||||
} do
|
||||
with_smartcase(false, case)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("when smartcase=OFF", function()
|
||||
for _, case in ipairs {
|
||||
{
|
||||
msg = "doesn't match",
|
||||
prompt = "abc def",
|
||||
entry = { index = 3, ordinal = "abc d" },
|
||||
expected_score = -1,
|
||||
expected_highlights = { { start = 1, finish = 3 } },
|
||||
},
|
||||
{
|
||||
msg = "matches with lower case letters only",
|
||||
prompt = "abc def",
|
||||
entry = { index = 3, ordinal = "abc def ghi" },
|
||||
expected_score = 3,
|
||||
expected_highlights = { { start = 1, finish = 3 }, { start = 5, finish = 7 } },
|
||||
},
|
||||
{
|
||||
msg = "matches with upper case letters",
|
||||
prompt = "ABC def",
|
||||
entry = { index = 3, ordinal = "ABC def ghi" },
|
||||
expected_score = 3,
|
||||
expected_highlights = { { start = 1, finish = 3 }, { start = 5, finish = 7 } },
|
||||
},
|
||||
} do
|
||||
with_smartcase(true, case)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,218 @@
|
||||
local picker = require "telescope.pickers"
|
||||
local Path = require "plenary.path"
|
||||
|
||||
local eq = assert.are.same
|
||||
|
||||
local function new_path(unix_path)
|
||||
return Path:new(unpack(vim.split(unix_path, "/"))).filename
|
||||
end
|
||||
|
||||
describe("telescope", function()
|
||||
describe("Picker", function()
|
||||
describe("window_dimensions", function()
|
||||
it("", function()
|
||||
assert(true)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("attach_mappings", function()
|
||||
local new_picker = function(a, b)
|
||||
a.finder = true
|
||||
return picker.new(a, b)
|
||||
end
|
||||
|
||||
it("should allow for passing in a function", function()
|
||||
local p = new_picker({}, {
|
||||
attach_mappings = function()
|
||||
return 1
|
||||
end,
|
||||
})
|
||||
eq(1, p.attach_mappings())
|
||||
end)
|
||||
|
||||
it("should override an attach mappings passed in by opts", function()
|
||||
local called_order = {}
|
||||
local p = new_picker({
|
||||
attach_mappings = function()
|
||||
table.insert(called_order, "opts")
|
||||
end,
|
||||
}, {
|
||||
attach_mappings = function()
|
||||
table.insert(called_order, "default")
|
||||
end,
|
||||
})
|
||||
|
||||
p.attach_mappings()
|
||||
|
||||
eq({ "default", "opts" }, called_order)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Sorters", function()
|
||||
describe("generic_fuzzy_sorter", function()
|
||||
it("sort matches well", function()
|
||||
local sorter = require("telescope.sorters").get_generic_fuzzy_sorter()
|
||||
|
||||
local exact_match = sorter:score("hello", { ordinal = "hello" })
|
||||
local no_match = sorter:score("abcdef", { ordinal = "ghijkl" })
|
||||
local ok_match = sorter:score("abcdef", { ordinal = "ab" })
|
||||
|
||||
assert(exact_match < no_match, "exact match better than no match")
|
||||
assert(exact_match < ok_match, "exact match better than ok match")
|
||||
assert(ok_match < no_match, "ok match better than no match")
|
||||
end)
|
||||
|
||||
it("sorts multiple finds better", function()
|
||||
local sorter = require("telescope.sorters").get_generic_fuzzy_sorter()
|
||||
|
||||
local multi_match = sorter:score("generics", "exercises/generics/generics2.rs")
|
||||
local one_match = sorter:score("abcdef", "exercises/generics/README.md")
|
||||
|
||||
-- assert(multi_match < one_match)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("fuzzy_file", function()
|
||||
it("sort matches well", function()
|
||||
local sorter = require("telescope.sorters").get_fuzzy_file()
|
||||
|
||||
local exact_match = sorter:score("abcdef", { ordinal = "abcdef" })
|
||||
local no_match = sorter:score("abcdef", { ordinal = "ghijkl" })
|
||||
local ok_match = sorter:score("abcdef", { ordinal = "ab" })
|
||||
|
||||
assert(exact_match < no_match, string.format("Exact match better than no match: %s %s", exact_match, no_match))
|
||||
assert(exact_match < ok_match, string.format("Exact match better than OK match: %s %s", exact_match, ok_match))
|
||||
assert(ok_match < no_match, "OK match better than no match")
|
||||
end)
|
||||
|
||||
it("sorts matches after last os sep better", function()
|
||||
local sorter = require("telescope.sorters").get_fuzzy_file()
|
||||
|
||||
local better_match = sorter:score("aaa", { ordinal = new_path "bbb/aaa" })
|
||||
local worse_match = sorter:score("aaa", { ordinal = new_path "aaa/bbb" })
|
||||
|
||||
assert(better_match < worse_match, "Final match should be stronger")
|
||||
end)
|
||||
|
||||
pending("sorts multiple finds better", function()
|
||||
local sorter = require("telescope.sorters").get_fuzzy_file()
|
||||
|
||||
local multi_match = sorter:score("generics", { ordinal = "exercises/generics/generics2.rs" })
|
||||
local one_match = sorter:score("abcdef", { ordinal = "exercises/generics/README.md" })
|
||||
|
||||
assert(multi_match < one_match)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("fzy", function()
|
||||
local sorter = require("telescope.sorters").get_fzy_sorter()
|
||||
local function score(prompt, line)
|
||||
line = new_path(line)
|
||||
return sorter:score(prompt, { ordinal = line }, function(val)
|
||||
return val
|
||||
end, function()
|
||||
return -1
|
||||
end)
|
||||
end
|
||||
|
||||
describe("matches", function()
|
||||
it("exact matches", function()
|
||||
assert.True(score("a", "a") >= 0)
|
||||
assert.True(score("a.bb", "a.bb") >= 0)
|
||||
end)
|
||||
it("ignore case", function()
|
||||
assert.True(score("AbB", "abb") >= 0)
|
||||
assert.True(score("abb", "ABB") >= 0)
|
||||
end)
|
||||
it("partial matches", function()
|
||||
assert.True(score("a", "ab") >= 0)
|
||||
assert.True(score("a", "ba") >= 0)
|
||||
assert.True(score("aba", "baabbaab") >= 0)
|
||||
end)
|
||||
it("with delimiters between", function()
|
||||
assert.True(score("abc", "a|b|c") >= 0)
|
||||
end)
|
||||
it("with empty query", function()
|
||||
assert.True(score("", "") >= 0)
|
||||
assert.True(score("", "a") >= 0)
|
||||
end)
|
||||
it("rejects non-matches", function()
|
||||
assert.True(score("a", "") < 0)
|
||||
assert.True(score("a", "b") < 0)
|
||||
assert.True(score("aa", "a") < 0)
|
||||
assert.True(score("ba", "a") < 0)
|
||||
assert.True(score("ab", "a") < 0)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("scoring", function()
|
||||
it("prefers beginnings of words", function()
|
||||
assert.True(score("amor", "app/models/order") < score("amor", "app/models/zrder"))
|
||||
end)
|
||||
it("prefers consecutive letters", function()
|
||||
assert.True(score("amo", "app/models/foo") < score("amo", "app/m/foo"))
|
||||
assert.True(score("erf", "perfect") < score("erf", "terrific"))
|
||||
end)
|
||||
it("prefers contiguous over letter following period", function()
|
||||
assert.True(score("gemfil", "Gemfile") < score("gemfil", "Gemfile.lock"))
|
||||
end)
|
||||
it("prefers shorter matches", function()
|
||||
assert.True(score("abce", "abcdef") < score("abce", "abc de"))
|
||||
assert.True(score("abc", " a b c ") < score("abc", " a b c "))
|
||||
assert.True(score("abc", " a b c ") < score("abc", " a b c "))
|
||||
end)
|
||||
it("prefers shorter candidates", function()
|
||||
assert.True(score("test", "tests") < score("test", "testing"))
|
||||
end)
|
||||
it("prefers matches at the beginning", function()
|
||||
assert.True(score("ab", "abbb") < score("ab", "babb"))
|
||||
assert.True(score("test", "testing") < score("test", "/testing"))
|
||||
end)
|
||||
it("prefers matches at some locations", function()
|
||||
assert.True(score("a", "/a") < score("a", "ba"))
|
||||
assert.True(score("a", "bA") < score("a", "ba"))
|
||||
assert.True(score("a", ".a") < score("a", "ba"))
|
||||
end)
|
||||
end)
|
||||
|
||||
local function positions(prompt, line)
|
||||
return sorter:highlighter(prompt, new_path(line))
|
||||
end
|
||||
|
||||
describe("positioning", function()
|
||||
it("favors consecutive positions", function()
|
||||
assert.same({ 1, 5, 6 }, positions("amo", "app/models/foo"))
|
||||
end)
|
||||
it("favors word beginnings", function()
|
||||
assert.same({ 1, 5, 12, 13 }, positions("amor", "app/models/order"))
|
||||
end)
|
||||
it("works when there are no bonuses", function()
|
||||
assert.same({ 2, 4 }, positions("as", "tags"))
|
||||
assert.same({ 3, 8 }, positions("as", "examples.txt"))
|
||||
end)
|
||||
it("favors smaller groupings of positions", function()
|
||||
assert.same({ 3, 5, 7 }, positions("abc", "a/a/b/c/c"))
|
||||
assert.same({ 3, 5 }, positions("ab", "caacbbc"))
|
||||
end)
|
||||
it("handles exact matches", function()
|
||||
assert.same({ 1, 2, 3 }, positions("foo", "foo"))
|
||||
end)
|
||||
it("ignores empty requests", function()
|
||||
assert.same({}, positions("", ""))
|
||||
assert.same({}, positions("", "foo"))
|
||||
assert.same({}, positions("foo", ""))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("layout_strategies", function()
|
||||
describe("center", function()
|
||||
it("should handle large terminals", function()
|
||||
-- TODO: This could call layout_strategies.center w/ some weird edge case.
|
||||
-- and then assert stuff about the dimensions.
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,309 @@
|
||||
local Path = require "plenary.path"
|
||||
local utils = require "telescope.utils"
|
||||
|
||||
local eq = assert.are.equal
|
||||
|
||||
describe("path_expand()", function()
|
||||
it("removes trailing os_sep", function()
|
||||
if utils.iswin then
|
||||
eq([[C:\Users\a\b]], utils.path_expand [[C:\Users\a\b\]])
|
||||
else
|
||||
eq("/home/user", utils.path_expand "/home/user/")
|
||||
end
|
||||
end)
|
||||
|
||||
it("works with root dir", function()
|
||||
if utils.iswin then
|
||||
eq([[C:\]], utils.path_expand [[C:\]])
|
||||
else
|
||||
eq("/", utils.path_expand "/")
|
||||
end
|
||||
end)
|
||||
|
||||
it("works with ~", function()
|
||||
eq(vim.loop.os_homedir() .. "/src/foo", utils.path_expand "~/src/foo")
|
||||
end)
|
||||
|
||||
it("handles duplicate os_sep", function()
|
||||
if utils.iswin then
|
||||
eq([[C:\Users\a]], utils.path_expand [[C:\\\Users\\a]])
|
||||
else
|
||||
eq("/home/user", utils.path_expand "/home///user")
|
||||
end
|
||||
end)
|
||||
|
||||
it("preserves fake whitespace characters and whitespace", function()
|
||||
local path_space = "/home/user/hello world"
|
||||
eq(path_space, utils.path_expand(path_space))
|
||||
local path_newline = [[/home/user/hello\nworld]]
|
||||
eq(path_newline, utils.path_expand(path_newline))
|
||||
end)
|
||||
describe("early return for uri", function()
|
||||
local uris = {
|
||||
[[https://www.example.com/index.html]],
|
||||
[[ftp://ftp.example.com/files/document.pdf]],
|
||||
[[mailto:user@example.com]],
|
||||
[[tel:+1234567890]],
|
||||
[[file:///home/user/documents/report.docx]],
|
||||
[[news:comp.lang.python]],
|
||||
[[ldap://ldap.example.com:389/dc=example,dc=com]],
|
||||
[[git://github.com/user/repo.git]],
|
||||
[[steam://run/123456]],
|
||||
[[magnet:?xt=urn:btih:6B4C3343E1C63A1BC36AEB8A3D1F52C4EDEEB096]],
|
||||
}
|
||||
|
||||
for _, uri in ipairs(uris) do
|
||||
it(uri, function()
|
||||
eq(uri, utils.path_expand(uri))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("is_uri", function()
|
||||
describe("detects valid uris", function()
|
||||
local uris = {
|
||||
[[https://www.example.com/index.html]],
|
||||
[[ftp://ftp.example.com/files/document.pdf]],
|
||||
[[mailto:user@example.com]],
|
||||
[[tel:+1234567890]],
|
||||
[[file:///home/user/documents/report.docx]],
|
||||
[[news:comp.lang.python]],
|
||||
[[ldap://ldap.example.com:389/dc=example,dc=com]],
|
||||
[[git://github.com/user/repo.git]],
|
||||
[[steam://run/123456]],
|
||||
[[magnet:?xt=urn:btih:6B4C3343E1C63A1BC36AEB8A3D1F52C4EDEEB096]],
|
||||
}
|
||||
|
||||
for _, uri in ipairs(uris) do
|
||||
it(uri, function()
|
||||
assert.True(utils.is_uri(uri))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("detects invalid uris/paths", function()
|
||||
local inputs = {
|
||||
"hello",
|
||||
"hello:",
|
||||
"123",
|
||||
"",
|
||||
}
|
||||
for _, input in ipairs(inputs) do
|
||||
it(input, function()
|
||||
assert.False(utils.is_uri(input))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("handles windows paths", function()
|
||||
local paths = {
|
||||
[[C:\Users\Usuario\Documents\archivo.txt]],
|
||||
[[D:\Projects\project_folder\source_code.py]],
|
||||
[[E:\Music\song.mp3]],
|
||||
}
|
||||
|
||||
for _, uri in ipairs(paths) do
|
||||
it(uri, function()
|
||||
assert.False(utils.is_uri(uri))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("handles linux paths", function()
|
||||
local paths = {
|
||||
[[/home/usuario/documents/archivo.txt]],
|
||||
[[/var/www/html/index.html]],
|
||||
[[/mnt/backup/backup_file.tar.gz]],
|
||||
}
|
||||
|
||||
for _, path in ipairs(paths) do
|
||||
it(path, function()
|
||||
assert.False(utils.is_uri(path))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("handles macos paths", function()
|
||||
local paths = {
|
||||
[[/Users/Usuario/Documents/archivo.txt]],
|
||||
[[/Applications/App.app/Contents/MacOS/app_executable]],
|
||||
[[/Volumes/ExternalDrive/Data/file.xlsx]],
|
||||
}
|
||||
|
||||
for _, path in ipairs(paths) do
|
||||
it(path, function()
|
||||
assert.False(utils.is_uri(path))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("__separates_file_path_location", function()
|
||||
local suites = {
|
||||
{
|
||||
input = "file.txt:12:4",
|
||||
file = "file.txt",
|
||||
row = 12,
|
||||
col = 4,
|
||||
},
|
||||
{
|
||||
input = "file.txt:12",
|
||||
file = "file.txt",
|
||||
row = 12,
|
||||
col = 0,
|
||||
},
|
||||
{
|
||||
input = "file:12:4",
|
||||
file = "file",
|
||||
row = 12,
|
||||
col = 4,
|
||||
},
|
||||
{
|
||||
input = "file:12:",
|
||||
file = "file",
|
||||
row = 12,
|
||||
col = 0,
|
||||
},
|
||||
{
|
||||
input = "file:",
|
||||
file = "file",
|
||||
},
|
||||
}
|
||||
|
||||
for _, suite in ipairs(suites) do
|
||||
it("separtates file path for " .. suite.input, function()
|
||||
local file, row, col = utils.__separate_file_path_location(suite.input)
|
||||
|
||||
eq(file, suite.file)
|
||||
eq(row, suite.row)
|
||||
eq(col, suite.col)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("transform_path", function()
|
||||
local cwd = (function()
|
||||
if utils.iswin then
|
||||
return [[C:\Users\user\projects\telescope.nvim]]
|
||||
else
|
||||
return "/home/user/projects/telescope.nvim"
|
||||
end
|
||||
end)()
|
||||
|
||||
local function new_relpath(unix_path)
|
||||
return Path:new(unpack(vim.split(unix_path, "/"))).filename
|
||||
end
|
||||
|
||||
local function assert_path(path_display, path, expect)
|
||||
local opts = { cwd = cwd, __length = 15 }
|
||||
if type(path_display) == "string" then
|
||||
opts.path_display = { path_display }
|
||||
eq(expect, utils.transform_path(opts, path))
|
||||
opts.path_display = { [path_display] = true }
|
||||
eq(expect, utils.transform_path(opts, path))
|
||||
elseif type(path_display) == "table" then
|
||||
opts.path_display = path_display
|
||||
eq(expect, utils.transform_path(opts, path))
|
||||
elseif type(path_display) == "function" then
|
||||
opts.path_display = path_display
|
||||
eq(expect, utils.transform_path(opts, path))
|
||||
elseif path_display == nil then
|
||||
eq(expect, utils.transform_path(opts, path))
|
||||
end
|
||||
end
|
||||
|
||||
it("handles nil path", function()
|
||||
assert_path(nil, nil, "")
|
||||
end)
|
||||
|
||||
it("returns back uri", function()
|
||||
local uri = [[https://www.example.com/index.html]]
|
||||
assert_path(nil, uri, uri)
|
||||
end)
|
||||
|
||||
it("handles 'hidden' path_display", function()
|
||||
eq("", utils.transform_path({ cwd = cwd, path_display = "hidden" }, "foobar"))
|
||||
assert_path("hidden", "foobar", "")
|
||||
end)
|
||||
|
||||
it("returns relative path for default opts", function()
|
||||
local relative = Path:new { "lua", "telescope", "init.lua" }
|
||||
local absolute = Path:new { cwd, relative }
|
||||
assert_path(nil, absolute.filename, relative.filename)
|
||||
assert_path(nil, relative.filename, relative.filename)
|
||||
end)
|
||||
|
||||
it("handles 'tail' path_display", function()
|
||||
local path = new_relpath "lua/telescope/init.lua"
|
||||
assert_path("tail", path, "init.lua")
|
||||
end)
|
||||
|
||||
it("handles 'smart' path_display", function()
|
||||
local path1 = new_relpath "lua/telescope/init.lua"
|
||||
local path2 = new_relpath "lua/telescope/finders.lua"
|
||||
local path3 = new_relpath "lua/telescope/finders/async_job_finder.lua"
|
||||
local path4 = new_relpath "plugin/telescope.lua"
|
||||
|
||||
assert_path("smart", path1, path1)
|
||||
assert_path("smart", path2, new_relpath "../telescope/finders.lua")
|
||||
assert_path("smart", path3, new_relpath "../telescope/finders/async_job_finder.lua")
|
||||
assert_path("smart", path4, path4)
|
||||
end)
|
||||
|
||||
it("handles 'absolute' path_display", function()
|
||||
local relative = Path:new { "lua", "telescope", "init.lua" }
|
||||
local absolute = Path:new { cwd, relative }
|
||||
|
||||
-- TODO: feels like 'absolute' should turn relative paths to absolute
|
||||
-- assert_path("absolute", relative.filename, absolute.filename)
|
||||
assert_path("absolute", absolute.filename, absolute.filename)
|
||||
end)
|
||||
|
||||
it("handles default 'shorten' path_display", function()
|
||||
assert_path("shorten", new_relpath "lua/telescope/init.lua", new_relpath "l/t/init.lua")
|
||||
end)
|
||||
|
||||
it("handles 'shorten' with number", function()
|
||||
assert_path({ shorten = 2 }, new_relpath "lua/telescope/init.lua", new_relpath "lu/te/init.lua")
|
||||
end)
|
||||
|
||||
it("handles 'shorten' with option table", function()
|
||||
assert_path({ shorten = { len = 2 } }, new_relpath "lua/telescope/init.lua", new_relpath "lu/te/init.lua")
|
||||
assert_path(
|
||||
{ shorten = { len = 2, exclude = { 1, 3, -1 } } },
|
||||
new_relpath "lua/telescope/builtin/init.lua",
|
||||
new_relpath "lua/te/builtin/init.lua"
|
||||
)
|
||||
end)
|
||||
|
||||
it("handles default 'truncate' path_display", function()
|
||||
assert_path({ "truncate" }, new_relpath "lua/telescope/init.lua", new_relpath "…scope/init.lua")
|
||||
end)
|
||||
|
||||
it("handles 'filename_first' path_display", function()
|
||||
assert_path("filename_first", new_relpath "init.lua", new_relpath "init.lua")
|
||||
assert_path("filename_first", new_relpath "lua/telescope/init.lua", new_relpath "init.lua lua/telescope")
|
||||
end)
|
||||
|
||||
it("handles 'filename_first' path_display with the option to reverse directories", function()
|
||||
assert_path({ filename_first = { reverse_directories = true } }, new_relpath "init.lua", new_relpath "init.lua")
|
||||
assert_path(
|
||||
{ filename_first = { reverse_directories = true } },
|
||||
new_relpath "lua/telescope/init.lua",
|
||||
new_relpath "init.lua telescope/lua"
|
||||
)
|
||||
assert_path({ filename_first = { reverse_directories = false } }, new_relpath "init.lua", new_relpath "init.lua")
|
||||
assert_path(
|
||||
{ filename_first = { reverse_directories = false } },
|
||||
new_relpath "lua/telescope/init.lua",
|
||||
new_relpath "init.lua lua/telescope"
|
||||
)
|
||||
end)
|
||||
|
||||
it("handles function passed to path_display", function()
|
||||
assert_path(function(_, path)
|
||||
return string.gsub(path, "^doc", "d")
|
||||
end, new_relpath "doc/mydoc.md", new_relpath "d/mydoc.md")
|
||||
end)
|
||||
end)
|
||||
0
config/neovim/store/lazy-plugins/telescope.nvim/lua/tests/fixtures/find_files/file_a.txt
vendored
Normal file
0
config/neovim/store/lazy-plugins/telescope.nvim/lua/tests/fixtures/find_files/file_a.txt
vendored
Normal file
0
config/neovim/store/lazy-plugins/telescope.nvim/lua/tests/fixtures/find_files/file_abc.txt
vendored
Normal file
0
config/neovim/store/lazy-plugins/telescope.nvim/lua/tests/fixtures/find_files/file_abc.txt
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
local finders = require "telescope.finders"
|
||||
local make_entry = require "telescope.make_entry"
|
||||
local previewers = require "telescope.previewers"
|
||||
local pickers = require "telescope.pickers"
|
||||
local sorters = require "telescope.sorters"
|
||||
local utils = require "telescope.utils"
|
||||
|
||||
local helpers = {}
|
||||
|
||||
-- TODO: We should do something with builtins to get those easily.
|
||||
helpers.auto_find_files = function(opts)
|
||||
opts = opts or {}
|
||||
opts.prompt_prefix = ""
|
||||
|
||||
local find_command = opts.find_command
|
||||
|
||||
if not find_command then
|
||||
if 1 == vim.fn.executable "fd" then
|
||||
find_command = { "fd", "--type", "f" }
|
||||
elseif 1 == vim.fn.executable "fdfind" then
|
||||
find_command = { "fdfind", "--type", "f" }
|
||||
elseif 1 == vim.fn.executable "rg" then
|
||||
find_command = { "rg", "--files" }
|
||||
end
|
||||
end
|
||||
|
||||
if opts.cwd then
|
||||
opts.cwd = utils.path_expand(opts.cwd)
|
||||
end
|
||||
|
||||
opts.entry_maker = opts.entry_maker or make_entry.gen_from_file(opts)
|
||||
|
||||
local p = pickers.new(opts, {
|
||||
prompt = "Find Files",
|
||||
finder = finders.new_oneshot_job(find_command, opts),
|
||||
previewer = previewers.cat.new(opts),
|
||||
sorter = sorters.get_fuzzy_file(),
|
||||
|
||||
track = true,
|
||||
})
|
||||
|
||||
local count = 0
|
||||
p:register_completion_callback(function(s)
|
||||
print(
|
||||
count,
|
||||
vim.inspect(s.stats, {
|
||||
process = function(item)
|
||||
if type(item) == "string" and item:sub(1, 1) == "_" then
|
||||
return nil
|
||||
end
|
||||
|
||||
return item
|
||||
end,
|
||||
})
|
||||
)
|
||||
|
||||
count = count + 1
|
||||
end)
|
||||
|
||||
local feed = function(text, feed_opts)
|
||||
feed_opts = feed_opts or "n"
|
||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(text, true, false, true), feed_opts, true)
|
||||
end
|
||||
|
||||
p:register_completion_callback(coroutine.wrap(function()
|
||||
local input = opts.input
|
||||
|
||||
for i = 1, #input do
|
||||
feed(input:sub(i, i))
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
vim.wait(300, function() end)
|
||||
feed("<CR>", "")
|
||||
|
||||
vim.defer_fn(function()
|
||||
PASSED = opts.condition()
|
||||
COMPLETED = true
|
||||
end, 500)
|
||||
|
||||
coroutine.yield()
|
||||
end))
|
||||
|
||||
p:find()
|
||||
end
|
||||
|
||||
return helpers
|
||||
@ -0,0 +1,8 @@
|
||||
local helper = require "telescope.testharness.helpers"
|
||||
local runner = require "telescope.testharness.runner"
|
||||
|
||||
runner.picker("find_files", "README.md", {
|
||||
post_close = {
|
||||
{ "README.md", helper.get_file },
|
||||
},
|
||||
})
|
||||
@ -0,0 +1,12 @@
|
||||
local tester = require "telescope.testharness"
|
||||
local helper = require "telescope.testharness.helpers"
|
||||
local runner = require "telescope.testharness.runner"
|
||||
|
||||
runner.picker("find_files", "telescope<c-n>", {
|
||||
post_close = {
|
||||
tester.not_ { "plugin/telescope.vim", helper.get_file },
|
||||
},
|
||||
}, {
|
||||
sorting_strategy = "descending",
|
||||
scroll_strategy = "cycle",
|
||||
})
|
||||
Reference in New Issue
Block a user