From 5a9e394e61925d71f9c1c0cd1fd30ce463d8be46 Mon Sep 17 00:00:00 2001 From: uga-rosa Date: Tue, 27 Sep 2022 15:27:44 +0900 Subject: [PATCH] feat: improve performance of highlighter --- README.md | 19 ++-- doc/ccc.txt | 26 +++-- lua/ccc/config/default.lua | 5 +- lua/ccc/config/init.lua | 14 ++- lua/ccc/highlighter.lua | 208 +++++++++++++++++++++++++------------ lua/ccc/utils/init.lua | 9 ++ 6 files changed, 189 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 8151ec9..d85d914 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ ## Colorizer -- supported colors are `hex`, `rgb()`, `hsl()`, 147 color names, and `textDocument/documentColor` of LSP. -- Color names and LSP are disabled by default. +- LSP `textDocument/documentColor` is supported. +- Highlighting is possible without LSP and supports `hex`, `rgb()`, `hsl()`, 147 color names. +- Color names are disabled by default. ![image](https://user-images.githubusercontent.com/430272/192379267-7b069281-021a-4ee5-bc65-58def20f9c0d.png) @@ -27,15 +28,14 @@ Super powerful color picker plugin. - Features - - RGB, HSL, CMYK, and other color space sliders for color adjustment. + - No dependency. + - RGB, HSL, CMYK, and other color space sliders for color creation. - Dynamic highlighting of sliders. - Restore previously used colors. - Selectable output formats. - - Transparent slider (for css `rgb()`/`hsl()`). + - Transparent slider for `rgb()` and `hsl()`. - Fast colorizer. -**If you use release version (0.7.2), use branch** `0.7.2` - # Setup If you do not want to change the default setting, there is no need to call `setup` (Empty `setup` is called automatically by plugin/ccc.lua). @@ -71,13 +71,16 @@ This plugin provides five commands and one mapping. - `:CccHighlighterEnable` - `:CccHighlighterDisable` - Highlight colors in the buffer. - - The colors to be highlighted are those registered in `ccc-option-pickers` and those returned by `textDocument/documentColor` request. + - The colors to be highlighted are those returned by textDocument/documentColor request or those registered in `ccc-option-pickers`. + - Highlight is managed on a buffer-by-buffer basis, so you must use this command each time to enable highlight on a new buffer. + - You can use `ccc-option-highlighter-auto-enable` to enable automatically on `BufEnter`. - The following options are available. - `ccc-option-highlighter-auto-enable` - `ccc-option-highlighter-filetypes` - - `ccc-option-highlighter-events` + - `ccc-option-highlighter-excludes` - `ccc-option-highlighter-lsp` + # Action All actions have been implemented as lua functions. diff --git a/doc/ccc.txt b/doc/ccc.txt index 3de4b75..5f4c0de 100644 --- a/doc/ccc.txt +++ b/doc/ccc.txt @@ -227,7 +227,7 @@ table highlighter.auto_enable ~ boolean Default: false - Whether to enable at startup. + Whether to enable automatically on BufEnter. *ccc-option-highlighter-filetypes* @@ -246,19 +246,13 @@ Default: {} can specify file types to be excludes. - *ccc-option-highlighter-events* -highlighter.events ~ -string[] -Default: { "WinScrolled", "TextChanged", "TextChangedI", "BufEnter" } - Events to update highlighting. - - *ccc-option-highlighter-lsp* highlighter.lsp ~ boolean -Default: false - If true, highlight using textDocument/documentColor. - Note: even if highlighted by this option, it cannot be picked up +Default: true + If true, highlight using textDocument/documentColor. If LS with the + color provider is not attached to a buffer, it falls back to highlight + with pickers. The color informations returned by LS are saved and used by |:CccPick|. @@ -329,12 +323,16 @@ Command *ccc-command* *:CccHighlighterEnable* :CccHighlighterEnable ~ Highlight colors in the buffer. The colors to be highlighted are those - registered in |ccc-option-pickers| and those returned by - textDocument/documentColor request. + returned by textDocument/documentColor request or those registered in + |ccc-option-pickers|. + Highlight is managed on a buffer-by-buffer basis, so you must use this + command each time to enable highlight on a new buffer. You can use + |ccc-option-highlighter-auto-enable| to enable automatically on + |BufEnter|. The following options are available. |ccc-option-highlighter-auto-enable| |ccc-option-highlighter-filetypes| - |ccc-option-highlighter-events| + |ccc-option-highlighter-excludes| |ccc-option-highlighter-lsp| diff --git a/lua/ccc/config/default.lua b/lua/ccc/config/default.lua index 7a3e005..050f0a2 100644 --- a/lua/ccc/config/default.lua +++ b/lua/ccc/config/default.lua @@ -60,13 +60,12 @@ return { highlight_mode = "bg", ---@type function output_line = ccc.output_line, - ---@type { auto_enable: boolean, filetypes: string[], excludes: string[], events: string[], lsp: boolean } + ---@type { auto_enable: boolean, filetypes: string[], excludes: string[], lsp: boolean } highlighter = { auto_enable = false, filetypes = {}, excludes = {}, - events = { "WinScrolled", "TextChanged", "TextChangedI", "BufEnter" }, - lsp = false, + lsp = true, }, ---@type {[1]: ColorPicker, [2]: ColorOutput}[] convert = { diff --git a/lua/ccc/config/init.lua b/lua/ccc/config/init.lua index 0844c98..73fe92c 100644 --- a/lua/ccc/config/init.lua +++ b/lua/ccc/config/init.lua @@ -1,3 +1,5 @@ +local api = vim.api + local M = { config = {}, } @@ -35,13 +37,21 @@ function M.setup(opt) ["config.highlighter.auto_enable"] = { M.config.highlighter.auto_enable, "b" }, ["config.highlighter.filetypes"] = { M.config.highlighter.filetypes, "t" }, ["config.highlighter.excludes"] = { M.config.highlighter.excludes, "t" }, - ["config.highlighter.events"] = { M.config.highlighter.events, "t" }, + ["config.highlighter.lsp"] = { M.config.highlighter.lsp, "b" }, ["config.convert"] = { M.config.convert, "t" }, ["config.mappings"] = { M.config.mappings, "t" }, }) if M.config.highlighter.auto_enable then - require("ccc.highlighter"):enable() + local aug_name = "ccc-highlighter-auto-enable" + api.nvim_create_augroup(aug_name, {}) + api.nvim_create_autocmd("BufEnter", { + pattern = "*", + group = aug_name, + callback = function() + require("ccc.highlighter"):enable() + end, + }) end end diff --git a/lua/ccc/highlighter.lua b/lua/ccc/highlighter.lua index bdcac43..3d95856 100644 --- a/lua/ccc/highlighter.lua +++ b/lua/ccc/highlighter.lua @@ -1,5 +1,4 @@ local api = vim.api -local fn = vim.fn local config = require("ccc.config") local utils = require("ccc.utils") @@ -7,18 +6,18 @@ local rgb2hex = require("ccc.output.hex").str ---@class Highlighter ---@field pickers ColorPicker[] ----@field ns_id integer ----@field aug_id integer +---@field picker_ns_id integer +---@field lsp_ns_id integer ---@field is_defined table #Set. Keys are hexes. ---@field ft_filter table ----@field events string[] ---@field lsp boolean ----@field enabled boolean +---@field attached_buffer table local Highlighter = {} function Highlighter:init() self.pickers = config.get("pickers") - self.ns_id = api.nvim_create_namespace("ccc-highlighter") + self.picker_ns_id = api.nvim_create_namespace("ccc-highlighter-picker") + self.lsp_ns_id = api.nvim_create_namespace("ccc-highlighter-lsp") self.is_defined = {} local highlighter_config = config.get("highlighter") local filetypes = highlighter_config.filetypes @@ -38,35 +37,142 @@ function Highlighter:init() end end self.ft_filter = ft_filter - self.events = highlighter_config.events self.lsp = highlighter_config.lsp + self.attached_buffer = {} end -function Highlighter:enable() +---@param bufnr? integer +function Highlighter:enable(bufnr) if self.pickers == nil then self:init() end - self.enabled = true - - self:update() - self.aug_id = api.nvim_create_augroup("ccc-highlighter", {}) - api.nvim_create_autocmd(self.events, { - group = self.aug_id, - pattern = "*", - callback = function() - self:update() + + if not self.ft_filter[vim.bo.filetype] then + return + end + + bufnr = utils.resolve_bufnr(bufnr) + + if self.attached_buffer[bufnr] then + return + end + self.attached_buffer[bufnr] = true + + self:start(bufnr) + + api.nvim_buf_attach(bufnr, false, { + on_lines = function(_, _, _, first_line, _, last_line) + if self.attached_buffer[bufnr] == nil then + return true + end + self:update(bufnr, first_line, last_line) + end, + on_detach = function() + self.attached_buffer[bufnr] = nil end, }) end -function Highlighter:update() - api.nvim_buf_clear_namespace(0, self.ns_id, 0, -1) - if not self.ft_filter[vim.bo.filetype] then - return +---@param bufnr integer +function Highlighter:start(bufnr) + vim.schedule(function() + if self.lsp then + if not self:update_lsp(bufnr) then + -- Wait for LS initialization + vim.defer_fn(function() + if not self.attached_buffer[bufnr] then + return + end + if not self:update_lsp(bufnr) then + self:update_picker(bufnr, 0, -1) + end + end, 200) + end + else + self:update_picker(bufnr, 0, -1) + end + end) +end + +---@param bufnr integer +---@param first_line integer 0-index +---@param last_line integer 0-index +function Highlighter:update(bufnr, first_line, last_line) + vim.schedule(function() + if not (self.lsp and self:update_lsp(bufnr)) then + self:update_picker(bufnr, first_line, last_line) + end + end) +end + +---@param bufnr integer +---@return boolean available +function Highlighter:update_lsp(bufnr) + local available = false + api.nvim_buf_clear_namespace(bufnr, self.lsp_ns_id, 0, -1) + + for _, client in pairs(vim.lsp.get_active_clients()) do + if client.server_capabilities.colorProvider then + local param = { textDocument = vim.lsp.util.make_text_document_params() } + client.request("textDocument/documentColor", param, function(err, color_informations) + if err or color_informations == nil then + return + end + available = #color_informations > 0 + + for _, color_info in ipairs(color_informations) do + local color = color_info.color + local range = color_info.range + + local hex = rgb2hex({ color.red, color.green, color.blue }) + + local hl_name = "CccHighlighter" .. hex:sub(2) + if not self.is_defined[hex] then + local highlight = utils.create_highlight(hex) + api.nvim_set_hl(0, hl_name, highlight) + self.is_defined[hex] = true + end + api.nvim_buf_add_highlight( + 0, + self.lsp_ns_id, + hl_name, + range.start.line, + range.start.character, + range["end"].character + ) + end + end) + end end - local start_row = fn.line("w0") - 1 - local end_row = fn.line("w$") - 1 - for i, line in ipairs(api.nvim_buf_get_lines(0, start_row, end_row + 1, false)) do + + return available +end + +---@param range Range +---@param color Color +---@return ls_color +function Highlighter:_create_ls_color(range, color) + local row = range.start.line + local start = range.start.character + local end_ = range["end"].character + local rgb = { color.red, color.green, color.blue } + local alpha = color.alpha or 1 + return { row = row, start = start, end_ = end_, rgb = rgb, alpha = alpha } +end + +---@param bufnr? integer +---@return ls_color[] +function Highlighter:get_ls_color(bufnr) + bufnr = utils.resolve_bufnr(bufnr) + return self.ls_colors[bufnr] +end + +---@param bufnr integer +---@param start_row integer 0-index +---@param end_row integer 0-index +function Highlighter:update_picker(bufnr, start_row, end_row) + api.nvim_buf_clear_namespace(bufnr, self.picker_ns_id, start_row, end_row) + for i, line in ipairs(api.nvim_buf_get_lines(bufnr, start_row, end_row, false)) do local row = start_row + i - 1 local init = 1 while true do @@ -90,55 +196,27 @@ function Highlighter:update() api.nvim_set_hl(0, hl_name, highlight) self.is_defined[hex] = true end - api.nvim_buf_add_highlight(0, self.ns_id, hl_name, row, start - 1, end_) + api.nvim_buf_add_highlight(bufnr, self.picker_ns_id, hl_name, row, start - 1, end_) init = end_ + 1 end end - - if self.lsp then - local param = { textDocument = vim.lsp.util.make_text_document_params() } - vim.lsp.buf_request(0, "textDocument/documentColor", param, function(err, colors) - if err or colors == nil then - return - end - - for _, color_info in pairs(colors) do - local range = color_info.range - if start_row <= range.start.line or range.start.line <= end_row then - local color = color_info.color - - local hex = rgb2hex({ color.red, color.green, color.blue }) - local hl_name = "CccHighlighter" .. hex:sub(2) - if not self.is_defined[hex] then - local highlight = utils.create_highlight(hex) - api.nvim_set_hl(0, hl_name, highlight) - self.is_defined[hex] = true - end - api.nvim_buf_add_highlight( - 0, - self.ns_id, - hl_name, - range.start.line, - range.start.character, - range["end"].character - ) - end - end - end) - end end -function Highlighter:disable() - self.enabled = false - api.nvim_buf_clear_namespace(0, self.ns_id, 0, -1) - api.nvim_del_augroup_by_id(self.aug_id) +---@param bufnr? integer +function Highlighter:disable(bufnr) + bufnr = utils.resolve_bufnr(bufnr) + self.attached_buffer[bufnr] = nil + api.nvim_buf_clear_namespace(bufnr, self.picker_ns_id, 0, -1) + api.nvim_buf_clear_namespace(bufnr, self.lsp_ns_id, 0, -1) end -function Highlighter:toggle() - if self.enabled then - self:disable() +---@param bufnr? integer +function Highlighter:toggle(bufnr) + bufnr = utils.resolve_bufnr(bufnr) + if self.attached_buffer[bufnr] then + self:disable(bufnr) else - self:enable() + self:enable(bufnr) end end diff --git a/lua/ccc/utils/init.lua b/lua/ccc/utils/init.lua index 48c334e..28a58ea 100644 --- a/lua/ccc/utils/init.lua +++ b/lua/ccc/utils/init.lua @@ -188,4 +188,13 @@ function utils.is_excluded(exclude_pattern, s, init, start, end_) return false end +---@param bufnr? integer +---@return integer +function utils.resolve_bufnr(bufnr) + if bufnr == nil or bufnr == 0 then + return api.nvim_get_current_buf() + end + return bufnr +end + return utils