1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 05:20:04 +08:00
SpaceVim/bundle/telescope.nvim-0.1.2/lua/telescope/utils.lua

535 lines
15 KiB
Lua
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---@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.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)
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
-- TODO(conni2461): If you understand this correctly then we sort the results table based on the bufnr
-- If you ask me this should be its own function, that happens after the filtering part and should be
-- called in the lsp function directly
local current_buf = vim.api.nvim_get_current_buf()
if not vim.tbl_isempty(filtered_symbols) then
-- filter adequately for workspace symbols
local filename_to_bufnr = {}
for _, symbol in ipairs(filtered_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(filtered_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 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 = {}
return function(filepath)
local final = filepath
if #paths ~= 0 then
local dirs = vim.split(filepath, "/")
local max = 1
for _, p in pairs(paths) do
if #p > 0 and p ~= filepath then
local _dirs = vim.split(p, "/")
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 "/" 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 "../" .. 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
local is_uri = function(filename)
return string.match(filename, "^%w+://") ~= nil
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.results_win) - 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: The path that should be formatted
---@return string: The transformed path ready to be displayed
utils.transform_path = function(opts, path)
if path == nil then
return
end
if is_uri(path) then
return path
end
local path_display = vim.F.if_nil(opts.path_display, require("telescope.config").values.path_display)
local transformed_path = path
if type(path_display) == "function" then
return path_display(opts, transformed_path)
elseif utils.is_path_hidden(nil, path_display) then
return ""
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") or path_display.absolute == false then
local cwd
if opts.cwd then
cwd = opts.cwd
if not vim.in_fast_event() then
cwd = vim.fn.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
transformed_path = Path:new(transformed_path):shorten(path_display["shorten"])
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
end
return transformed_path
else
log.warn("`path_display` must be either a function or a table.", "See `:help telescope.defaults.path_display.")
return transformed_path
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)
--- 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
return utils