diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | doc/colorizer.txt | 64 | ||||
-rw-r--r-- | doc/modules/colorizer.buffer_utils.html | 4 | ||||
-rw-r--r-- | doc/modules/colorizer.color_utils.html | 110 | ||||
-rw-r--r-- | doc/modules/colorizer.html | 7 | ||||
-rw-r--r-- | doc/modules/colorizer.utils.html | 87 | ||||
-rw-r--r-- | doc/modules/trie.html | 80 | ||||
-rw-r--r-- | doc/modules/utils.html | 64 | ||||
-rw-r--r-- | doc/tags | 4 | ||||
-rw-r--r-- | lua/colorizer.lua | 42 | ||||
-rw-r--r-- | lua/colorizer/buffer_utils.lua | 49 | ||||
-rw-r--r-- | lua/colorizer/color_utils.lua | 306 | ||||
-rw-r--r-- | lua/colorizer/matcher_utils.lua | 18 | ||||
-rw-r--r-- | lua/colorizer/utils.lua | 66 | ||||
-rw-r--r-- | test/sample.scss | 25 | ||||
-rw-r--r-- | test/scss/colors1.sass | 12 | ||||
-rw-r--r-- | test/scss/colors2.sass | 4 |
17 files changed, 744 insertions, 200 deletions
@@ -97,6 +97,8 @@ library to do custom highlighting themselves. -- Available methods are false / true / "normal" / "lsp" / "both" -- True is same as normal tailwind = false, -- Enable tailwind colors + -- parsers can contain values used in |user_default_options| + sass = { enable = false, parsers = { css }, }, -- Enable sass colors virtualtext = "■", }, -- all the sub-options of filetypes apply to buftypes diff --git a/doc/colorizer.txt b/doc/colorizer.txt index 3661d8f..87a3a90 100644 --- a/doc/colorizer.txt +++ b/doc/colorizer.txt @@ -208,7 +208,9 @@ user_default_options *colorizer.user_default_options* mode = "background", -- Set the display mode. -- Available methods are false / true / "normal" / "lsp" / "both" -- True is same as normal - tailwind = false -- Enable tailwind colors + tailwind = false, -- Enable tailwind colors + -- parsers can contain values used in |user_default_options| + sass = { enable = false, parsers = { css }, }, -- Enable sass colors virtualtext = "■", } < @@ -226,6 +228,7 @@ user_default_options *colorizer.user_default_options* {css_fn} - boolean {mode} - string {tailwind} - boolean|string + {sass} - table {virtualtext} - string @@ -292,7 +295,7 @@ highlight_buffer({buf}, {ns}, {lines}, {line_start}, {line_end}, {options}, {options_local} - table: Buffer local variables returns:~ - nil or boolean or number,function or nil + nil or boolean or number,table @@ -309,7 +312,7 @@ rehighlight_buffer({buf}, {options}, {options_local}, {use_local_lines}) options_local returns:~ - nil or boolean or number,function or nil + nil or boolean or number,table @@ -348,6 +351,14 @@ Functions: ~ |color_name_parser| - Grab all the colour values from `vim.api.nvim_get_color_map` and create a lookup table. + |sass_cleanup| - Cleanup sass variables + + |sass_update_variables| - Parse the given lines for sass variabled and add + to SASS[buf].DEFINITIONS_ALL. + + |sass_name_parser| - Parse the given line for sass color names + check for value in SASS[buf].DEFINITIONS_ALL + |rgb_function_parser| - Parse for rgb() css function and return rgb hex. |rgba_function_parser| - Parse for rgba() css function and return rgb hex. @@ -390,6 +401,53 @@ color_name_parser({line}, {i}, {opts}) *colorizer.color_utils.color_name_parser* +sass_cleanup({buf}) *colorizer.color_utils.sass_cleanup* + Cleanup sass variables + + Parameters: ~ + {buf} - number + + + + + *colorizer.color_utils.sass_update_variables* +sass_update_variables({buf}, {line_start}, {line_end}, {lines}, {color_parser}, +{options}, {options_local}) + Parse the given lines for sass variabled and add to + SASS[buf].DEFINITIONS_ALL. + + which is then used in |sass_name_parser| + If lines are not given, then fetch the lines with line_start and line_end + + + Parameters: ~ + {buf} - number + {line_start} - number + {line_end} - number + {lines} - table|nil + {color_parser} - function|boolean + {options} - table: Buffer options + {options_local} - table|nil: Buffer local variables + + returns:~ + boolean + + + +sass_name_parser({line}, {i}, {buf}) *colorizer.color_utils.sass_name_parser* + Parse the given line for sass color names + check for value in SASS[buf].DEFINITIONS_ALL + + Parameters: ~ + {line} - string: Line to parse + {i} - number: Index of line from where to start parsing + {buf} - number + + returns:~ + number or nil, string or nil + + + rgb_function_parser({line}, {i}) *colorizer.color_utils.rgb_function_parser* Parse for rgb() css function and return rgb hex. diff --git a/doc/modules/colorizer.buffer_utils.html b/doc/modules/colorizer.buffer_utils.html index 7260c5c..8d7beaa 100644 --- a/doc/modules/colorizer.buffer_utils.html +++ b/doc/modules/colorizer.buffer_utils.html @@ -148,7 +148,7 @@ <h3>Returns:</h3> <ol> - nil|boolean|number,function|nil + nil|boolean|number,table </ol> @@ -182,7 +182,7 @@ <h3>Returns:</h3> <ol> - nil|boolean|number,function|nil + nil|boolean|number,table </ol> diff --git a/doc/modules/colorizer.color_utils.html b/doc/modules/colorizer.color_utils.html index a02853b..6e26961 100644 --- a/doc/modules/colorizer.color_utils.html +++ b/doc/modules/colorizer.color_utils.html @@ -68,6 +68,19 @@ <td class="summary">Grab all the colour values from <code>vim.api.nvim_get_color_map</code> and create a lookup table.</td> </tr> <tr> + <td class="name" nowrap><a href="#sass_cleanup">sass_cleanup (buf)</a></td> + <td class="summary">Cleanup sass variables</td> + </tr> + <tr> + <td class="name" nowrap><a href="#sass_update_variables">sass_update_variables (buf, line_start, line_end, lines, color_parser, options, options_local)</a></td> + <td class="summary">Parse the given lines for sass variabled and add to SASS[buf].DEFINITIONS_ALL.</td> + </tr> + <tr> + <td class="name" nowrap><a href="#sass_name_parser">sass_name_parser (line, i, buf)</a></td> + <td class="summary">Parse the given line for sass color names + check for value in SASS[buf].DEFINITIONS_ALL</td> + </tr> + <tr> <td class="name" nowrap><a href="#rgb_function_parser">rgb_function_parser (line, i)</a></td> <td class="summary">Parse for rgb() css function and return rgb hex.</td> </tr> @@ -157,6 +170,103 @@ </dd> <dt> + <a name = "sass_cleanup"></a> + <strong>sass_cleanup (buf)</strong> + </dt> + <dd> + Cleanup sass variables + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">buf</span> + number + </li> + </ul> + + + + + +</dd> + <dt> + <a name = "sass_update_variables"></a> + <strong>sass_update_variables (buf, line_start, line_end, lines, color_parser, options, options_local)</strong> + </dt> + <dd> + Parse the given lines for sass variabled and add to SASS[buf].DEFINITIONS<em>ALL. + which is then used in |sass</em>name<em>parser| + If lines are not given, then fetch the lines with line</em>start and line_end + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">buf</span> + number + </li> + <li><span class="parameter">line_start</span> + number + </li> + <li><span class="parameter">line_end</span> + number + </li> + <li><span class="parameter">lines</span> + table|nil + </li> + <li><span class="parameter">color_parser</span> + function|boolean + </li> + <li><span class="parameter">options</span> + table: Buffer options + </li> + <li><span class="parameter">options_local</span> + table|nil: Buffer local variables + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + boolean + </ol> + + + + +</dd> + <dt> + <a name = "sass_name_parser"></a> + <strong>sass_name_parser (line, i, buf)</strong> + </dt> + <dd> + Parse the given line for sass color names + check for value in SASS[buf].DEFINITIONS_ALL + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">line</span> + string: Line to parse + </li> + <li><span class="parameter">i</span> + number: Index of line from where to start parsing + </li> + <li><span class="parameter">buf</span> + number + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + number|nil, string|nil + </ol> + + + + +</dd> + <dt> <a name = "rgb_function_parser"></a> <strong>rgb_function_parser (line, i)</strong> </dt> diff --git a/doc/modules/colorizer.html b/doc/modules/colorizer.html index 311bd60..edc06db 100644 --- a/doc/modules/colorizer.html +++ b/doc/modules/colorizer.html @@ -400,7 +400,9 @@ Setup an autocmd which enables colorizing for the filetypes and options specifie mode = "background", -- Set the display mode. -- Available methods are false / true / "normal" / "lsp" / "both" -- True is same as normal - tailwind = false -- Enable tailwind colors + tailwind = false, -- Enable tailwind colors + -- parsers can contain values used in |user_default_options| + sass = { enable = false, parsers = { css }, }, -- Enable sass colors virtualtext = "■", } </pre> @@ -443,6 +445,9 @@ Setup an autocmd which enables colorizing for the filetypes and options specifie <li><span class="parameter">tailwind</span> boolean|string </li> + <li><span class="parameter">sass</span> + table + </li> <li><span class="parameter">virtualtext</span> string </li> diff --git a/doc/modules/colorizer.utils.html b/doc/modules/colorizer.utils.html deleted file mode 100644 index 9bca259..0000000 --- a/doc/modules/colorizer.utils.html +++ /dev/null @@ -1,87 +0,0 @@ -============================================================================== -UTILS *colorizer.utils* - -Helper utils - - -============================================================================== -LUA API *colorizer.utils* - -Available Functions: - - |colorizer.utils.byte_is_alphanumeric| - Obvious - - |colorizer.utils.byte_is_hex| - Obvious - - |colorizer.utils.merge| - Merge two tables - TODO Remove this and use vim.tbl_deep_extend - - |colorizer.utils.parse_hex| - Obvious - - |colorizer.utils.percent_or_hex| - Obvious - - - -byte_is_alphanumeric({byte}) |colorizer.utils.byte_is_alphanumeric| - Obvious - - - Parameters: - {byte} - number - - Returns: - boolean - - - -byte_is_hex({byte}) |colorizer.utils.byte_is_hex| - Obvious - - - Parameters: - {byte} - number - - Returns: - boolean - - - -merge({...}) |colorizer.utils.merge| - Merge two tables - TODO Remove this and use vim.tbl_deep_extend - - - Parameters: - {...} - - - Returns: - table - - - -parse_hex({byte}) |colorizer.utils.parse_hex| - Obvious - - - Parameters: - {byte} - number - - Returns: - number - - - -percent_or_hex({v}) |colorizer.utils.percent_or_hex| - Obvious - - - Parameters: - {v} - string - - Returns: - number|nil - - - - - vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/doc/modules/trie.html b/doc/modules/trie.html deleted file mode 100644 index e353630..0000000 --- a/doc/modules/trie.html +++ /dev/null @@ -1,80 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html> -<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> -<head> - <title>colorizer Docs</title> - <link rel="stylesheet" href="../ldoc.css" type="text/css" /> -</head> -<body> - -<div id="container"> - -<div id="product"> - <div id="product_logo"></div> - <div id="product_name"><big><b></b></big></div> - <div id="product_description"></div> -</div> <!-- id="product" --> - - -<div id="main"> - - -<!-- Menu --> - -<div id="navigation"> -<br/> -<h1>colorizer</h1> - -<ul> - <li><a href="../index.html">Index</a></li> -</ul> - - - -<h2>Modules</h2> -<ul class="nowrap"> - <li><a href="../modules/colorizer.html">colorizer</a></li> - <li><a href="../modules/colorizer.buffer_utils.html">buffer_utils</a></li> - <li><a href="../modules/colorizer.color_utils.html">color_utils</a></li> - <li><a href="../modules/colorizer.matcher_utils.html">matcher_utils</a></li> - <li><strong>trie</strong></li> - <li><a href="../modules/utils.html">utils</a></li> -</ul> - -</div> - -<div id="content"> - -<h1>Module <code>trie</code></h1> -<p>Trie implementation in luajit.</p> -<p> Copyright © 2019 Ashkan Kiani - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version.</p> - -<p> This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p> - - - -<br/> -<br/> - - - - -</div> <!-- id="content" --> -</div> <!-- id="main" --> -<div id="about"> -<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> -<i style="float:right;">Last updated 2022-09-02 21:37:16 </i> -</div> <!-- id="about" --> -</div> <!-- id="container" --> -</body> -</html> diff --git a/doc/modules/utils.html b/doc/modules/utils.html index 8ff8fed..3e6fb0a 100644 --- a/doc/modules/utils.html +++ b/doc/modules/utils.html @@ -79,6 +79,14 @@ <td class="name" nowrap><a href="#percent_or_hex">percent_or_hex (v)</a></td> <td class="summary">Obvious.</td> </tr> + <tr> + <td class="name" nowrap><a href="#get_last_modified">get_last_modified (path)</a></td> + <td class="summary">Get last modified time of a file</td> + </tr> + <tr> + <td class="name" nowrap><a href="#watch_file">watch_file (path, callback, ...)</a></td> + <td class="summary">Watch a file for changes and execute callback</td> + </tr> </table> <br/> @@ -217,6 +225,62 @@ </dd> + <dt> + <a name = "get_last_modified"></a> + <strong>get_last_modified (path)</strong> + </dt> + <dd> + Get last modified time of a file + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + string: file path + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + number|nil: modified time + </ol> + + + + +</dd> + <dt> + <a name = "watch_file"></a> + <strong>watch_file (path, callback, ...)</strong> + </dt> + <dd> + Watch a file for changes and execute callback + + + <h3>Parameters:</h3> + <ul> + <li><span class="parameter">path</span> + string: File path + </li> + <li><span class="parameter">callback</span> + function: Callback to execute + </li> + <li><span class="parameter">...</span> + array: params for callback + </li> + </ul> + + <h3>Returns:</h3> + <ol> + + function|nil + </ol> + + + + +</dd> </dl> @@ -12,6 +12,7 @@ colorizer.buffer_utils-introduction colorizer.txt /*colorizer.buffer_utils-intro colorizer.buffer_utils-lua-api colorizer.txt /*colorizer.buffer_utils-lua-api* colorizer.buffer_utils.DEFAULT_NAMESPACE colorizer.txt /*colorizer.buffer_utils.DEFAULT_NAMESPACE* colorizer.buffer_utils.HIGHLIGHT_MODE_NAMES colorizer.txt /*colorizer.buffer_utils.HIGHLIGHT_MODE_NAMES* +colorizer.buffer_utils.clear_hl_cache colorizer.txt /*colorizer.buffer_utils.clear_hl_cache* colorizer.buffer_utils.highlight_buffer colorizer.txt /*colorizer.buffer_utils.highlight_buffer* colorizer.buffer_utils.rehighlight_buffer colorizer.txt /*colorizer.buffer_utils.rehighlight_buffer* colorizer.clear_highlight_cache colorizer.txt /*colorizer.clear_highlight_cache* @@ -25,6 +26,9 @@ colorizer.color_utils.hsla_function_parser colorizer.txt /*colorizer.color_utils colorizer.color_utils.rgb_function_parser colorizer.txt /*colorizer.color_utils.rgb_function_parser* colorizer.color_utils.rgba_function_parser colorizer.txt /*colorizer.color_utils.rgba_function_parser* colorizer.color_utils.rgba_hex_parser colorizer.txt /*colorizer.color_utils.rgba_hex_parser* +colorizer.color_utils.sass_cleanup colorizer.txt /*colorizer.color_utils.sass_cleanup* +colorizer.color_utils.sass_name_parser colorizer.txt /*colorizer.color_utils.sass_name_parser* +colorizer.color_utils.sass_update_variables colorizer.txt /*colorizer.color_utils.sass_update_variables* colorizer.detach_from_buffer colorizer.txt /*colorizer.detach_from_buffer* colorizer.get_buffer_options colorizer.txt /*colorizer.get_buffer_options* colorizer.highlight_buffer colorizer.txt /*colorizer.highlight_buffer* diff --git a/lua/colorizer.lua b/lua/colorizer.lua index 4af765c..90b964a 100644 --- a/lua/colorizer.lua +++ b/lua/colorizer.lua @@ -108,7 +108,9 @@ local BUFFER_LOCAL = {} -- mode = "background", -- Set the display mode. -- -- Available methods are false / true / "normal" / "lsp" / "both" -- -- True is same as normal --- tailwind = false -- Enable tailwind colors +-- tailwind = false, -- Enable tailwind colors +-- -- parsers can contain values used in |user_default_options| +-- sass = { enable = false, parsers = { css }, }, -- Enable sass colors -- virtualtext = "■", -- } --</pre> @@ -124,6 +126,7 @@ local BUFFER_LOCAL = {} --@field css_fn boolean --@field mode string --@field tailwind boolean|string +--@field sass table --@field virtualtext string local USER_DEFAULT_OPTIONS = { RGB = true, @@ -137,6 +140,7 @@ local USER_DEFAULT_OPTIONS = { css_fn = false, mode = "background", tailwind = false, + sass = { enable = false, parsers = { css = true } }, virtualtext = "■", } @@ -198,10 +202,13 @@ local function detach_from_buffer(buf, ns) clear_namespace(buf, ns or DEFAULT_NAMESPACE, 0, -1) if BUFFER_LOCAL[buf] then - if BUFFER_LOCAL[buf].__tailwind_ns then - clear_namespace(buf, BUFFER_LOCAL[buf].__tailwind_ns, 0, -1) - if type(BUFFER_LOCAL[buf].__tailwind_detach) == "function" then - BUFFER_LOCAL[buf].__tailwind_detach() + for _, namespace in pairs(BUFFER_LOCAL[buf].__detach.ns) do + clear_namespace(buf, namespace, 0, -1) + end + + for _, f in pairs(BUFFER_LOCAL[buf].__detach.functions) do + if type(f) == "function" then + f(buf) end end @@ -210,6 +217,7 @@ local function detach_from_buffer(buf, ns) end BUFFER_LOCAL[buf].__autocmds = nil + BUFFER_LOCAL[buf].__detach = nil end -- because now the buffer is not visible, so delete its information BUFFER_OPTIONS[buf] = nil @@ -253,9 +261,13 @@ local function attach_to_buffer(buf, options, typ) BUFFER_OPTIONS[buf] = options BUFFER_LOCAL[buf] = BUFFER_LOCAL[buf] or {} - local tailwind_ns, tailwind_detach = rehighlight_buffer(buf, options) - BUFFER_LOCAL[buf].__tailwind_ns = BUFFER_LOCAL[buf].__tailwind_ns or tailwind_ns - BUFFER_LOCAL[buf].__tailwind_detach = BUFFER_LOCAL[buf].__tailwind_detach or tailwind_detach + local highlighted, returns = rehighlight_buffer(buf, options) + + if not highlighted then + return + end + + BUFFER_LOCAL[buf].__detach = BUFFER_LOCAL[buf].__detach or returns.detach BUFFER_LOCAL[buf].__init = true @@ -266,13 +278,20 @@ local function attach_to_buffer(buf, options, typ) local autocmds = {} local au_group_id = AUGROUP_ID - autocmds[#autocmds + 1] = autocmd({ "TextChanged", "TextChangedI", "TextChangedP" }, { + local text_changed_au = { "TextChanged", "TextChangedI", "TextChangedP" } + -- only enable InsertLeave in sass, rest don't require it + if options.sass and options.sass.enable then + table.insert(text_changed_au, "InsertLeave") + end + + autocmds[#autocmds + 1] = autocmd(text_changed_au, { group = au_group_id, buffer = buf, callback = function(args) -- only reload if it was not disabled using detach_from_buffer if BUFFER_OPTIONS[buf] then - if args.event == "TextChanged" then + BUFFER_LOCAL[buf].__event = args.event + if args.event == "TextChanged" or args.event == "InsertLeave" then rehighlight_buffer(buf, options, BUFFER_LOCAL[buf]) else local pos = vim.fn.getpos "." @@ -287,9 +306,10 @@ local function attach_to_buffer(buf, options, typ) autocmds[#autocmds + 1] = autocmd({ "WinScrolled" }, { group = au_group_id, buffer = buf, - callback = function() + callback = function(args) -- only reload if it was not disabled using detach_from_buffer if BUFFER_OPTIONS[buf] then + BUFFER_LOCAL[buf].__event = args.event rehighlight_buffer(buf, options, BUFFER_LOCAL[buf]) end end, diff --git a/lua/colorizer/buffer_utils.lua b/lua/colorizer/buffer_utils.lua index a567d19..7801d0c 100644 --- a/lua/colorizer/buffer_utils.lua +++ b/lua/colorizer/buffer_utils.lua @@ -9,9 +9,10 @@ local set_highlight = api.nvim_set_hl local color_utils = require "colorizer.color_utils" local color_is_bright = color_utils.color_is_bright +local sass_update_variables = color_utils.sass_update_variables +local sass_cleanup = color_utils.sass_cleanup -local matcher_utils = require "colorizer.matcher_utils" -local make_matcher = matcher_utils.make_matcher +local make_matcher = require("colorizer.matcher_utils").make_matcher local highlight_buffer, rehighlight_buffer local BUFFER_LINES = {} @@ -159,8 +160,9 @@ local TW_LSP_CLIENT = {} ---@param line_end number: Last line to highlight ---@param options table: Configuration options as described in `setup` ---@param options_local table: Buffer local variables ----@return nil|boolean|number,function|nil +---@return nil|boolean|number,table function highlight_buffer(buf, ns, lines, line_start, line_end, options, options_local) + local returns = { detach = { ns = {}, functions = {} } } if buf == 0 or buf == nil then buf = api.nvim_get_current_buf() end @@ -168,7 +170,13 @@ function highlight_buffer(buf, ns, lines, line_start, line_end, options, options ns = ns or DEFAULT_NAMESPACE local loop_parse_fn = make_matcher(options) if not loop_parse_fn then - return false + return false, returns + end + + -- only update sass varibled when text is changed + if options_local.__event ~= "WinScrolled" and options.sass and options.sass.enable then + table.insert(returns.detach.functions, sass_cleanup) + sass_update_variables(buf, 0, -1, nil, make_matcher(options.sass.parsers or { css = true }), options, options_local) end local data = {} @@ -178,7 +186,7 @@ function highlight_buffer(buf, ns, lines, line_start, line_end, options, options -- Upvalues are options and current_linenum local i = 1 while i < #line do - local length, rgb_hex = loop_parse_fn(line, i) + local length, rgb_hex = loop_parse_fn(line, i, buf) if length and rgb_hex then local name = create_highlight(rgb_hex, mode) local d = data[current_linenum] or {} @@ -193,7 +201,7 @@ function highlight_buffer(buf, ns, lines, line_start, line_end, options, options add_highlight(options, buf, ns, data, line_start, line_end) if not options.tailwind or (options.tailwind ~= "lsp" and options.tailwind ~= "both") then - return + return true, returns end if not TW_LSP_CLIENT[buf] or TW_LSP_CLIENT[buf].is_stopped() then @@ -246,11 +254,15 @@ function highlight_buffer(buf, ns, lines, line_start, line_end, options, options end) tailwind_client = ok and tailwind_client or {} if vim.tbl_isempty(tailwind_client) then - return DEFAULT_NAMESPACE_TAILWIND, del_tailwind_stuff + table.insert(returns.detach.functions, del_tailwind_stuff) + table.insert(returns.detach.ns, DEFAULT_NAMESPACE_TAILWIND) + return true, returns end if not tailwind_client[1] or not tailwind_client[1].supports_method "textDocument/documentColor" then - return DEFAULT_NAMESPACE_TAILWIND, del_tailwind_stuff + table.insert(returns.detach.functions, del_tailwind_stuff) + table.insert(returns.detach.ns, DEFAULT_NAMESPACE_TAILWIND) + return true, returns end TW_LSP_CLIENT[buf] = tailwind_client[1] @@ -260,13 +272,18 @@ function highlight_buffer(buf, ns, lines, line_start, line_end, options, options highlight_buffer_tailwind(buf, DEFAULT_NAMESPACE_TAILWIND, mode, options) end, 100) - return DEFAULT_NAMESPACE_TAILWIND, del_tailwind_stuff + table.insert(returns.detach.functions, del_tailwind_stuff) + table.insert(returns.detach.ns, DEFAULT_NAMESPACE_TAILWIND) + + return true, returns end -- only try to do tailwindcss highlight if lsp is attached if TW_LSP_CLIENT[buf] then highlight_buffer_tailwind(buf, DEFAULT_NAMESPACE_TAILWIND, mode, options) end + + return true, returns end -- get the amount lines to highlight @@ -288,7 +305,7 @@ local function getrow(buf) if old_min and old_max then -- Triggered for TextChanged autocmds -- TODO: Find a way to just apply highlight to changed text lines - if old_max == new_max then + if (old_max == new_max) or (old_min == new_min) then min, max = new_min, new_max -- Triggered for WinScrolled autocmd - Scroll Down elseif old_max < new_max then @@ -318,7 +335,7 @@ end ---@param options table: Buffer options ---@param options_local table|nil: Buffer local variables ---@param use_local_lines boolean|nil Whether to use lines num range from options_local ----@return nil|boolean|number,function|nil +---@return nil|boolean|number,table function rehighlight_buffer(buf, options, options_local, use_local_lines) if buf == 0 or buf == nil then buf = api.nvim_get_current_buf() @@ -328,13 +345,17 @@ function rehighlight_buffer(buf, options, options_local, use_local_lines) local min, max if use_local_lines and options_local then - min, max = options_local.__startline, options_local.__endline + min, max = options_local.__startline or 0, options_local.__endline or -1 else min, max = getrow(buf) end - local lines = buf_get_lines(buf, min, max, false) - return highlight_buffer(buf, ns, lines, min, max, options, options_local or {}) + + local bool, returns = highlight_buffer(buf, ns, lines, min, max, options, options_local or {}) + table.insert(returns.detach.functions, function() + BUFFER_LINES[buf] = nil + end) + return bool, returns end --- @export diff --git a/lua/colorizer/color_utils.lua b/lua/colorizer/color_utils.lua index 34a04f5..adb3f19 100644 --- a/lua/colorizer/color_utils.lua +++ b/lua/colorizer/color_utils.lua @@ -7,6 +7,10 @@ local byte_is_alphanumeric = utils.byte_is_alphanumeric local byte_is_hex = utils.byte_is_hex local parse_hex = utils.parse_hex local percent_or_hex = utils.percent_or_hex +local get_last_modified = utils.get_last_modified +local watch_file = utils.watch_file + +local uv = vim.loop local bit = require "bit" local floor, min, max = math.floor, math.min, math.max @@ -102,6 +106,7 @@ local function color_name_parser(line, i, opts) end TAILWIND_ENABLED = opts.tailwind end + if #line < i + COLOR_NAME_MINLEN - 1 then return end @@ -123,6 +128,304 @@ local function color_name_parser(line, i, opts) end end +local SASS = {} +--- Cleanup sass variables +---@param buf number +local function sass_cleanup(buf) + SASS[buf] = nil +end + +local dollar_hash = ("$"):byte() +local at_hash = ("@"):byte() +local colon_hash = (";"):byte() + +-- Helper function for sass_update_variables +local function sass_parse_lines(buf, line_start, content, name) + SASS[buf].DEFINITIONS_ALL = SASS[buf].DEFINITIONS_ALL or {} + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT = SASS[buf].DEFINITIONS_RECURSIVE_CURRENT or {} + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE = SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE or {} + + SASS[buf].DEFINITIONS_LINEWISE[name] = SASS[buf].DEFINITIONS_LINEWISE[name] or {} + SASS[buf].DEFINITIONS[name] = SASS[buf].DEFINITIONS[name] or {} + SASS[buf].IMPORTS[name] = SASS[buf].IMPORTS[name] or {} + SASS[buf].WATCH_IMPORTS[name] = SASS[buf].WATCH_IMPORTS[name] or {} + SASS[buf].CURRENT_IMPORTS[name] = {} + + local import_find_colon = false + for i, line in ipairs(content) do + local linenum = i - 1 + line_start + -- Invalidate any existing definitions for the lines we are processing. + if not vim.tbl_isempty(SASS[buf].DEFINITIONS_LINEWISE[name][linenum] or {}) then + for v, _ in pairs(SASS[buf].DEFINITIONS_LINEWISE[name][linenum]) do + SASS[buf].DEFINITIONS[name][v] = nil + end + SASS[buf].DEFINITIONS_LINEWISE[name][linenum] = {} + else + SASS[buf].DEFINITIONS_LINEWISE[name][linenum] = {} + end + + local index = 1 + while index < #line do + -- ignore comments + if line:match("^//", index) then + index = #line + -- line starting with variables $var + elseif not import_find_colon and line:byte(index) == dollar_hash then + local variable_name, variable_value = line:match("^%$([%w_-]+)%s*:%s*(.+)%s*", index) + -- Check if we got a variable definition + if variable_name and variable_value then + -- Check for a recursive variable definition. + if variable_value:byte() == dollar_hash then + local target_variable_name, len = variable_value:match "^%$([%w_-]+)()" + if target_variable_name then + -- Update the value. + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT[variable_name] = target_variable_name + SASS[buf].DEFINITIONS_LINEWISE[name][linenum][variable_name] = true + index = index + len + end + index = index + 1 + else + -- Check for a recursive variable definition. + -- If it's not recursive, then just update the value. + if SASS[buf].COLOR_PARSER then + local length, rgb_hex = SASS[buf].COLOR_PARSER(variable_value, 1) + if length and rgb_hex then + SASS[buf].DEFINITIONS[name][variable_name] = rgb_hex + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT[variable_name] = rgb_hex + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE[variable_name] = rgb_hex + SASS[buf].DEFINITIONS_LINEWISE[name][linenum][variable_name] = true + -- added 3 because the color parsers returns 3 less + -- todo: need to fix + index = index + length + 3 + end + end + end + index = index + #variable_name + end + -- color ( ; ) found + elseif import_find_colon and line:byte(index) == colon_hash then + import_find_colon, index = false, index + 1 + -- imports @import 'somefile' + elseif line:byte(index) == at_hash or import_find_colon then + local variable_value, colon, import_kw + if import_find_colon then + variable_value, colon = line:match("%s*(.*[^;])%s*([;]?)", index) + else + import_kw, variable_value, colon = line:match("@(%a+)%s+(.+[^;])%s*([;]?)", index) + import_kw = (import_kw == "import" or import_kw == "use") + end + + if not colon or colon == "" then + -- now loop until ; is found + import_find_colon = true + else + import_find_colon = false + end + + -- if import/use key word is found along with file name + if import_kw and variable_value then + local files = {} + -- grab files to be imported + for s, a in variable_value:gmatch "['\"](.-)()['\"]" do + local folder_path, file_name = vim.fn.fnamemodify(s, ":h"), vim.fn.fnamemodify(s, ":t") + if file_name ~= "" then + -- get the root directory of the file + local parent_dir = vim.fn.fnamemodify(name, ":h") + parent_dir = (parent_dir ~= "") and parent_dir .. "/" or "" + folder_path = vim.fn.fnamemodify(parent_dir .. folder_path, ":p") + file_name = file_name + files = { + folder_path .. file_name .. ".scss", + folder_path .. "_" .. file_name .. ".scss", + folder_path .. file_name .. ".sass", + folder_path .. "_" .. file_name .. ".sass", + } + end + -- why 2 * a ? I don't know + index = index + 2 * a + end + + -- process imported files + for _, v in ipairs(files) do + -- parse the sass files + local last_modified = get_last_modified(v) + if last_modified then + -- grab the full path + v = uv.fs_realpath(v) + SASS[buf].CURRENT_IMPORTS[name][v] = true + + if not SASS[buf].WATCH_IMPORTS[name][v] then + SASS[buf].IMPORTS[name][v] = last_modified + local c, ind = {}, 0 + for l in io.lines(v) do + ind = ind + 1 + c[ind] = l + end + sass_parse_lines(buf, 0, c, v) + c = nil + + local function watch_callback() + local dimen = vim.api.nvim_buf_call(buf, function() + return { vim.fn.line "w0", vim.fn.line "w$", vim.fn.line "$", vim.api.nvim_win_get_height(0) } + end) + -- todo: Improve this to only refresh highlight for visible lines + -- can't find out how to get visible rows from another window + -- probably a neovim bug, it is returning 1 and 1 or 1 and 5 + if + dimen[1] ~= dimen[2] + and ((dimen[3] > dimen[4] and dimen[2] > dimen[4]) or (dimen[2] >= dimen[3])) + then + SASS[buf].LOCAL_OPTIONS.__startline = dimen[1] + SASS[buf].LOCAL_OPTIONS.__endline = dimen[2] + end + SASS[buf].LOCAL_OPTIONS.__event = "" + + local lastm = get_last_modified(v) + if lastm then + SASS[buf].IMPORTS[name][v] = lastm + local cc, inde = {}, 0 + for l in io.lines(v) do + inde = inde + 1 + cc[inde] = l + end + sass_parse_lines(buf, 0, cc, v) + cc = nil + end + + require("colorizer.buffer_utils").rehighlight_buffer( + buf, + SASS[buf].OPTIONS, + SASS[buf].LOCAL_OPTIONS, + true + ) + end + SASS[buf].WATCH_IMPORTS[name][v] = watch_file(v, watch_callback) + end + else + -- if file does not exists then remove related variables + SASS[buf].IMPORTS[name][v] = nil + pcall(uv.fs_event_stop, SASS[buf].WATCH_IMPORTS[name][v]) + SASS[buf].WATCH_IMPORTS[name][v] = nil + end + end -- process imported files + end + end -- parse lines + index = index + 1 + end -- while loop end + end -- for loop end + + local function remove_unused_imports(import_name) + if type(SASS[buf].IMPORTS[import_name]) == "table" then + for file, _ in pairs(SASS[buf].IMPORTS[import_name]) do + remove_unused_imports(file) + end + end + SASS[buf].DEFINITIONS[import_name] = nil + SASS[buf].DEFINITIONS_LINEWISE[import_name] = nil + SASS[buf].IMPORTS[import_name] = nil + -- stop the watch handler + pcall(uv.fs_event_stop, SASS[buf].WATCH_IMPORTS[import_name]) + SASS[buf].WATCH_IMPORTS[import_name] = nil + end + + -- remove definitions of files which are not imported now + for file, _ in pairs(SASS[buf].IMPORTS[name]) do + if not SASS[buf].CURRENT_IMPORTS[name][file] then + remove_unused_imports(name) + end + end +end -- sass_parse_lines end + +--- Parse the given lines for sass variabled and add to SASS[buf].DEFINITIONS_ALL. +-- which is then used in |sass_name_parser| +-- If lines are not given, then fetch the lines with line_start and line_end +---@param buf number +---@param line_start number +---@param line_end number +---@param lines table|nil +---@param color_parser function|boolean +---@param options table: Buffer options +---@param options_local table|nil: Buffer local variables +local function sass_update_variables(buf, line_start, line_end, lines, color_parser, options, options_local) + lines = lines or vim.api.nvim_buf_get_lines(buf, line_start, line_end, false) + + if not SASS[buf] then + SASS[buf] = { + DEFINITIONS_ALL = {}, + DEFINITIONS = {}, + IMPORTS = {}, + WATCH_IMPORTS = {}, + CURRENT_IMPORTS = {}, + DEFINITIONS_LINEWISE = {}, + OPTIONS = options, + LOCAL_OPTIONS = options_local, + } + end + + SASS[buf].COLOR_PARSER = color_parser + SASS[buf].DEFINITIONS_ALL = {} + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT = {} + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE = {} + + sass_parse_lines(buf, line_start, lines, api.nvim_buf_get_name(buf)) + + -- add non-recursive def to DEFINITIONS_ALL + for _, color_table in pairs(SASS[buf].DEFINITIONS) do + for color_name, color in pairs(color_table) do + SASS[buf].DEFINITIONS_ALL[color_name] = color + end + end + + -- normally this is just a wasted step as all the values here are + -- already present in SASS[buf].DEFINITIONS + -- but when undoing a pasted text, it acts as a backup + for name, color in pairs(SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE) do + SASS[buf].DEFINITIONS_ALL[name] = color + end + + -- try to find the absolute color value for the given name + -- use tail call recursion + -- https://www.lua.org/pil/6.3.html + local function find_absolute_value(name, color_name) + return SASS[buf].DEFINITIONS_ALL[color_name] + or ( + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT[color_name] + and find_absolute_value(name, SASS[buf].DEFINITIONS_RECURSIVE_CURRENT[color_name]) + ) + end + + local function set_color_value(name, color_name) + local value = find_absolute_value(name, color_name) + if value then + SASS[buf].DEFINITIONS_ALL[name] = value + end + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT[name] = nil + end + + for name, color_name in pairs(SASS[buf].DEFINITIONS_RECURSIVE_CURRENT) do + set_color_value(name, color_name) + end + + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT = nil + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE = nil +end + +--- Parse the given line for sass color names +-- check for value in SASS[buf].DEFINITIONS_ALL +---@param line string: Line to parse +---@param i number: Index of line from where to start parsing +---@param buf number +---@return number|nil, string|nil +local function sass_name_parser(line, i, buf) + local variable_name = line:sub(i):match "^%$([%w_-]+)" + if variable_name then + local rgb_hex = SASS[buf].DEFINITIONS_ALL[variable_name] + if rgb_hex then + return #variable_name + 1, rgb_hex + end + end +end + --- Converts an HSL color value to RGB. ---@param h number: Hue ---@param s number: Saturation @@ -407,4 +710,7 @@ return { rgba_function_parser = rgba_function_parser, hsl_function_parser = hsl_function_parser, hsla_function_parser = hsla_function_parser, + sass_name_parser = sass_name_parser, + sass_cleanup = sass_cleanup, + sass_update_variables = sass_update_variables, } diff --git a/lua/colorizer/matcher_utils.lua b/lua/colorizer/matcher_utils.lua index 29cd5e9..7803eac 100644 --- a/lua/colorizer/matcher_utils.lua +++ b/lua/colorizer/matcher_utils.lua @@ -5,6 +5,7 @@ local min, max = math.min, math.max local color_utils = require "colorizer.color_utils" local color_name_parser = color_utils.color_name_parser +local sass_name_parser = color_utils.sass_name_parser local rgba_hex_parser = color_utils.rgba_hex_parser local parser = {} @@ -13,6 +14,7 @@ parser["_rgb"] = color_utils.rgb_function_parser parser["_rgba"] = color_utils.rgba_function_parser parser["_hsl"] = color_utils.hsl_function_parser parser["_hsla"] = color_utils.hsla_function_parser +local b_hash, dollar_hash = ("#"):byte(), ("$"):byte() ---Form a trie stuct with the given prefixes ---@param matchers table: List of prefixes, {"rgb", "hsl"} @@ -21,8 +23,7 @@ parser["_hsla"] = color_utils.hsla_function_parser local function compile_matcher(matchers, matchers_trie) local trie = Trie(matchers_trie) - local b_hash = ("#"):byte() - local function parse_fn(line, i) + local function parse_fn(line, i, buf) -- prefix # if matchers.rgba_hex_parser then if line:byte(i) == b_hash then @@ -30,6 +31,13 @@ local function compile_matcher(matchers, matchers_trie) end end + -- prefix $, SASS Colour names + if matchers.sass_name_parser then + if line:byte(i) == dollar_hash then + return sass_name_parser(line, i, buf) + end + end + -- Prefix 0x, rgba, rgb, hsla, hsl local prefix = trie:longest_prefix(line, i) if prefix then @@ -55,6 +63,7 @@ local MATCHER_CACHE = {} ---@return function|boolean: function which will just parse the line for enabled parsers local function make_matcher(options) local enable_names = options.css or options.names + local enable_sass = options.sass and options.sass.enable local enable_tailwind = options.tailwind local enable_RGB = options.css or options.RGB local enable_RRGGBB = options.css or options.RRGGBB @@ -74,6 +83,7 @@ local function make_matcher(options) + ((enable_tailwind == true or enable_tailwind == "normal") and 1 or 7) + (enable_tailwind == "lsp" and 1 or 8) + (enable_tailwind == "both" and 1 or 9) + + (enable_sass and 1 or 10) if matcher_key == 0 then return false @@ -92,6 +102,10 @@ local function make_matcher(options) matchers.color_name_parser = { tailwind = options.tailwind } end + if enable_sass then + matchers.sass_name_parser = true + end + local valid_lengths = { [3] = enable_RGB, [6] = enable_RRGGBB, [8] = enable_RRGGBBAA } local minlen, maxlen for k, v in pairs(valid_lengths) do diff --git a/lua/colorizer/utils.lua b/lua/colorizer/utils.lua index bfffb60..9dad9d8 100644 --- a/lua/colorizer/utils.lua +++ b/lua/colorizer/utils.lua @@ -3,6 +3,8 @@ local bit, ffi = require "bit", require "ffi" local band, bor, rshift, lshift = bit.band, bit.bor, bit.rshift, bit.lshift +local uv = vim.loop + -- -- TODO use rgb as the return value from the matcher functions -- -- instead of the rgb_hex. Can be the highlight key as well -- -- when you shift it left 8 bits. Use the lower 8 bits for @@ -101,6 +103,68 @@ local function percent_or_hex(v) return x end +--- Get last modified time of a file +---@param path string: file path +---@return number|nil: modified time +local function get_last_modified(path) + local fd = uv.fs_open(path, "r", 438) + if not fd then + return + end + + local stat = uv.fs_fstat(fd) + uv.fs_close(fd) + if stat then + return stat.mtime.nsec + else + return + end +end + +--- Watch a file for changes and execute callback +---@param path string: File path +---@param callback function: Callback to execute +---@param ... array: params for callback +---@return function|nil +local function watch_file(path, callback, ...) + if not path or type(callback) ~= "function" then + return + end + + local fullpath = uv.fs_realpath(path) + if not fullpath then + return + end + + local start + local args = { ... } + + local handle = uv.new_fs_event() + local function on_change(err, filename, _) + -- Do work... + callback(filename, unpack(args)) + -- Debounce: stop/start. + handle:stop() + if not err or not get_last_modified(filename) then + start() + end + end + + function start() + uv.fs_event_start( + handle, + fullpath, + {}, + vim.schedule_wrap(function(...) + on_change(...) + end) + ) + end + + start() + return handle +end + --- @export return { byte_is_alphanumeric = byte_is_alphanumeric, @@ -108,4 +172,6 @@ return { merge = merge, parse_hex = parse_hex, percent_or_hex = percent_or_hex, + get_last_modified = get_last_modified, + watch_file = watch_file, } diff --git a/test/sample.scss b/test/sample.scss new file mode 100644 index 0000000..9384fd5 --- /dev/null +++ b/test/sample.scss @@ -0,0 +1,25 @@ +@import "scss/colors1"; + +$dd: $test7; +$test0: $color1; + +// recursive definitions +$d: $test1; +$dark: $d; +$e: $dark; +$f: $e; +$g: $f; +$h: $g; +$test1: $color2; + +$test2: hotpink; +$test3: $color3; +$test4: $color4; +$test5: rgb(123, 255, 123); +$test6: $color6; // this should be absent and this comment shoudl be ignored $color6: black +$test7: rgba(255, 123, 123, 0.9); +$test8: $color8; +$test9: $color9; +$test10: $color10; +$test11: $color11; +$aki: red; diff --git a/test/scss/colors1.sass b/test/scss/colors1.sass new file mode 100644 index 0000000..4304ff3 --- /dev/null +++ b/test/scss/colors1.sass @@ -0,0 +1,12 @@ +@import "colors2"; + +$color2: #E33224; +$color4: rgb(195, 60, 190); + +$color5: $color2; + +$color7: linear-gradient(to left, #0055ff, #7201b2 50%, #f83371); +$color8: #ff0; +$color9: #B0B; +$color10: cornflowerblue; +$color11: hsla(120, 60%, 70%, 0.3); diff --git a/test/scss/colors2.sass b/test/scss/colors2.sass new file mode 100644 index 0000000..ccfbaf0 --- /dev/null +++ b/test/scss/colors2.sass @@ -0,0 +1,4 @@ +$color1: #203CDB; +$color3: rgb(250, 145, 55); + +$color6: radial-gradient(circle farthest-corner at 32% 106%, rgb(255, 225, 125) 0%, rgb(255, 205, 105) 10%, rgb(250, 145, 55) 28%, rgb(235, 65, 65) 42%, transparent 82%), linear-gradient(135deg, rgb(35, 75, 215) 12%, rgb(195, 60, 190) 58%); |