198 lines
7.1 KiB
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
|