1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-23 17:40:06 +08:00
SpaceVim/bundle/telescope.nvim-0.1.2/lua/telescope/utils.lua

535 lines
15 KiB
Lua
Raw Permalink Normal View History

---@tag telescope.utils
---@config { ["module"] = "telescope.utils" }
---@brief [[
--- Utilities for writing telescope pickers
---@brief ]]
2022-05-16 22:20:10 +08:00
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
2022-05-16 22:20:10 +08:00
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)
2022-05-16 22:20:10 +08:00
return path_display == nil
or path_display == "hidden"
or type(path_display) == "table" and (vim.tbl_contains(path_display, "hidden") or path_display.hidden)
2022-05-16 22:20:10 +08:00
end
local is_uri = function(filename)
return string.match(filename, "^%w+://") ~= nil
2022-05-16 22:20:10 +08:00
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
2022-05-16 22:20:10 +08:00
utils.transform_path = function(opts, path)
if path == nil then
return
end
if is_uri(path) then
2022-05-16 22:20:10 +08:00
return path
end
local path_display = vim.F.if_nil(opts.path_display, require("telescope.config").values.path_display)
2022-05-16 22:20:10 +08:00
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)
2022-05-16 22:20:10 +08:00
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()
2022-05-16 22:20:10 +08:00
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
2022-05-16 22:20:10 +08:00
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
2022-05-16 22:20:10 +08:00
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 "")
2022-05-16 22:20:10 +08:00
if conf.color_devicons then
return icon_display, icon_highlight, icon
2022-05-16 22:20:10 +08:00
else
return icon_display, nil, icon
2022-05-16 22:20:10 +08:00
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
2022-05-16 22:20:10 +08:00
if conf.color_devicons then
return icon, icon_highlight
else
return icon, nil
2022-05-16 22:20:10 +08:00
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