From 8889ad88481cd07b9f32eab2879ab6a32a6cc75a Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Sat, 24 Apr 2021 22:24:40 +0200 Subject: [PATCH] feat: "day" variant --- README.md | 8 +- lua/lualine/themes/tokyonight.lua | 13 +- lua/tokyonight/colors.lua | 98 ++++------ lua/tokyonight/hsluv.lua | 291 ++++++++++++++++++++++++++++++ lua/tokyonight/util.lua | 54 +++++- 5 files changed, 388 insertions(+), 76 deletions(-) create mode 100644 lua/tokyonight/hsluv.lua diff --git a/README.md b/README.md index fd9720e..9c1085f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # 🏙 Tokyo Night -A dark Neovim theme written in Lua ported from the Visual Studio Code [TokyoNight](https://github.com/enkia/tokyo-night-vscode-theme) theme. Includes extra themes for Kitty, Alacritty, iTerm and Fish. +A dark and light Neovim theme written in Lua ported from the Visual Studio Code [TokyoNight](https://github.com/enkia/tokyo-night-vscode-theme) theme. Includes extra themes for Kitty, Alacritty, iTerm and Fish. ## Storm @@ -11,6 +11,8 @@ A dark Neovim theme written in Lua ported from the Visual Studio Code [TokyoNigh ![image](https://user-images.githubusercontent.com/292349/115295327-7afdce80-a10e-11eb-89b3-2591262bf95a.png) +## Day + ## ✨ Features + supports the latest Neovim 5.0 features like TreeSitter and LSP @@ -86,11 +88,11 @@ require('lualine').setup { ## ⚙️ Configuration -The theme comes in two styles, `storm` and a darker variant `night`. +The theme comes in three styles, `storm`, a darker variant `night` and `day`. | Option | Default | Description | | ----------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| tokyonight_style | `"storm"` | The theme comes in two styles, `"storm"` and a darker variant `"night"`. | +| tokyonight_style | `"storm"` | The theme comes in three styles, `storm`, a darker variant `night` and `day`. | | tokyonight_terminal_colors | `true` | Configure the colors used when opening a `:terminal` in Neovim | | tokyonight_italic_comments | `true` | Make comments italic | | tokyonight_italic_keywords | `true` | Make keywords italic | diff --git a/lua/lualine/themes/tokyonight.lua b/lua/lualine/themes/tokyonight.lua index 8ba9d42..5db1d77 100644 --- a/lua/lualine/themes/tokyonight.lua +++ b/lua/lualine/themes/tokyonight.lua @@ -1,4 +1,6 @@ -local colors = require("tokyonight.colors").setup() +local config = require("tokyonight.config") +local colors = require("tokyonight.colors").setup(config) +local util = require("tokyonight.util") local tokyonight = {} @@ -34,4 +36,13 @@ tokyonight.inactive = { c = { bg = colors.bg_statusline, fg = colors.fg_gutter }, } +if config.style == "day" then + for _, mode in pairs(tokyonight) do + for _, section in pairs(mode) do + if section.bg then section.bg = util.getColor(section.bg, config) end + if section.fg then section.fg = util.getColor(section.fg, config) end + end + end +end + return tokyonight diff --git a/lua/tokyonight/colors.lua b/lua/tokyonight/colors.lua index e21b181..8c8f87e 100644 --- a/lua/tokyonight/colors.lua +++ b/lua/tokyonight/colors.lua @@ -11,71 +11,38 @@ function M.setup(config) ---@class ColorScheme local colors = {} - if config.style == "day" then - colors = { - none = "NONE", - bg_dark = "#F3F4F6", - bg = "#ffffff", - bg_highlight = "#F9FAFB", - terminal_black = "#414868", - fg = "#4B5563", - fg_dark = "#374151", - fg_gutter = "#D1D5DB", - dark3 = "#545c7e", - comment = "#9CA3AF", - dark5 = "#737aa2", - blue0 = "#90CAF9", - blue = "#3B82F6", - cyan = "#00B0FF", - blue1 = "#10B981", - blue5 = "#80D8FF", - blue6 = "#B4F9F8", - magenta = "#8B5CF6", - purple = "#A78BFA", - orange = "#F57F17", - yellow = "#FBBF24", - green = "#9CCC65", - green1 = "#00BFA5", - teal = "#1abc9c", - red = "#f7768e", - red1 = "#db4b4b", - diff = { change = "#394b70", add = "#164846", delete = "#823c41" }, - git = { change = "#6183bb", add = "#449dab", delete = "#914c54" }, - } - else - colors = { - none = "NONE", - bg_dark = "#1f2335", - bg = "#24283b", - bg_highlight = "#292e42", - terminal_black = "#414868", - fg = "#c0caf5", - fg_dark = "#a9b1d6", - fg_gutter = "#3b4261", - dark3 = "#545c7e", - comment = "#565f89", - dark5 = "#737aa2", - blue0 = "#3d59a1", - blue = "#7aa2f7", - cyan = "#7dcfff", - blue1 = "#2ac3de", - blue2 = "#0db9d7", - blue5 = "#89ddff", - blue6 = "#B4F9F8", - magenta = "#bb9af7", - purple = "#9d7cd8", - orange = "#ff9e64", - yellow = "#e0af68", - green = "#9ece6a", - green1 = "#73daca", - teal = "#1abc9c", - red = "#f7768e", - red1 = "#db4b4b", - diff = { change = "#394b70", add = "#164846", delete = "#823c41" }, - git = { change = "#6183bb", add = "#449dab", delete = "#f7768e" }, - } - if config.style == "night" then colors.bg = "#1a1b26" end - end + colors = { + none = "NONE", + bg_dark = "#1f2335", + bg = "#24283b", + bg_highlight = "#292e42", + terminal_black = "#414868", + fg = "#c0caf5", + fg_dark = "#a9b1d6", + fg_gutter = "#3b4261", + dark3 = "#545c7e", + comment = "#565f89", + dark5 = "#737aa2", + blue0 = "#3d59a1", + blue = "#7aa2f7", + cyan = "#7dcfff", + blue1 = "#2ac3de", + blue2 = "#0db9d7", + blue5 = "#89ddff", + blue6 = "#B4F9F8", + magenta = "#bb9af7", + purple = "#9d7cd8", + orange = "#ff9e64", + yellow = "#e0af68", + green = "#9ece6a", + green1 = "#73daca", + teal = "#1abc9c", + red = "#f7768e", + red1 = "#db4b4b", + diff = { change = "#394b70", add = "#164846", delete = "#823c41" }, + git = { change = "#6183bb", add = "#449dab", delete = "#f7768e" }, + } + if config.style == "night" or config.style == "day" then colors.bg = "#1a1b26" end util.bg = colors.bg colors.git.ignore = colors.dark3 @@ -101,6 +68,7 @@ function M.setup(config) colors.hint = colors.teal util.color_overrides(colors, config) + return colors end diff --git a/lua/tokyonight/hsluv.lua b/lua/tokyonight/hsluv.lua new file mode 100644 index 0000000..636ee22 --- /dev/null +++ b/lua/tokyonight/hsluv.lua @@ -0,0 +1,291 @@ +--[[ +Lua implementation of HSLuv and HPLuv color spaces +Homepage: http://www.hsluv.org/ + +Copyright (C) 2019 Alexei Boronine + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] local hsluv = {} + +local hexChars = "0123456789abcdef" + +local distance_line_from_origin = function(line) + return math.abs(line.intercept) / math.sqrt((line.slope ^ 2) + 1) +end + +local length_of_ray_until_intersect = function(theta, line) + return line.intercept / (math.sin(theta) - line.slope * math.cos(theta)) +end + +hsluv.get_bounds = function(l) + local result = {}; + local sub2; + local sub1 = ((l + 16) ^ 3) / 1560896; + if sub1 > hsluv.epsilon then + sub2 = sub1; + else + sub2 = l / hsluv.kappa; + end + + for i = 1, 3 do + local m1 = hsluv.m[i][1]; + local m2 = hsluv.m[i][2]; + local m3 = hsluv.m[i][3]; + + for t = 0, 1 do + local top1 = (284517 * m1 - 94839 * m3) * sub2; + local top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * l * sub2 - 769860 * t * l; + local bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t; + table.insert(result, { slope = top1 / bottom, intercept = top2 / bottom }) + end + end + return result +end + +hsluv.max_safe_chroma_for_l = function(l) + local bounds = hsluv.get_bounds(l); + local min = 1.7976931348623157e+308; + + for i = 1, 6 do + local length = distance_line_from_origin(bounds[i]); + if length >= 0 then min = math.min(min, length); end + end + return min; +end + +hsluv.max_safe_chroma_for_lh = function(l, h) + local hrad = h / 360 * math.pi * 2; + local bounds = hsluv.get_bounds(l); + local min = 1.7976931348623157e+308; + + for i = 1, 6 do + local bound = bounds[i]; + local length = length_of_ray_until_intersect(hrad, bound); + if length >= 0 then min = math.min(min, length); end + end + return min +end + +hsluv.dot_product = function(a, b) + local sum = 0; + for i = 1, 3 do sum = sum + a[i] * b[i]; end + return sum +end + +hsluv.from_linear = function(c) + if c <= 0.0031308 then + return 12.92 * c + else + return 1.055 * (c ^ 0.416666666666666685) - 0.055 + end +end + +hsluv.to_linear = function(c) + if c > 0.04045 then + return ((c + 0.055) / 1.055) ^ 2.4 + else + return c / 12.92 + end +end + +hsluv.xyz_to_rgb = function(tuple) + return { + hsluv.from_linear(hsluv.dot_product(hsluv.m[1], tuple)), + hsluv.from_linear(hsluv.dot_product(hsluv.m[2], tuple)), + hsluv.from_linear(hsluv.dot_product(hsluv.m[3], tuple)), + } +end + +hsluv.rgb_to_xyz = function(tuple) + local rgbl = { hsluv.to_linear(tuple[1]), hsluv.to_linear(tuple[2]), hsluv.to_linear(tuple[3]) }; + return { + hsluv.dot_product(hsluv.minv[1], rgbl), + hsluv.dot_product(hsluv.minv[2], rgbl), + hsluv.dot_product(hsluv.minv[3], rgbl), + } +end + +hsluv.y_to_l = function(Y) + if Y <= hsluv.epsilon then + return Y / hsluv.refY * hsluv.kappa + else + return 116 * ((Y / hsluv.refY) ^ 0.333333333333333315) - 16 + end +end + +hsluv.l_to_y = function(L) + if L <= 8 then + return hsluv.refY * L / hsluv.kappa + else + return hsluv.refY * (((L + 16) / 116) ^ 3) + end +end + +hsluv.xyz_to_luv = function(tuple) + local X = tuple[1]; + local Y = tuple[2]; + local divider = X + 15 * Y + 3 * tuple[3]; + local varU = 4 * X; + local varV = 9 * Y; + if divider ~= 0 then + varU = varU / divider; + varV = varV / divider; + else + varU = 0; + varV = 0; + end + local L = hsluv.y_to_l(Y); + if L == 0 then return { 0, 0, 0 } end + return { L, 13 * L * (varU - hsluv.refU), 13 * L * (varV - hsluv.refV) } +end + +hsluv.luv_to_xyz = function(tuple) + local L = tuple[1]; + local U = tuple[2]; + local V = tuple[3]; + if L == 0 then return { 0, 0, 0 } end + local varU = U / (13 * L) + hsluv.refU; + local varV = V / (13 * L) + hsluv.refV; + local Y = hsluv.l_to_y(L); + local X = 0 - (9 * Y * varU) / ((((varU - 4) * varV) - varU * varV)); + return { X, Y, (9 * Y - 15 * varV * Y - varV * X) / (3 * varV) } +end + +hsluv.luv_to_lch = function(tuple) + local L = tuple[1]; + local U = tuple[2]; + local V = tuple[3]; + local C = math.sqrt(U * U + V * V); + local H + if C < 0.00000001 then + H = 0; + else + H = math.atan2(V, U) * 180.0 / 3.1415926535897932; + if H < 0 then H = 360 + H; end + end + return { L, C, H } +end + +hsluv.lch_to_luv = function(tuple) + local L = tuple[1]; + local C = tuple[2]; + local Hrad = tuple[3] / 360.0 * 2 * math.pi; + return { L, math.cos(Hrad) * C, math.sin(Hrad) * C }; +end + +hsluv.hsluv_to_lch = function(tuple) + local H = tuple[1]; + local S = tuple[2]; + local L = tuple[3]; + if L > 99.9999999 then return { 100, 0, H } end + if L < 0.00000001 then return { 0, 0, H } end + return { L, hsluv.max_safe_chroma_for_lh(L, H) / 100 * S, H } +end + +hsluv.lch_to_hsluv = function(tuple) + local L = tuple[1]; + local C = tuple[2]; + local H = tuple[3]; + local max_chroma = hsluv.max_safe_chroma_for_lh(L, H) + if L > 99.9999999 then return { H, 0, 100 } end + if L < 0.00000001 then return { H, 0, 0 } end + + return { H, C / max_chroma * 100, L } +end + +hsluv.hpluv_to_lch = function(tuple) + local H = tuple[1]; + local S = tuple[2]; + local L = tuple[3]; + if L > 99.9999999 then return { 100, 0, H } end + if L < 0.00000001 then return { 0, 0, H } end + return { L, hsluv.max_safe_chroma_for_l(L) / 100 * S, H } +end + +hsluv.lch_to_hpluv = function(tuple) + local L = tuple[1]; + local C = tuple[2]; + local H = tuple[3]; + if L > 99.9999999 then return { H, 0, 100 } end + if L < 0.00000001 then return { H, 0, 0 } end + return { H, C / hsluv.max_safe_chroma_for_l(L) * 100, L } +end + +hsluv.rgb_to_hex = function(tuple) + local h = "#"; + for i = 1, 3 do + local c = math.floor(tuple[i] * 255 + 0.5); + local digit2 = math.fmod(c, 16); + local x = (c - digit2) / 16; + local digit1 = math.floor(x); + h = h .. string.sub(hexChars, digit1 + 1, digit1 + 1) + h = h .. string.sub(hexChars, digit2 + 1, digit2 + 1) + end + return h +end + +hsluv.hex_to_rgb = function(hex) + hex = string.lower(hex) + local ret = {} + for i = 0, 2 do + local char1 = string.sub(hex, i * 2 + 2, i * 2 + 2); + local char2 = string.sub(hex, i * 2 + 3, i * 2 + 3); + local digit1 = string.find(hexChars, char1) - 1 + local digit2 = string.find(hexChars, char2) - 1 + ret[i + 1] = (digit1 * 16 + digit2) / 255.0; + end + return ret +end + +hsluv.lch_to_rgb = function(tuple) return + hsluv.xyz_to_rgb(hsluv.luv_to_xyz(hsluv.lch_to_luv(tuple))) end + +hsluv.rgb_to_lch = function(tuple) return + hsluv.luv_to_lch(hsluv.xyz_to_luv(hsluv.rgb_to_xyz(tuple))) end + +hsluv.hsluv_to_rgb = function(tuple) return hsluv.lch_to_rgb(hsluv.hsluv_to_lch(tuple)) end + +hsluv.rgb_to_hsluv = function(tuple) return hsluv.lch_to_hsluv(hsluv.rgb_to_lch(tuple)) end + +hsluv.hpluv_to_rgb = function(tuple) return hsluv.lch_to_rgb(hsluv.hpluv_to_lch(tuple)) end + +hsluv.rgb_to_hpluv = function(tuple) return hsluv.lch_to_hpluv(hsluv.rgb_to_lch(tuple)) end + +hsluv.hsluv_to_hex = function(tuple) return hsluv.rgb_to_hex(hsluv.hsluv_to_rgb(tuple)) end + +hsluv.hpluv_to_hex = function(tuple) return hsluv.rgb_to_hex(hsluv.hpluv_to_rgb(tuple)) end + +hsluv.hex_to_hsluv = function(s) return hsluv.rgb_to_hsluv(hsluv.hex_to_rgb(s)) end + +hsluv.hex_to_hpluv = function(s) return hsluv.rgb_to_hpluv(hsluv.hex_to_rgb(s)) end + +hsluv.m = { + { 3.240969941904521, -1.537383177570093, -0.498610760293 }, + { -0.96924363628087, 1.87596750150772, 0.041555057407175 }, + { 0.055630079696993, -0.20397695888897, 1.056971514242878 }, +} +hsluv.minv = { + { 0.41239079926595, 0.35758433938387, 0.18048078840183 }, + { 0.21263900587151, 0.71516867876775, 0.072192315360733 }, + { 0.019330818715591, 0.11919477979462, 0.95053215224966 }, +} +hsluv.refY = 1.0 +hsluv.refU = 0.19783000664283 +hsluv.refV = 0.46831999493879 +hsluv.kappa = 903.2962962 +hsluv.epsilon = 0.0088564516 + +return hsluv diff --git a/lua/tokyonight/util.lua b/lua/tokyonight/util.lua index ebb4d15..7f7c1d6 100644 --- a/lua/tokyonight/util.lua +++ b/lua/tokyonight/util.lua @@ -1,6 +1,7 @@ local util = {} util.colorsUsed = {} +util.colorCache = {} util.bg = "#000000" util.fg = "#ffffff" @@ -34,15 +35,32 @@ end function util.darken(hex, amount, bg) return util.blend(hex, bg or util.bg, math.abs(amount)) end function util.lighten(hex, amount, fg) return util.blend(hex, fg or util.fg, math.abs(amount)) end -function util.highlight(group, color) +function util.getDayColor(color) + local hsluv = require("tokyonight.hsluv") + if color ~= "NONE" then + local hsl = hsluv.hex_to_hsluv(color) + hsl[3] = 100 - hsl[3] + hsl[3] = hsl[3] + (100 - hsl[3]) * .1 + return hsluv.hsluv_to_hex(hsl) + end + return color +end + +function util.getColor(color, config) + if config.style ~= "day" then return color end + if not util.colorCache[color] then util.colorCache[color] = util.getDayColor(color) end + return util.colorCache[color] +end + +function util.highlight(group, color, opts) if color.fg then util.colorsUsed[color.fg] = true end if color.bg then util.colorsUsed[color.bg] = true end if color.sp then util.colorsUsed[color.sp] = true end local style = color.style and "gui=" .. color.style or "gui=NONE" - local fg = color.fg and "guifg=" .. color.fg or "guifg=NONE" - local bg = color.bg and "guibg=" .. color.bg or "guibg=NONE" - local sp = color.sp and "guisp=" .. color.sp or "" + local fg = color.fg and "guifg=" .. util.getColor(color.fg, opts) or "guifg=NONE" + local bg = color.bg and "guibg=" .. util.getColor(color.bg, opts) or "guibg=NONE" + local sp = color.sp and "guisp=" .. util.getColor(color.sp, opts) or "" local hl = "highlight " .. group .. " " .. style .. " " .. fg .. " " .. bg .. " " .. sp @@ -99,7 +117,9 @@ function util.template(str, table) return (str:gsub("($%b{})", function(w) return table[w:sub(3, -2)] or w end)) end -function util.syntax(syntax) for group, colors in pairs(syntax) do util.highlight(group, colors) end end +function util.syntax(syntax, opts) + for group, colors in pairs(syntax) do util.highlight(group, colors, opts) end +end ---@param colors ColorScheme function util.terminal(colors) @@ -140,13 +160,13 @@ function util.load(theme) vim.g.colors_name = "tokyonight" -- load base theme - util.syntax(theme.base) + util.syntax(theme.base, theme.config) -- load syntax for plugins and terminal async local async async = vim.loop.new_async(vim.schedule_wrap(function() util.terminal(theme.colors) - util.syntax(theme.plugins) + util.syntax(theme.plugins, theme.config) util.autocmds(theme.config) async:close() end)) @@ -172,5 +192,25 @@ function util.color_overrides(colors, config) end end +function util.light() + for hl_name, hl in pairs(vim.api.nvim__get_hl_defs(0)) do + local def = {} + for key, def_key in pairs({ foreground = "fg", background = "bg", special = "sp" }) do + if type(hl[key]) == "number" then + local hex = string.format("#%06x", hl[key]) + local color = util.getDayColor(hex) + table.insert(def, "gui" .. def_key .. "=" .. color) + end + end + if hl_name ~= "" and #def > 0 then + for _, style in pairs({ "bold", "italic", "underline", "undercurl" }) do + if hl[style] then table.insert(def, "gui=" .. style) end + end + + vim.cmd("highlight! " .. hl_name .. " " .. table.concat(def, " ")) + end + end +end + return util