Skip to content

Commit

Permalink
refactor!: use winhl instead of setting cc directly
Browse files Browse the repository at this point in the history
This is a major refactor, including:

* use `winhl` instead of setting `cc` directly, which alleviates the
  need recording/restoring/resetting `cc` options in each window, which
  is tricky and unreliable, leading to misaligned behavior with vim
  default settings.

* remove module `autocmds`, move necessary functions to main module

* use `nvim_get_hl()` instead of deprecated `nvim_get_hl_by_name()`
  • Loading branch information
bekaboo committed Jan 12, 2024
1 parent b7cdc07 commit ce15b17
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 512 deletions.
33 changes: 8 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,23 @@ function to override the default options.
```lua
local opts = {
scope = 'line',
modes = { 'i', 'ic', 'ix', 'R', 'Rc', 'Rx', 'Rv', 'Rvc', 'Rvx' },
---@type string[]|fun(mode: string): boolean
modes = function(mode)
return mode:find('^[ictRss\x13]') ~= nil
end,
blending = {
threshold = 0.75,
colorcode = '#000000',
hlgroup = {
'Normal',
'background',
},
hlgroup = { 'Normal', 'bg' },
},
warning = {
alpha = 0.4,
offset = 0,
colorcode = '#FF0000',
hlgroup = {
'Error',
'background',
},
hlgroup = { 'Error', 'bg' },
},
extra = {
---@type string?
follow_tw = nil,
},
}
Expand Down Expand Up @@ -203,18 +201,6 @@ require('deadcolumn').setup(opts) -- Call the setup function

## FAQ

### Why `:echo &cc` or `lua =vim.wo.cc` is empty?

If you are using the default config, this is expected.

The default config makes colorcolumn visible only in insert mode and replace
mode, so it clears `cc` in normal mode and reset it to the original value when
you enter insert mode or replace mode. As long as the colorcolumn is displayed
correctly in insert mode and replace mode, you don't need to worry about this.

If you want to see colorcolumn in normal mode, you can change the `modes`
option, see [Options](#options).

### Why can't I see the colored column?

This can have several reasons:
Expand All @@ -225,10 +211,7 @@ This can have several reasons:
to show the colored column in normal mode.

2. Please make sure you have set `colorcolumn` to a value greater than 0 in
your config. Notice that the output of `:echo &cc` or `lua =vim.wo.cc` may
be empty even if you have set `colorcolumn` to a value greater than 0. This
is because this plugin clears `colorcolumn` when it is not needed to conceal
the colored column, see point 1.
your config.

3. If you set `colorcolumn` to a relative value (e.g. `'-10'`), make sure
`textwidth` is set to a value greater than 0.
Expand Down
16 changes: 7 additions & 9 deletions doc/deadcolumn.nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,23 @@ the default options, the plugin should work out of the box.
>lua
local opts = {
scope = 'line',
modes = { 'i', 'ic', 'ix', 'R', 'Rc', 'Rx', 'Rv', 'Rvc', 'Rvx' },
---@type string[]|fun(mode: string): boolean
modes = function(mode)
return mode:find('^[ictRss\x13]') ~= nil
end,
blending = {
threshold = 0.75,
colorcode = '#000000',
hlgroup = {
'Normal',
'background',
},
hlgroup = { 'Normal', 'bg' },
},
warning = {
alpha = 0.4,
offset = 0,
colorcode = '#FF0000',
hlgroup = {
'Error',
'background',
},
hlgroup = { 'Error', 'bg' },
},
extra = {
---@type string?
follow_tw = nil,
},
}
Expand Down
191 changes: 183 additions & 8 deletions lua/deadcolumn.lua
Original file line number Diff line number Diff line change
@@ -1,22 +1,197 @@
local configs = require('deadcolumn.configs')
local autocmds = require('deadcolumn.autocmds')
local colors = require('deadcolumn.colors')
local utils = require('deadcolumn.utils')

local C_NORMAL, C_CC, C_ERROR

---Get background color in hex
---@param hlgroup_name string
---@param field string 'foreground' or 'background'
---@param fallback string|nil fallback color in hex, default to '#000000' if &bg is 'dark' and '#FFFFFF' if &bg is 'light'
---@return string hex color
local function get_hl_hex(hlgroup_name, field, fallback)
fallback = fallback or vim.opt.bg == 'dark' and '#000000' or '#FFFFFF'
if not vim.fn.hlexists(hlgroup_name) then
return fallback
end
local attr_val =
colors.get(0, { name = hlgroup_name, winhl_link = false })[field]
return attr_val and colors.dec2hex(attr_val) or fallback
end

---Update base colors: bg color of Normal & ColorColumn, and fg of Error
---@return nil
local function update_hl_hex()
C_NORMAL = get_hl_hex(
configs.opts.blending.hlgroup[1],
configs.opts.blending.hlgroup[2],
configs.opts.blending.colorcode
)
C_ERROR = get_hl_hex(
configs.opts.warning.hlgroup[1],
configs.opts.warning.hlgroup[2],
configs.opts.warning.colorcode
)
C_CC = get_hl_hex('ColorColumn', 'bg')
end

---Resolve the colorcolumn value
---@param cc string|nil
---@return integer|nil cc_number smallest integer >= 0 or nil
local function cc_resolve(cc)
if not cc or cc == '' then
return nil
end
local cc_tbl = vim.split(cc, ',')
local cc_min = nil
for _, cc_str in ipairs(cc_tbl) do
local cc_number = tonumber(cc_str)
if vim.startswith(cc_str, '+') or vim.startswith(cc_str, '-') then
cc_number = vim.bo.tw > 0 and vim.bo.tw + cc_number or nil
end
if cc_number and cc_number > 0 and (not cc_min or cc_number < cc_min) then
cc_min = cc_number
end
end
return cc_min
end

---Hide colorcolumn
---@param winid integer? window handler
local function cc_conceal(winid)
winid = winid or 0
local new_winhl = (
vim.wo[winid].winhl:gsub('ColorColumn:[^,]*', '') .. ',ColorColumn:'
):gsub(',*$', ''):gsub('^,*', ''):gsub(',+', ',')
if new_winhl ~= vim.wo[winid].winhl then
vim.wo[winid].winhl = new_winhl
end
end

---Show colorcolumn
---@param winid integer? window handler
local function cc_show(winid)
winid = winid or 0
local new_winhl = (
vim.wo[winid].winhl:gsub('ColorColumn:[^,]*', '')
.. ',ColorColumn:_ColorColumn'
):gsub(',*$', ''):gsub('^,*', ''):gsub(',+', ',')
if new_winhl ~= vim.wo[winid].winhl then
vim.wo[winid].winhl = new_winhl
end
end

---Check if the current mode is in the correct mode
---@param mode string? default to current mode
---@return boolean
local function is_in_correct_mode(mode)
mode = mode or vim.fn.mode()
if type(configs.opts.modes) == 'function' then
return configs.opts.modes(mode)
end
return type(configs.opts.modes) == 'table'
and vim.tbl_contains(configs.opts.modes --[=[@as string[]]=], mode)
or false
end
---Setup function
---@param opts ColorColumnOptions
---@param opts ColorColumnOptions?
local function setup(opts)
configs.set_options(opts)
if not vim.g.loaded_deadcolumn then
vim.g.loaded_deadcolumn = true
autocmds.init()
autocmds.make_autocmds()
---Conceal colorcolumn in each window
for _, win in ipairs(vim.api.nvim_list_wins()) do
cc_conceal(win)
end
---Create autocmds for concealing / showing colorcolumn
local id = vim.api.nvim_create_augroup('Deadcolumn', {})
vim.api.nvim_create_autocmd('WinLeave', {
desc = 'Conceal colorcolumn in other windows.',
group = id,
callback = function()
if vim.fn.win_gettype() == '' then
cc_conceal(0)
end
end,
})
vim.api.nvim_create_autocmd('ColorScheme', {
desc = 'Update base colors.',
group = id,
callback = update_hl_hex,
})
local cc_bg = nil
vim.api.nvim_create_autocmd({
'BufWinEnter',
'ColorScheme',
'CursorMoved',
'CursorMovedI',
'ModeChanged',
'TextChanged',
'TextChangedI',
'WinEnter',
'WinScrolled',
}, {
desc = 'Change colorcolumn color.',
group = id,
callback = function()
local cc = cc_resolve(vim.wo.cc)
if
not is_in_correct_mode(vim.fn.mode())
or vim.fn.win_gettype() ~= ''
or not cc
then
cc_conceal(0)
return
end
-- Fix 'E976: using Blob as a String' after select a snippet
-- entry from LSP server using omnifunc `<C-x><C-o>`
---@diagnostic disable-next-line: param-type-mismatch
local length = vim.fn.strdisplaywidth(vim.fn.getline('.'))
local thresh = configs.opts.blending.threshold
if 0 < thresh and thresh <= 1 then
thresh = math.floor(thresh * cc)
end
if length < thresh then
cc_conceal(0)
return
end
-- Show blended color when len < cc
if not C_CC or not C_NORMAL or not C_ERROR then
update_hl_hex()
end
local new_cc_color = length < cc
and colors.cblend(C_CC, C_NORMAL, (length - thresh) / (cc - thresh)).dec
or colors.cblend(C_ERROR, C_NORMAL, configs.opts.warning.alpha).dec
if new_cc_color ~= cc_bg then
cc_bg = new_cc_color
vim.api.nvim_set_hl(0, '_ColorColumn', {
bg = cc_bg,
})
end
cc_show(0)
end,
})
if configs.opts.extra.follow_tw then
vim.api.nvim_create_autocmd('OptionSet', {
pattern = 'textwidth',
desc = 'Set colorcolumn according to textwidth.',
callback = function()
if vim.v.option_new ~= 0 then
vim.opt_local.colorcolumn = configs.opts.extra.follow_tw
end
end,
})
end
end
return {
setup = setup,
configs = configs,
colors = colors,
utils = utils,
}

0 comments on commit ce15b17

Please sign in to comment.