From 864a2bcc41675740a8c1198eaed6e9d9ca4d9fd0 Mon Sep 17 00:00:00 2001 From: stepit Date: Tue, 10 Mar 2026 15:05:22 +0100 Subject: [PATCH 01/14] refactor: palette colors and shares --- lua/flow/palette.lua | 280 +++++++++++++++++-------------------------- 1 file changed, 112 insertions(+), 168 deletions(-) diff --git a/lua/flow/palette.lua b/lua/flow/palette.lua index 0df88e3..0e563d8 100644 --- a/lua/flow/palette.lua +++ b/lua/flow/palette.lua @@ -2,209 +2,153 @@ local hsl = require("flow.util").hsl_to_hex local M = {} ----Generates and returns a color palette using HSL and HEX values. ---- @param o FlowConfig: The available options. ----@return table -function M.get(o) - local is_dark = o.theme.style == "dark" - -- The colorscheme support 5 shades of colors. +--- Default fluo light values. +M.fluo_lightness = { + default = 50, + light = 90, + dark = 35, +} + +--- Default grey lightness steps used by both chromatic and monochrome palettes. +M.grey_lightness = { 10, 13, 15, 18, 24, 29, 50, 65, 85, 88, 90 } + +--- Default grey hue for the chromatic palette. +M.grey_hue = 203 +--- Default grey saturation for the chromatic palette. +M.grey_saturations = { 20, 20, 20, 12, 15, 20, 20, 20, 20, 20, 20 } + +M.hue = { + red = 355, + purple = 270, + blue = 230, + light_blue = 205, + sky_blue = 190, + cyan = 165, + green = 115, + yellow = 60, + orange = 25, +} + +M.fluo_hue = { + pink = 331, + cyan = 187, + green = 115, + yellow = 61, + orange = 25, +} + +--- Build an 11-step grey scale. +--- @param h_value number Hue (0 for achromatic). +--- @param s_values table Array of 11 saturation values. +--- @param l_values table Array of 11 lightness values. +--- @return table +function M.build_greys(h_value, s_values, l_values) + local grey = {} + for i = 1, 11 do + grey[i] = hsl(h_value, s_values[i], l_values[i]) + end + return grey +end + +--- Build a 5-shade color from a hue and shade definition. +--- @param h number Hue value +--- @param shade table Shade definitions with S (saturation) and L (light) for each level +--- @return table +function M.build_color(h, shade) + return { + very_dark = hsl(h, shade.very_dark.S, shade.very_dark.L), + dark = hsl(h, shade.dark.S, shade.dark.L), + default = hsl(h, shade.default.S, shade.default.L), + light = hsl(h, shade.light.S, shade.light.L), + very_light = hsl(h, shade.very_light.S, shade.very_light.L), + } +end + +--- Build a 3-shade fluorescent color. +--- @param h number Hue value. +--- @param light_l number? Optional lightness for light shade. +--- @param dark_l number? Optional lightness for dark shade. +--- @return table +function M.build_fluo(h, light_l, dark_l) + return { + default = hsl(h, 100, M.fluo_lightness.default), + light = hsl(h, 100, light_l or M.fluo_lightness.light), + dark = hsl(h, 100, dark_l or M.fluo_lightness.dark), + } +end + +--- Generates and returns a color palette using HSL and HEX values. +--- @param config FlowConfig: The available theme configuration options. +--- @return table +function M.get(config) + local is_dark = config.theme.style == "dark" + local shade = { very_dark = { S = 27, L = 20 }, dark = { S = 50, L = 35 }, - default = { - S = (not is_dark and 40) or 60, - L = (not is_dark and 55) or 65, - }, + default = { S = (not is_dark and 40) or 60, L = (not is_dark and 55) or 65 }, light = { S = 40, L = 65 }, very_light = { S = 50, L = 85 }, } - if o.colors.custom.light ~= "" then + if config.colors.custom.light ~= "" then ---@diagnostic disable-next-line: assign-type-mismatch - shade[o.colors.mode].L = tonumber(o.colors.custom.light) + shade[config.colors.mode].L = tonumber(config.colors.custom.light) end - if o.colors.custom.saturation ~= "" then + if config.colors.custom.saturation ~= "" then ---@diagnostic disable-next-line: assign-type-mismatch - shade[o.colors.mode].S = tonumber(o.colors.custom.saturation) + shade[config.colors.mode].S = tonumber(config.colors.custom.saturation) end - local hue = { - red = 355, - purple = 270, - blue = 230, - light_blue = 205, - sky_blue = 190, - cyan = 165, - green = 115, - yellow = 60, - orange = 25, - } - - local fluo_hue = { - pink = 331, - cyan = 187, - green = 115, - yellow = 61, - orange = 25, - } - - -- Get the configured fluo color - local configured_fluo = (o.colors and o.colors.fluo) or "pink" - local fluo_hue_value = fluo_hue[configured_fluo] + -- Get the configured fluo color. + local configured_fluo = (config.colors and config.colors.fluo) or "pink" + local fluo_hue_value = M.fluo_hue[configured_fluo] local palette = { - -- Transparent color + -- Transparent color. transparent = "NONE", - -- Base colors + -- Base colors. black = hsl(0, 0, 5), white = hsl(0, 0, 95), - -- Greyscale colors - grey = { - [1] = hsl(203, 20, 10), -- Very dark grey - [2] = hsl(203, 20, 13), - [3] = hsl(203, 20, 15), - [4] = hsl(203, 12, 18), - [5] = hsl(203, 15, 24), - [6] = hsl(203, 20, 29), - [7] = hsl(203, 20, 50), - [8] = hsl(203, 20, 65), - [9] = hsl(203, 20, 85), - [10] = hsl(203, 20, 88), - [11] = hsl(203, 20, 90), -- Very light grey - }, + -- Greyscale colors. + grey = M.build_greys(M.grey_hue, M.grey_saturations, M.grey_lightness), - -- CursorLine background - uses configured fluo color hue + -- CursorLine background. cursorline_bg = { - dark = hsl(fluo_hue_value, 20, 13), -- For dark theme + dark = hsl(fluo_hue_value, 20, 13), -- For dark theme light = hsl(fluo_hue_value, 20, 87), -- For light theme }, - -- Visual selection background - uses configured fluo color hue + -- Visual selection background. visual_bg = { - dark = hsl(fluo_hue_value, 90, 23), -- For dark theme - light = hsl(fluo_hue_value, 90, 77), -- For light theme (inverted lightness) + dark = hsl(fluo_hue_value, 90, 23), -- For dark theme + light = hsl(fluo_hue_value, 90, 77), -- For light theme }, - -- Float window colors + -- Float window colors. float_bg = { - dark = hsl(203, 20, 18), -- For dark theme - light = hsl(203, 20, 82), -- For light theme (inverted lightness) + dark = hsl(203, 20, 18), -- For dark theme + light = hsl(203, 20, 82), -- For light theme }, - -- Fluorescent colors + -- Fluorescent colors. fluo = { - pink = { - default = hsl(fluo_hue.pink, 100, 50), - light = hsl(fluo_hue.pink, 100, 90), - dark = hsl(fluo_hue.pink, 100, 35), - }, - cyan = { - default = hsl(fluo_hue.cyan, 100, 50), - light = hsl(fluo_hue.cyan, 100, 90), - dark = hsl(fluo_hue.cyan, 100, 30), - }, - green = { - default = hsl(fluo_hue.green, 100, 50), - light = hsl(fluo_hue.green, 100, 100), - dark = hsl(fluo_hue.green, 100, 30), - }, - orange = { - default = hsl(fluo_hue.orange, 100, 50), - light = hsl(fluo_hue.orange, 100, 90), - dark = hsl(fluo_hue.orange, 100, 35), - }, - yellow = { - default = hsl(fluo_hue.yellow, 100, 50), - light = hsl(fluo_hue.yellow, 100, 90), - dark = hsl(fluo_hue.yellow, 100, 30), - }, - }, - - -- Red shades - red = { - very_dark = hsl(hue.red, shade.very_dark.S, shade.very_dark.L), - dark = hsl(hue.red, shade.dark.S, shade.dark.L), - default = hsl(hue.red, shade.default.S, shade.default.L), - light = hsl(hue.red, shade.light.S, shade.light.L), - very_light = hsl(hue.red, shade.very_light.S, shade.very_light.L), - }, - - -- Purple shades - purple = { - very_dark = hsl(hue.purple, shade.very_dark.S, shade.very_dark.L), - dark = hsl(hue.purple, shade.dark.S, shade.dark.L), - default = hsl(hue.purple, shade.default.S, shade.default.L), - light = hsl(hue.purple, shade.light.S, shade.light.L), - very_light = hsl(hue.purple, shade.very_light.S, shade.very_light.L), - }, - - -- Blue shades - blue = { - very_dark = hsl(hue.blue, shade.very_dark.S, shade.very_dark.L), - dark = hsl(hue.blue, shade.dark.S, shade.dark.L), - default = hsl(hue.blue, shade.default.S, shade.default.L), - light = hsl(hue.blue, shade.light.S, shade.light.L), - very_light = hsl(hue.blue, shade.very_light.S, shade.very_light.L), - }, - - -- Light blue shades - light_blue = { - very_dark = hsl(hue.light_blue, shade.very_dark.S, shade.very_dark.L), - dark = hsl(hue.light_blue, shade.dark.S, shade.dark.L), - default = hsl(hue.light_blue, shade.default.S, shade.default.L), - light = hsl(hue.light_blue, shade.light.S, shade.light.L), - very_light = hsl(hue.light_blue, shade.very_light.S, shade.very_light.L), - }, - - -- Sky blue shades - sky_blue = { - very_dark = hsl(hue.sky_blue, shade.very_dark.S, shade.very_dark.L), - dark = hsl(hue.sky_blue, shade.dark.S, shade.dark.L), - default = hsl(hue.sky_blue, shade.default.S, shade.default.L), - light = hsl(hue.sky_blue, shade.light.S, shade.light.L), - very_light = hsl(hue.sky_blue, shade.very_light.S, shade.very_light.L), - }, - - -- Cyan shades - cyan = { - very_dark = hsl(hue.cyan, shade.very_dark.S, shade.very_dark.L), - dark = hsl(hue.cyan, shade.dark.S, shade.dark.L), - default = hsl(hue.cyan, shade.default.S, shade.default.L), - light = hsl(hue.cyan, shade.light.S, shade.light.L), - very_light = hsl(hue.cyan, shade.very_light.S, shade.very_light.L), - }, - - -- Green shades - green = { - very_dark = hsl(hue.green, shade.very_dark.S, shade.very_dark.L), - dark = hsl(hue.green, shade.dark.S, shade.dark.L), - default = hsl(hue.green, shade.default.S, shade.default.L), - light = hsl(hue.green, shade.light.S, shade.light.L), - very_light = hsl(hue.green, shade.very_light.S, shade.very_light.L), - }, - - -- Orange shades - orange = { - very_dark = hsl(hue.orange, shade.very_dark.S, shade.very_dark.L), - dark = hsl(hue.orange, shade.dark.S, shade.dark.L), - default = hsl(hue.orange, shade.default.S, shade.default.L), - light = hsl(hue.orange, shade.light.S, shade.light.L), - very_light = hsl(hue.orange, shade.very_light.S, shade.very_light.L), - }, - - -- Yellow shades - yellow = { - very_dark = hsl(hue.yellow, shade.very_dark.S, shade.very_dark.L), - dark = hsl(hue.yellow, shade.dark.S, shade.dark.L), - default = hsl(hue.yellow, shade.default.S, shade.default.L), - light = hsl(hue.yellow, shade.light.S, shade.light.L), - very_light = hsl(hue.yellow, shade.very_light.S, shade.very_light.L), + pink = M.build_fluo(M.fluo_hue.pink), + orange = M.build_fluo(M.fluo_hue.orange), + cyan = M.build_fluo(M.fluo_hue.cyan, 90, 30), + green = M.build_fluo(M.fluo_hue.green, 100, 30), + yellow = M.build_fluo(M.fluo_hue.yellow, 90, 30), }, } + -- Build chromatic colors. + for name, h in pairs(M.hue) do + palette[name] = M.build_color(h, shade) + end + return palette end From b38c0413f610fc4bcfa9eed1c9e92bf726b61f22 Mon Sep 17 00:00:00 2001 From: stepit Date: Tue, 10 Mar 2026 15:51:03 +0100 Subject: [PATCH 02/14] chore: config cleanup and update comments --- lua/flow/config.lua | 106 ++++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 47 deletions(-) diff --git a/lua/flow/config.lua b/lua/flow/config.lua index c652e70..e72fbd1 100644 --- a/lua/flow/config.lua +++ b/lua/flow/config.lua @@ -3,35 +3,44 @@ local M = {} --- Default configuration options for the colorscheme. --- @class FlowConfig M.defaults = { - --- @class Theme theme = { - ---@alias Style "dark" | "light" + --- Defines the colorscheme theme style. + --- @type "dark" | "light" style = "dark", - ---@alias Contrast "default" | "high" + --- Defines whether details contrasts should be accentuated or not. + --- @type "default" | "high" contrast = "default", - ---@boolean + --- @boolean transparent = false, + --- @boolean Internal flag set by flow-mono entry point + mono = false, }, colors = { - ---@alias Mode "default" | "dark" | "light" + --- Defines the colors used in the syntax and UI. + ---@type "default" | "dark" | "light" mode = "default", - ---@alias Fluo "pink" | "cyan" | "yellow" | "orange" | "green" + --- Fluo color to use in the theme. + ---@type "pink" | "cyan" | "yellow" | "orange" | "green" fluo = "pink", - ---@alias CustomSaturation string A number between 0-100 as string or empty string - ---@alias CustomLight string A number between 0-100 as string or empty string + --- Allows to specify custom saturation and light for theme. Despite the option is present for + --- maximum customizability, it is recommended to use defaults. custom = { - ---@type CustomSaturation - saturation = "", -- Custom saturation value (0-100) - ---@type CustomLight - light = "", -- Custom lightness value (0-100) + --- A number between 0-100 as string or empty string. + --- @type string + saturation = "", + --- A number between 0-100 as string or empty string. + --- @type string + light = "", }, }, ui = { - ---@alias Borders "light" | "dark" | "none" + --- @type "light" | "dark" | "none" borders = "dark", - ---@boolean + --- Defines if spell errors have to be highlighted with a more outstanding color. + --- @boolean aggressive_spell = false, - ---@boolean + --- Defines if special comments (TODO, FIX, etc.) has to be highlighted with different colors. + --- @boolean aggressive_special_comment = false, }, } @@ -39,7 +48,7 @@ M.defaults = { --- @type FlowConfig M.options = {} ---- Valid values for configuration options +--- Valid values for configuration options. M.valid_options = { fluo_colors = { "pink", "cyan", "yellow", "orange", "green" }, modes = { "default", "dark", "light" }, @@ -51,33 +60,36 @@ M.valid_options = { }, } ---- Validate configuration options ---- @param opts FlowConfig +--- Validate configuration options. +--- @param config FlowConfig --- @return boolean, string? -local function validate_options(opts) +local function validate_options(config) -- Validate theme options. - if opts.theme then + if config.theme then if - opts.theme.contrast and not vim.tbl_contains(M.valid_options.contrast, opts.theme.contrast) + config.theme.contrast + and not vim.tbl_contains(M.valid_options.contrast, config.theme.contrast) then - return false, string.format("Invalid border: %s", opts.theme.contrast) + return false, string.format("Invalid border: %s", config.theme.contrast) end end -- Validate color options. - if opts.colors then - if opts.colors.fluo and not vim.tbl_contains(M.valid_options.fluo_colors, opts.colors.fluo) then - return false, string.format("Invalid fluo color: %s", opts.colors.fluo) + if config.colors then + if + config.colors.fluo and not vim.tbl_contains(M.valid_options.fluo_colors, config.colors.fluo) + then + return false, string.format("Invalid fluo color: %s", config.colors.fluo) end - if opts.colors.mode and not vim.tbl_contains(M.valid_options.modes, opts.colors.mode) then - return false, string.format("Invalid mode: %s", opts.colors.mode) + if config.colors.mode and not vim.tbl_contains(M.valid_options.modes, config.colors.mode) then + return false, string.format("Invalid mode: %s", config.colors.mode) end - -- Validate custom color values for hue and light - if opts.colors.custom then - if opts.colors.custom.saturation and opts.colors.custom.saturation ~= "" then - local s = tonumber(opts.colors.custom.saturation) + -- Validate custom color values for hue and light. + if config.colors.custom then + if config.colors.custom.saturation and config.colors.custom.saturation ~= "" then + local s = tonumber(config.colors.custom.saturation) if not s or s < M.valid_options.custom_ranges.saturation.min @@ -86,14 +98,14 @@ local function validate_options(opts) return false, string.format( "Invalid saturation value: %s (must be between %d and %d)", - opts.colors.custom.saturation, + config.colors.custom.saturation, M.valid_options.custom_ranges.saturation.min, M.valid_options.custom_ranges.saturation.max ) end end - if opts.colors.custom.light and opts.colors.custom.light ~= "" then - local l = tonumber(opts.colors.custom.light) + if config.colors.custom.light and config.colors.custom.light ~= "" then + local l = tonumber(config.colors.custom.light) if not l or l < M.valid_options.custom_ranges.light.min @@ -102,7 +114,7 @@ local function validate_options(opts) return false, string.format( "Invalid light value: %s (must be between %d and %d)", - opts.colors.custom.light, + config.colors.custom.light, M.valid_options.custom_ranges.light.min, M.valid_options.custom_ranges.light.max ) @@ -111,10 +123,10 @@ local function validate_options(opts) end end - -- Validate ui options - if opts.ui then - if opts.ui.borders and not vim.tbl_contains(M.valid_options.borders, opts.ui.borders) then - return false, string.format("Invalid border: %s", opts.ui.borders) + -- Validate UI options. + if config.ui then + if config.ui.borders and not vim.tbl_contains(M.valid_options.borders, config.ui.borders) then + return false, string.format("Invalid border: %s", config.ui.borders) end end @@ -122,23 +134,23 @@ local function validate_options(opts) end --- This is the entry point of the configuration before loading the plugin. ---- It sets up the colorscheme options by merging the provided options with +--- It sets the colorscheme options by merging the user provided ones with --- the default configuration. ---- @param opts FlowConfig? Optional table to customize the colorscheme setup. -function M._setup(opts) - -- -- Short circuit if options have been already set. This happen when the colorscheme is loaded from - -- -- the plugin manager because first set the options,and then set the colorscheme. +--- @param config FlowConfig? Optional table to customize the colorscheme setup. +function M._setup(config) + -- Short circuit if options have been already set. This happen when the colorscheme is loaded from + -- the plugin manager because it first set the options, and then set the colorscheme. if not vim.tbl_isempty(M.options) then return end - local ok, err = validate_options(opts or {}) + local ok, err = validate_options(config or {}) if not ok then - opts = {} + config = {} vim.notify("Error setting user options, fallback to defaults: " .. err, vim.log.levels.WARN) end - M.options = vim.tbl_deep_extend("force", {}, M.defaults, opts or {}) + M.options = vim.tbl_deep_extend("force", {}, M.defaults, config or {}) end return M From 72ca1e1ea8f42fccba93754b38f00030c3517347 Mon Sep 17 00:00:00 2001 From: stepit Date: Thu, 12 Mar 2026 07:59:11 +0100 Subject: [PATCH 03/14] chore: order active plugins --- lua/flow/theme.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lua/flow/theme.lua b/lua/flow/theme.lua index f573b9d..ba137c6 100644 --- a/lua/flow/theme.lua +++ b/lua/flow/theme.lua @@ -3,13 +3,12 @@ local util = require("flow.util") local M = {} ---- List of active highlight groups to be configured. +--- List of active pulugins highlight groups to be configured. --- @type string[] M.active_highlights = { + "avante", "base", "blink", - "syntax", - "markdown", "completion", "dap", "diagnostic", @@ -21,9 +20,12 @@ M.active_highlights = { "lsp", "mini-hipatterns", "mini-files", + "markdown", + "mason", "oil", "render-markdown", "statusline", + "syntax", "telescope", "todo-comments", "treesitter", @@ -32,8 +34,6 @@ M.active_highlights = { "winbar", "undotree", "vim-highlighturl", - "mason", - "avante", } M.b = {} From 5099f14f672d2ee9d7fca91fd8fa8ab0dcb1382f Mon Sep 17 00:00:00 2001 From: stepit Date: Fri, 13 Mar 2026 12:25:23 +0100 Subject: [PATCH 04/14] feat: add draft mono scheme --- lua/flow/colors.lua | 54 ++++--- lua/flow/highlights/base.lua | 41 ++++++ lua/flow/highlights/syntax.lua | 9 ++ lua/flow/highlights/treesitter.lua | 9 +- lua/flow/init.lua | 12 +- lua/flow/mono.lua | 100 +++++++++++++ lua/flow/palette.lua | 228 +++++++++++++++++++---------- lua/flow/theme.lua | 69 +++++---- lua/lualine/themes/flow.lua | 117 +++++++++------ 9 files changed, 452 insertions(+), 187 deletions(-) create mode 100644 lua/flow/mono.lua diff --git a/lua/flow/colors.lua b/lua/flow/colors.lua index 3653028..4506e28 100644 --- a/lua/flow/colors.lua +++ b/lua/flow/colors.lua @@ -1,20 +1,31 @@ -local M = {} - -M.colors = nil - -M._color_names = - { "orange", "yellow", "red", "purple", "blue", "light_blue", "sky_blue", "cyan", "green" } +local palette = require("flow.palette") + +local M = { + _color_names = { + "blue", + "cyan", + "green", + "light_blue", + "orange", + "purple", + "red", + "sky_blue", + "yellow", + }, + + colors = nil, +} --- Setup the colorscheme colors based on the options and palette. ---- @param opts FlowConfig: The options to setup the colorscheme. ---- @return table: The colors used by the colorscheme. -function M.setup(opts) - local default_palette = require("flow.palette").get(opts or {}) +--- @param config FlowConfig The configuration options to setup the colorscheme. +--- @return table The colors used by the colorscheme. +function M.setup(config) + local default_palette = palette.get(config or {}) - opts = opts or {} + config = config or {} -- Get the configured fluo color (with fallback to default) - local fluo_color = (opts.colors and opts.colors.fluo) or "pink" + local fluo_color = (config.colors and config.colors.fluo) or "pink" local colors = { -- Core colors @@ -32,11 +43,11 @@ function M.setup(opts) to_check = default_palette.fluo.green.default, } - M._apply_opts(default_palette, colors, opts) + M._apply_opts(default_palette, colors, config) for _, key in ipairs(M._color_names) do -- Set the specific mode of the colors. - colors[key] = default_palette[key][opts.colors.mode] + colors[key] = default_palette[key][config.colors.mode] -- Store all the color variations. These variables are used for hi that -- requires contrasts with the current theme, like git. local Key = key:gsub("^%l", string.upper) @@ -44,7 +55,7 @@ function M.setup(opts) end -- Comments - use lighter grey for light theme - colors.comment = opts.theme.style == "dark" and colors.grey[7] -- Dark theme: 50% lightness + colors.comment = config.theme.style == "dark" and colors.grey[7] -- Dark theme: 50% lightness or colors.grey[4] -- Light theme: inverts to 65% lightness (less dark) -- +----------------------------------------------------------------------------------------+ @@ -74,7 +85,7 @@ function M.setup(opts) colors.fg_sidebar = default_palette.grey[8] colors.bg_sidebar = colors.bg - local is_transparent = opts.theme.transparent == true + local is_transparent = config.theme.transparent == true -- Gutter: used for line numbers, signs, and fold column. colors.fg_gutter = colors.grey[5] @@ -82,7 +93,7 @@ function M.setup(opts) -- Float: used for visual elements that are floating and triggered by the user. colors.fg_float = colors.grey[8] - colors.bg_float = opts.theme.style == "dark" and default_palette.float_bg.dark + colors.bg_float = config.theme.style == "dark" and default_palette.float_bg.dark or default_palette.float_bg.light -- Popups: use for completion menu and all visual components that appears autonomously. @@ -98,7 +109,7 @@ function M.setup(opts) colors.bg_highlight = colors.grey[2] -- Visual - uses configured fluo color - colors.bg_visual = opts.theme.style == "dark" and default_palette.visual_bg.dark + colors.bg_visual = config.theme.style == "dark" and default_palette.visual_bg.dark or default_palette.visual_bg.light colors.fg_visual = colors.grey[2] @@ -111,7 +122,7 @@ function M.setup(opts) untracked = colors.sky_blue, -- New untracked files } - local is_dark = opts.theme.style == "dark" + local is_dark = config.theme.style == "dark" colors.diff = { add = not is_dark and colors.Green.very_light or colors.Green.very_dark, delete = not is_dark and colors.Red.very_light or colors.Red.very_dark, @@ -132,6 +143,11 @@ function M.setup(opts) colors.fixme = is_dark and colors.Red.default or colors.Red.dark -- FIXME comments colors.hack = is_dark and colors.Yellow.default or colors.Yellow.dark -- HACK comments + -- Apply monochrome transformation if enabled + if config.theme.mono then + require("flow.mono").apply(colors, config) + end + M.colors = colors return colors diff --git a/lua/flow/highlights/base.lua b/lua/flow/highlights/base.lua index 8d394c6..e1e15b2 100644 --- a/lua/flow/highlights/base.lua +++ b/lua/flow/highlights/base.lua @@ -135,6 +135,47 @@ function M.get(c, o) FlowInfo = { fg = c.light_blue }, -- Info/neutral action state } + -- Monochrome: collapse FlowKind groups to ~6 grey lightness tiers. + if o.theme.mono then + -- Tier 1: Keywords/constructors/classes + theme.FlowKindKeyword = { fg = c.red, bold = true } + theme.FlowKindConstructor = { fg = c.red, bold = true } + theme.FlowKindClass = { fg = c.red, bold = true } + theme.FlowKindInterface = { fg = c.red, bold = true } + theme.FlowKindStruct = { fg = c.red, bold = true } + theme.FlowKindEnum = { fg = c.red, bold = true } + theme.FlowKindEnumMember = { fg = c.red, bold = true } + + -- Tier 2: Functions/methods. + theme.FlowKindFunction = { fg = c.light_blue } + theme.FlowKindMethod = { fg = c.light_blue } + + -- Tier 3: Variables/fields/properties. + theme.FlowKindVariable = { fg = c.cyan } + theme.FlowKindField = { fg = c.cyan } + theme.FlowKindProperty = { fg = c.cyan } + + -- Tier 4: Constants/values. + theme.FlowKindConstant = { fg = c.yellow } + theme.FlowKindValue = { fg = c.yellow } + theme.FlowKindUnit = { fg = c.yellow } + + -- Tier 5: Modules/files. + theme.FlowKindModule = { fg = c.blue } + theme.FlowKindFile = { fg = c.blue } + theme.FlowKindFolder = { fg = c.blue } + + -- Tier 6: Others. + theme.FlowKindText = { fg = c.purple, italic = true } + theme.FlowKindSnippet = { fg = c.purple, italic = true } + theme.FlowKindColor = { fg = c.purple, italic = true } + theme.FlowKindEvent = { fg = c.purple, italic = true } + theme.FlowKindOperator = { fg = c.cyan } + theme.FlowKindReference = { fg = c.grey[8] } + theme.FlowKindTypeParameter = { fg = c.grey[8] } + theme.FlowKindType = { fg = c.light_blue } + end + return theme end diff --git a/lua/flow/highlights/syntax.lua b/lua/flow/highlights/syntax.lua index 41bc022..32cd859 100644 --- a/lua/flow/highlights/syntax.lua +++ b/lua/flow/highlights/syntax.lua @@ -94,6 +94,15 @@ function M.get(c, o) theme.Hack = { fg = c.hack, bg = c.comment } end + -- Monochrome: bold keywords/operators, italic comments/delimiters/special. + if o.theme.mono then + theme.Comment = { fg = c.comment, italic = true } + theme.Keyword = { fg = c.red, bold = true } + theme.Operator = { fg = c.red, bold = true } + theme.Delimiter = { fg = c.purple, italic = true } + theme.Special = { fg = c.purple, italic = true } + end + return theme end diff --git a/lua/flow/highlights/treesitter.lua b/lua/flow/highlights/treesitter.lua index 465c820..e46c65b 100644 --- a/lua/flow/highlights/treesitter.lua +++ b/lua/flow/highlights/treesitter.lua @@ -1,8 +1,9 @@ local M = {} --- @param c table: The available colors. +--- @param o FlowConfig: The available options. --- @return table: Treesitter plugin highlights. -function M.get(c, _) +function M.get(c, o) local theme = { TreesitterContext = { bg = c.grey[2] }, @@ -160,6 +161,12 @@ function M.get(c, _) ["@function.call.just"] = { link = "@variable.parameter" }, } + -- Monochrome: italic fields/properties to distinguish from comments. + if o.theme.mono then + theme["@variable.member"] = { fg = c.light_blue, italic = true } + theme["@property"] = { fg = c.cyan, italic = true } + end + return theme end diff --git a/lua/flow/init.lua b/lua/flow/init.lua index 8dac904..e3146d6 100644 --- a/lua/flow/init.lua +++ b/lua/flow/init.lua @@ -13,8 +13,8 @@ M.setup = config._setup --- It sets up the colorscheme by clearing existing highlights, --- enabling true color support, setting the colorscheme name, --- and applying the highlight groups defined in the theme. ---- @param opts FlowConfig? -function M.load(opts) +--- @param cfg FlowConfig? +function M.load(cfg) -- Check if the current colorscheme is different from the one to be loaded. if vim.g.colors_name ~= M.name then -- Clear existing highlights. @@ -26,15 +26,15 @@ function M.load(opts) vim.g.colors_name = M.name end - if opts then - M.config(opts) + if cfg then + M.config(cfg) end local highlights = theme.configure() -- The heart of the plugin, where highlight groups are set. - for group, c in pairs(highlights) do - vim.api.nvim_set_hl(0, group, c) + for group, hi in pairs(highlights) do + vim.api.nvim_set_hl(0, group, hi) end end diff --git a/lua/flow/mono.lua b/lua/flow/mono.lua new file mode 100644 index 0000000..c152f73 --- /dev/null +++ b/lua/flow/mono.lua @@ -0,0 +1,100 @@ +local hsl = require("flow.util").hsl_to_hex + +local M = {} + +--- Cool blue hue and low saturation for a metallic/steel tone on syntax colors. +M.hue = 220 +M.sat = 3 + +--- Lightness values for desaturated chromatic color replacements. +--- Each color maps to a unique grey lightness for visual distinction. +--- @type table +M.dark_lightness = { + light_blue = 75, + cyan = 68, + purple = 50, + yellow = 82, + green = 62, + orange = 72, + blue = 55, + sky_blue = 78, + -- red is special: replaced by fluo +} + +--- Build a 5-shade metallic color at a given base lightness. +--- @param base_l number Base lightness (0-100) +--- @return table +local function metallic_shades(base_l) + return { + very_dark = hsl(M.hue, M.sat, math.max(base_l - 30, 5)), + dark = hsl(M.hue, M.sat, math.max(base_l - 15, 10)), + default = hsl(M.hue, M.sat, base_l), + light = hsl(M.hue, M.sat, math.min(base_l + 10, 90)), + very_light = hsl(M.hue, M.sat, math.min(base_l + 20, 95)), + } +end + +--- Apply monochrome transformation to the colors table. +--- Desaturates chromatic syntax colors to metallic grey shades and maps +--- keywords (red) to the fluorescent accent color. +--- Greys, black, white, and all UI scaffolding colors are left untouched. +--- @param colors table The colors table from colors.setup() +--- @param opts FlowConfig The configuration options +function M.apply(colors, opts) + local is_dark = opts.theme.style == "dark" + + -- 1. Desaturate chromatic colors to unique metallic grey lightness values + for name, base_l in pairs(M.dark_lightness) do + local l = is_dark and base_l or (100 - base_l) + colors[name] = hsl(M.hue, M.sat, l) + local Key = name:gsub("^%l", string.upper) + colors[Key] = metallic_shades(l) + end + + -- 2. Override red β†’ fluo (keywords/operators use c.red in syntax.lua) + colors.red = colors.fluo + colors.Red = { + very_dark = colors.Fluo.dark, + dark = colors.Fluo.dark, + default = colors.Fluo.default, + light = colors.Fluo.light, + very_light = colors.Fluo.light, + } + + -- 3. Remap semantic colors + -- Diagnostics: fluo shades for error/warning/info, grey for hint + colors.error = colors.Fluo.default + colors.warning = colors.Fluo.dark + colors.info = colors.Fluo.light + colors.hint = colors.grey[7] + + -- Git: distinct metallic grey values + colors.git = { + add = colors.green, + change = colors.light_blue, + delete = colors.red, + ignore = colors.grey[7], + untracked = colors.sky_blue, + } + + -- Diff: use existing greys for subtle background differences + local n = #colors.grey + colors.diff = { + add = is_dark and colors.grey[4] or colors.grey[n - 3], + delete = is_dark and colors.grey[2] or colors.grey[n - 1], + change = is_dark and colors.grey[5] or colors.grey[n - 4], + text = is_dark and colors.grey[4] or colors.grey[n - 3], + parent = is_dark and colors.grey[5] or colors.grey[n - 4], + } + + -- Comments: use a grey that's clearly dimmer than syntax elements + colors.comment = is_dark and colors.grey[6] or colors.grey[6] + + -- Special comments: fluo for fixme (urgent), grey for others + colors.fixme = colors.Fluo.default + colors.todo = is_dark and colors.grey[9] or colors.grey[2] + colors.note = is_dark and colors.grey[8] or colors.grey[3] + colors.hack = is_dark and colors.grey[7] or colors.grey[4] +end + +return M diff --git a/lua/flow/palette.lua b/lua/flow/palette.lua index 0e563d8..629bad3 100644 --- a/lua/flow/palette.lua +++ b/lua/flow/palette.lua @@ -1,51 +1,107 @@ -local hsl = require("flow.util").hsl_to_hex +local hsl_to_hex = require("flow.util").hsl_to_hex + +--- @class HSL +--- @field h number Hue (0-360) +--- @field s number Saturation (0-100) +--- @field l number Lightness (0-100) +local HSL = {} +HSL.__index = HSL + +--- @param h number +--- @param s number +--- @param l number +--- @return HSL +function HSL.new(h, s, l) + return setmetatable({ h = h, s = s, l = l }, HSL) +end -local M = {} +local function n_equidistant_values(n, range) + assert(n >= 2, "number of points should be >= 2") + local step = math.floor(range / n) ---- Default fluo light values. -M.fluo_lightness = { - default = 50, - light = 90, - dark = 35, -} + local values = {} + for i = 0, n do + values[i + 1] = i * step + step + end ---- Default grey lightness steps used by both chromatic and monochrome palettes. -M.grey_lightness = { 10, 13, 15, 18, 24, 29, 50, 65, 85, 88, 90 } - ---- Default grey hue for the chromatic palette. -M.grey_hue = 203 ---- Default grey saturation for the chromatic palette. -M.grey_saturations = { 20, 20, 20, 12, 15, 20, 20, 20, 20, 20, 20 } - -M.hue = { - red = 355, - purple = 270, - blue = 230, - light_blue = 205, - sky_blue = 190, - cyan = 165, - green = 115, - yellow = 60, - orange = 25, -} + return values +end + +--- Generate n+1 Chebyshev-spaced values over [low, high]. +--- Nodes cluster near both endpoints, giving finer gradation in the +--- darks and lights with wider spacing through the mid-tones. +local function n_chebyshev_values(n, low, high) + assert(n >= 2, "number of points should be >= 2") + + local values = {} + for i = 0, n do + -- Chebyshev nodes on [-1, 1]: cos(i * pi / n), mapped to [low, high] + local node = math.cos(i * math.pi / n) + values[i + 1] = math.floor((1 - node) / 2 * (high - low) + low + 0.5) + end -M.fluo_hue = { - pink = 331, - cyan = 187, - green = 115, - yellow = 61, - orange = 25, + return values +end + +local M = { + default_fluo = "pink", + + --- Default fluo values. + fluo_lightness = { + default = 50, + light = 90, + dark = 35, + }, + + fluo_saturation = 100, + + fluo_hue = { + pink = 331, + cyan = 187, + green = 115, + yellow = 61, + orange = 25, + }, + + --- Default grey lightness steps used by both chromatic and monochrome palettes. + grey_lightness = n_chebyshev_values(15, 10, 90), + + --- Default grey hue for the chromatic palette. + grey_hue = 203, + + --- Default grey saturation for the chromatic palette. + grey_saturation = 20, + + color_hue = { + red = 355, + purple = 270, + blue = 230, + light_blue = 205, + sky_blue = 190, + cyan = 165, + green = 115, + yellow = 60, + orange = 25, + }, + + shade = { + very_dark = { S = 27, L = 20 }, + dark = { S = 50, L = 35 }, + default = { S = 40, L = 55 }, + light = { S = 40, L = 65 }, + very_light = { S = 50, L = 85 }, + }, } ---- Build an 11-step grey scale. ---- @param h_value number Hue (0 for achromatic). ---- @param s_values table Array of 11 saturation values. ---- @param l_values table Array of 11 lightness values. ---- @return table -function M.build_greys(h_value, s_values, l_values) +--- Build an n-step grey scale. +--- @param hue number Hue (0 for achromatic). +--- @param saturation number +--- @param lightness number[] Array of grey lightness values. +--- @return string[] +function M.build_greys(hue, saturation, lightness) local grey = {} - for i = 1, 11 do - grey[i] = hsl(h_value, s_values[i], l_values[i]) + for i = 1, #lightness do + grey[i] = hsl_to_hex(hue, saturation, lightness[i]) end return grey end @@ -53,14 +109,14 @@ end --- Build a 5-shade color from a hue and shade definition. --- @param h number Hue value --- @param shade table Shade definitions with S (saturation) and L (light) for each level ---- @return table +--- @return table function M.build_color(h, shade) return { - very_dark = hsl(h, shade.very_dark.S, shade.very_dark.L), - dark = hsl(h, shade.dark.S, shade.dark.L), - default = hsl(h, shade.default.S, shade.default.L), - light = hsl(h, shade.light.S, shade.light.L), - very_light = hsl(h, shade.very_light.S, shade.very_light.L), + very_dark = hsl_to_hex(h, shade.very_dark.S, shade.very_dark.L), + dark = hsl_to_hex(h, shade.dark.S, shade.dark.L), + default = hsl_to_hex(h, shade.default.S, shade.default.L), + light = hsl_to_hex(h, shade.light.S, shade.light.L), + very_light = hsl_to_hex(h, shade.very_light.S, shade.very_light.L), } end @@ -68,41 +124,55 @@ end --- @param h number Hue value. --- @param light_l number? Optional lightness for light shade. --- @param dark_l number? Optional lightness for dark shade. ---- @return table +--- @return table function M.build_fluo(h, light_l, dark_l) return { - default = hsl(h, 100, M.fluo_lightness.default), - light = hsl(h, 100, light_l or M.fluo_lightness.light), - dark = hsl(h, 100, dark_l or M.fluo_lightness.dark), + dark = hsl_to_hex(h, M.fluo_saturation, dark_l or M.fluo_lightness.dark), + default = hsl_to_hex(h, M.fluo_saturation, M.fluo_lightness.default), + light = hsl_to_hex(h, M.fluo_saturation, light_l or M.fluo_lightness.light), } end +--- Update default saturation and light of the selected mode if user provided +--- custom values. +--- @param shade table +--- @param mode string +--- @param l string +--- @param s string +--- @return table +function M.update_light_and_saturation(shade, mode, l, s) + if l ~= "" then + shade[mode].L = tonumber(l) + end + + if s ~= "" then + shade[mode].S = tonumber(s) + end + + return shade +end + --- Generates and returns a color palette using HSL and HEX values. ---- @param config FlowConfig: The available theme configuration options. ---- @return table +--- @param config FlowConfig The available theme configuration options. +--- @return table function M.get(config) - local is_dark = config.theme.style == "dark" + local shade = M.shade - local shade = { - very_dark = { S = 27, L = 20 }, - dark = { S = 50, L = 35 }, - default = { S = (not is_dark and 40) or 60, L = (not is_dark and 55) or 65 }, - light = { S = 40, L = 65 }, - very_light = { S = 50, L = 85 }, - } + local is_dark = config.theme.style == "dark" - if config.colors.custom.light ~= "" then - ---@diagnostic disable-next-line: assign-type-mismatch - shade[config.colors.mode].L = tonumber(config.colors.custom.light) + if is_dark then + shade.default = { S = 60, L = 65 } end - if config.colors.custom.saturation ~= "" then - ---@diagnostic disable-next-line: assign-type-mismatch - shade[config.colors.mode].S = tonumber(config.colors.custom.saturation) - end + shade = M.update_light_and_saturation( + shade, + config.colors.mode, + config.colors.custom.light, + config.colors.custom.saturation + ) -- Get the configured fluo color. - local configured_fluo = (config.colors and config.colors.fluo) or "pink" + local configured_fluo = (config.colors and config.colors.fluo) or M.default_fluo local fluo_hue_value = M.fluo_hue[configured_fluo] local palette = { @@ -110,28 +180,28 @@ function M.get(config) transparent = "NONE", -- Base colors. - black = hsl(0, 0, 5), - white = hsl(0, 0, 95), + black = hsl_to_hex(0, 0, 5), + white = hsl_to_hex(0, 0, 95), -- Greyscale colors. - grey = M.build_greys(M.grey_hue, M.grey_saturations, M.grey_lightness), + grey = M.build_greys(M.grey_hue, M.grey_saturation, M.grey_lightness), -- CursorLine background. cursorline_bg = { - dark = hsl(fluo_hue_value, 20, 13), -- For dark theme - light = hsl(fluo_hue_value, 20, 87), -- For light theme + dark = hsl_to_hex(fluo_hue_value, 20, 13), -- For dark theme + light = hsl_to_hex(fluo_hue_value, 20, 87), -- For light theme }, -- Visual selection background. visual_bg = { - dark = hsl(fluo_hue_value, 90, 23), -- For dark theme - light = hsl(fluo_hue_value, 90, 77), -- For light theme + dark = hsl_to_hex(fluo_hue_value, 90, 23), -- For dark theme + light = hsl_to_hex(fluo_hue_value, 90, 77), -- For light theme }, -- Float window colors. float_bg = { - dark = hsl(203, 20, 18), -- For dark theme - light = hsl(203, 20, 82), -- For light theme + dark = hsl_to_hex(203, 20, 18), -- For dark theme + light = hsl_to_hex(203, 20, 82), -- For light theme }, -- Fluorescent colors. @@ -145,7 +215,7 @@ function M.get(config) } -- Build chromatic colors. - for name, h in pairs(M.hue) do + for name, h in pairs(M.color_hue) do palette[name] = M.build_color(h, shade) end diff --git a/lua/flow/theme.lua b/lua/flow/theme.lua index ba137c6..a213cae 100644 --- a/lua/flow/theme.lua +++ b/lua/flow/theme.lua @@ -1,44 +1,43 @@ local flow_colors = require("flow.colors") local util = require("flow.util") -local M = {} +local M = { + --- List of active pulugins highlight groups to be configured. + --- @type string[] + active_highlights = { + "avante", + "base", + "blink", + "completion", + "dap", + "diagnostic", + "flash", + "fzf-lua", + "git", + "ibl", + "lazy", + "lsp", + "mini-hipatterns", + "mini-files", + "markdown", + "mason", + "oil", + "render-markdown", + "statusline", + "syntax", + "telescope", + "todo-comments", + "treesitter", + "trouble", + "whichkey", + "winbar", + "undotree", + "vim-highlighturl", + }, ---- List of active pulugins highlight groups to be configured. ---- @type string[] -M.active_highlights = { - "avante", - "base", - "blink", - "completion", - "dap", - "diagnostic", - "flash", - "fzf-lua", - "git", - "ibl", - "lazy", - "lsp", - "mini-hipatterns", - "mini-files", - "markdown", - "mason", - "oil", - "render-markdown", - "statusline", - "syntax", - "telescope", - "todo-comments", - "treesitter", - "trouble", - "whichkey", - "winbar", - "undotree", - "vim-highlighturl", + b = {}, } -M.b = {} - ---- Configure the colorscheme highlights. --- This function sets up the highlight groups by merging the colors --- and options for each active highlight group. --- @return table: A table of configured highlight groups. diff --git a/lua/lualine/themes/flow.lua b/lua/lualine/themes/flow.lua index a575daf..a574114 100644 --- a/lua/lualine/themes/flow.lua +++ b/lua/lualine/themes/flow.lua @@ -1,53 +1,76 @@ local colors = require("flow.colors").colors +local is_mono = vim.g.colors_name == "flow-mono" local flow = {} --- Normal mode colors. a will be used also for x, --- b for y, and c for z to have a symmetrical coloration. -flow.normal = { - a = { bg = colors.light_blue, fg = colors.bg_highlight }, - b = { bg = colors.bg_highlight, fg = colors.light_blue }, - c = { bg = colors.bg_statusline, fg = colors.fg_statusline }, -} - --- Insert mode colors. a will be used also for x, --- b for y, and c for z to have a symmetrical coloration. -flow.insert = { - a = { bg = colors.cyan, fg = colors.bg_highlight }, - b = { bg = colors.bg_highlight, fg = colors.cyan }, -} - --- Command mode colors. a will be used also for x, --- b for y, and c for z to have a symmetrical coloration. -flow.command = { - a = { bg = colors.yellow, fg = colors.bg_highlight }, - b = { bg = colors.bg_highlight, fg = colors.yellow }, -} - --- Visual mode colors. a will be used also for x, --- b for y, and c for z to have a symmetrical coloration. -flow.visual = { - a = { bg = colors.purple, fg = colors.bg_highlight }, - b = { bg = colors.bg_highlight, fg = colors.purple }, -} - --- Replace mode colors. a will be used also for x, --- b for y, and c for z to have a symmetrical coloration. -flow.replace = { - a = { bg = colors.red, fg = colors.bg_highlight }, - b = { bg = colors.bg_highlight, fg = colors.red }, -} - --- Terminal mode colors. a will be used also for x, --- b for y, and c for z to have a symmetrical coloration. -flow.terminal = { - a = { bg = colors.green, fg = colors.bg_highlight }, - b = { bg = colors.bg_highlight, fg = colors.green }, -} - --- Inactive mode colors. Only c seems to be applied here -flow.inactive = { - c = { bg = colors.bg_statusline, fg = colors.fg_gutter }, -} +if is_mono then + -- Monochrome: grey backgrounds, fluo for active mode indicator. + -- All vim modes shares the same colors. + local grey_a = { bg = colors.grey[6], fg = colors.bg_highlight } + local grey_b = { bg = colors.bg_highlight, fg = colors.grey[6] } + local fluo_a = { bg = colors.fluo, fg = colors.bg_highlight } + + flow.normal = { + a = fluo_a, + b = grey_b, + c = { bg = colors.bg_statusline, fg = colors.fg_statusline }, + } + flow.insert = { a = grey_a, b = grey_b } + flow.command = { a = grey_a, b = grey_b } + flow.visual = { a = grey_a, b = grey_b } + flow.replace = { a = grey_a, b = grey_b } + flow.terminal = { a = grey_a, b = grey_b } + flow.inactive = { + c = { bg = colors.bg_statusline, fg = colors.fg_gutter }, + } +else + -- Normal mode colors. a will be used also for x, + -- b for y, and c for z to have a symmetrical coloration. + flow.normal = { + a = { bg = colors.light_blue, fg = colors.bg_highlight }, + b = { bg = colors.bg_highlight, fg = colors.light_blue }, + c = { bg = colors.bg_statusline, fg = colors.fg_statusline }, + } + + -- Insert mode colors. a will be used also for x, + -- b for y, and c for z to have a symmetrical coloration. + flow.insert = { + a = { bg = colors.cyan, fg = colors.bg_highlight }, + b = { bg = colors.bg_highlight, fg = colors.cyan }, + } + + -- Command mode colors. a will be used also for x, + -- b for y, and c for z to have a symmetrical coloration. + flow.command = { + a = { bg = colors.yellow, fg = colors.bg_highlight }, + b = { bg = colors.bg_highlight, fg = colors.yellow }, + } + + -- Visual mode colors. a will be used also for x, + -- b for y, and c for z to have a symmetrical coloration. + flow.visual = { + a = { bg = colors.purple, fg = colors.bg_highlight }, + b = { bg = colors.bg_highlight, fg = colors.purple }, + } + + -- Replace mode colors. a will be used also for x, + -- b for y, and c for z to have a symmetrical coloration. + flow.replace = { + a = { bg = colors.red, fg = colors.bg_highlight }, + b = { bg = colors.bg_highlight, fg = colors.red }, + } + + -- Terminal mode colors. a will be used also for x, + -- b for y, and c for z to have a symmetrical coloration. + flow.terminal = { + a = { bg = colors.green, fg = colors.bg_highlight }, + b = { bg = colors.bg_highlight, fg = colors.green }, + } + + -- Inactive mode colors. Only c seems to be applied here + flow.inactive = { + c = { bg = colors.bg_statusline, fg = colors.fg_gutter }, + } +end return flow From b58b8adb348bbc34fb445793440f15dbcc02cb0b Mon Sep 17 00:00:00 2001 From: stepit Date: Fri, 13 Mar 2026 18:52:18 +0100 Subject: [PATCH 05/14] feat: set the terminal colors properly --- lua/flow/init.lua | 2 ++ lua/flow/util.lua | 42 +++++++++++++++++++++++------------------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/lua/flow/init.lua b/lua/flow/init.lua index e3146d6..3c477ea 100644 --- a/lua/flow/init.lua +++ b/lua/flow/init.lua @@ -36,6 +36,8 @@ function M.load(cfg) for group, hi in pairs(highlights) do vim.api.nvim_set_hl(0, group, hi) end + + util.terminal(colors) end return M diff --git a/lua/flow/util.lua b/lua/flow/util.lua index 73a2bee..d99d43d 100644 --- a/lua/flow/util.lua +++ b/lua/flow/util.lua @@ -60,34 +60,38 @@ function M.hsl_to_rbg(h, s, l) return math.floor(0.5 + r * 255), math.floor(0.5 + g * 255), math.floor(0.5 + b * 255) end --- TODO: complete function M.terminal(colors) - -- dark + -- black vim.g.terminal_color_0 = colors.black - vim.g.terminal_color_8 = colors.terminal_black + vim.g.terminal_color_8 = colors.grey[5] - -- light - vim.g.terminal_color_7 = colors.terminal_white - vim.g.terminal_color_15 = colors.fg + -- red + vim.g.terminal_color_1 = colors.Red.dark + vim.g.terminal_color_9 = colors.Red.light - -- colors - vim.g.terminal_color_1 = colors.red - vim.g.terminal_color_9 = colors.red + -- green + vim.g.terminal_color_2 = colors.Green.dark + vim.g.terminal_color_10 = colors.Green.light - vim.g.terminal_color_2 = colors.cyan - vim.g.terminal_color_10 = colors.cyan + -- yellow + vim.g.terminal_color_3 = colors.Yellow.dark + vim.g.terminal_color_11 = colors.Yellow.light - vim.g.terminal_color_3 = colors.yellow - vim.g.terminal_color_11 = colors.yellow + -- blue + vim.g.terminal_color_4 = colors.Blue.dark + vim.g.terminal_color_12 = colors.Blue.light - vim.g.terminal_color_4 = colors.blue - vim.g.terminal_color_12 = colors.blue + -- magenta + vim.g.terminal_color_5 = colors.Purple.dark + vim.g.terminal_color_13 = colors.Purple.light - vim.g.terminal_color_5 = colors.purple - vim.g.terminal_color_13 = colors.purple + -- cyan + vim.g.terminal_color_6 = colors.Cyan.dark + vim.g.terminal_color_14 = colors.Cyan.light - vim.g.terminal_color_6 = colors.cyan - vim.g.terminal_color_14 = colors.cyan + -- white + vim.g.terminal_color_7 = colors.grey[7] + vim.g.terminal_color_15 = colors.white end --- Interpolates a string by replacing placeholders with corresponding values from a table. From 8e6dec468ca36ce2fe031760f2c8344922325bcf Mon Sep 17 00:00:00 2001 From: stepit Date: Fri, 13 Mar 2026 18:53:14 +0100 Subject: [PATCH 06/14] feat: add sampling to utils --- lua/flow/palette.lua | 5 +++-- lua/flow/util.lua | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lua/flow/palette.lua b/lua/flow/palette.lua index 629bad3..b28aaed 100644 --- a/lua/flow/palette.lua +++ b/lua/flow/palette.lua @@ -1,4 +1,5 @@ -local hsl_to_hex = require("flow.util").hsl_to_hex +local util = require("flow.util") +local hsl_to_hex = util.hsl_to_hex --- @class HSL --- @field h number Hue (0-360) @@ -64,7 +65,7 @@ local M = { }, --- Default grey lightness steps used by both chromatic and monochrome palettes. - grey_lightness = n_chebyshev_values(15, 10, 90), + grey_lightness = util.n_chebyshev_values(15, 10, 90), --- Default grey hue for the chromatic palette. grey_hue = 203, diff --git a/lua/flow/util.lua b/lua/flow/util.lua index d99d43d..e026165 100644 --- a/lua/flow/util.lua +++ b/lua/flow/util.lua @@ -9,6 +9,34 @@ function M.merge(left, right) return left end +function M.n_equidistant_values(n, range) + assert(n >= 2, "number of points should be >= 2") + local step = math.floor(range / n) + + local values = {} + for i = 0, n do + values[i + 1] = i * step + step + end + + return values +end + +--- Generate n+1 Chebyshev-spaced values over [low, high]. +--- Nodes cluster near both endpoints, giving finer gradation in the +--- darks and lights with wider spacing through the mid-tones. +function M.n_chebyshev_values(n, low, high) + assert(n >= 2, "number of points should be >= 2") + + local values = {} + for i = 0, n do + -- Chebyshev nodes on [-1, 1]: cos(i * pi / n), mapped to [low, high] + local node = math.cos(i * math.pi / n) + values[i + 1] = math.floor((1 - node) / 2 * (high - low) + low + 0.5) + end + + return values +end + function M.hsl_to_hex(h, s, l) local r, g, b = M.hsl_to_rbg(h, s, l) return M.rgb_to_hex(r, g, b) From fd36d40315a8da418e64b96a895af3ebc7529819 Mon Sep 17 00:00:00 2001 From: stepit Date: Fri, 13 Mar 2026 18:54:56 +0100 Subject: [PATCH 07/14] chore: render md hi cleanup --- lua/flow/highlights/render-markdown.lua | 47 ++++++++++--------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/lua/flow/highlights/render-markdown.lua b/lua/flow/highlights/render-markdown.lua index 617101f..9585245 100644 --- a/lua/flow/highlights/render-markdown.lua +++ b/lua/flow/highlights/render-markdown.lua @@ -1,47 +1,36 @@ local M = {} ---- @param c table: The available colors. ---- @param o FlowConfig: The available options. ---- @return table: Render markdown plugin highlights. -function M.get(c, o) +--- @param colors table The available colors. +--- @param config FlowConfig The available options. +--- @return table Render markdown plugin highlights. +function M.get(colors, config) local theme = { - RenderMarkdownCode = { link = "@markup.raw.block" }, -- Code blocks. - RenderMarkdownCodeInline = { link = "@markup.raw.markdown_inline" }, -- Code inline. + RenderMarkdownCode = { link = "@markup.raw.block" }, + RenderMarkdownCodeInline = { link = "@markup.raw.markdown_inline" }, RenderMarkdownBullet = { link = "@markup.list" }, RenderMarkdownDash = { link = "Comment" }, - - -- TODO: complete - -- | RenderMarkdownTableHead | @markup.heading | Pipe table heading rows | - -- | RenderMarkdownTableRow | Normal | Pipe table body rows | - -- | RenderMarkdownTableFill | Conceal | Pipe table inline padding | - -- | RenderMarkdownSuccess | DiagnosticOk | Success related callouts | - -- | RenderMarkdownInfo | DiagnosticInfo | Info related callouts | - -- | RenderMarkdownHint | DiagnosticHint | Hint related callouts | - -- | RenderMarkdownWarn | DiagnosticWarn | Warning related callouts | - -- | RenderMarkdownError | DiagnosticError | Error related callouts | } -- Sequentially define headers background colors. local headersBg = { - c.Fluo.light, - c.Blue.very_light, - c.Light_blue.very_light, - c.Cyan.very_light, - c.Sky_blue.very_light, + colors.Fluo.light, + colors.Blue.very_light, + colors.Light_blue.very_light, + colors.Cyan.very_light, + colors.Sky_blue.very_light, } - if o.theme.style == "dark" then + if config.theme.style == "dark" then headersBg = { - c.Fluo.dark, - c.Blue.very_dark, - c.Light_blue.very_dark, - c.Cyan.very_dark, - c.Sky_blue.very_dark, + colors.Fluo.dark, + colors.Blue.very_dark, + colors.Light_blue.very_dark, + colors.Cyan.very_dark, + colors.Sky_blue.very_dark, } end - -- TODO: hardcoded transparent background but should be based on config. for i, _ in ipairs(headersBg) do - theme["RenderMarkdownH" .. i .. "Bg"] = { bg = c.transparent, bold = true } + theme["RenderMarkdownH" .. i .. "Bg"] = { bg = colors.transparent, bold = true } end return theme From 57e358a39b7fa3f74709717b154f21a73bf64f1d Mon Sep 17 00:00:00 2001 From: stepit Date: Fri, 13 Mar 2026 18:58:43 +0100 Subject: [PATCH 08/14] chore: remove annoying hi in luadoc --- lua/flow/highlights/treesitter.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lua/flow/highlights/treesitter.lua b/lua/flow/highlights/treesitter.lua index e46c65b..638ffd6 100644 --- a/lua/flow/highlights/treesitter.lua +++ b/lua/flow/highlights/treesitter.lua @@ -144,6 +144,21 @@ function M.get(c, o) ["@lsp.typemod.variable.injected"] = { link = "@variable" }, ["@lsp.typemod.variable.static"] = { link = "@constant" }, + -- Lua: clear LSP semantic tokens inside doc comments so luadoc + -- captures (linked to Comment above) are not overridden. + ["@lsp.type.keyword.lua"] = {}, + ["@lsp.type.type.lua"] = {}, + ["@lsp.type.variable.lua"] = {}, + -- Lua docstrings: keep all luadoc captures as comments so annotations + -- like @param, @return, @type don't mix syntax colors into docstrings. + ["@keyword.luadoc"] = { link = "Comment" }, + ["@type.luadoc"] = { link = "Comment" }, + ["@variable.parameter.luadoc"] = { link = "Comment" }, + ["@operator.luadoc"] = { link = "Comment" }, + ["@punctuation.delimiter.luadoc"] = { link = "Comment" }, + ["@punctuation.bracket.luadoc"] = { link = "Comment" }, + ["@string.luadoc"] = { link = "Comment" }, + -- Golang ["@module.go"] = { fg = c.blue }, ["@keyword.function.go"] = { link = "Statement" }, From fe7589e8959fc47518ad03116f75cb0860e2c32c Mon Sep 17 00:00:00 2001 From: stepit Date: Fri, 13 Mar 2026 19:03:08 +0100 Subject: [PATCH 09/14] chore: return colors from theme config --- lua/flow/theme.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lua/flow/theme.lua b/lua/flow/theme.lua index a213cae..2cf177a 100644 --- a/lua/flow/theme.lua +++ b/lua/flow/theme.lua @@ -38,9 +38,10 @@ local M = { b = {}, } ---- This function sets up the highlight groups by merging the colors +--- Sets the highlight groups up by merging the colors --- and options for each active highlight group. ---- @return table: A table of configured highlight groups. +--- @return table Highlight groups. +--- @return table Flow colors. function M.configure() -- Options retrieved can be the default one or those modified by the call to -- `require("flow").config{}` @@ -59,9 +60,7 @@ function M.configure() vim.api.nvim_set_hl(0, "@lsp.type.property.lua", {}) - -- util.autocmds() - - return highlights + return highlights, colors end return M From dfb950dbbd6275edc05d6f7d912e43beec5ddf46 Mon Sep 17 00:00:00 2001 From: stepit Date: Fri, 13 Mar 2026 19:05:19 +0100 Subject: [PATCH 10/14] fix: missing import --- lua/flow/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/flow/init.lua b/lua/flow/init.lua index 3c477ea..7732375 100644 --- a/lua/flow/init.lua +++ b/lua/flow/init.lua @@ -1,5 +1,6 @@ local config = require("flow.config") local theme = require("flow.theme") +local util = require("flow.util") local M = {} @@ -30,7 +31,7 @@ function M.load(cfg) M.config(cfg) end - local highlights = theme.configure() + local highlights, colors = theme.configure() -- The heart of the plugin, where highlight groups are set. for group, hi in pairs(highlights) do From 12c3281c5a5e001e38adfccd01c9664620671285 Mon Sep 17 00:00:00 2001 From: stepit Date: Sat, 14 Mar 2026 16:16:08 +0100 Subject: [PATCH 11/14] refactor: initialization --- README.md | 22 ++++ colors/flow-mono.lua | 1 + colors/flow.lua | 2 +- lua/flow/config.lua | 181 +++++++++++++---------------- lua/flow/highlights/treesitter.lua | 1 + lua/flow/init.lua | 24 ++-- lua/flow/palette.lua | 2 +- lua/flow/theme.lua | 14 +-- 8 files changed, 124 insertions(+), 123 deletions(-) create mode 100644 colors/flow-mono.lua diff --git a/README.md b/README.md index 07fe61f..92feeb3 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,28 @@ Why fluo? Because it's simply cool! ![flow-multiple-terminal](https://github.com/user-attachments/assets/9d1f367a-7a9d-478d-9fe0-a67bd33eca1a) +## Variants + +Flow colorscheme family offers two variants: + +- `default`: is the original version of the colorscheme providing a wide range of colors. + This variant is recommended for people that likes selected colors for every semantic syntax in the UI. + +- `mono`: the mono variant is designed to be minimal, offering only shades of grey and the selected + fluo. This variant is recommended for people that want a super focusing environment. + +To variant can be selected by loading the colorscheme with the specific name: + +```lua + vim.cmd("colorscheme flow") +``` + +Or: + +```lua + vim.cmd("colorscheme flow-mono") +``` + ## Palette Flow uses a palette of nine HSL-based colors, chosen to create a cohesive and visually appealing diff --git a/colors/flow-mono.lua b/colors/flow-mono.lua new file mode 100644 index 0000000..697e047 --- /dev/null +++ b/colors/flow-mono.lua @@ -0,0 +1 @@ +require("flow").load("mono") diff --git a/colors/flow.lua b/colors/flow.lua index 42e44c6..5a9568d 100644 --- a/colors/flow.lua +++ b/colors/flow.lua @@ -1 +1 @@ -require("flow").load() +require("flow").load("classic") diff --git a/lua/flow/config.lua b/lua/flow/config.lua index e72fbd1..fc84d8a 100644 --- a/lua/flow/config.lua +++ b/lua/flow/config.lua @@ -1,155 +1,134 @@ -local M = {} +--- Stores allowed values for configuration options. +--- @class ConfigEnum +--- @field name string The name of the enum. +--- @field path string[] The path to get the enum field in the config. +--- @field valid string[] Valid values. + +local M = { + --- ConfigEnum[] + enum_values = { + { name = "style", path = { "theme", "style" }, valid = { "dark", "light" } }, + { + name = "contrast", + path = { "theme", "contrast" }, + valid = { "default", "high" }, + }, + { name = "mode", path = { "colors", "mode" }, valid = { "default", "dark", "light" } }, + { + path = { "colors", "fluo" }, + valid = { "pink", "cyan", "yellow", "orange", "green" }, + name = "fluo color", + }, + { + name = "borders", + path = { "ui", "borders" }, + valid = { "light", "dark", "none" }, + }, + }, +} --- Default configuration options for the colorscheme. --- @class FlowConfig M.defaults = { + --- @class FlowThemeConfig + --- @field style "dark" | "light" Defines the colorscheme theme style. + --- @field contrast "default" | "high" Defines whether details contrasts should be accentuated or not. + --- @field transparent boolean Defines whether neovim background should be transparent (fallback to terminal value) or not. theme = { - --- Defines the colorscheme theme style. - --- @type "dark" | "light" style = "dark", - --- Defines whether details contrasts should be accentuated or not. - --- @type "default" | "high" contrast = "default", - --- @boolean transparent = false, - --- @boolean Internal flag set by flow-mono entry point - mono = false, }, + --- @class FlowColorsConfig + --- @field mode "default" | "dark" | "light" Defines the colors used in the syntax and UI. + --- @field fluo "pink" | "cyan" | "yellow" | "orange" | "green" Fluo color used in the theme. + --- @field custom { saturation: string, lightness: string } Allows to specify custom saturation + --- (0, 100) and lightness (0, 100) for theme. Despite the option is present for maximum + --- customizability, it is recommended to use defaults. colors = { - --- Defines the colors used in the syntax and UI. - ---@type "default" | "dark" | "light" mode = "default", - --- Fluo color to use in the theme. - ---@type "pink" | "cyan" | "yellow" | "orange" | "green" fluo = "pink", - --- Allows to specify custom saturation and light for theme. Despite the option is present for - --- maximum customizability, it is recommended to use defaults. - custom = { - --- A number between 0-100 as string or empty string. - --- @type string - saturation = "", - --- A number between 0-100 as string or empty string. - --- @type string - light = "", - }, + custom = { saturation = "", lightness = "" }, }, + --- @class FlowUiConfig + --- @field borders "light" | "dark" | "none" Defines how borders of elements are displayed. + --- @field aggressive_spell boolean Defines if spell errors have to be highlighted with + --- a more outstanding color. + --- @field aggressive_special_comment boolean Defines if special comments (TODO, FIX, etc.) + --- has to be highlighted with different colors. ui = { - --- @type "light" | "dark" | "none" - borders = "dark", - --- Defines if spell errors have to be highlighted with a more outstanding color. - --- @boolean + borders = "none", aggressive_spell = false, - --- Defines if special comments (TODO, FIX, etc.) has to be highlighted with different colors. - --- @boolean aggressive_special_comment = false, }, } +--- Used to store colorscheme configuration with user provided custom values. --- @type FlowConfig M.options = {} ---- Valid values for configuration options. -M.valid_options = { - fluo_colors = { "pink", "cyan", "yellow", "orange", "green" }, - modes = { "default", "dark", "light" }, - borders = { "light", "dark", "none" }, - contrast = { "default", "high" }, - custom_ranges = { - saturation = { min = 0, max = 100 }, - light = { min = 0, max = 100 }, - }, -} - --- Validate configuration options. --- @param config FlowConfig --- @return boolean, string? -local function validate_options(config) - -- Validate theme options. - if config.theme then - if - config.theme.contrast - and not vim.tbl_contains(M.valid_options.contrast, config.theme.contrast) - then - return false, string.format("Invalid border: %s", config.theme.contrast) +function M:validate_options(config) + for _, v in ipairs(self.enum_values) do + local val = config + for _, key in ipairs(v.path) do + val = val and val[key] end - end - - -- Validate color options. - if config.colors then - if - config.colors.fluo and not vim.tbl_contains(M.valid_options.fluo_colors, config.colors.fluo) - then - return false, string.format("Invalid fluo color: %s", config.colors.fluo) + if val and not vim.tbl_contains(v.valid, val) then + return false, string.format("Invalid %s: %s", v.name, val) end + end - if config.colors.mode and not vim.tbl_contains(M.valid_options.modes, config.colors.mode) then - return false, string.format("Invalid mode: %s", config.colors.mode) - end + -- Validate custom color values for hue and light. + if config.colors and config.colors.custom then + local sl = { saturation = { 0, 100 }, lightness = { 0, 100 } } + for property, range in pairs(sl) do + local min, max = range[1], range[2] - -- Validate custom color values for hue and light. - if config.colors.custom then - if config.colors.custom.saturation and config.colors.custom.saturation ~= "" then - local s = tonumber(config.colors.custom.saturation) - if - not s - or s < M.valid_options.custom_ranges.saturation.min - or s > M.valid_options.custom_ranges.saturation.max - then - return false, - string.format( - "Invalid saturation value: %s (must be between %d and %d)", - config.colors.custom.saturation, - M.valid_options.custom_ranges.saturation.min, - M.valid_options.custom_ranges.saturation.max - ) - end - end - if config.colors.custom.light and config.colors.custom.light ~= "" then - local l = tonumber(config.colors.custom.light) - if - not l - or l < M.valid_options.custom_ranges.light.min - or l > M.valid_options.custom_ranges.light.max - then + local custom_val = config.colors.custom[property] + if custom_val and custom_val ~= "" then + local val = tonumber(custom_val) + if not val or val < min or val > max then return false, string.format( - "Invalid light value: %s (must be between %d and %d)", - config.colors.custom.light, - M.valid_options.custom_ranges.light.min, - M.valid_options.custom_ranges.light.max + "Invalid %s value: %s (must be between %d and %d)", + property, + custom_val, + min, + max ) end end end end - -- Validate UI options. - if config.ui then - if config.ui.borders and not vim.tbl_contains(M.valid_options.borders, config.ui.borders) then - return false, string.format("Invalid border: %s", config.ui.borders) - end - end - return true end --- This is the entry point of the configuration before loading the plugin. --- It sets the colorscheme options by merging the user provided ones with --- the default configuration. ---- @param config FlowConfig? Optional table to customize the colorscheme setup. -function M._setup(config) +--- @param config FlowConfig? Optional table to customize the colorscheme configuration. +function M:_setup(config) + vim.notify("Flow configuration setup", vim.log.levels.DEBUG) + -- Short circuit if options have been already set. This happen when the colorscheme is loaded from -- the plugin manager because it first set the options, and then set the colorscheme. if not vim.tbl_isempty(M.options) then return end - local ok, err = validate_options(config or {}) - if not ok then - config = {} - vim.notify("Error setting user options, fallback to defaults: " .. err, vim.log.levels.WARN) + if config then + local ok, err = self:validate_options(config) + if not ok then + config = {} + vim.notify("Error setting user options, fallback to defaults: " .. err, vim.log.levels.WARN) + end end + -- Set to the table the default config updated with the user provided one. M.options = vim.tbl_deep_extend("force", {}, M.defaults, config or {}) end diff --git a/lua/flow/highlights/treesitter.lua b/lua/flow/highlights/treesitter.lua index 638ffd6..d613985 100644 --- a/lua/flow/highlights/treesitter.lua +++ b/lua/flow/highlights/treesitter.lua @@ -147,6 +147,7 @@ function M.get(c, o) -- Lua: clear LSP semantic tokens inside doc comments so luadoc -- captures (linked to Comment above) are not overridden. ["@lsp.type.keyword.lua"] = {}, + ["@lsp.type.property.lua"] = {}, ["@lsp.type.type.lua"] = {}, ["@lsp.type.variable.lua"] = {}, -- Lua docstrings: keep all luadoc captures as comments so annotations diff --git a/lua/flow/init.lua b/lua/flow/init.lua index 7732375..b560a7c 100644 --- a/lua/flow/init.lua +++ b/lua/flow/init.lua @@ -2,9 +2,12 @@ local config = require("flow.config") local theme = require("flow.theme") local util = require("flow.util") -local M = {} - -M.name = "flow" +local M = { + names = { + classic = "flow", + mono = "flow-mono", + }, +} --- This function is called to setup the colorscheme options. --- NOTE: this function is not called when executing `vim.cmd("colorscheme flow")` @@ -14,21 +17,20 @@ M.setup = config._setup --- It sets up the colorscheme by clearing existing highlights, --- enabling true color support, setting the colorscheme name, --- and applying the highlight groups defined in the theme. ---- @param cfg FlowConfig? -function M.load(cfg) +--- This method is called with `vim.cmd("colorscheme flow")`. +--- @param variant string +function M.load(variant) + vim.notify("Calling load with variant: " .. variant, vim.log.levels.WARN) -- Check if the current colorscheme is different from the one to be loaded. - if vim.g.colors_name ~= M.name then + if not vim.tbl_contains(M.names, vim.g.colors_name) then + vim.notify("Entering", vim.log.levels.WARN) -- Clear existing highlights. vim.cmd("hi clear") -- Enable true color support. vim.o.termguicolors = true -- Set the current colorscheme name. -- `:lua print(vim.g.colors_name)` - vim.g.colors_name = M.name - end - - if cfg then - M.config(cfg) + vim.g.colors_name = M.names[variant] end local highlights, colors = theme.configure() diff --git a/lua/flow/palette.lua b/lua/flow/palette.lua index b28aaed..7ba9c8e 100644 --- a/lua/flow/palette.lua +++ b/lua/flow/palette.lua @@ -168,7 +168,7 @@ function M.get(config) shade = M.update_light_and_saturation( shade, config.colors.mode, - config.colors.custom.light, + config.colors.custom.lightness, config.colors.custom.saturation ) diff --git a/lua/flow/theme.lua b/lua/flow/theme.lua index 2cf177a..d9974ed 100644 --- a/lua/flow/theme.lua +++ b/lua/flow/theme.lua @@ -1,4 +1,4 @@ -local flow_colors = require("flow.colors") +local colors = require("flow.colors") local util = require("flow.util") local M = { @@ -34,8 +34,6 @@ local M = { "undotree", "vim-highlighturl", }, - - b = {}, } --- Sets the highlight groups up by merging the colors @@ -44,23 +42,21 @@ local M = { --- @return table Flow colors. function M.configure() -- Options retrieved can be the default one or those modified by the call to - -- `require("flow").config{}` + -- `require("flow").setup{}` local options = require("flow.config").options - local colors = flow_colors.setup(options) + local flow_colors = colors.setup(options) local highlights = {} for _, h in ipairs(M.active_highlights) do local group_path = "flow.highlights." .. h local group = require(group_path) - local hi = group.get(colors, options) + local hi = group.get(flow_colors, options) highlights = util.merge(highlights, hi) end - vim.api.nvim_set_hl(0, "@lsp.type.property.lua", {}) - - return highlights, colors + return highlights, flow_colors end return M From caf3520f000b7d29761566e640e4693fdf5ad6f6 Mon Sep 17 00:00:00 2001 From: stepit Date: Sat, 14 Mar 2026 17:00:40 +0100 Subject: [PATCH 12/14] chore: refactor + cleaning --- lua/flow/colors.lua | 80 +++++++++----- lua/flow/config.lua | 8 +- lua/flow/highlights/base.lua | 2 +- lua/flow/highlights/syntax.lua | 4 +- lua/flow/highlights/treesitter.lua | 3 +- lua/flow/init.lua | 3 + lua/flow/mono.lua | 3 + lua/flow/palette.lua | 163 +++++++++-------------------- 8 files changed, 117 insertions(+), 149 deletions(-) diff --git a/lua/flow/colors.lua b/lua/flow/colors.lua index 4506e28..245dd9e 100644 --- a/lua/flow/colors.lua +++ b/lua/flow/colors.lua @@ -1,7 +1,8 @@ local palette = require("flow.palette") local M = { - _color_names = { + --- Names of the colors used in the colorscheme. + names = { "blue", "cyan", "green", @@ -20,15 +21,15 @@ local M = { --- @param config FlowConfig The configuration options to setup the colorscheme. --- @return table The colors used by the colorscheme. function M.setup(config) - local default_palette = palette.get(config or {}) - config = config or {} + local default_palette = palette.get(config) + -- Get the configured fluo color (with fallback to default) local fluo_color = (config.colors and config.colors.fluo) or "pink" local colors = { - -- Core colors + -- Core colors. transparent = default_palette.transparent, black = default_palette.black, white = default_palette.white, @@ -45,18 +46,23 @@ function M.setup(config) M._apply_opts(default_palette, colors, config) - for _, key in ipairs(M._color_names) do + for _, key in ipairs(M.names) do -- Set the specific mode of the colors. colors[key] = default_palette[key][config.colors.mode] -- Store all the color variations. These variables are used for hi that - -- requires contrasts with the current theme, like git. + -- require contrasts with the current theme, like git. local Key = key:gsub("^%l", string.upper) colors[Key] = default_palette[key] end + -- Yellow is perceptually too light for light backgrounds, use the dark shade instead. + if config.theme.style ~= "dark" then + colors.yellow = colors.Yellow.dark + end + -- Comments - use lighter grey for light theme colors.comment = config.theme.style == "dark" and colors.grey[7] -- Dark theme: 50% lightness - or colors.grey[4] -- Light theme: inverts to 65% lightness (less dark) + or colors.grey[6] -- Light theme: darker grey for readability against light background -- +----------------------------------------------------------------------------------------+ -- | Sidebar (e.g., NERDTree, Telescope, Quickfix) | <- Sidebar @@ -90,11 +96,24 @@ function M.setup(config) -- Gutter: used for line numbers, signs, and fold column. colors.fg_gutter = colors.grey[5] colors.bg_gutter = (is_transparent and default_palette.transparent) or colors.bg - + -- + -- -- CursorLine background. + -- + -- -- Visual selection background. + -- visual_bg = { + -- dark = hsl_to_hex(fluo_hue_value, 90, 23), -- For dark theme + -- light = hsl_to_hex(fluo_hue_value, 90, 77), -- For light theme + -- }, + -- + + -- -- Float window colors. + -- float_bg = { + -- dark = hsl_to_hex(203, 20, 18), -- For dark theme + -- light = hsl_to_hex(203, 20, 82), -- For light theme + -- }, -- Float: used for visual elements that are floating and triggered by the user. colors.fg_float = colors.grey[8] - colors.bg_float = config.theme.style == "dark" and default_palette.float_bg.dark - or default_palette.float_bg.light + colors.bg_float = default_palette.grey[2] -- Popups: use for completion menu and all visual components that appears autonomously. colors.fg_popup = default_palette.grey[9] @@ -108,9 +127,12 @@ function M.setup(config) colors.fg_highlight = colors.grey[6] colors.bg_highlight = colors.grey[2] + -- visual_bg = { + -- dark = hsl_to_hex(fluo_hue_value, 90, 23), -- For dark theme + -- light = hsl_to_hex(fluo_hue_value, 90, 77), -- For light theme + -- }, -- Visual - uses configured fluo color - colors.bg_visual = config.theme.style == "dark" and default_palette.visual_bg.dark - or default_palette.visual_bg.light + colors.bg_visual = config.theme.style == "dark" and colors.Fluo.dark or colors.Fluo.light colors.fg_visual = colors.grey[2] -- Git @@ -144,7 +166,7 @@ function M.setup(config) colors.hack = is_dark and colors.Yellow.default or colors.Yellow.dark -- HACK comments -- Apply monochrome transformation if enabled - if config.theme.mono then + if config.variant == "mono" then require("flow.mono").apply(colors, config) end @@ -170,33 +192,35 @@ function M._swap(t, a, b) t[a], t[b] = t[b], t[a] end ---- @param opts FlowConfig -function M._apply_opts(default_palette, colors, opts) - if opts.colors.fluo then - colors.fluo = default_palette.fluo[opts.colors.fluo].default - colors.Fluo = default_palette.fluo[opts.colors.fluo] +--- @param default_palette table +--- @param colors table +--- @param config FlowConfig +function M._apply_opts(default_palette, colors, config) + if config.colors.fluo then + colors.fluo = default_palette.fluo[config.colors.fluo].default + colors.Fluo = default_palette.fluo[config.colors.fluo] end -- Apply changes if the theme is not dark. -- HACK: this has to be executed before all changes that involves white, black, and grey. - if opts.theme.style ~= "dark" then + if config.theme.style ~= "dark" then M._invert_colors_for_theme(colors) end -- If high contrast the darkest color is swap for the next color and the -- lightest color is swap for the color before. - if opts.theme.contrast == "high" then + if config.theme.contrast == "high" then M._invert_colors_for_contrast(colors) end - colors.bg = (opts.theme.transparent and default_palette.transparent) or default_palette.grey[3] -- used for theme background - colors.fg = (opts.theme.style == "dark" and colors.grey[8]) or colors.grey[8] + colors.bg = (config.theme.transparent and default_palette.transparent) or default_palette.grey[3] -- used for theme background + colors.fg = (config.theme.style == "dark" and colors.grey[8]) or colors.grey[8] -- Borders - if opts.ui.borders == "none" then + if config.ui.borders == "none" then colors.fg_border = colors.bg -- Match background to make borders invisible colors.fg_vsplit = colors.grey[2] - elseif opts.ui.borders == "light" then + elseif config.ui.borders == "light" then colors.fg_border = colors.grey[4] colors.fg_vsplit = colors.grey[4] else -- dark (default) @@ -204,9 +228,13 @@ function M._apply_opts(default_palette, colors, opts) colors.fg_vsplit = colors.grey[1] end + -- cursorline_bg = { + -- dark = hsl_to_hex(fluo_hue_value, 20, 13), -- For dark theme + -- light = hsl_to_hex(fluo_hue_value, 20, 87), -- For light theme + -- }, -- CursorLine background - uses configured fluo color - colors.bg_cursorline = opts.theme.style == "dark" and default_palette.cursorline_bg.dark - or default_palette.cursorline_bg.light + colors.bg_cursorline = config.theme.style == "dark" and colors.Fluo.very_dark + or colors.Fluo.very_light -- NOTE bg_border is currently not used. colors.bg_border = colors.grey[7] diff --git a/lua/flow/config.lua b/lua/flow/config.lua index fc84d8a..a6ec658 100644 --- a/lua/flow/config.lua +++ b/lua/flow/config.lua @@ -70,8 +70,8 @@ M.options = {} --- Validate configuration options. --- @param config FlowConfig --- @return boolean, string? -function M:validate_options(config) - for _, v in ipairs(self.enum_values) do +function M.validate_options(config) + for _, v in ipairs(M.enum_values) do local val = config for _, key in ipairs(v.path) do val = val and val[key] @@ -111,7 +111,7 @@ end --- It sets the colorscheme options by merging the user provided ones with --- the default configuration. --- @param config FlowConfig? Optional table to customize the colorscheme configuration. -function M:_setup(config) +function M._setup(config) vim.notify("Flow configuration setup", vim.log.levels.DEBUG) -- Short circuit if options have been already set. This happen when the colorscheme is loaded from @@ -121,7 +121,7 @@ function M:_setup(config) end if config then - local ok, err = self:validate_options(config) + local ok, err = M.validate_options(config) if not ok then config = {} vim.notify("Error setting user options, fallback to defaults: " .. err, vim.log.levels.WARN) diff --git a/lua/flow/highlights/base.lua b/lua/flow/highlights/base.lua index e1e15b2..bbf1384 100644 --- a/lua/flow/highlights/base.lua +++ b/lua/flow/highlights/base.lua @@ -136,7 +136,7 @@ function M.get(c, o) } -- Monochrome: collapse FlowKind groups to ~6 grey lightness tiers. - if o.theme.mono then + if o.variant == "mono" then -- Tier 1: Keywords/constructors/classes theme.FlowKindKeyword = { fg = c.red, bold = true } theme.FlowKindConstructor = { fg = c.red, bold = true } diff --git a/lua/flow/highlights/syntax.lua b/lua/flow/highlights/syntax.lua index 32cd859..b7b9cb2 100644 --- a/lua/flow/highlights/syntax.lua +++ b/lua/flow/highlights/syntax.lua @@ -95,11 +95,11 @@ function M.get(c, o) end -- Monochrome: bold keywords/operators, italic comments/delimiters/special. - if o.theme.mono then + if o.variant == "mono" then theme.Comment = { fg = c.comment, italic = true } theme.Keyword = { fg = c.red, bold = true } theme.Operator = { fg = c.red, bold = true } - theme.Delimiter = { fg = c.purple, italic = true } + theme.Delimiter = { fg = c.bracket } theme.Special = { fg = c.purple, italic = true } end diff --git a/lua/flow/highlights/treesitter.lua b/lua/flow/highlights/treesitter.lua index d613985..f699ff0 100644 --- a/lua/flow/highlights/treesitter.lua +++ b/lua/flow/highlights/treesitter.lua @@ -178,9 +178,10 @@ function M.get(c, o) } -- Monochrome: italic fields/properties to distinguish from comments. - if o.theme.mono then + if o.variant == "mono" then theme["@variable.member"] = { fg = c.light_blue, italic = true } theme["@property"] = { fg = c.cyan, italic = true } + theme["@punctuation.bracket"] = { fg = c.bracket } end return theme diff --git a/lua/flow/init.lua b/lua/flow/init.lua index b560a7c..45730b3 100644 --- a/lua/flow/init.lua +++ b/lua/flow/init.lua @@ -33,6 +33,9 @@ function M.load(variant) vim.g.colors_name = M.names[variant] end + -- Set the variant on the config so highlight modules can check it. + config.options.variant = variant + local highlights, colors = theme.configure() -- The heart of the plugin, where highlight groups are set. diff --git a/lua/flow/mono.lua b/lua/flow/mono.lua index c152f73..d87b802 100644 --- a/lua/flow/mono.lua +++ b/lua/flow/mono.lua @@ -87,6 +87,9 @@ function M.apply(colors, opts) parent = is_dark and colors.grey[5] or colors.grey[n - 4], } + -- Parentheses/brackets: use fluo accent, dark shade for dark theme, light for light. + colors.bracket = is_dark and colors.Fluo.dark or colors.Fluo.light + -- Comments: use a grey that's clearly dimmer than syntax elements colors.comment = is_dark and colors.grey[6] or colors.grey[6] diff --git a/lua/flow/palette.lua b/lua/flow/palette.lua index 7ba9c8e..c0d5106 100644 --- a/lua/flow/palette.lua +++ b/lua/flow/palette.lua @@ -16,81 +16,48 @@ function HSL.new(h, s, l) return setmetatable({ h = h, s = s, l = l }, HSL) end -local function n_equidistant_values(n, range) - assert(n >= 2, "number of points should be >= 2") - local step = math.floor(range / n) - - local values = {} - for i = 0, n do - values[i + 1] = i * step + step - end - - return values -end - ---- Generate n+1 Chebyshev-spaced values over [low, high]. ---- Nodes cluster near both endpoints, giving finer gradation in the ---- darks and lights with wider spacing through the mid-tones. -local function n_chebyshev_values(n, low, high) - assert(n >= 2, "number of points should be >= 2") - - local values = {} - for i = 0, n do - -- Chebyshev nodes on [-1, 1]: cos(i * pi / n), mapped to [low, high] - local node = math.cos(i * math.pi / n) - values[i + 1] = math.floor((1 - node) / 2 * (high - low) + low + 0.5) - end - - return values -end - local M = { - default_fluo = "pink", - - --- Default fluo values. - fluo_lightness = { - default = 50, - light = 90, - dark = 35, - }, - - fluo_saturation = 100, - - fluo_hue = { - pink = 331, - cyan = 187, - green = 115, - yellow = 61, - orange = 25, + fluo = { + default = "pink", + hue = { + pink = 331, + cyan = 187, + green = 115, + yellow = 61, + orange = 25, + }, + shade = { + very_dark = { S = 25, L = 13 }, + dark = { S = 90, L = 25 }, + default = { S = 100, L = 50 }, + light = { S = 100, L = 77 }, + very_light = { S = 100, L = 90 }, + }, }, - - --- Default grey lightness steps used by both chromatic and monochrome palettes. - grey_lightness = util.n_chebyshev_values(15, 10, 90), - - --- Default grey hue for the chromatic palette. - grey_hue = 203, - - --- Default grey saturation for the chromatic palette. - grey_saturation = 20, - - color_hue = { - red = 355, - purple = 270, - blue = 230, - light_blue = 205, - sky_blue = 190, - cyan = 165, - green = 115, - yellow = 60, - orange = 25, + grey = { + lightness = util.n_chebyshev_values(15, 10, 90), + hue = 203, + saturation = 20, }, - - shade = { - very_dark = { S = 27, L = 20 }, - dark = { S = 50, L = 35 }, - default = { S = 40, L = 55 }, - light = { S = 40, L = 65 }, - very_light = { S = 50, L = 85 }, + base = { + hue = { + red = 355, + purple = 270, + blue = 230, + light_blue = 205, + sky_blue = 190, + cyan = 165, + green = 115, + yellow = 60, + orange = 25, + }, + shade = { + very_dark = { S = 27, L = 20 }, + dark = { S = 50, L = 35 }, + default = { S = 40, L = 55 }, + light = { S = 40, L = 65 }, + very_light = { S = 50, L = 85 }, + }, }, } @@ -121,19 +88,6 @@ function M.build_color(h, shade) } end ---- Build a 3-shade fluorescent color. ---- @param h number Hue value. ---- @param light_l number? Optional lightness for light shade. ---- @param dark_l number? Optional lightness for dark shade. ---- @return table -function M.build_fluo(h, light_l, dark_l) - return { - dark = hsl_to_hex(h, M.fluo_saturation, dark_l or M.fluo_lightness.dark), - default = hsl_to_hex(h, M.fluo_saturation, M.fluo_lightness.default), - light = hsl_to_hex(h, M.fluo_saturation, light_l or M.fluo_lightness.light), - } -end - --- Update default saturation and light of the selected mode if user provided --- custom values. --- @param shade table @@ -157,7 +111,8 @@ end --- @param config FlowConfig The available theme configuration options. --- @return table function M.get(config) - local shade = M.shade + -- Deep copy to avoid mutating the module-level shade table across calls. + local shade = vim.deepcopy(M.base.shade) local is_dark = config.theme.style == "dark" @@ -172,10 +127,6 @@ function M.get(config) config.colors.custom.saturation ) - -- Get the configured fluo color. - local configured_fluo = (config.colors and config.colors.fluo) or M.default_fluo - local fluo_hue_value = M.fluo_hue[configured_fluo] - local palette = { -- Transparent color. transparent = "NONE", @@ -185,38 +136,20 @@ function M.get(config) white = hsl_to_hex(0, 0, 95), -- Greyscale colors. - grey = M.build_greys(M.grey_hue, M.grey_saturation, M.grey_lightness), - - -- CursorLine background. - cursorline_bg = { - dark = hsl_to_hex(fluo_hue_value, 20, 13), -- For dark theme - light = hsl_to_hex(fluo_hue_value, 20, 87), -- For light theme - }, - - -- Visual selection background. - visual_bg = { - dark = hsl_to_hex(fluo_hue_value, 90, 23), -- For dark theme - light = hsl_to_hex(fluo_hue_value, 90, 77), -- For light theme - }, - - -- Float window colors. - float_bg = { - dark = hsl_to_hex(203, 20, 18), -- For dark theme - light = hsl_to_hex(203, 20, 82), -- For light theme - }, + grey = M.build_greys(M.grey.hue, M.grey.saturation, M.grey.lightness), -- Fluorescent colors. fluo = { - pink = M.build_fluo(M.fluo_hue.pink), - orange = M.build_fluo(M.fluo_hue.orange), - cyan = M.build_fluo(M.fluo_hue.cyan, 90, 30), - green = M.build_fluo(M.fluo_hue.green, 100, 30), - yellow = M.build_fluo(M.fluo_hue.yellow, 90, 30), + pink = M.build_color(M.fluo.hue.pink, M.fluo.shade), + orange = M.build_color(M.fluo.hue.orange, M.fluo.shade), + cyan = M.build_color(M.fluo.hue.cyan, M.fluo.shade), + green = M.build_color(M.fluo.hue.green, M.fluo.shade), + yellow = M.build_color(M.fluo.hue.yellow, M.fluo.shade), }, } -- Build chromatic colors. - for name, h in pairs(M.color_hue) do + for name, h in pairs(M.base.hue) do palette[name] = M.build_color(h, shade) end From 594c798088456eee25b2478cb7842199ab902397 Mon Sep 17 00:00:00 2001 From: stepit Date: Sat, 14 Mar 2026 17:14:39 +0100 Subject: [PATCH 13/14] chore: remove notify --- lua/flow/config.lua | 3 --- lua/flow/init.lua | 3 +-- lua/flow/mono.lua | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lua/flow/config.lua b/lua/flow/config.lua index a6ec658..0243544 100644 --- a/lua/flow/config.lua +++ b/lua/flow/config.lua @@ -112,8 +112,6 @@ end --- the default configuration. --- @param config FlowConfig? Optional table to customize the colorscheme configuration. function M._setup(config) - vim.notify("Flow configuration setup", vim.log.levels.DEBUG) - -- Short circuit if options have been already set. This happen when the colorscheme is loaded from -- the plugin manager because it first set the options, and then set the colorscheme. if not vim.tbl_isempty(M.options) then @@ -124,7 +122,6 @@ function M._setup(config) local ok, err = M.validate_options(config) if not ok then config = {} - vim.notify("Error setting user options, fallback to defaults: " .. err, vim.log.levels.WARN) end end diff --git a/lua/flow/init.lua b/lua/flow/init.lua index 45730b3..ef2b34b 100644 --- a/lua/flow/init.lua +++ b/lua/flow/init.lua @@ -19,11 +19,10 @@ M.setup = config._setup --- and applying the highlight groups defined in the theme. --- This method is called with `vim.cmd("colorscheme flow")`. --- @param variant string +--- function M.load(variant) - vim.notify("Calling load with variant: " .. variant, vim.log.levels.WARN) -- Check if the current colorscheme is different from the one to be loaded. if not vim.tbl_contains(M.names, vim.g.colors_name) then - vim.notify("Entering", vim.log.levels.WARN) -- Clear existing highlights. vim.cmd("hi clear") -- Enable true color support. diff --git a/lua/flow/mono.lua b/lua/flow/mono.lua index d87b802..f9917e6 100644 --- a/lua/flow/mono.lua +++ b/lua/flow/mono.lua @@ -91,7 +91,7 @@ function M.apply(colors, opts) colors.bracket = is_dark and colors.Fluo.dark or colors.Fluo.light -- Comments: use a grey that's clearly dimmer than syntax elements - colors.comment = is_dark and colors.grey[6] or colors.grey[6] + colors.comment = is_dark and colors.grey[7] or colors.grey[6] -- Special comments: fluo for fixme (urgent), grey for others colors.fixme = colors.Fluo.default From ab558adb010644fccdefd188cf23cf4bcc6e53c5 Mon Sep 17 00:00:00 2001 From: stepit Date: Sat, 14 Mar 2026 17:14:54 +0100 Subject: [PATCH 14/14] doc: update --- README.md | 8 +++++--- doc/flow.nvim.txt | 30 +++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 92feeb3..d6c1ba6 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,10 @@ Flow colorscheme family offers two variants: - `default`: is the original version of the colorscheme providing a wide range of colors. This variant is recommended for people that likes selected colors for every semantic syntax in the UI. -- `mono`: the mono variant is designed to be minimal, offering only shades of grey and the selected - fluo. This variant is recommended for people that want a super focusing environment. +- `mono`: the mono variant desaturates all syntax colors to metallic grey shades with a cool blue + hue, while mapping keywords and operators to the selected fluo accent color. Diagnostics also use + fluo shades. This variant is recommended for people that want a super focusing environment where + the accent color draws attention to what matters most. To variant can be selected by loading the colorscheme with the specific name: @@ -114,7 +116,7 @@ return { }, }, ui = { - borders = "inverse", -- "theme" | "inverse" | "fluo" | "none" + borders = "none", -- "light" | "dark" | "none" aggressive_spell = false, -- true | false }, }, diff --git a/doc/flow.nvim.txt b/doc/flow.nvim.txt index 340f95f..fbe9648 100644 --- a/doc/flow.nvim.txt +++ b/doc/flow.nvim.txt @@ -5,6 +5,7 @@ Table of Contents *flow.nvim-table-of-contents* 1. 🌊 Flow |flow.nvim-🌊-flow| - Showcase |flow.nvim-🌊-flow-showcase| + - Variants |flow.nvim-🌊-flow-variants| - Palette |flow.nvim-🌊-flow-palette| - Requirements |flow.nvim-🌊-flow-requirements| - Installation |flow.nvim-🌊-flow-installation| @@ -31,6 +32,33 @@ Why fluo? Because it’s simply cool! SHOWCASE *flow.nvim-🌊-flow-showcase* +VARIANTS *flow.nvim-🌊-flow-variants* + +Flow colorscheme family offers two variants: + +- `default`: is the original version of the colorscheme providing a wide range + of colors. This variant is recommended for people that likes selected colors + for every semantic syntax in the UI. + +- `mono`: the mono variant desaturates all syntax colors to metallic grey shades + with a cool blue hue, while mapping keywords and operators to the selected + fluo accent color. Diagnostics also use fluo shades. This variant is + recommended for people that want a super focusing environment where the + accent color draws attention to what matters most. + +The variant can be selected by loading the colorscheme with the specific name: + +>lua + vim.cmd("colorscheme flow") +< + +Or: + +>lua + vim.cmd("colorscheme flow-mono") +< + + PALETTE *flow.nvim-🌊-flow-palette* Flow uses a palette of nine HSL-based colors, chosen to create a cohesive and @@ -114,7 +142,7 @@ DEFAULT ~ }, }, ui = { - borders = "inverse", -- "theme" | "inverse" | "fluo" | "none" + borders = "none", -- "light" | "dark" | "none" aggressive_spell = false, -- true | false }, },