diff --git a/modules/plugins/theme/supported-themes.nix b/modules/plugins/theme/supported-themes.nix index c2a5e1ca..47d8d5af 100644 --- a/modules/plugins/theme/supported-themes.nix +++ b/modules/plugins/theme/supported-themes.nix @@ -5,115 +5,98 @@ inherit (lib.strings) optionalString splitString; inherit (lib.attrsets) mapCartesianProduct; inherit (lib.lists) intersectLists; - inherit (lib.trivial) boolToString warnIf; + inherit (lib.trivial) warnIf; inherit (lib.nvim.lua) toLuaObject; in { + # FIXME: those all need to be converted to setupOpts format + # or else we explode... base16 = { - setup = {base16-colors, ...}: '' + setup = setupOpts: '' -- Base16 theme - require('base16-colorscheme').setup(${toLuaObject base16-colors}) + require('base16-colorscheme').setup(${toLuaObject setupOpts}) ''; }; + mini-base16 = { - setup = {base16-colors, ...}: '' + setup = setupOpts: '' -- Base16 theme require('mini.base16').setup({ - palette = ${toLuaObject base16-colors} + palette = ${toLuaObject setupOpts} }) ''; }; + onedark = { - setup = { - style ? "dark", - transparent, - ... - }: '' + styles = ["dark" "darker" "cool" "deep" "warm" "warmer"]; + setup = setupOpts: '' -- OneDark theme - require('onedark').setup { - transparent = ${boolToString transparent}, - style = "${style}" - } + require('onedark').setup(${toLuaObject setupOpts}) require('onedark').load() ''; - styles = ["dark" "darker" "cool" "deep" "warm" "warmer"]; }; tokyonight = { - setup = { - style ? "night", - transparent, - ... - }: '' - require('tokyonight').setup { - transparent = ${boolToString transparent}; - styles = { - sidebars = ${ - if transparent - then ''"transparent"'' - else ''"dark"'' - }, - floats = ${ - if transparent - then ''"transparent"'' - else ''"dark"'' - }, - }, - } - vim.cmd[[colorscheme tokyonight-${style}]] - ''; styles = ["day" "night" "storm" "moon"]; + setup = setupOpts: '' + require('tokyonight').setup(${toLuaObject setupOpts}) + vim.cmd[[colorscheme tokyonight-${setupOpts.style or "night"}]] + ''; }; dracula = { - setup = {transparent, ...}: '' - require('dracula').setup({ - transparent_bg = ${boolToString transparent}, - }); + setup = setupOpts: let + cleanedOpts = + (removeAttrs setupOpts ["transparent"]) + // { + transparent_bg = setupOpts.transparent or false; + }; + in '' + require('dracula').setup(${toLuaObject cleanedOpts}); require('dracula').load(); ''; }; catppuccin = { - setup = { - style ? "mocha", - transparent ? false, - ... - }: '' - -- Catppuccin theme - require('catppuccin').setup { - flavour = "${style}", - transparent_background = ${boolToString transparent}, - float = { - transparent = ${boolToString transparent}, - }, - term_colors = true, - integrations = { - nvimtree = { - enabled = true, - transparent_panel = ${boolToString transparent}, - show_root = true, - }, + setup = setupOpts: let + cleanedOpts = + (removeAttrs setupOpts ["style" "transparent"]) + // { + flavour = setupOpts.style or "mocha"; + transparent_background = setupOpts.transparent or false; + float = { + transparent = setupOpts.transparent or false; + }; + term_colors = true; + integrations = { + nvimtree = { + enabled = true; + transparent_panel = setupOpts.transparent or false; + show_root = true; + }; - hop = true, - gitsigns = true, - telescope = true, - treesitter = true, - treesitter_context = true, - ts_rainbow = true, - fidget = true, - alpha = true, - leap = true, - lsp_saga = true, - markdown = true, - noice = true, - notify = true, -- nvim-notify - which_key = true, - navic = { - enabled = true, - custom_bg = "NONE", -- "lualine" will set background to mantle - }, - }, - } + hop = true; + gitsigns = true; + telescope = true; + treesitter = true; + treesitter_context = true; + ts_rainbow = true; + fidget = true; + alpha = true; + leap = true; + lsp_saga = true; + markdown = true; + noice = true; + notify = true; # nvim-notify + which_key = true; + navic = { + enabled = true; + custom_bg = "NONE"; # "lualine" will set background to mantle + }; + }; + }; + in '' + -- Catppuccin theme + require('catppuccin').setup(${toLuaObject cleanedOpts}) -- setup must be called before loading vim.cmd.colorscheme "catppuccin" ''; @@ -121,17 +104,14 @@ in { }; oxocarbon = { - setup = { - style ? "dark", - transparent ? false, - ... - }: let - style' = - warnIf (style == "light") "oxocarbon: light theme is not well-supported" style; + setup = setupOpts: let + style = setupOpts.style or "dark"; + style' = warnIf (style == "light") "oxocarbon: light theme is not well-supported" style; + transparent = setupOpts.transparent or false; in '' require('oxocarbon') vim.opt.background = "${style'}" - vim.cmd.colorscheme = "oxocarbon" + vim.cmd.colorscheme "oxocarbon" ${optionalString transparent '' vim.api.nvim_set_hl(0, "Normal", { bg = "none" }) vim.api.nvim_set_hl(0, "NormalFloat", { bg = "none" }) @@ -146,93 +126,99 @@ in { }; gruvbox = { - setup = { - style ? "dark", - transparent ? false, - ... - }: '' + setup = setupOpts: let + cleanedOpts = + (removeAttrs setupOpts ["transparent" "style"]) + // { + terminal_colors = true; # add neovim terminal colors + undercurl = true; + underline = true; + bold = true; + italic = { + strings = true; + emphasis = true; + comments = true; + operators = false; + folds = true; + }; + strikethrough = true; + invert_selection = false; + invert_signs = false; + invert_tabline = false; + invert_intend_guides = false; + inverse = true; + contrast = ""; + palette_overrides = {}; + overrides = {}; + dim_inactive = false; + transparent_mode = setupOpts.transparent or false; + }; + style = setupOpts.style or "dark"; + in '' + styles = ["dark" "light"]; -- Gruvbox theme - require("gruvbox").setup({ - terminal_colors = true, -- add neovim terminal colors - undercurl = true, - underline = true, - bold = true, - italic = { - strings = true, - emphasis = true, - comments = true, - operators = false, - folds = true, - }, - strikethrough = true, - invert_selection = false, - invert_signs = false, - invert_tabline = false, - invert_intend_guides = false, - inverse = true, - contrast = "", - palette_overrides = {}, - overrides = {}, - dim_inactive = false, - transparent_mode = ${boolToString transparent}, - }) + require("gruvbox").setup(${toLuaObject cleanedOpts}) vim.o.background = "${style}" vim.cmd("colorscheme gruvbox") ''; - styles = ["dark" "light"]; }; + rose-pine = { - setup = { - style ? "main", - transparent ? false, - ... - }: '' - require("rose-pine").setup({ - dark_variant = "${style}", -- main, moon, or dawn - dim_inactive_windows = false, - extend_background_behind_borders = true, + setup = setupOpts: let + cleanedOpts = + (removeAttrs setupOpts ["style" "transparent"]) + // { + dark_variant = setupOpts.style or "main"; # main, moon, or dawn + dim_inactive_windows = false; + extend_background_behind_borders = true; - enable = { - terminal = true, - migrations = true, - }, - - styles = { - bold = false, - italic = false, -- I would like to add more options for this - transparency = ${boolToString transparent}, - }, - }) + enable = { + terminal = true; + migrations = true; + }; + styles = { + bold = false; + italic = false; # I would like to add more options for this + transparency = setupOpts.transparent or false; + }; + }; + in '' + require("rose-pine").setup(${toLuaObject cleanedOpts}) vim.cmd("colorscheme rose-pine") ''; styles = ["main" "moon" "dawn"]; }; - nord = { - setup = {transparent ? false, ...}: '' - require("nord").setup({ - transparent = ${boolToString transparent}, - search = "vscode", -- [vim|vscode] - }) + nord = { + setup = setupOpts: let + cleanedOpts = + (removeAttrs setupOpts ["transparent"]) + // { + transparent = setupOpts.transparent or false; + search = "vscode"; # [vim|vscode] + }; + in '' + require("nord").setup(${toLuaObject cleanedOpts}) vim.cmd.colorscheme("nord") ''; }; - github = { - setup = { - style ? "dark", - transparent ? false, - ... - }: '' - require('github-theme').setup({ - options = { - transparent = ${boolToString transparent}, - }, - }) + github = { + styles = ["dark" "light" "dark_dimmed" "dark_default" "light_default" "dark_high_contrast" "light_high_contrast" "dark_colorblind" "light_colorblind" "dark_tritanopia" "light_tritanopia"]; + setup = setupOpts: let + cleanedOpts = + (removeAttrs setupOpts ["transparent" "style"]) + // { + options = { + transparent = setupOpts.transparent or false; + }; + }; + style = setupOpts.style or "dark"; + in '' + require('github-theme').setup(${toLuaObject cleanedOpts}) vim.cmd[[colorscheme github_${style}]] ''; - styles = ["dark" "light" "dark_dimmed" "dark_default" "light_default" "dark_high_contrast" "light_high_contrast" "dark_colorblind" "light_colorblind" "dark_tritanopia" "light_tritanopia"]; }; solarized = let @@ -240,11 +226,8 @@ in { palettes = ["solarized" "selenized"]; variants = ["spring" "summer" "autumn" "winter"]; in { - setup = { - style ? "", # use plugin defaults - transparent ? false, - ... - }: let + setup = setupOpts: let + style = setupOpts.style or ""; parts = splitString "-" style; detect = list: let intersection = intersectLists parts list; @@ -255,15 +238,28 @@ in { background = detect backgrounds; palette = detect palettes; variant = detect variants; + baseSetup = + { + transparent = { + enabled = setupOpts.transparent or false; + }; + } + // (removeAttrs setupOpts ["style" "transparent"]); + finalSetup = + baseSetup + // ( + if palette != null + then {palette = palette;} + else {} + ) + // ( + if variant != null + then {variant = variant;} + else {} + ); in '' -- Solarized theme - require('solarized').setup { - transparent = { - enabled = ${boolToString transparent}, - }, - ${optionalString (palette != null) ''palette = "${palette}",''} - ${optionalString (variant != null) ''variant = "${variant}",''} - } + require('solarized').setup(${toLuaObject finalSetup}) ${optionalString (background != null) ''vim.opt.background = "${background}"''} vim.cmd.colorscheme "solarized" ''; @@ -284,25 +280,28 @@ in { }; solarized-osaka = { - setup = {transparent ? false, ...}: '' - require("solarized-osaka").setup({ - transparent = ${boolToString transparent}, - styles = { - comments = { italic = false }, - keywords = { italic = false }, - } - }) - + setup = setupOpts: let + cleanedOpts = + (removeAttrs setupOpts ["transparent"]) + // { + transparent = setupOpts.transparent or false; + styles = { + comments = {italic = false;}; + keywords = {italic = false;}; + }; + }; + in '' + require("solarized-osaka").setup(${toLuaObject cleanedOpts}) vim.cmd.colorscheme("solarized-osaka") ''; }; everforest = { - setup = { - style ? "medium", - transparent ? false, - ... - }: '' + styles = ["hard" "medium" "soft"]; + setup = setupOpts: let + style = setupOpts.style or "medium"; + transparent = setupOpts.transparent or false; + in '' vim.g.everforest_background = "${style}" vim.g.everforest_transparent_background = ${ if transparent @@ -312,8 +311,6 @@ in { vim.cmd.colorscheme("everforest") ''; - - styles = ["hard" "medium" "soft"]; }; mellow = { diff --git a/modules/plugins/theme/theme.nix b/modules/plugins/theme/theme.nix index b3147c00..88fcc2fa 100644 --- a/modules/plugins/theme/theme.nix +++ b/modules/plugins/theme/theme.nix @@ -3,14 +3,15 @@ lib, ... }: let - inherit (lib.options) mkOption; - inherit (lib.attrsets) attrNames; - inherit (lib.strings) hasPrefix; - inherit (lib.types) bool lines enum; + inherit (lib.options) mkOption mkEnableOption; inherit (lib.modules) mkIf; + inherit (lib.types) bool lines enum submodule attrsOf nullOr; + inherit (lib.attrsets) attrNames filterAttrs; + inherit (lib.strings) hasPrefix; inherit (lib.nvim.attrsets) mapListToAttrs; inherit (lib.nvim.dag) entryBefore; - inherit (lib.nvim.types) hexColor; + inherit (lib.nvim.types) hexColor mkPluginSetupOption; + inherit (lib.nvim.lua) toLuaObject; cfg = config.vim.theme; supportedThemes = import ./supported-themes.nix { @@ -31,45 +32,169 @@ }; }) numbers; + + # Get all enabled themes + enabledThemes = filterAttrs (_: themeCfg: themeCfg.enable) cfg.themes; + + # Get the default theme configuration + defaultTheme = + if cfg.default != null && enabledThemes ? ${cfg.default} + then enabledThemes.${cfg.default} + else null; in { options.vim.theme = { - enable = mkOption { - type = bool; - description = "Enable theming"; + enable = mkEnableOption "theming"; + + default = mkOption { + type = nullOr enum (attrNames supportedThemes); + default = null; + description = '' + The default theme to load in the built configuration. While this option + is set and the matching theme is enabled in {option}`vim.theme.themes` + the theme specified by this option will be set automatically as the + default theme. If `null`, the user is responsible for setting their + preferred theme either by explicitly setting this option, or using Lua. + ''; }; + + themes = let + themeType = {name, ...}: { + options = { + enable = mkEnableOption "the ${name} theme"; + setupOpts = mkPluginSetupOption name {}; + }; + }; + in + mkOption { + type = attrsOf (submodule themeType); + default = {}; + example = { + tokyonight = { + enable = true; + setupOpts = { + style = "night"; + transparent = true; + }; + }; + + catppuccin = { + enable = true; + setupOpts = { + flavour = "mocha"; + transparent_background = true; + integrations = { + nvimtree = { + enabled = true; + transparent_panel = true; + }; + telescope = true; + treesitter = true; + }; + }; + }; + + onedark = { + enable = false; # Available but not loaded + setupOpts = { + style = "darker"; + transparent = false; + }; + }; + }; + + description = '' + New theme configuration option for v0.8 and above. This system allows + you to set multiple themes at once, where **all** enabled themes will + be loaded in the configuration. While {option}`vim.theme.default` is + set, the default theme will be set automatically in the configuration. + ''; + }; + + # Legacy options for backwards compatibility + # FIXME: this could have been handled directly with mkRenamedOptionModule + # or similar, but I found it too difficult to handle it gracefully. Those + # are kept here **with a warning** but without completely removing the + # relevant options. Worth completely dropping in the future. name = mkOption { type = enum (attrNames supportedThemes); + default = "onedark"; description = '' Supported themes can be found in {file}`supportedThemes.nix`. Setting the theme to "base16" enables base16 theming and requires all of the colors in {option}`vim.theme.base16-colors` to be set. + + ::: {.note} + + Legacy option: use vim.theme.themes..enable = true and vim.theme.default = "" instead. + + ::: + ''; }; - base16-colors = base16Options; style = mkOption { type = enum supportedThemes.${cfg.name}.styles; - description = "Specific style for theme if it supports it"; + default = builtins.head supportedThemes.${cfg.name}.styles; + description = "Legacy option: use `vim.theme.themes..setupOpts.style` instead"; }; + transparent = mkOption { type = bool; default = false; - description = "Whether or not transparency should be enabled. Has no effect for themes that do not support transparency"; + description = "Legacy option: use `vim.theme.themes..setupOpts.transparent` instead"; }; + base16-colors = base16Options; + extraConfig = mkOption { type = lines; - description = "Additional lua configuration to add before setup"; + default = ""; + description = "Additional Lua configuration to add before setup"; }; }; config = mkIf cfg.enable { vim = { - startPlugins = [cfg.name]; + # Include plugins for all enabled themes + startPlugins = attrNames enabledThemes; + luaConfigRC.theme = entryBefore ["pluginConfigs" "lazyConfigs"] '' + -- Theme configurations ${cfg.extraConfig} - ${supportedThemes.${cfg.name}.setup {inherit (cfg) style transparent base16-colors;}} + + ${lib.concatStringsSep "\n" (lib.mapAttrsToList ( + # FIXME: this only works when the plugin accepts setupOpts. We need to + # either check for this in Lua, or list the plugins that support the + # setup table instead of using globals. + themeName: themeCfg: '' + -- Setup ${themeName} theme + require('${themeName}').setup(${toLuaObject themeCfg.setupOpts}) + '' + ) + enabledThemes)} + + ${lib.optionalString (defaultTheme != null) '' + -- Load default theme: + -- ${cfg.default} + vim.cmd.colorscheme "${cfg.default}" + ''} ''; }; + + # We'd like to warn when the user is using a completely legacy configuration + warnings = let + # FIXME: what + legacyUsed = cfg.style != builtins.head supportedThemes.${cfg.name}.styles || !cfg.transparent; + newConfigUsed = builtins.length (attrNames enabledThemes) > 0; + in + mkIf (legacyUsed && !newConfigUsed) [ + '' + The theming module has been refactored to allow more powerful configurations and multiple theme setups + through the module system in v0.8. This warning indicates that you are using the legacy API and have not + yet used any of the new APIs. Please migrate your configuration to 'vim.theme.themes' API. + + Refer to the documentation for more details. + '' + ]; }; }