Skip to content

Instantly share code, notes, and snippets.

@bluss
Last active June 20, 2024 21:23
Show Gist options
  • Save bluss/31ab305c11c07d7560b26b374864ac4e to your computer and use it in GitHub Desktop.
Save bluss/31ab305c11c07d7560b26b374864ac4e to your computer and use it in GitHub Desktop.
Telescope find_files/live_grep but with support for toggling no-ignore/hidden
-- Flextelescope
--
-- Copyright bluss; Apache 2.0 license
--
-- Replace telescope builtins find_files and live_grep
-- with wrappers that support toggling no-ignore/hidden
local M = {}
local this_plugin = "flextelescope"
---@class FlextelescopeOptions
---@field replace_builtins boolean: Override telescope builtins with wrappers, default true
---@field show_hidden_in_git boolean?: Always show hidden in git repos, default true
---@field toggle_ignore_key string|boolean?: Mapping for toggle ignore/hidden, default C-h
---@field toggle_layout_key string|boolean?: Mapping for toggle vertical/horizontal, default M-h
---@field parent_dir_key string|boolean?: Mapping for set cwd to parent dir, default M-p
---@field reset_cwd_key string|boolean?: Mapping for reset cwd, default M-c
M.defaults = {
replace_builtins = true,
show_hidden_in_git = true,
toggle_ignore_key = "<C-h>",
toggle_layout_key = "<M-h>",
parent_dir_key = "<M-p>",
reset_cwd_key = "<M-c>",
}
---@type FlextelescopeOptions
M.options = M.defaults -- written in setup
local attach_mapping_modes = {"i"}
local relaunch_marker = this_plugin .. "_relaunch"
local state_prefix = this_plugin
local telescope_builtin_original = nil
-- on first call, original telescope builtins are copied to a separate table
-- return saved builtins table
---@return table
local function _save_builtins()
if not telescope_builtin_original then
telescope_builtin_original = {}
for key, values in pairs(require("telescope.builtin")) do
telescope_builtin_original[key] = values
end
M._telescope_builtin = telescope_builtin_original
end
return telescope_builtin_original
end
---@param searcher string
---@param opts table
local function _call_telescope_builtin(searcher, opts)
-- vim.notify("Calling " .. searcher .. " with " .. vim.inspect(opts), vim.log.levels.DEBUG)
_save_builtins()[searcher](opts)
end
local function install_replacement(name, func)
-- install an override of a telescope builtin
-- on first call, original builtins are copied to a separate table
_save_builtins()
require("telescope.builtin")[name] = func
end
local function find_git_root_from(path)
local ret = vim.fs.dirname(vim.fs.find({".git"}, { path = path, upward = true })[1])
return ret
end
---@class SearcherMemory
---@field opts table
---@field orig_opts table
---@field func function
local SearcherMemory = {}
SearcherMemory.__index = SearcherMemory
function SearcherMemory.new(func, opts)
local self = {}
opts = opts or {}
self.opts = opts
self.orig_opts = vim.deepcopy(opts)
self.func = func
return setmetatable(self, SearcherMemory)
end
function SearcherMemory.reset(self)
self.opts = vim.deepcopy(self.orig_opts)
end
function SearcherMemory.get_opts(self)
return self.opts
end
function SearcherMemory.close_and_relaunch(self, prompt_bufnr)
local opts = self.opts
local func = self.func
assert(func)
assert(opts)
local actions = require("telescope.actions")
actions.close(prompt_bufnr)
local action_state = require("telescope.actions.state")
local curline = action_state.get_current_line()
opts.default_text = curline
opts[relaunch_marker] = true
return func(opts)
end
---@param func function
function SearcherMemory.modify_opts(self, func)
func(self.opts)
end
---@return boolean: true if not relaunch
local function enter_searcher(func, opts)
if opts[relaunch_marker] then
opts[relaunch_marker] = nil
return false
end
local state = require("telescope.state")
state.set_global_key(state_prefix, SearcherMemory.new(func, opts))
return true
end
function M.modify_relaunch_mapping(modifier)
return function(prompt_bufnr)
local state = require("telescope.state")
---@type SearcherMemory
local memory = state.get_global_key(state_prefix)
if not memory then
vim.notify_once("Expected searcher memory but found none", vim.log.levels.ERROR)
return
end
memory:modify_opts(modifier)
vim.schedule(function() memory:close_and_relaunch(prompt_bufnr) end)
end
end
local function isstring(obj)
return type(obj) == "string"
end
function M._common_mappings(_, map)
if isstring(M.options.toggle_ignore_key) then
local function toggle_ignore(...)
return M.modify_relaunch_mapping(function(o) o.no_ignore = not o.no_ignore end)(...)
end
map(attach_mapping_modes, M.options.toggle_ignore_key, toggle_ignore)
end
if isstring(M.options.toggle_layout_key) then
local function toggle_layout(...)
return M.modify_relaunch_mapping(function(o)
local default_strategy = require("telescope.config").values.layout_strategy
if (o.layout_strategy or default_strategy) == "horizontal" then
o.layout_strategy = "vertical"
else
o.layout_strategy = "horizontal"
end
end)(...)
end
map(attach_mapping_modes, M.options.toggle_layout_key, toggle_layout)
end
if isstring(M.options.parent_dir_key) then
local function go_to_parent_dir(...)
M.modify_relaunch_mapping(function(o)
local cwd = o.cwd or vim.fn.getcwd()
o.cwd = vim.fn.fnamemodify(cwd, ":h")
end)(...)
end
map(attach_mapping_modes, M.options.parent_dir_key, go_to_parent_dir)
end
if isstring(M.options.reset_cwd_key) then
local function reset_cwd(...)
M.modify_relaunch_mapping(function(o) o.cwd = nil end)(...)
end
map(attach_mapping_modes, M.options.reset_cwd_key, reset_cwd)
end
return true
end
local function my_find_files(opts)
opts = opts or {}
local searcher = "find_files"
local first_call = enter_searcher(my_find_files, opts)
if first_call then
opts.orig_prompt = opts.prompt_title or "Find Files"
end
opts.attach_mappings = M._common_mappings
local searcher_name = opts.orig_prompt
local suffix = nil
local cwd_state = opts.cwd
if cwd_state then
opts.cwd = cwd_state
suffix = "CWD=" .. vim.fn.pathshorten(tostring(cwd_state), 2)
end
if opts.no_ignore then
opts.hidden = true
opts.prompt_title = searcher_name .. " <ALL>"
else
opts.hidden = false
opts.prompt_title = searcher_name
end
-- show hidden in git repos - because they are tracked when not ignored
if M.options.show_hidden_in_git and not opts.no_ignore then
if find_git_root_from(opts.cwd or vim.fn.getcwd()) then
opts.hidden = true
end
end
if suffix then
opts.prompt_title = opts.prompt_title .. " " .. suffix
end
_call_telescope_builtin(searcher, opts)
end
M.find_files = my_find_files
local function my_live_grep(opts)
opts = opts or {}
local searcher = "live_grep"
local first_call = enter_searcher(my_live_grep, opts)
if first_call then
opts.orig_prompt = opts.prompt_title or "Live Grep"
end
opts.attach_mappings = M._common_mappings
local searcher_name = opts.orig_prompt
local suffix = nil
if opts.cwd then
suffix = "CWD=" .. vim.fn.pathshorten(tostring(opts.cwd), 2)
end
if opts.no_ignore then
-- rg -uu
if not opts.additional_args then
opts.additional_args = {}
end
table.insert(opts.additional_args, "-uu")
opts.prompt_title = searcher_name .. " <ALL>"
else
opts.additional_args = vim.tbl_filter(function(elt) return elt ~= "-uu" end, opts.additional_args or {})
opts.prompt_title = searcher_name
end
-- show hidden in git repos - because they are tracked when not ignored
if M.options.show_hidden_in_git and not opts.no_ignore then
if find_git_root_from(opts.cwd or vim.fn.getcwd()) then
if not vim.tbl_contains(opts.additional_args, "--hidden") then
table.insert(opts.additional_args, "--hidden")
end
end
end
if suffix then
opts.prompt_title = opts.prompt_title .. " " .. suffix
end
_call_telescope_builtin(searcher, opts)
end
M.live_grep = my_live_grep
---@param opts FlextelescopeOptions?
function M.setup(opts)
vim.validate({
opts = {opts, "t", true},
})
M.options = vim.tbl_deep_extend("force", M.defaults, opts or {})
if M.options.replace_builtins then
install_replacement("find_files", M.find_files)
install_replacement("live_grep", M.live_grep)
end
end
return M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment