dotfiles/.config/nvim/lua/yourmom/cloak.lua

294 lines
8.6 KiB
Lua

local group = vim.api.nvim_create_augroup('cloak', {})
local namespace = vim.api.nvim_create_namespace('cloak')
-- In case cmp is lazy loaded, we check :CmpStatus instead of a pcall to require
-- so we maintain the lazy load.
local has_cmp = function()
return vim.fn.exists(':CmpStatus') > 0
end
local M = {}
M.opts = {
enabled = true,
cloak_character = '*',
cloak_length = nil,
highlight_group = 'Comment',
try_all_patterns = true,
patterns = { { file_pattern = '.env*', cloak_pattern = '=.+' } },
cloak_telescope = true,
uncloaked_line_num = nil,
cloak_on_leave = false,
}
M.setup = function(opts)
M.opts = vim.tbl_deep_extend('force', M.opts, opts or {})
vim.b.cloak_enabled = M.opts.enabled
for _, pattern in ipairs(M.opts.patterns) do
if type(pattern.cloak_pattern) == 'string' then
pattern.cloak_pattern = { { pattern.cloak_pattern, replace = pattern.replace } }
else
for i, inner_pattern in ipairs(pattern.cloak_pattern) do
pattern.cloak_pattern[i] =
type(inner_pattern) == 'string'
and { inner_pattern, replace = pattern.cloak_pattern.replace or pattern.replace }
or inner_pattern
end
end
vim.api.nvim_create_autocmd(
{ 'BufEnter', 'TextChanged', 'TextChangedI', 'TextChangedP' }, {
pattern = pattern.file_pattern,
callback = function()
if M.opts.enabled then
M.cloak(pattern)
else
M.uncloak()
end
end,
group = group,
}
)
if M.opts.cloak_on_leave then
vim.api.nvim_create_autocmd(
'BufWinLeave', {
pattern = pattern.file_pattern,
callback = function()
M.enable()
end,
group = group,
}
)
end
end
if M.opts.cloak_telescope then
vim.api.nvim_create_autocmd(
'User', {
pattern = 'TelescopePreviewerLoaded',
callback = function(args)
-- Feature not in 0.1.x
if not M.opts.enabled or args.data == nil then
return
end
local buffer = require('telescope.state').get_existing_prompt_bufnrs()[1]
local picker = require('telescope.actions.state').get_current_picker(
buffer
)
-- If our state variable is set, meaning we have just refreshed after cloaking a buffer,
-- set the selection to that row again.
if picker.__cloak_selection then
picker:set_selection(picker.__cloak_selection)
picker.__cloak_selection = nil
vim.schedule(
function()
picker:refresh_previewer()
end
)
return
end
local is_cloaked, _ = pcall(
vim.api.nvim_buf_get_var, args.buf, 'cloaked'
)
-- Check the buffer agains all configured patterns,
-- if matched, set a variable on the picker to know where we left off,
-- set a buffer variable to know we already cloaked it later, and refresh.
-- a refresh will result in the cloak being visible, and will make this
-- aucmd be called again right away with the first result, which we will then
-- set to what we have stored in the code above.
if M.recloak_file(args.data.bufname) then
vim.api.nvim_buf_set_var(args.buf, 'cloaked', true)
if is_cloaked then
return
end
local row = picker:get_selection_row()
picker.__cloak_selection = row
picker:refresh()
return
end
end,
group = group,
}
)
end
-- Handle cloaking the Telescope preview.
vim.api.nvim_create_user_command('CloakEnable', M.enable, {})
vim.api.nvim_create_user_command('CloakDisable', M.disable, {})
vim.api.nvim_create_user_command('CloakToggle', M.toggle, {})
vim.api.nvim_create_user_command('CloakPreviewLine', M.uncloak_line, {})
end
M.uncloak = function()
vim.api.nvim_buf_clear_namespace(0, namespace, 0, -1)
end
M.uncloak_line = function()
if not M.opts.enabled then
return
end
local buf = vim.api.nvim_win_get_buf(0)
local cursor = vim.api.nvim_win_get_cursor(0)
M.opts.uncloaked_line_num = cursor[1]
vim.api.nvim_create_autocmd(
{ 'CursorMoved', 'CursorMovedI', 'BufLeave' }, {
buffer = buf,
callback = function(args)
if not M.opts.enabled then
M.opts.uncloaked_line_num = nil
return true
end
if args.event == 'BufLeave' then
M.opts.uncloaked_line_num = nil
M.recloak_file(vim.api.nvim_buf_get_name(buf))
return true
end
local ncursor = vim.api.nvim_win_get_cursor(0)
if ncursor[1] == M.opts.uncloaked_line_num then
return
end
M.opts.uncloaked_line_num = nil
M.recloak_file(vim.api.nvim_buf_get_name(buf))
-- deletes the auto command
return true
end,
group = group,
}
)
M.recloak_file(vim.api.nvim_buf_get_name(buf))
end
M.cloak = function(pattern)
M.uncloak()
if has_cmp() then
require('cmp').setup.buffer({ enabled = false })
end
local function determine_replacement(length, prefix)
local cloak_str = prefix
.. M.opts.cloak_character:rep(
tonumber(M.opts.cloak_length)
or length - vim.fn.strchars(prefix))
local remaining_length = length - vim.fn.strchars(cloak_str)
-- Fixme:
-- - When cloak_length is longer than the text underlying it,
-- it results in overlaying of extra text
-- => Possiblie solutions would could be implemented using inline virtual text
-- (https://github.com/neovim/neovim/pull/20130)
return cloak_str -- :sub(1, math.min(remaining_length - 1, -1))
.. (' '):rep(remaining_length)
end
local found_pattern = false
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
for i, line in ipairs(lines) do
-- Find all matches for the current line
local searchStartIndex = 1
while searchStartIndex < #line and
-- if the line is uncloaked skip
i ~= M.opts.uncloaked_line_num do
-- Find best pattern based on starting position and tiebreak with length
local first, last, matching_pattern, has_groups = -1, 1, nil, false
for _, inner_pattern in ipairs(pattern.cloak_pattern) do
local current_first, current_last, capture_group =
line:find(inner_pattern[1], searchStartIndex)
if current_first ~= nil
and (first < 0
or current_first < first
or (current_first == first and current_last > last)) then
first, last, matching_pattern, has_groups =
current_first, current_last, inner_pattern, capture_group ~= nil
if M.opts.try_all_patterns == false then break end
end
end
if first >= 0 then
found_pattern = true
local prefix = line:sub(first,first)
if has_groups and matching_pattern.replace ~= nil then
prefix = line:sub(first,last)
:gsub(matching_pattern[1], matching_pattern.replace, 1)
end
local last_of_prefix = first + vim.fn.strchars(prefix) - 1
if prefix == line:sub(first, last_of_prefix) then
first, prefix = last_of_prefix + 1, ''
end
vim.api.nvim_buf_set_extmark(
0, namespace, i - 1, first-1, {
hl_mode = 'combine',
virt_text = {
{
determine_replacement(last - first + 1, prefix),
M.opts.highlight_group,
},
},
virt_text_pos = 'overlay',
}
)
else break end
searchStartIndex = last
end
end
if found_pattern then
vim.opt_local.wrap = false
end
end
M.recloak_file = function(filename)
local base_name = vim.fn.fnamemodify(filename, ':t')
for _, pattern in ipairs(M.opts.patterns) do
-- Could be a string or a table of patterns.
local file_patterns = pattern.file_pattern
if type(file_patterns) == 'string' then
file_patterns = { file_patterns }
end
for _, file_pattern in ipairs(file_patterns) do
if base_name ~= nil and
vim.fn.match(base_name, vim.fn.glob2regpat(file_pattern)) ~= -1 then
M.cloak(pattern)
return true
end
end
end
return false
end
M.disable = function()
M.uncloak()
M.opts.enabled = false
vim.b.cloak_enabled = false
end
M.enable = function()
M.opts.enabled = true
vim.b.cloak_enabled = true
vim.cmd('doautocmd TextChanged')
end
M.toggle = function()
if M.opts.enabled then
M.disable()
else
M.enable()
end
end
return M