294 lines
8.6 KiB
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
|