commit 694515c168ad17b1609ed438ab356e7b71852806 Author: Henrik Hautakoski Date: Sun May 17 16:26:31 2026 +0200 Initial Commit diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..2a4b485 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "runtime.version": "LuaJIT", + "runtime.path": ["lua/?.lua", "lua/?/init.lua"], + "diagnostics.globals": [ + "vim", + "ipairs", + "require", + "pcall", + "pairs", + "type", + "unpack", + "table", + "string", + "os", + "tostring", + ], + "workspace.checkThirdParty": false, + "workspace.library": [ "$VIMRUNTIME" ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..d30d966 --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# skift.nvim + +A clean, dark Neovim colorscheme with strong syntax contrast, tasteful UI accents, and built-in support for common plugins. + +## Features + +- Thoughtful palette built from HSL tokens +- Consistent editor, Treesitter, and LSP highlight groups +- Integrations for popular plugins (Telescope, cmp, blink.cmp, Neo-tree, GitSigns, and more) +- Optional transparent backgrounds +- Config hooks to override colors and highlight groups +- Included lualine theme + +## Requirements + +- Neovim `>= 0.8` + +## Installation + +### lazy.nvim + +```lua +{ + "https://codeberg.org/pnx/skift.nvim", + lazy = false, + priority = 1000, + config = function() + require("skift").setup({ + transparent = false, + bold = true, + italic = true, + italic_comments = true, + }) + vim.cmd.colorscheme("skift") + end, +} +``` + +### packer.nvim + +```lua +use({ + "https://codeberg.org/pnx/skift.nvim", + config = function() + require("skift").setup() + vim.cmd.colorscheme("skift") + end, +}) +``` + +## Configuration + +Default config: + +```lua +require("skift").setup({ + transparent = false, + bold = true, + italic = true, + italic_comments = true, + on_colors = function(colors) + -- colors.accent = "#7aa2f7" + end, + on_highlights = function(highlights, colors) + -- highlights.CursorLine = { bg = colors.bg_elevated } + end, +}) +``` + +## Usage + +```vim +:colorscheme skift +``` + +Lua equivalent: + +```lua +vim.cmd.colorscheme("skift") +``` + +## Lualine + +Use the bundled lualine theme: + +```lua +require("lualine").setup({ + options = { + theme = "skift", + }, +}) +``` + +## Cache + +`skift.nvim` caches resolved highlights for faster startup. If you are iterating on the theme and want to clear cache: + +```lua +require("skift").clear_cache() +``` + +## Plugin Integrations + +Skift includes highlight definitions for: + +- GitSigns +- Telescope +- nvim-cmp +- blink.cmp +- indent-blankline (`Ibl*`) +- which-key +- Trouble +- nvim-tree +- Neo-tree +- Lazy +- Mason +- Noice +- nvim-notify +- mini.nvim statusline groups +- Snacks +- Flash +- Treesitter context +- Dashboard/Alpha +- render-markdown +- Oil +- fzf-lua + +## Contributing + +Issues and pull requests are welcome. diff --git a/after/queries/php_only/highlights.scm b/after/queries/php_only/highlights.scm new file mode 100644 index 0000000..5be633c --- /dev/null +++ b/after/queries/php_only/highlights.scm @@ -0,0 +1,46 @@ +; extends + +; NOTE: Some LSPs sets semantic tokens, so override those with high priority (150) + +; php tags should be tagged as preprocessor tokens +(php_tag) @preproc + +; Tag names in class definition +(class_declaration + (name) @class.declaration.name + (#set! priority 150)) + +(class_declaration + (base_clause (name) @class.declaration.inherit.name) + (#set! priority 150)) + +(class_declaration + (class_interface_clause (name) @class.declaration.implement.name) + (#set! priority 150)) + +(interface_declaration (name) + @interface.declaration.name + (#set! priority 150)) + +; semantic tokens sometimes targets the entire namespace. So tag it here to override. +(qualified_name [ (namespace_name) "\\"] + @class.use.base (#set! priority 150)) + +; last name node in namespace definition +(namespace_definition + name: (namespace_name + (name) @namespace.name (#set! priority 150) .) +) + +; tag trait name in use declaration +(use_declaration (name) @use.trait.name) + +; tag name token in "use" use namespace declaration +(namespace_use_declaration (namespace_use_clause [ + (qualified_name [ + (name) @namespace.name (#set! priority 150) + ]) + alias: (name) @namespace.alias.name (#set! priority 150) +])) + + diff --git a/colors/skift.lua b/colors/skift.lua new file mode 100644 index 0000000..d472ced --- /dev/null +++ b/colors/skift.lua @@ -0,0 +1 @@ +require("skift").load() diff --git a/lua/lualine/themes/skift.lua b/lua/lualine/themes/skift.lua new file mode 100644 index 0000000..501aa3a --- /dev/null +++ b/lua/lualine/themes/skift.lua @@ -0,0 +1,26 @@ +local c = require("skift.colors") + +return { + normal = { + a = { fg = c.fg, bg = c.bg_float, gui = "bold" }, + b = { fg = c.fg, bg = c.bg_float }, + c = { fg = c.fg, bg = c.bg_float }, + }, + insert = { + a = { fg = c.bg, bg = c.accent, gui = "bold" }, + }, + visual = { + a = { fg = c.bg, bg = c.purple, gui = "bold" }, + }, + replace = { + a = { fg = c.bg, bg = c.rose, gui = "bold" }, + }, + command = { + a = { fg = c.bg, bg = c.yellow, gui = "bold" }, + }, + inactive = { + a = { fg = c.fg_dim, bg = c.bg_float }, + b = { fg = c.fg_dim, bg = c.bg_float }, + c = { fg = c.fg_dim, bg = c.bg_float }, + } +} diff --git a/lua/skift/colors.lua b/lua/skift/colors.lua new file mode 100644 index 0000000..0b46f10 --- /dev/null +++ b/lua/skift/colors.lua @@ -0,0 +1,126 @@ +local resolve = require('skift.utils.resolve_color_vars') +local hsl = require('skift.utils.hsl').hslToHex + +--- @class Colors +local colors = { + -- Base colors + slate50 = hsl(213, 27, 94), + slate100 = hsl(233, 18, 54), + slate200 = hsl(228, 28, 32), + slate300 = hsl(228, 28, 27), + slate500 = hsl(228, 28, 14), + slate700 = hsl(228, 28, 8), --base + slate800 = hsl(228, 28, 7), + slate900 = hsl(228, 28, 6), + + -- Color tokens + black = "slate900", + white = "slate50", + blue = hsl(217, 92, 76), + sky = hsl(227, 92, 40), + green = hsl(115, 54, 76), + red = hsl(343, 81, 75), + yellow = hsl(41, 86, 83), + orange = hsl(23, 92, 75), + purple = hsl(267, 84, 81), + rose = hsl(10, 56, 91), + lavender = hsl(232, 97, 85), + cyan = hsl(199, 76, 69), + teal = hsl(170, 57, 73), + + -- Dimmed color tokens + red_dim = hsl(328, 22, 25), + orange_dim = hsl(15, 18, 27), + yellow_dim = hsl(37, 9, 29), + green_dim = hsl(140, 14, 26), + blue_dim = hsl(230, 19, 29), + cyan_dim = hsl(205, 32, 25), + + -- Backgrounds + bg = "slate700", + bg_dim = "slate800", + bg_float = "slate900", + bg_elevated = "slate900", + bg_cursorline = "slate500", + bg_selection = "slate200", + bg_search = "accent", + + -- Foregrounds + fg = "slate50", + fg_dim = "slate300", + fg_muted = "slate500", + fg_bright = "slate50", -- Remove? + fg_comment = "slate100", + fg_gutter = "slate200", + fg_gutter_active = "lavender", + + -- Syntax + keyword = "yellow", + func = "fg", + string = "lavender", + number = "orange", + type = "blue", + constant = "orange", + variable = "green", + property = "white", + operator = "rose", + special = "purple", + tag = "cyan", + regex = "lavender", + + -- UI + accent = "blue", + border = "slate500", + match_paren = "orange", + guide = "slate500", + guide_active = "#282e3c", + nontext = "fg_muted", + + -- Diagnostics + error = "red", + warning = "yellow", + info = "blue", + hint = "lavender", + ok = "green", + + -- Diff + diff_add = "green", + diff_change = "yellow", + diff_delete = "red", + diff_text = "fg_dim", + + -- Diff backgrounds + diff_add_bg = "green_dim", + diff_change_bg = "orange_dim", + diff_delete_bg = "red_dim", + diff_text_bg = "fg_dim", + + -- Git + git_add = "diff_add", + git_change = "diff_change", + git_delete = "diff_delete", + git_ignore = "diff_text", + + -- Terminal + terminal_black = "black", + terminal_red = "red", + terminal_green = "green", + terminal_yellow = "yellow", + terminal_blue = "blue", + terminal_magenta = "purple", + terminal_cyan = "cyan", + terminal_white = "white", + + terminal_bright_black = "black", + terminal_bright_red = "red", + terminal_bright_green = "green", + terminal_bright_yellow = "yellow", + terminal_bright_blue = "blue", + terminal_bright_magenta = "purple", + terminal_bright_cyan = "cyan", + terminal_bright_white = "white", + + none = "NONE", +} + +return resolve(colors) diff --git a/lua/skift/config.lua b/lua/skift/config.lua new file mode 100644 index 0000000..46da721 --- /dev/null +++ b/lua/skift/config.lua @@ -0,0 +1,22 @@ +---@class Config +local defaults = { + transparent = false, + bold = true, + italic = true, + italic_comments = true, + on_colors = function(colors) end, + on_highlights = function(highlights, colors) end, +} + +---@return Config +local config = {} + +function config:merge(other) + for key, value in pairs(other or {}) do + self[key] = value + end +end + +return setmetatable(config, { + __index = defaults, +}) diff --git a/lua/skift/groups/editor.lua b/lua/skift/groups/editor.lua new file mode 100644 index 0000000..07fd2f0 --- /dev/null +++ b/lua/skift/groups/editor.lua @@ -0,0 +1,79 @@ +local M = {} + +---@param c Colors +function M.get(c) + return { + Normal = { fg = c.fg, bg = c.bg }, + NormalNC = { fg = c.fg, bg = c.bg }, + NormalFloat = { fg = c.fg, bg = c.bg_float }, + FloatBorder = { fg = c.border, bg = c.bg_float }, + FloatTitle = { fg = c.accent, bg = c.bg_float, bold = true }, + WinBar = { fg = c.fg_dim, bg = c.none }, + WinBarNC = { fg = c.fg_gutter, bg = c.none }, + + Cursor = { fg = c.bg, bg = c.fg }, + lCursor = { link = "Cursor" }, + CursorIM = { link = "Cursor" }, + TermCursor = { link = "Cursor" }, + TermCursorNC = { fg = c.bg, bg = c.fg_dim }, + CursorLine = { bg = c.bg_cursorline }, + CursorColumn = { link = "CursorLine" }, + + LineNr = { fg = c.fg_gutter }, + CursorLineNr = { fg = c.fg_gutter_active, bold = true }, + SignColumn = { fg = c.fg_gutter, bg = c.none }, + FoldColumn = { fg = c.fg_gutter, bg = c.none }, + + Search = { fg = c.fg_bright, bg = c.bg_search }, + IncSearch = { fg = c.bg, bg = c.bg_search }, + CurSearch = { link = "IncSearch" }, + Substitute = { fg = c.bg, bg = c.operator }, + + Visual = { bg = c.bg_selection }, + VisualNOS = { link = "Visual" }, + + StatusLine = { fg = c.fg, bg = c.bg_float }, + StatusLineNC = { fg = c.fg_dim, bg = c.bg_float }, + WinSeparator = { fg = c.border }, + VertSplit = { link = "WinSeparator" }, + + TabLine = { fg = c.fg_dim, bg = c.bg_float }, + TabLineFill = { bg = c.bg_float }, + TabLineSel = { fg = c.fg_bright, bg = c.bg, bold = true }, + + Pmenu = { fg = c.fg, bg = c.bg_float }, + PmenuSel = { fg = c.fg_bright, bg = c.bg_selection }, + PmenuSbar = { bg = c.bg_elevated }, + PmenuThumb = { bg = c.fg_gutter }, + PmenuKind = { fg = c.keyword, bg = c.bg_float }, + PmenuKindSel = { fg = c.keyword, bg = c.bg_selection }, + PmenuExtra = { fg = c.fg_dim, bg = c.bg_float }, + PmenuExtraSel = { fg = c.fg_dim, bg = c.bg_selection }, + + Folded = { fg = c.fg_comment, bg = c.none }, + + NonText = { fg = c.nontext }, + SpecialKey = { link = "NonText" }, + Whitespace = { link = "NonText" }, + EndOfBuffer = { link = "NonText" }, + MatchParen = { fg = c.match_paren, bold = true }, + ModeMsg = { fg = c.fg_bright, bold = true }, + MsgArea = { fg = c.fg }, + MoreMsg = { fg = c.accent }, + Question = { fg = c.accent }, + Directory = { fg = c.accent }, + Title = { fg = c.accent, bold = true }, + ErrorMsg = { fg = c.error }, + WarningMsg = { fg = c.warning }, + Conceal = { fg = c.fg_dim }, + SpellBad = { sp = c.error, undercurl = true }, + SpellCap = { sp = c.warning, undercurl = true }, + SpellLocal = { sp = c.info, undercurl = true }, + SpellRare = { sp = c.hint, undercurl = true }, + ColorColumn = { bg = c.bg_cursorline }, + QuickFixLine = { bg = c.bg_selection, bold = true }, + WildMenu = { link = "PmenuSel" }, + } +end + +return M diff --git a/lua/skift/groups/integrations.lua b/lua/skift/groups/integrations.lua new file mode 100644 index 0000000..3bfa59b --- /dev/null +++ b/lua/skift/groups/integrations.lua @@ -0,0 +1,241 @@ +local M = {} + +---@param c Colors +function M.get(c) + return { + -- Git signs + GitSignsAdd = { fg = c.git_add }, + GitSignsChange = { fg = c.git_change }, + GitSignsDelete = { fg = c.git_delete }, + + -- Diff + DiffAdd = { bg = c.diff_add_bg }, + DiffChange = { bg = c.diff_change_bg }, + DiffDelete = { bg = c.diff_delete_bg }, + DiffText = { bg = c.diff_text_bg }, + Added = { fg = c.git_add }, + Changed = { fg = c.git_change }, + Removed = { fg = c.git_delete }, + + -- Telescope + TelescopeBorder = { fg = c.border, bg = c.bg_float }, + TelescopeNormal = { fg = c.fg, bg = c.bg_float }, + TelescopePromptBorder = { fg = c.border, bg = c.bg_float }, + TelescopePromptNormal = { fg = c.fg, bg = c.bg_float }, + TelescopePromptPrefix = { fg = c.accent }, + TelescopePromptTitle = { fg = c.accent, bold = true }, + TelescopePreviewTitle = { fg = c.string, bold = true }, + TelescopeResultsTitle = { fg = c.keyword, bold = true }, + TelescopeSelection = { bg = c.bg_selection }, + TelescopeMatching = { fg = c.accent, bold = true }, + + -- nvim-cmp + CmpItemAbbr = { fg = c.fg }, + CmpItemAbbrDeprecated = { fg = c.fg_gutter, strikethrough = true }, + CmpItemAbbrMatch = { fg = c.accent, bold = true }, + CmpItemAbbrMatchFuzzy = { fg = c.accent, bold = true }, + CmpItemMenu = { fg = c.fg_comment }, + CmpItemKindDefault = { fg = c.fg_dim }, + CmpItemKindKeyword = { fg = c.keyword }, + CmpItemKindVariable = { fg = c.variable }, + CmpItemKindConstant = { fg = c.constant }, + CmpItemKindReference = { fg = c.variable }, + CmpItemKindValue = { fg = c.constant }, + CmpItemKindFunction = { fg = c.func }, + CmpItemKindMethod = { fg = c.func }, + CmpItemKindConstructor = { fg = c.type }, + CmpItemKindClass = { fg = c.type }, + CmpItemKindInterface = { fg = c.type }, + CmpItemKindStruct = { fg = c.type }, + CmpItemKindEvent = { fg = c.keyword }, + CmpItemKindEnum = { fg = c.type }, + CmpItemKindUnit = { fg = c.constant }, + CmpItemKindModule = { fg = c.fg_dim }, + CmpItemKindProperty = { fg = c.operator }, + CmpItemKindField = { fg = c.operator }, + CmpItemKindTypeParameter = { fg = c.type }, + CmpItemKindEnumMember = { fg = c.constant }, + CmpItemKindOperator = { fg = c.operator }, + CmpItemKindSnippet = { fg = c.special }, + CmpItemKindText = { fg = c.fg }, + CmpItemKindFile = { fg = c.fg }, + CmpItemKindFolder = { fg = c.accent }, + CmpItemKindColor = { fg = c.special }, + + -- blink.cmp + BlinkCmpMenu = { fg = c.fg, bg = c.bg_float }, + BlinkCmpMenuBorder = { fg = c.border, bg = c.bg_float }, + BlinkCmpMenuSelection = { bg = c.bg_selection }, + BlinkCmpLabel = { fg = c.fg }, + BlinkCmpLabelMatch = { fg = c.accent, bold = true }, + BlinkCmpLabelDeprecated = { fg = c.fg_gutter, strikethrough = true }, + BlinkCmpKind = { fg = c.fg_dim }, + BlinkCmpKindFunction = { fg = c.func }, + BlinkCmpKindMethod = { fg = c.func }, + BlinkCmpKindConstructor = { fg = c.type }, + BlinkCmpKindClass = { fg = c.type }, + BlinkCmpKindStruct = { fg = c.type }, + BlinkCmpKindInterface = { fg = c.type }, + BlinkCmpKindModule = { fg = c.fg_dim }, + BlinkCmpKindProperty = { fg = c.operator }, + BlinkCmpKindField = { fg = c.operator }, + BlinkCmpKindVariable = { fg = c.variable }, + BlinkCmpKindKeyword = { fg = c.keyword }, + BlinkCmpKindSnippet = { fg = c.special }, + BlinkCmpKindText = { fg = c.fg }, + BlinkCmpKindValue = { fg = c.constant }, + BlinkCmpKindEnum = { fg = c.type }, + BlinkCmpKindColor = { fg = c.special }, + BlinkCmpKindFile = { fg = c.fg }, + BlinkCmpKindFolder = { fg = c.accent }, + BlinkCmpKindConstant = { fg = c.constant }, + BlinkCmpKindTypeParameter = { fg = c.type }, + BlinkCmpKindOperator = { fg = c.operator }, + BlinkCmpDoc = { fg = c.fg, bg = c.bg_float }, + BlinkCmpDocBorder = { fg = c.border, bg = c.bg_float }, + + -- Indent blankline + IblIndent = { fg = c.guide }, + IblScope = { fg = c.guide_active }, + IblWhitespace = { fg = c.guide }, + + -- Which-key + WhichKey = { fg = c.accent }, + WhichKeyGroup = { fg = c.keyword }, + WhichKeyDesc = { fg = c.fg }, + WhichKeySeparator = { fg = c.fg_comment }, + WhichKeyValue = { fg = c.fg_dim }, + WhichKeyBorder = { fg = c.border }, + + -- Trouble + TroubleText = { fg = c.fg }, + TroubleCount = { fg = c.accent, bold = true }, + TroubleNormal = { fg = c.fg, bg = c.bg_float }, + + -- Nvim-tree + NvimTreeNormal = { fg = c.fg, bg = c.bg_dim }, + NvimTreeRootFolder = { fg = c.accent, bold = true }, + NvimTreeFolderName = { fg = c.accent }, + NvimTreeFolderIcon = { link = "NvimTreeFolderName" }, + NvimTreeOpenedFolderName = { link = "NvimTreeFolderName" }, + NvimTreeOpenedHL = { fg = c.accent }, + NvimTreeGitDirty = { fg = c.git_change }, + NvimTreeGitNew = { fg = c.fg_dim }, + NvimTreeGitDeleted = { fg = c.git_delete }, + NvimTreeGitIgnored = { fg = c.git_ignore }, + NvimTreeGitStaged = { fg = c.git_add }, + NvimTreeSpecialFile = { fg = c.special }, + NvimTreeIndentMarker = { fg = c.guide }, + NvimTreeWinSeparator = { fg = c.border, bg = c.bg_dim }, + + -- Neo-tree + NeoTreeNormal = { fg = c.fg, bg = c.bg_float }, + NeoTreeNormalNC = { fg = c.fg, bg = c.bg_float }, + NeoTreeDimText = { fg = c.fg_dim }, + NeoTreeRootName = { fg = c.accent, bold = true }, + NeoTreeDirectoryName = { fg = c.accent }, + NeoTreeDirectoryIcon = { fg = c.accent }, + NeoTreeGitAdded = { fg = c.git_add }, + NeoTreeGitModified = { fg = c.git_change }, + NeoTreeGitDeleted = { fg = c.git_delete }, + NeoTreeGitIgnored = { fg = c.git_ignore }, + NeoTreeIndentMarker = { fg = c.guide }, + NeoTreeWinSeparator = { fg = c.border }, + + -- Lazy + LazyButton = { fg = c.fg, bg = c.bg_elevated }, + LazyButtonActive = { fg = c.fg_bright, bg = c.bg_selection, bold = true }, + LazyH1 = { fg = c.bg, bg = c.accent, bold = true }, + LazyProgressDone = { fg = c.accent }, + LazyProgressTodo = { fg = c.fg_gutter }, + LazySpecial = { fg = c.accent }, + + -- Mason + MasonHeader = { fg = c.bg, bg = c.accent, bold = true }, + MasonHighlight = { fg = c.accent }, + MasonHighlightBlock = { fg = c.bg, bg = c.accent }, + MasonMutedBlock = { fg = c.fg, bg = c.bg_elevated }, + + -- Noice + NoiceCmdlinePopup = { fg = c.fg, bg = c.bg_float }, + NoiceCmdlinePopupBorder = { fg = c.border }, + NoiceCmdlineIcon = { fg = c.accent }, + NoiceConfirm = { fg = c.fg, bg = c.bg_float }, + NoiceConfirmBorder = { fg = c.border }, + + -- Notify + NotifyERRORBorder = { fg = c.error }, + NotifyERRORIcon = { fg = c.error }, + NotifyERRORTitle = { fg = c.error }, + NotifyWARNBorder = { fg = c.warning }, + NotifyWARNIcon = { fg = c.warning }, + NotifyWARNTitle = { fg = c.warning }, + NotifyINFOBorder = { fg = c.info }, + NotifyINFOIcon = { fg = c.info }, + NotifyINFOTitle = { fg = c.info }, + NotifyDEBUGBorder = { fg = c.fg_comment }, + NotifyDEBUGIcon = { fg = c.fg_comment }, + NotifyDEBUGTitle = { fg = c.fg_comment }, + NotifyTRACEBorder = { fg = c.keyword }, + NotifyTRACEIcon = { fg = c.keyword }, + NotifyTRACETitle = { fg = c.keyword }, + + -- Mini + MiniStatuslineModeNormal = { fg = c.bg, bg = c.accent, bold = true }, + MiniStatuslineModeInsert = { fg = c.bg, bg = c.string, bold = true }, + MiniStatuslineModeVisual = { fg = c.bg, bg = c.keyword, bold = true }, + MiniStatuslineModeReplace = { fg = c.bg, bg = c.error, bold = true }, + MiniStatuslineModeCommand = { fg = c.bg, bg = c.warning, bold = true }, + MiniStatuslineModeOther = { fg = c.bg, bg = c.special, bold = true }, + MiniStatuslineFilename = { fg = c.fg, bg = c.bg_elevated }, + MiniStatuslineFileinfo = { fg = c.fg_dim, bg = c.bg_elevated }, + MiniStatuslineDevinfo = { fg = c.fg_dim, bg = c.bg_elevated }, + MiniStatuslineInactive = { fg = c.fg_gutter, bg = c.bg_elevated }, + + -- Snacks + SnacksIndent = { fg = c.guide }, + SnacksIndentScope = { fg = c.guide_active }, + + -- Flash + FlashLabel = { fg = c.bg, bg = c.accent, bold = true }, + FlashMatch = { fg = c.fg_dim, bg = c.bg_selection }, + FlashCurrent = { fg = c.fg_bright, bg = c.bg_search }, + + -- Treesitter context + TreesitterContext = { bg = c.bg_elevated }, + TreesitterContextLineNumber = { fg = c.fg_gutter_active, bg = c.bg_elevated }, + + -- Dashboard / Alpha + DashboardHeader = { fg = c.accent }, + DashboardFooter = { fg = c.fg_comment, italic = true }, + DashboardDesc = { fg = c.fg }, + DashboardKey = { fg = c.constant }, + DashboardIcon = { fg = c.accent }, + DashboardShortCut = { fg = c.keyword }, + + -- Render-markdown + RenderMarkdownH1Bg = { fg = c.red, bg = c.red_dim }, + RenderMarkdownH2Bg = { fg = c.orange, bg = c.orange_dim }, + RenderMarkdownH3Bg = { fg = c.yellow, bg = c.yellow_dim }, + RenderMarkdownH4Bg = { fg = c.green, bg = c.green_dim }, + RenderMarkdownH5Bg = { fg = c.cyan, bg = c.cyan_dim }, + RenderMarkdownH6Bg = { fg = c.blue, bg = c.blue_dim }, + RenderMarkdownCode = { bg = c.bg_elevated }, + + -- Oil + OilDir = { fg = c.accent }, + OilDirIcon = { fg = c.accent }, + OilFile = { fg = c.fg }, + OilCreate = { fg = c.git_add }, + OilDelete = { fg = c.git_delete }, + OilMove = { fg = c.git_change }, + OilCopy = { fg = c.info }, + + -- Fzf-lua + FzfLuaBorder = { fg = c.border }, + FzfLuaTitle = { fg = c.accent, bold = true }, + FzfLuaCursorLine = { bg = c.bg_selection }, + } +end + +return M diff --git a/lua/skift/groups/lsp.lua b/lua/skift/groups/lsp.lua new file mode 100644 index 0000000..56911eb --- /dev/null +++ b/lua/skift/groups/lsp.lua @@ -0,0 +1,98 @@ +local M = {} + +---@param c Colors +function M.get(c) + return { + LspReferenceText = { bg = c.bg_selection }, + LspReferenceRead = { bg = c.bg_selection }, + LspReferenceWrite = { bg = c.bg_selection, bold = true }, + + LspCodeLens = { fg = c.fg_comment }, + LspCodeLensSeparator = { fg = c.fg_gutter }, + LspSignatureActiveParameter = { fg = c.accent, bold = true }, + LspInlayHint = { fg = c.fg_gutter, italic = true }, + + DiagnosticError = { fg = c.error }, + DiagnosticWarn = { fg = c.warning }, + DiagnosticInfo = { fg = c.info }, + DiagnosticHint = { fg = c.hint }, + DiagnosticOk = { fg = c.ok }, + + DiagnosticSignError = { fg = c.error }, + DiagnosticSignWarn = { fg = c.warning }, + DiagnosticSignInfo = { fg = c.info }, + DiagnosticSignHint = { fg = c.hint }, + DiagnosticSignOk = { fg = c.ok }, + + DiagnosticVirtualTextError = { fg = c.error, bg = c.bg_elevated }, + DiagnosticVirtualTextWarn = { fg = c.warning, bg = c.bg_elevated }, + DiagnosticVirtualTextInfo = { fg = c.info, bg = c.bg_elevated }, + DiagnosticVirtualTextHint = { fg = c.hint, bg = c.bg_elevated }, + DiagnosticVirtualTextOk = { fg = c.ok, bg = c.bg_elevated }, + + DiagnosticUnderlineError = { sp = c.error, undercurl = true }, + DiagnosticUnderlineWarn = { sp = c.warning, undercurl = true }, + DiagnosticUnderlineInfo = { sp = c.info, undercurl = true }, + DiagnosticUnderlineHint = { sp = c.hint, undercurl = true }, + DiagnosticUnderlineOk = { sp = c.ok, undercurl = true }, + + DiagnosticFloatingError = { fg = c.error }, + DiagnosticFloatingWarn = { fg = c.warning }, + DiagnosticFloatingInfo = { fg = c.info }, + DiagnosticFloatingHint = { fg = c.hint }, + DiagnosticFloatingOk = { fg = c.ok }, + + ["@lsp.type.boolean"] = { link = "@boolean" }, + ["@lsp.type.builtinType"] = { link = "@type.builtin" }, + ["@lsp.type.comment"] = { link = "@comment" }, + ["@lsp.type.decorator"] = { link = "@attribute" }, + ["@lsp.type.deriveHelper"] = { link = "@attribute" }, + ["@lsp.type.enum"] = { link = "@type" }, + ["@lsp.type.enumMember"] = { link = "@constant" }, + ["@lsp.type.escapeSequence"] = { link = "@string.escape" }, + ["@lsp.type.formatSpecifier"] = { link = "@punctuation.special" }, + ["@lsp.type.function"] = { link = "@function" }, + ["@lsp.type.generic"] = { link = "@variable" }, + ["@lsp.type.interface"] = { link = "@type" }, + ["@lsp.type.keyword"] = { link = "@keyword" }, + ["@lsp.type.lifetime"] = { link = "@keyword.modifier" }, + ["@lsp.type.macro"] = { link = "@function.macro" }, + ["@lsp.type.method"] = { link = "@function.method" }, + ["@lsp.type.namespace"] = { link = "@module" }, + ["@lsp.type.number"] = { link = "@number" }, + ["@lsp.type.operator"] = { link = "@operator" }, + ["@lsp.type.parameter"] = { link = "@variable.parameter" }, + ["@lsp.type.property"] = { link = "@property" }, + ["@lsp.type.selfKeyword"] = { link = "@variable.builtin" }, + ["@lsp.type.selfTypeKeyword"] = { link = "@variable.builtin" }, + ["@lsp.type.string"] = { link = "@string" }, + ["@lsp.type.struct"] = { link = "@type" }, + ["@lsp.type.type"] = { link = "@type" }, + ["@lsp.type.typeAlias"] = { link = "@type.definition" }, + ["@lsp.type.typeParameter"] = { link = "@type" }, + ["@lsp.type.unresolvedReference"] = { sp = c.error, undercurl = true }, + ["@lsp.type.variable"] = {}, + + ["@lsp.typemod.class.defaultLibrary"] = { link = "@type.builtin" }, + ["@lsp.typemod.enum.defaultLibrary"] = { link = "@type.builtin" }, + ["@lsp.typemod.enumMember.defaultLibrary"] = { link = "@constant.builtin" }, + ["@lsp.typemod.function.defaultLibrary"] = { link = "@function.builtin" }, + ["@lsp.typemod.keyword.async"] = { link = "@keyword.coroutine" }, + ["@lsp.typemod.keyword.injected"] = { link = "@keyword" }, + ["@lsp.typemod.macro.defaultLibrary"] = { link = "@function.builtin" }, + ["@lsp.typemod.method.defaultLibrary"] = { link = "@function.builtin" }, + ["@lsp.typemod.operator.injected"] = { link = "@operator" }, + ["@lsp.typemod.string.injected"] = { link = "@string" }, + ["@lsp.typemod.struct.defaultLibrary"] = { link = "@type.builtin" }, + ["@lsp.typemod.type.defaultLibrary"] = { link = "@type.builtin" }, + ["@lsp.typemod.typeAlias.defaultLibrary"] = { link = "@type.builtin" }, + ["@lsp.typemod.variable.callable"] = { link = "@function" }, + ["@lsp.typemod.variable.defaultLibrary"] = { link = "@variable.builtin" }, + ["@lsp.typemod.variable.injected"] = { link = "@variable" }, + ["@lsp.typemod.variable.static"] = { link = "@constant" }, + ["@lsp.typemod.property"] = { link = "@property" }, + ["@lsp.typemod.property.declaration"] = { link = "@variable" }, -- a property declaration is a variable + } +end + +return M diff --git a/lua/skift/groups/syntax.lua b/lua/skift/groups/syntax.lua new file mode 100644 index 0000000..bba3df0 --- /dev/null +++ b/lua/skift/groups/syntax.lua @@ -0,0 +1,46 @@ +local M = {} + +---@param c Colors +function M.get(c) + return { + Comment = { fg = c.fg_comment, italic = true }, + String = { fg = c.string }, + Character = { fg = c.string }, + Number = { fg = c.number }, + Float = { link = "Number" }, + Boolean = { link = "Constant" }, + Identifier = { link = "Constant" }, + Function = { link = "Normal" }, + Statement = { link = "Keyword" }, + Conditional = { link = "Keyword" }, + Repeat = { link = "Keyword" }, + Label = { fg = c.tag }, + Operator = { fg = c.operator }, + Keyword = { fg = c.keyword }, + Exception = { fg = c.error }, + PreProc = { fg = c.special }, + Include = { link = "PreProc" }, + Define = { link = "PreProc" }, + Macro = { link = "PreProc" }, + PreCondit = { link = "PreProc" }, + Type = { fg = c.type }, + StorageClass = { fg = c.keyword, italic = true }, + Structure = { fg = c.type, bold = true }, + Typedef = { link = "PreProc" }, + Constant = { fg = c.constant }, + Special = { fg = c.special }, + SpecialChar = { link = "Special" }, + Tag = { fg = c.tag }, + Delimiter = { fg = c.fg_dim }, + SpecialComment = { fg = c.constant, italic = true }, + Debug = { fg = c.hint }, + Underlined = { fg = c.accent, underline = true }, + Bold = { bold = true }, + Italic = { italic = true }, + Ignore = { fg = c.nontext }, + Error = { fg = c.error }, + Todo = { fg = c.bg, bg = c.info, bold = true }, + } +end + +return M diff --git a/lua/skift/groups/treesitter.lua b/lua/skift/groups/treesitter.lua new file mode 100644 index 0000000..5a3e59c --- /dev/null +++ b/lua/skift/groups/treesitter.lua @@ -0,0 +1,121 @@ +local M = {} + +---@param c Colors +function M.get(c) + return { + ["@variable"] = { fg = c.variable }, + ["@variable.builtin"] = { fg = c.variable, italic = true }, + ["@variable.parameter"] = { link = "@variable" }, + ["@variable.parameter.builtin"] = { link = "@variable.builtin" }, + ["@variable.member"] = { link = "@variable" }, + + ["@property"] = { fg = c.property }, + + ["@constant"] = { fg = c.constant }, + ["@constant.builtin"] = { fg = c.constant, bold = true }, + ["@constant.macro"] = { fg = c.special, bold = true }, + + ["@preproc"] = { link = "PreProc" }, + ["@module"] = { link = "@preproc" }, + ["@module.builtin"] = { link = "@module" }, + + ["@label"] = { fg = c.tag }, + + ["@string"] = { link = "String" }, + ["@string.documentation"] = { link = "@string" }, + ["@string.escape"] = { fg = c.special }, + ["@string.regexp"] = { fg = c.special }, + ["@string.special"] = { fg = c.special }, + ["@string.special.symbol"] = { link = "@string.special" }, + ["@string.special.url"] = { fg = c.special, underline = true }, + ["@string.special.path"] = { link = "@string.special" }, + + ["@character"] = { link = "String" }, + ["@character.special"] = { link = "@string.special" }, + + ["@number"] = { link = "Number" }, + ["@number.float"] = { link = "@number" }, + ["@boolean"] = { link = "Boolean" }, + + ["@type"] = { link = "Type" }, + ["@type.builtin"] = { link = "@type" }, + ["@type.definition"] = { link = "@type" }, + ["@type.qualifier"] = { link = "@type" }, + + ["@attribute"] = { fg = c.type }, + ["@attribute.builtin"] = { fg = c.type, italic = true }, + + ["@function"] = { link = "Function" }, + ["@function.builtin"] = { link = "@function" }, + ["@function.call"] = { link = "@function" }, + ["@function.macro"] = { link = "@function" }, + ["@function.method"] = { link = "@function" }, + ["@function.method.call"] = { link = "@function" }, + + ["@constructor"] = { link = "Normal" }, + ["@class"] = { link = "Normal" }, + + ["@operator"] = { link = "Operator" }, + + ["@keyword"] = { link = "Keyword" }, + ["@keyword.coroutine"] = { fg = c.special }, + ["@keyword.function"] = { link = "@keyword" }, + ["@keyword.operator"] = { link = "@operator" }, + ["@keyword.import"] = { link = "@keyword" }, + ["@keyword.type"] = { link = "@keyword" }, + ["@keyword.modifier"] = { link = "@keyword" }, + ["@keyword.repeat"] = { link = "@keyword" }, + ["@keyword.return"] = { link = "@keyword" }, + ["@keyword.debug"] = { fg = c.error }, + ["@keyword.exception"] = { fg = c.error }, + ["@keyword.conditional"] = { link = "Conditional" }, + ["@keyword.conditional.ternary"] = { link = "Conditional" }, + ["@keyword.directive"] = { link = "PreProc" }, + ["@keyword.directive.define"] = { link = "@keyword.directive" }, + + ["@punctuation.bracket"] = { link = "Operator" }, + ["@punctuation.delimiter"] = { link = "Operator" }, + ["@punctuation.special"] = { link = "Operator" }, + + ["@comment"] = { link = "Comment" }, + ["@comment.documentation"] = { link = "@comment" }, + ["@comment.error"] = { fg = c.error }, + ["@comment.warning"] = { fg = c.warning }, + ["@comment.todo"] = { fg = c.info }, + ["@comment.note"] = { fg = c.hint }, + + ["@markup.strong"] = { fg = c.fg_bright, bold = true }, + ["@markup.italic"] = { fg = c.fg_bright, italic = true }, + ["@markup.strikethrough"] = { fg = c.fg_dim, strikethrough = true }, + ["@markup.underline"] = { underline = true }, + ["@markup.heading"] = { fg = c.red, bold = true }, + ["@markup.heading.1"] = { fg = c.red, bold = true }, + ["@markup.heading.2"] = { fg = c.orange, bold = true }, + ["@markup.heading.3"] = { fg = c.yellow, bold = true }, + ["@markup.heading.4"] = { fg = c.green, bold = true }, + ["@markup.heading.5"] = { fg = c.cyan, bold = true }, + ["@markup.heading.6"] = { fg = c.blue, bold = true }, + ["@markup.quote"] = { fg = c.fg_dim, italic = true }, + ["@markup.math"] = { fg = c.constant }, + ["@markup.environment"] = { fg = c.keyword }, + ["@markup.link"] = { fg = c.string, underline = true }, + ["@markup.link.label"] = { fg = c.string }, + ["@markup.link.url"] = { fg = c.string, underline = true }, + ["@markup.raw"] = { fg = c.rose }, + ["@markup.raw.block"] = { fg = c.fg }, + ["@markup.list"] = { fg = c.operator }, + ["@markup.list.checked"] = { fg = c.ok }, + ["@markup.list.unchecked"] = { fg = c.fg_dim }, + + ["@tag"] = { fg = c.keyword }, + ["@tag.builtin"] = { fg = c.keyword, italic = true }, + ["@tag.attribute"] = { link = "@tag" }, + ["@tag.delimiter"] = { fg = c.operator }, + + ["@diff.plus"] = { fg = c.git_add }, + ["@diff.minus"] = { fg = c.git_delete }, + ["@diff.delta"] = { fg = c.git_change }, + } +end + +return M diff --git a/lua/skift/init.lua b/lua/skift/init.lua new file mode 100644 index 0000000..91a2654 --- /dev/null +++ b/lua/skift/init.lua @@ -0,0 +1,138 @@ +local config = require('skift.config') +local cache = require("skift.utils.cache") + +local M = {} + +local function apply_highlights(highlights) + for name, val in pairs(highlights) do + if type(val) == 'string' then + val = { link = val } + end + vim.api.nvim_set_hl(0, name, val) + end +end + +local function apply_terminal_colors(c) + vim.g.terminal_color_0 = c.terminal_black + vim.g.terminal_color_1 = c.terminal_red + vim.g.terminal_color_2 = c.terminal_green + vim.g.terminal_color_3 = c.terminal_yellow + vim.g.terminal_color_4 = c.terminal_blue + vim.g.terminal_color_5 = c.terminal_magenta + vim.g.terminal_color_6 = c.terminal_cyan + vim.g.terminal_color_7 = c.terminal_white + vim.g.terminal_color_8 = c.terminal_bright_black + vim.g.terminal_color_9 = c.terminal_bright_red + vim.g.terminal_color_10 = c.terminal_bright_green + vim.g.terminal_color_11 = c.terminal_bright_yellow + vim.g.terminal_color_12 = c.terminal_bright_blue + vim.g.terminal_color_13 = c.terminal_bright_magenta + vim.g.terminal_color_14 = c.terminal_bright_cyan + vim.g.terminal_color_15 = c.terminal_bright_white +end + +function M.setup(opts) + config:merge(opts) +end + +function M.load() + if vim.g.colors_name then + vim.cmd("hi clear") + end + vim.g.colors_name = "skift" + vim.o.termguicolors = true + vim.o.background = "dark" + + local cache_key = cache.key(config) + local cached = cache.read(cache_key) + if cached and cached.highlights and cached.colors then + apply_highlights(cached.highlights) + apply_terminal_colors(cached.colors) + return + end + + local c = vim.deepcopy(require("skift.colors")) + + config.on_colors(c) + + local highlights = {} + local groups = { + require("skift.groups.editor").get(c), + require("skift.groups.syntax").get(c), + require("skift.groups.treesitter").get(c), + require("skift.groups.lsp").get(c), + require("skift.groups.integrations").get(c), + } + + for _, group in ipairs(groups) do + highlights = vim.tbl_extend("force", highlights, group) + end + + if config.transparent then + local transparent_groups = { + "Normal", "NormalNC", "NormalFloat", "SignColumn", + "FoldColumn", "TabLineFill", "StatusLine", "StatusLineNC", + "NvimTreeNormal", "NeoTreeNormal", "NeoTreeNormalNC", + "TreesitterContext", + } + for _, name in ipairs(transparent_groups) do + if highlights[name] then + highlights[name].bg = "NONE" + end + end + highlights.WinSeparator = { fg = "NONE" } + highlights.VertSplit = { fg = "NONE" } + highlights.FloatBorder = { fg = "NONE", bg = "NONE" } + end + + if not config.bold then + for _, hl in pairs(highlights) do + hl.bold = false + end + end + + if not config.italic then + for _, hl in pairs(highlights) do + hl.italic = false + end + elseif not config.italic_comments then + for _, name in ipairs({ "Comment", "@comment", "@comment.documentation" }) do + if highlights[name] then + highlights[name].italic = false + end + end + end + + config.on_highlights(highlights, c) + + apply_highlights(highlights) + apply_terminal_colors(c) + + cache.write(cache_key, { + highlights = highlights, + colors = { + terminal_black = c.terminal_black, + terminal_red = c.terminal_red, + terminal_green = c.terminal_green, + terminal_yellow = c.terminal_yellow, + terminal_blue = c.terminal_blue, + terminal_magenta = c.terminal_magenta, + terminal_cyan = c.terminal_cyan, + terminal_white = c.terminal_white, + terminal_bright_black = c.terminal_bright_black, + terminal_bright_red = c.terminal_bright_red, + terminal_bright_green = c.terminal_bright_green, + terminal_bright_yellow = c.terminal_bright_yellow, + terminal_bright_blue = c.terminal_bright_blue, + terminal_bright_magenta = c.terminal_bright_magenta, + terminal_bright_cyan = c.terminal_bright_cyan, + terminal_bright_white = c.terminal_bright_white, + }, + }) +end + +function M.clear_cache() + cache.clear() +end + +return M diff --git a/lua/skift/utils/cache.lua b/lua/skift/utils/cache.lua new file mode 100644 index 0000000..2b8a77f --- /dev/null +++ b/lua/skift/utils/cache.lua @@ -0,0 +1,107 @@ +local M = {} + +local CACHE_VERSION = 1 +local CACHE_DIR = vim.fn.stdpath("cache") .. "/skift" + +local SOURCE_FILES = { + "lua/skift/palette.lua", + "lua/skift/init.lua", + "lua/skift/groups/editor.lua", + "lua/skift/groups/syntax.lua", + "lua/skift/groups/treesitter.lua", + "lua/skift/groups/lsp.lua", + "lua/skift/groups/integrations.lua", + "lua/skift/utils/palette.lua", +} + +local function function_fingerprint(fn) + if type(fn) ~= "function" then + return tostring(fn) + end + + local ok, dumped = pcall(string.dump, fn) + if ok and dumped then + return vim.fn.sha256(dumped) + end + + local info = debug.getinfo(fn, "S") + if info and info.source then + return string.format("%s:%d", info.source, info.linedefined or 0) + end + + return tostring(fn) +end + +local function source_fingerprint() + local stamp = {} + + for _, pattern in ipairs(SOURCE_FILES) do + local path = vim.api.nvim_get_runtime_file(pattern, false)[1] + if path then + local stat = vim.loop.fs_stat(path) + stamp[#stamp + 1] = { + file = pattern, + mtime = stat and stat.mtime and stat.mtime.sec or 0, + size = stat and stat.size or 0, + } + else + stamp[#stamp + 1] = { file = pattern, mtime = 0, size = 0 } + end + end + + return stamp +end + +local function cache_file(cache_key) + return CACHE_DIR .. "/highlights-" .. cache_key .. ".json" +end + +---@param config Config +function M.key(config) + local payload = { + version = CACHE_VERSION, + transparent = config.transparent, + bold = config.bold, + italic = config.italic, + italic_comments = config.italic_comments, + on_colors = function_fingerprint(config.on_colors), + on_highlights = function_fingerprint(config.on_highlights), + sources = source_fingerprint(), + } + + return vim.fn.sha256(vim.json.encode(payload)) +end + +function M.read(cache_key) + local path = cache_file(cache_key) + if vim.fn.filereadable(path) == 0 then + return nil + end + + local ok_read, lines = pcall(vim.fn.readfile, path) + if not ok_read or not lines then + return nil + end + + local raw = table.concat(lines, "\n") + local ok_decode, decoded = pcall(vim.json.decode, raw) + if not ok_decode then + return nil + end + + return decoded +end + +function M.write(cache_key, payload) + vim.fn.mkdir(CACHE_DIR, "p") + local path = cache_file(cache_key) + local encoded = vim.json.encode(payload) + local ok_write = pcall(vim.fn.writefile, { encoded }, path) + return ok_write +end + +function M.clear() + vim.fn.delete(CACHE_DIR, "rf") +end + +return M diff --git a/lua/skift/utils/hsl.lua b/lua/skift/utils/hsl.lua new file mode 100644 index 0000000..da7f99c --- /dev/null +++ b/lua/skift/utils/hsl.lua @@ -0,0 +1,71 @@ +-- https://github.com/EmmanuelOga/columns/blob/master/utils/color.lua + +local M = {} + +--- Converts an HSL color value to RGB. Conversion formula +--- adapted from http://en.wikipedia.org/wiki/HSL_color_space. +--- Assumes h, s, and l are contained in the set [0, 1] and +--- returns r, g, and b in the set [0, 255]. +--- +--- @param h number The hue +--- @param s number The saturation +--- @param l number The lightness +--- @return number, number, number # The RGB representation +function M.hslToRgb(h, s, l) + --- @type number, number, number + local r, g, b + + if s == 0 then + r, g, b = l, l, l -- achromatic + else + --- @param p number + --- @param q number + --- @param t number + local function hue2rgb(p, q, t) + if t < 0 then + t = t + 1 + end + if t > 1 then + t = t - 1 + end + if t < 1 / 6 then + return p + (q - p) * 6 * t + end + if t < 1 / 2 then + return q + end + if t < 2 / 3 then + return p + (q - p) * (2 / 3 - t) * 6 + end + return p + end + + --- @type number + local q + if l < 0.5 then + q = l * (1 + s) + else + q = l + s - l * s + end + local p = 2 * l - q + + r = hue2rgb(p, q, h + 1 / 3) + g = hue2rgb(p, q, h) + b = hue2rgb(p, q, h - 1 / 3) + end + + return r * 255, g * 255, b * 255 +end + +--- Converts an HSL color value to RGB in Hex representation. +--- @param h number The hue +--- @param s number The saturation +--- @param l number The lightness +--- @return string # The hex representation +function M.hslToHex(h, s, l) + local r, g, b = M.hslToRgb(h / 360, s / 100, l / 100) + + return string.format("#%02x%02x%02x", r, g, b) +end + +return M diff --git a/lua/skift/utils/resolve_color_vars.lua b/lua/skift/utils/resolve_color_vars.lua new file mode 100644 index 0000000..5898b49 --- /dev/null +++ b/lua/skift/utils/resolve_color_vars.lua @@ -0,0 +1,53 @@ +local cache = setmetatable({}, { __mode = 'k' }) + +---@param definitions table +---@return table +return function(definitions) + local cached = cache[definitions] + if cached ~= nil then + return cached + end + + local resolved = {} + local resolving = {} + + local function resolve(key) + if resolved[key] ~= nil then + return resolved[key] + end + + local value = definitions[key] + if value == nil then + return nil + end + + if type(value) ~= 'string' then + resolved[key] = value + return value + end + + if resolving[key] then + error('sora.palette: cyclic color alias detected for key "' .. key .. '"') + end + + if definitions[value] == nil then + resolved[key] = value + return value + end + + resolving[key] = true + local alias_value = resolve(value) + resolving[key] = nil + + resolved[key] = alias_value + return alias_value + end + + for key in pairs(definitions) do + resolve(key) + end + + cache[definitions] = resolved + + return resolved +end