dotfiles/.config/nvim/lua/mini/trailspace.lua

198 lines
7.1 KiB
Lua

--- *mini.trailspace* Trailspace (highlight and remove)
--- *MiniTrailspace*
---
--- MIT License Copyright (c) 2021 Evgeni Chasnovski
---
--- ==============================================================================
---
--- Features:
--- - Highlighting is done only in modifiable buffer by default, only in Normal
--- mode, and stops in Insert mode and when leaving window.
---
--- - Trim all trailing whitespace with |MiniTrailspace.trim()|.
---
--- - Trim all trailing empty lines with |MiniTrailspace.trim_last_lines()|.
---
--- # Setup~
---
--- This module needs a setup with `require('mini.trailspace').setup({})`
--- (replace `{}` with your `config` table). It will create global Lua table
--- `MiniTrailspace` which you can use for scripting or manually (with
--- `:lua MiniTrailspace.*`).
---
--- See |MiniTrailspace.config| for `config` structure and default values.
---
--- You can override runtime config settings locally to buffer inside
--- `vim.b.minitrailspace_config` which should have same structure as
--- `MiniTrailspace.config`. See |mini.nvim-buffer-local-config| for more details.
---
--- # Highlight groups~
---
--- * `MiniTrailspace` - highlight group for trailing space.
---
--- To change any highlight group, modify it directly with |:highlight|.
---
--- # Disabling~
---
--- To disable, set `vim.g.minitrailspace_disable` (globally) or
--- `vim.b.minitrailspace_disable` (for a buffer) to `true`. Considering high
--- number of different scenarios and customization intentions, writing exact
--- rules for disabling module's functionality is left to user. See
--- |mini.nvim-disabling-recipes| for common recipes. Note: after disabling
--- there might be highlighting left; it will be removed after next
--- highlighting update (see |events| and `MiniTrailspace` |augroup|).
-- Module definition ==========================================================
local MiniTrailspace = {}
local H = {}
--- Module setup
---
---@param config table|nil Module config table. See |MiniTrailspace.config|.
---
---@usage `require('mini.trailspace').setup({})` (replace `{}` with your `config` table)
MiniTrailspace.setup = function(config)
-- Export module
_G.MiniTrailspace = MiniTrailspace
-- Setup config
config = H.setup_config(config)
-- Apply config
H.apply_config(config)
-- Define behavior
H.create_autocommands(config)
-- Create default highlighting
H.create_default_hl()
-- Initialize highlight (usually takes effect during startup)
vim.defer_fn(MiniTrailspace.highlight, 0)
end
--- Module config
---
--- Default values:
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
MiniTrailspace.config = {
-- Highlight only in normal buffers (ones with empty 'buftype'). This is
-- useful to not show trailing whitespace where it usually doesn't matter.
only_in_normal_buffers = true,
}
--minidoc_afterlines_end
-- Module functionality =======================================================
--- Highlight trailing whitespace in current window
MiniTrailspace.highlight = function()
-- Highlight only in normal mode
if H.is_disabled() or vim.fn.mode() ~= 'n' then
MiniTrailspace.unhighlight()
return
end
-- Possibly work only in normal buffers
if H.get_config().only_in_normal_buffers and not H.is_buffer_normal() then return end
-- Don't add match id on top of existing one
if H.get_match_id() ~= nil then return end
vim.fn.matchadd('MiniTrailspace', [[\s\+$]])
end
--- Unhighlight trailing whitespace in current window
MiniTrailspace.unhighlight = function()
-- Use `pcall` because there is an error if match id is not present. It can
-- happen if something else called `clearmatches`.
pcall(vim.fn.matchdelete, H.get_match_id())
end
--- Trim trailing whitespace
MiniTrailspace.trim = function()
-- Save cursor position to later restore
local curpos = vim.api.nvim_win_get_cursor(0)
-- Search and replace trailing whitespace
vim.cmd([[keeppatterns %s/\s\+$//e]])
vim.api.nvim_win_set_cursor(0, curpos)
end
--- Trim last blank lines
MiniTrailspace.trim_last_lines = function()
local n_lines = vim.api.nvim_buf_line_count(0)
local last_nonblank = vim.fn.prevnonblank(n_lines)
if last_nonblank < n_lines then vim.api.nvim_buf_set_lines(0, last_nonblank, n_lines, true, {}) end
end
-- Helper data ================================================================
-- Module default config
H.default_config = vim.deepcopy(MiniTrailspace.config)
-- Helper functionality =======================================================
-- Settings -------------------------------------------------------------------
H.setup_config = function(config)
-- General idea: if some table elements are not present in user-supplied
-- `config`, take them from default config
vim.validate({ config = { config, 'table', true } })
config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
vim.validate({ only_in_normal_buffers = { config.only_in_normal_buffers, 'boolean' } })
return config
end
H.apply_config = function(config) MiniTrailspace.config = config end
H.create_autocommands = function(config)
local augroup = vim.api.nvim_create_augroup('MiniTrailspace', {})
local au = function(event, pattern, callback, desc)
vim.api.nvim_create_autocmd(event, { group = augroup, pattern = pattern, callback = callback, desc = desc })
end
-- NOTE: Respecting both `WinEnter` and `BufEnter` seems to be useful to
-- account of different order of handling buffer opening in new window.
-- Notable example: 'nvim-tree' at commit a1600e5.
au({ 'WinEnter', 'BufEnter', 'InsertLeave' }, '*', MiniTrailspace.highlight, 'Highlight')
au({ 'WinLeave', 'BufLeave', 'InsertEnter' }, '*', MiniTrailspace.unhighlight, 'Unhighlight')
if config.only_in_normal_buffers then
-- Add tracking of 'buftype' changing because it can be set after events on
-- which highlighting is done. If not done, highlighting appears but
-- disappears if buffer is reentered.
au('OptionSet', 'buftype', H.track_normal_buffer, 'Track normal buffer')
end
end
H.create_default_hl = function() vim.api.nvim_set_hl(0, 'MiniTrailspace', { default = true, link = 'Error' }) end
H.is_disabled = function() return vim.g.minitrailspace_disable == true or vim.b.minitrailspace_disable == true end
H.get_config = function(config)
return vim.tbl_deep_extend('force', MiniTrailspace.config, vim.b.minitrailspace_config or {}, config or {})
end
H.track_normal_buffer = function()
if not H.get_config().only_in_normal_buffers then return end
-- This should be used with 'OptionSet' event for 'buftype' option
-- Empty 'buftype' means "normal buffer"
if vim.v.option_new == '' then
MiniTrailspace.highlight()
else
MiniTrailspace.unhighlight()
end
end
H.is_buffer_normal = function(buf_id) return vim.bo[buf_id or 0].buftype == '' end
H.get_match_id = function()
-- NOTE: this can be replaced with more efficient custom tracking of id per
-- window but it will have more edge cases (like won't update on manual
-- `clearmatches()`)
for _, match in ipairs(vim.fn.getmatches()) do
if match.group == 'MiniTrailspace' then return match.id end
end
end
return MiniTrailspace