Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ MINIMAL_SCRIPT=$(SCRIPTS_DIR)/minimal-colorizer.sh
MINIMAL_DEV_SCRIPT=$(SCRIPTS_DIR)/minimal-colorizer-dev.sh
MINIMAL_TAILWIND_SCRIPT=$(SCRIPTS_DIR)/minimal-tailwind.sh
MINIMAL_TAILWIND_DEV_SCRIPT=$(SCRIPTS_DIR)/minimal-tailwind-dev.sh
MINIMAL_CSS_VAR_SCRIPT=$(SCRIPTS_DIR)/minimal-css-var.sh
MINIMAL_CSS_VAR_DEV_SCRIPT=$(SCRIPTS_DIR)/minimal-css-var-dev.sh
MINIMAL_COLORIZER=colorizer_minimal
MINIMAL_TAILWIND=colorizer_tailwind
MINIMAL_CSS_VAR=colorizer_css_var
MINIMAL_TRIE=colorizer_trie

TEST_SCRIPT=$(SCRIPTS_DIR)/run_tests.sh
Expand All @@ -23,6 +26,8 @@ help:
@echo " make minimal-dev - Run the minimal script (local)"
@echo " make minimal-tailwind - Run the minimal tailwind config (remote)"
@echo " make minimal-tailwind-dev - Run the minimal tailwind config (local)"
@echo " make minimal-css-var - Run the minimal css-var config (remote)"
@echo " make minimal-css-var-dev - Run the minimal css-var config (local)"
@echo " make fmt - Auto-format Lua files with StyLua"
@echo " make fmt-check - Check Lua formatting (no changes)"
@echo " make docs - Generate vimdoc and HTML docs"
Expand Down Expand Up @@ -60,11 +65,21 @@ minimal-tailwind-dev:
@echo "Running minimal tailwind config (local)..."
@bash $(MINIMAL_TAILWIND_DEV_SCRIPT)

minimal-css-var:
@echo "Running minimal css-var config (remote)..."
@bash $(MINIMAL_CSS_VAR_SCRIPT)

minimal-css-var-dev:
@echo "Running minimal css-var config (local)..."
@bash $(MINIMAL_CSS_VAR_DEV_SCRIPT)

clean:
@echo "Removing test/"$(MINIMAL_COLORIZER)
@rm -rf test/$(MINIMAL_COLORIZER)
@echo "Removing test/"$(MINIMAL_TAILWIND)
@rm -rf test/$(MINIMAL_TAILWIND)
@echo "Removing test/"$(MINIMAL_CSS_VAR)
@rm -rf test/$(MINIMAL_CSS_VAR)
@echo "Removing test/tailwind/node_modules"
@rm -rf test/tailwind/node_modules
@echo "Removing test/trie/"$(MINIMAL_TRIE)
Expand Down Expand Up @@ -106,4 +121,4 @@ readme:
readme-check:
@lua scripts/readme/gen_readme.lua --check

.PHONY: help fmt fmt-check test test-file trie trie-test trie-benchmark minimal minimal-dev minimal-tailwind minimal-tailwind-dev clean docs docs-html demo screenshots screenshots-list readme readme-check
.PHONY: help fmt fmt-check test test-file trie trie-test trie-benchmark minimal minimal-dev minimal-tailwind minimal-tailwind-dev minimal-css-var minimal-css-var-dev clean docs docs-html demo screenshots screenshots-list readme readme-check
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,8 +479,23 @@ Features:

- Resolves aliased variables: `--alias: var(--base)` chains are followed
- Handles `var(--name, fallback)` syntax (highlights using the definition)
- Follows `@import` declarations to resolve variables from imported CSS files
- Re-scans definitions on every text change

### Cross-file variable resolution

`css_var` automatically follows `@import` declarations to resolve variables
defined in other files. All standard import syntaxes are supported:

```css
@import url("variables.css");
@import url('tokens.css');
@import "theme.css";
```

Import paths are resolved relative to the current file. Buffer-local
definitions always take precedence over imported ones.

## Lua API

```lua
Expand Down
95 changes: 75 additions & 20 deletions lua/colorizer/parser/css_var.lua
Original file line number Diff line number Diff line change
Expand Up @@ -52,33 +52,20 @@ end

local DEF_PATTERN = "^%-%-([%w_-]+)%s*:%s*()(.+)"

--- Scan buffer lines for CSS custom property definitions
---@param bufnr number
---@param line_start number 0-indexed
---@param line_end number -1 for end of buffer
---@param lines table|nil
---@param color_parser function Parser function to extract colors from values
function M.update_variables(bufnr, line_start, line_end, lines, color_parser)
lines = lines or vim.api.nvim_buf_get_lines(bufnr, line_start, line_end, false)

if not state[bufnr] then
state[bufnr] = { definitions = {} }
end

local defs = {}
-- First pass: collect direct color definitions
local recursive = {}
--- Scan lines for CSS custom property definitions into defs/recursive tables.
---@param lines table Lines to scan
---@param defs table Direct color definitions (name -> rgb_hex), mutated
---@param recursive table Recursive references (name -> ref_name), mutated
---@param color_parser function|nil
local function scan_lines_for_defs(lines, defs, recursive, color_parser)
for _, line in ipairs(lines) do
-- Find -- at any position in the line (CSS custom properties can be indented)
local s = line:find("%-%-")
if s then
local name, value_pos, value = line:match(DEF_PATTERN, s)
local name, _, value = line:match(DEF_PATTERN, s)
if name and value then
-- Strip trailing semicolons, whitespace, !important
value = value:match("^(.-)%s*;?%s*$")
value = value and value:match("^(.-)%s*!important%s*$") or value
if value and #value > 0 then
-- Check if value references another variable
local ref_name = value:match("^var%(%s*%-%-([%w_-]+)")
if ref_name then
recursive[name] = ref_name
Expand All @@ -92,6 +79,74 @@ function M.update_variables(bufnr, line_start, line_end, lines, color_parser)
end
end
end
end

--- Extract @import file paths from CSS lines.
--- Supports @import url("..."), @import url('...'), @import "...", @import '...'.
---@param lines table Lines to scan
---@return string[] import_paths
local function extract_imports(lines)
local paths = {}
for _, line in ipairs(lines) do
-- @import url("path") or @import url('path')
local p = line:match('@import%s+url%(%s*"([^"]+)"')
or line:match("@import%s+url%(%s*'([^']+)'")
-- @import "path" or @import 'path'
or line:match('@import%s+"([^"]+)"')
or line:match("@import%s+'([^']+)'")
if p then
paths[#paths + 1] = p
end
end
return paths
end

--- Read an imported CSS file relative to the buffer's directory.
---@param bufnr number
---@param import_path string
---@return string[]|nil lines
local function read_import(bufnr, import_path)
local buf_name = vim.api.nvim_buf_get_name(bufnr)
if buf_name == "" then
return nil
end
local buf_dir = vim.fn.fnamemodify(buf_name, ":h")
local full_path = buf_dir .. "/" .. import_path
-- Normalize and check existence
full_path = vim.fn.resolve(full_path)
if vim.fn.filereadable(full_path) ~= 1 then
return nil
end
return vim.fn.readfile(full_path)
end

--- Scan buffer lines for CSS custom property definitions
---@param bufnr number
---@param line_start number 0-indexed
---@param line_end number -1 for end of buffer
---@param lines table|nil
---@param color_parser function Parser function to extract colors from values
function M.update_variables(bufnr, line_start, line_end, lines, color_parser)
lines = lines or vim.api.nvim_buf_get_lines(bufnr, line_start, line_end, false)

if not state[bufnr] then
state[bufnr] = { definitions = {} }
end

local defs = {}
local recursive = {}

-- Scan imported files first (lower priority — buffer definitions override)
local imports = extract_imports(lines)
for _, import_path in ipairs(imports) do
local import_lines = read_import(bufnr, import_path)
if import_lines then
scan_lines_for_defs(import_lines, defs, recursive, color_parser)
end
end

-- Scan buffer lines (higher priority — overwrites imported definitions)
scan_lines_for_defs(lines, defs, recursive, color_parser)

-- Resolve recursive references (var(--other))
local function resolve(name, seen)
Expand Down
4 changes: 4 additions & 0 deletions scripts/minimal-colorizer-dev.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#!/usr/bin/env bash

cd test || exit

# Clear Neovim's bytecode cache so local source changes are always picked up
rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}/nvim/luac"

nvim --clean -u minimal-colorizer-dev.lua
8 changes: 8 additions & 0 deletions scripts/minimal-css-var-dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

cd test/css-var || exit

# Clear Neovim's bytecode cache so local source changes are always picked up
rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}/nvim/luac"

nvim --clean -u ../minimal-css-var-dev.lua main.css
5 changes: 5 additions & 0 deletions scripts/minimal-css-var.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash

cd test/css-var || exit

nvim --clean -u ../minimal-css-var.lua main.css
3 changes: 3 additions & 0 deletions scripts/minimal-tailwind-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ if [ ! -d node_modules ]; then
npm install
fi

# Clear Neovim's bytecode cache so local source changes are always picked up
rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}/nvim/luac"

nvim --clean -u ../minimal-tailwind-dev.lua tailwind.html
1 change: 1 addition & 0 deletions test/colorizer_css_var
Submodule colorizer_css_var added at 85c7ff
79 changes: 79 additions & 0 deletions test/css-var/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* Import shared variables — LSP resolves these across files */
@import url("variables.css");

/* Same-buffer definitions (resolved by buffer scanning) */
:root {
--local-color: #ff6b6b;
--local-accent: #4ecdc4;
}

/* Cross-file var() references (resolved by LSP) */
body {
background-color: var(--bg);
color: var(--text);
}

header {
background-color: var(--primary);
color: var(--primary-light);
border-bottom: 2px solid var(--border);
}

.alert-success {
background-color: var(--success);
color: var(--bg);
}

.alert-warning {
background-color: var(--warning);
}

.alert-danger {
background-color: var(--danger);
}

.alert-info {
background-color: var(--info);
}

/* Same-buffer var() references (resolved by buffer scanning) */
.local-example {
color: var(--local-color);
border: 1px solid var(--local-accent);
}

/* Mixed: some from this file, some from import */
.card {
background: var(--bg-muted);
color: var(--text);
border: 1px solid var(--border);
box-shadow: 0 1px 3px var(--overlay);
}

/* Aliased variables (chain resolution) */
a {
color: var(--link-color);
}

a:hover {
color: var(--brand);
}

/* Var with fallback */
.fallback-example {
color: var(--undefined-color, #888888);
}

/* Semantic usage */
.sidebar {
background-color: var(--bg-muted);
color: var(--text-muted);
accent-color: var(--accent);
}

/* Direct color values (highlighted by parser, not LSP) */
.direct-colors {
color: #ff0000;
background: rgb(0, 128, 255);
border-color: hsl(120, 50%, 50%);
}
Loading
Loading