diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..228e8a0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# Contributing to the catalog + +This repository holds the LimaNix catalog, the NixOS modules that +`limanix.toml` chooses, and the base disk every new VM boots from. The +[organization guide](https://github.com/limanix/.github/blob/main/CONTRIBUTING.md) +covers issues, release labels and the license; this guide covers the work in +this repository. + +## Set up + +You need [Task](https://taskfile.dev) 3.53.1 or newer and Docker. Every task +runs in the container that CI uses; `task -l` lists them. + +## Find the rules for your change + +| You want to | Read | Check with | +| -- | -- | -- | +| Add or change a module | [Catalog contract](catalog/README.md) | `task ci/catalog/modules MODULES=""` | +| Use shared helpers, capabilities or theme colors | [Shared helpers](catalog/_shared/README.md) | `task ci/catalog/layer` | +| Give a tool the guest's colors | [Colors and themes](catalog/README.md#colors-and-themes) | The module's checks | +| Add a theme | [Colors and themes](catalog/README.md#colors-and-themes) | `task generate/previews`, then the layer and the modules with colors | +| Change the harness in `tools/` or `checks/` | [Repository layout](README.md#repository-layout) | `task ci/python/test` and the other `ci/python/*` tasks | +| Move a Nixpkgs pin | [Releases](README.md#releases) | `task harness -- update nixpkgs` | + +`task harness -- plan --base main` prints the checks CI will select for your +branch, dependent modules included. + +## Add a module + +1. Create `catalog///` with `module.toml`, `default.nix`, `test.nix` + and `README.md`, as the [catalog contract](catalog/README.md#add-a-module) + describes. +2. Give every public entry a help card, and every setting a user may replace + `lib.mkDefault`. +3. Use other modules only through their public options and the shared + capabilities. +4. If the tool draws colors, take them from the theme by role. +5. Write `eval` checks for the configuration, `run` checks for behavior that + runs offline, and list in `builds` each derivation the checks build locally. +6. Write the README for a user: select, versions, use, settings and corner + cases. It becomes the module's page on docs.limanix.dev. +7. Add a `graph.toml` node and run `task generate/graph` if the module belongs + on the site's catalog graph. + +## Before you open a pull request + +| Step | Command | +| -- | -- | +| Check the modules you changed, or the layer for `_shared` | `task ci/catalog/modules MODULES=""`, `task ci/catalog/layer` | +| Check formatting and lint | `task ci/nix/fmt`, `task ci/nix/lint`, `task ci/markdown/fmt` | +| Check the harness after a change in `tools/` | `task ci/python/fmt`, `task ci/python/lint`, `task ci/python/typecheck`, `task ci/python/test` | +| Regenerate generated files | `task generate/graph` after a `graph.toml` change; `task generate/previews` after a theme or color change | + +The `nix/fmt/fix`, `nix/lint/fix`, `markdown/fmt/fix`, `python/fmt/fix` and +`python/lint/fix` tasks rewrite files in place. Update a module's README in the +same pull request as its behavior. Keep one change per pull request, and give it +a release label. + +## Releases and the client + +A tag `v` on `main` releases the catalog and its disks; the +[README](README.md#releases) lists what creates a tag. Each client build bundles +one catalog release. A change to selectors, metadata, public options, +capabilities or the list of themes reaches users only with a client release that +bundles it; release the catalog first, then open the client pull request that +moves to it. diff --git a/README.md b/README.md index 2b90ca7..41b3236 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ modules/ ├── catalog/ the catalog: _shared/, shell/, toolchains/, modules/ and graph.json ├── checks/ the harness in Nix: discovery, contract validation and test aggregation ├── tools/ the harness in Python: the check runner, updates, the disk build, releases and the graph +├── previews/ the theme previews that the site shows, written by task generate/previews ├── interface.nix the options every VM gets: account identity, sessions and help cards ├── platform.nix the guest platform: disk layout, boot loader and Lima integration ├── image.nix the base disk, built from platform.nix @@ -98,6 +99,7 @@ library; ruff, mypy and pytest are development tools in that image. | `task ci/catalog/modules MODE=eval MODULES="go docker"` | Checks the named modules, or all of them; `MODE` is `check`, `eval` or `run` | | `task ci/catalog/layer SUITE=shared MODE=eval` | Checks the shared layer, or the harness with `SUITE=platform` | | `task generate/graph` | Writes `catalog/graph.json`, the site's catalog graph, from the `graph.toml` of each module | +| `task generate/previews` | Writes `previews/`, a picture of tmux, Neovim, eza and delta in each theme and the themes' palettes, for the site | | `task harness -- plan --base main` | Prints the checks selected by committed changes from `main` to `HEAD`, including dependent modules | | `task harness -- update nixpkgs` | Updates a pinned input; accepts `nixpkgs`, `unstable` or `nixos` | | `task release/build` | Builds the base disk of this machine's architecture into `dist/` | @@ -112,7 +114,9 @@ change. ## Contributing -Follow the -[contribution guide](https://github.com/limanix/.github/blob/main/CONTRIBUTING.md). -Use [Issues](https://github.com/limanix/modules/issues) for questions, bug -reports and feature requests. Licensed under [Apache 2.0](LICENSE). +Follow [CONTRIBUTING.md](CONTRIBUTING.md) for the work in this repository and +the +[organization guide](https://github.com/limanix/.github/blob/main/CONTRIBUTING.md) +for issues and release labels. Use +[Issues](https://github.com/limanix/modules/issues) for questions, bug reports +and feature requests. Licensed under [Apache 2.0](LICENSE). diff --git a/Taskfile.yml b/Taskfile.yml index aeb75d8..e64b1dc 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -147,6 +147,13 @@ tasks: CLI_ARGS: graph CONTAINER_USER: host + generate/previews: + desc: Write previews/, the theme previews the site shows + cmds: + - task: harness + vars: + CLI_ARGS: previews + release/build: desc: Build the base disk of ARCH into dist/ requires: diff --git a/catalog/README.md b/catalog/README.md index 553750d..4c7a5ad 100644 --- a/catalog/README.md +++ b/catalog/README.md @@ -1,6 +1,6 @@ # Catalog contract -Rules for catalog module authors, revision 6. NixOS combines modules into one +Rules for catalog module authors, revision 7. NixOS combines modules into one configuration. Nix evaluates that configuration; builds and system activation are separate steps. @@ -95,6 +95,43 @@ name. See [language tools](_shared/README.md#share-language-tools) and capabilities. A tool provider must not enable an editor just by declaring its tools. +## Colors and themes + +The guest has one theme, chosen with `[theme]` in `limanix.toml`. A module that +draws colors takes them from `lmx.capabilities.theme` by role, so one choice +recolors the whole guest; [theme colors](_shared/README.md#theme-colors) +describes the roles and the options. + +| Do | Do not | +| -- | -- | +| Draw each element with `colors.`, and use `palette.` only for a tool that needs explicit colors even with `system` | Name a theme or write a color literal in a module | +| With `system`, draw with the terminal colors that `colors` holds and leave backgrounds to the terminal, or keep the tool's own terminal-color mode | Fall back to the terminal's colors when another theme is chosen | +| Set colors with `lib.mkDefault`, an environment variable or a system file that a personal setting replaces | Override a user's own configuration | +| Draw a template written for Catppuccin through [`lib/catppuccin.nix`](_shared/lib/catppuccin.nix) and a Textual theme through [`lib/textual.nix`](_shared/lib/textual.nix) | Copy the colors of one theme into a module | +| Say in the module README which settings carry the colors and how a user replaces them | Leave a tool that has colors out of the theme without saying why | + +The editor is the only exception to drawing by role: a theme may bring its own +Neovim colorscheme, listed in +[`shell/neovim/colorschemes.nix`](shell/neovim/colorschemes.nix), and a theme +without one draws its palette with catppuccin-nvim. A tool that takes no colors +a module can set system-wide keeps its own colors, and its README names it. + +To add a theme: + +1. Add a table to [`_shared/themes.toml`](_shared/themes.toml) with `title`, + `homepage`, `dark` and all 18 roles. Take the values from the theme's own + Neovim colorscheme and its published palette, name the source in a comment, + and give a role the theme has no color for its nearest one. +1. Add the theme's colorscheme to `shell/neovim/colorschemes.nix` when Nixpkgs + packages it. +1. Run `task generate/previews` to write the preview that the site shows for the + theme. +1. Run `task ci/catalog/layer` and the checks of the modules that read the + theme: + `task ci/catalog/modules MODULES="tmux lazygit yazi zsh cli-tools astronvim neovim harlequin posting"`. +1. The client accepts the theme once its `[theme] name` lists it: add the name + to the client's choices and release the client with this catalog. + ## Version lines `lmx:` selects `default.nix`; `lmx:-` selects diff --git a/catalog/_shared/README.md b/catalog/_shared/README.md index 6ef2e5b..43a332c 100644 --- a/catalog/_shared/README.md +++ b/catalog/_shared/README.md @@ -13,7 +13,7 @@ flowchart LR subgraph shared[_shared] schemas[Schemas: languageSupport, pins, theme] helpers[Test helpers in test/] - data[palette.toml, unstable.toml] + data[themes.toml, unstable.toml] end default -- sets and reads lmx options --> schemas default -. reads .-> data @@ -370,36 +370,58 @@ python ${../../../_shared/test/lsp-smoke.py} --timeout 10 ${profile}/bin/gopls ## Theme colors `lmx.capabilities.theme` holds the guest's colors: the terminal's own colors, or -a Catppuccin flavor and its colors. Read it in configuration values: +one of the themes in [themes.toml](themes.toml). Every module that draws colors +takes them from here, by role, so that one choice in `limanix.toml` recolors the +whole guest. Read it in configuration values: ```nix { config, ... }: let - inherit (config.lmx.capabilities.theme) flavor palette ansi; + inherit (config.lmx.capabilities.theme) name palette colors dark; in { - # flavor == "system" by default: ansi.blue == 4, the terminal's blue - # flavor == "mocha": palette.blue == "#89b4fa" + # name == "system" by default: colors.accent == 5, the terminal's magenta + # name == "tokyo-night": colors.accent == palette.accent == "#7aa2f7" } ``` -- `flavor` is `system`, the default, or `latte`, `frappe`, `macchiato` or - `mocha`. A system has one flavor: equal definitions agree, different - definitions at the same priority fail. The user selects it with `[theme]` in - `limanix.toml`; modules never set it. Every catalog module with colors reads +- `name` is `system`, the default, or a table of `themes.toml`. A system has one + theme: equal definitions agree, different definitions at the same priority + fail. The user selects it with `[theme]` in `limanix.toml`; modules never set it. -- `palette` is read-only: the flavor's 26 colors by name, such as `blue`, as - `#rrggbb`. With `system` it holds Mocha's colors, for a tool that knows only +- `palette` is read-only: the theme's 18 colors by role, as `#rrggbb`. With + `system` it holds catppuccin-mocha's colors, for a tool that knows only explicit colors. -- `ansi` is read-only: for each name of `palette`, the terminal color that - stands for it with `system`. A number from 0 to 15 is one of the terminal's 16 - colors; `null` is its default foreground or background, so the terminal's own - background shows through. [ansi.toml](ansi.toml) holds the table. -- With `system`, use `ansi` and leave backgrounds to the terminal: the colors - then follow the theme of the terminal on the Mac, and change with it. -- [palette.toml](palette.toml) copies the four flavors of Catppuccin palette - v1.8.0 from the revision that `pkgs.catppuccin` packages; a check compares the - two. +- `dark` is read-only: whether the palette's colors sit on a dark background. +- `ansi` is read-only: for each role, the terminal color that stands for it with + `system`. A number from 0 to 15 is one of the terminal's 16 colors; `null` is + its default foreground or background, so the terminal's own background shows + through. [ansi.toml](ansi.toml) holds the table. +- `colors` is read-only: what to draw each role with, `ansi`'s value with + `system` and `palette`'s otherwise. With `system`, leave backgrounds to the + terminal: the colors then follow the theme of the terminal on the Mac, and + change with it. + +| Role | Draws | +| -- | -- | +| `background` | Main background | +| `background_dark` | Panels and bars one step darker, such as a status line | +| `background_darker` | The deepest background, such as a border between panels | +| `surface` | Raised elements: the current line, a menu, a message | +| `selection` | Selected text and items | +| `border` | Borders, separators and inactive outlines | +| `muted` | Comments, hints and disabled text | +| `subtle` | Secondary text and labels | +| `foreground` | Main text | +| `accent` | The theme's signature color: focus, the active item, highlights | +| `red`, `orange`, `yellow`, `green`, `cyan`, `blue`, `purple`, `pink` | Hues; red marks errors, yellow warnings, green success, blue information | + +A theme that has no color for a role takes its nearest one, so two roles may +share a color. `themes.toml` names the source of each theme: its own Neovim +colorscheme in the locked Nixpkgs and its published palette. Add a theme as one +more table with `title`, `homepage`, `dark` and all 18 roles; every module then +draws it without a change. A check compares catppuccin-mocha with the palette +that `pkgs.catppuccin` packages. ## Change `_shared` diff --git a/catalog/_shared/ansi.toml b/catalog/_shared/ansi.toml index 9fdc275..20c378f 100644 --- a/catalog/_shared/ansi.toml +++ b/catalog/_shared/ansi.toml @@ -1,31 +1,23 @@ -# The terminal colors that stand for the palette's names with the system flavor: the number of one -# of the terminal's 16 colors, 0 to 15, or "default" for its own foreground or background. Accents -# take their ANSI hue; quiet text and surfaces take bright black, which terminal themes keep -# readable on their background, light or dark. +# The terminal colors that stand for the palette's roles with the system theme: the number of one +# of the terminal's 16 colors, 0 to 15, or "default" for its own foreground or background. Hues +# and the accent take their ANSI color; quiet text and surfaces take bright black, which terminal +# themes keep readable on their background, light or dark. -rosewater = 13 -flamingo = 13 -pink = 13 -mauve = 5 +background = "default" +background_dark = "default" +background_darker = "default" +surface = 8 +selection = 8 +border = 8 +muted = 8 +subtle = 8 +foreground = "default" +accent = 5 red = 1 -maroon = 9 -peach = 11 +orange = 11 yellow = 3 green = 2 -teal = 6 -sky = 14 -sapphire = 14 +cyan = 6 blue = 4 -lavender = 12 -text = "default" -subtext1 = "default" -subtext0 = 8 -overlay2 = 8 -overlay1 = 8 -overlay0 = 8 -surface2 = 8 -surface1 = 8 -surface0 = 8 -base = "default" -mantle = "default" -crust = "default" +purple = 5 +pink = 13 diff --git a/catalog/_shared/graph.toml b/catalog/_shared/graph.toml index 913f339..5c9d0e0 100644 --- a/catalog/_shared/graph.toml +++ b/catalog/_shared/graph.toml @@ -1,3 +1,3 @@ [theme] name = "`[theme]`" -text = "One `[theme]` in `limanix.toml` colors the editor, the prompt, tmux, Lazygit, Yazi, bat, delta, Harlequin and Posting." +text = "One `[theme]` in `limanix.toml` colors the editor, the prompt, tmux, Lazygit, Yazi, bat, delta, Harlequin and Posting by the same roles." diff --git a/catalog/_shared/lib/catppuccin.nix b/catalog/_shared/lib/catppuccin.nix new file mode 100644 index 0000000..cb88056 --- /dev/null +++ b/catalog/_shared/lib/catppuccin.nix @@ -0,0 +1,32 @@ +# The 26 colors of a Catppuccin flavor drawn from a theme's palette, so that a template written +# for Catppuccin draws any theme by role. +palette: { + rosewater = palette.pink; + flamingo = palette.pink; + inherit (palette) + pink + red + yellow + green + blue + ; + mauve = palette.purple; + maroon = palette.red; + peach = palette.orange; + teal = palette.cyan; + sky = palette.cyan; + sapphire = palette.blue; + lavender = palette.accent; + text = palette.foreground; + subtext1 = palette.subtle; + subtext0 = palette.subtle; + overlay2 = palette.muted; + overlay1 = palette.muted; + overlay0 = palette.border; + surface2 = palette.border; + surface1 = palette.selection; + surface0 = palette.surface; + base = palette.background; + mantle = palette.background_dark; + crust = palette.background_darker; +} diff --git a/catalog/_shared/lib/textual.nix b/catalog/_shared/lib/textual.nix new file mode 100644 index 0000000..e0afe37 --- /dev/null +++ b/catalog/_shared/lib/textual.nix @@ -0,0 +1,8 @@ +# The keyword arguments of a Textual Theme that draws the guest's palette by role. +theme: +let + inherit (theme) palette; +in +''primary="${palette.accent}", secondary="${palette.blue}", accent="${palette.orange}", warning="${palette.yellow}", error="${palette.red}", success="${palette.green}", foreground="${palette.foreground}", background="${palette.background}", surface="${palette.surface}", panel="${palette.selection}", dark=${ + if theme.dark then "True" else "False" +}'' diff --git a/catalog/_shared/palette.toml b/catalog/_shared/palette.toml deleted file mode 100644 index c88983c..0000000 --- a/catalog/_shared/palette.toml +++ /dev/null @@ -1,115 +0,0 @@ -# Catppuccin palette v1.8.0 (MIT): https://github.com/catppuccin/palette -# Copied from palette.json at 07d02aa110ef9eb7e7427afca5c73ba9cf7f8ebd, the revision that -# pkgs.catppuccin uses in the locked Nixpkgs; _shared/test.nix compares the two. - -[latte] -rosewater = "#dc8a78" -flamingo = "#dd7878" -pink = "#ea76cb" -mauve = "#8839ef" -red = "#d20f39" -maroon = "#e64553" -peach = "#fe640b" -yellow = "#df8e1d" -green = "#40a02b" -teal = "#179299" -sky = "#04a5e5" -sapphire = "#209fb5" -blue = "#1e66f5" -lavender = "#7287fd" -text = "#4c4f69" -subtext1 = "#5c5f77" -subtext0 = "#6c6f85" -overlay2 = "#7c7f93" -overlay1 = "#8c8fa1" -overlay0 = "#9ca0b0" -surface2 = "#acb0be" -surface1 = "#bcc0cc" -surface0 = "#ccd0da" -base = "#eff1f5" -mantle = "#e6e9ef" -crust = "#dce0e8" - -[frappe] -rosewater = "#f2d5cf" -flamingo = "#eebebe" -pink = "#f4b8e4" -mauve = "#ca9ee6" -red = "#e78284" -maroon = "#ea999c" -peach = "#ef9f76" -yellow = "#e5c890" -green = "#a6d189" -teal = "#81c8be" -sky = "#99d1db" -sapphire = "#85c1dc" -blue = "#8caaee" -lavender = "#babbf1" -text = "#c6d0f5" -subtext1 = "#b5bfe2" -subtext0 = "#a5adce" -overlay2 = "#949cbb" -overlay1 = "#838ba7" -overlay0 = "#737994" -surface2 = "#626880" -surface1 = "#51576d" -surface0 = "#414559" -base = "#303446" -mantle = "#292c3c" -crust = "#232634" - -[macchiato] -rosewater = "#f4dbd6" -flamingo = "#f0c6c6" -pink = "#f5bde6" -mauve = "#c6a0f6" -red = "#ed8796" -maroon = "#ee99a0" -peach = "#f5a97f" -yellow = "#eed49f" -green = "#a6da95" -teal = "#8bd5ca" -sky = "#91d7e3" -sapphire = "#7dc4e4" -blue = "#8aadf4" -lavender = "#b7bdf8" -text = "#cad3f5" -subtext1 = "#b8c0e0" -subtext0 = "#a5adcb" -overlay2 = "#939ab7" -overlay1 = "#8087a2" -overlay0 = "#6e738d" -surface2 = "#5b6078" -surface1 = "#494d64" -surface0 = "#363a4f" -base = "#24273a" -mantle = "#1e2030" -crust = "#181926" - -[mocha] -rosewater = "#f5e0dc" -flamingo = "#f2cdcd" -pink = "#f5c2e7" -mauve = "#cba6f7" -red = "#f38ba8" -maroon = "#eba0ac" -peach = "#fab387" -yellow = "#f9e2af" -green = "#a6e3a1" -teal = "#94e2d5" -sky = "#89dceb" -sapphire = "#74c7ec" -blue = "#89b4fa" -lavender = "#b4befe" -text = "#cdd6f4" -subtext1 = "#bac2de" -subtext0 = "#a6adc8" -overlay2 = "#9399b2" -overlay1 = "#7f849c" -overlay0 = "#6c7086" -surface2 = "#585b70" -surface1 = "#45475a" -surface0 = "#313244" -base = "#1e1e2e" -mantle = "#181825" -crust = "#11111b" diff --git a/catalog/_shared/test/theme-tests.nix b/catalog/_shared/test/theme-tests.nix index b3ef5cd..e6ad3fd 100644 --- a/catalog/_shared/test/theme-tests.nix +++ b/catalog/_shared/test/theme-tests.nix @@ -1,11 +1,18 @@ { evalSystem, pkgs }: let - palettes = builtins.fromTOML (builtins.readFile ../palette.toml); + themes = builtins.fromTOML (builtins.readFile ../themes.toml); terminal = builtins.fromTOML (builtins.readFile ../ansi.toml); - flavors = builtins.attrNames palettes; - colors = builtins.attrNames palettes.mocha; + names = builtins.attrNames themes; + colorsOf = + name: + removeAttrs themes.${name} [ + "dark" + "title" + "homepage" + ]; + roles = builtins.attrNames (colorsOf "catppuccin-mocha"); theme = modules: (evalSystem modules).lmx.capabilities.theme; - chosen = flavor: theme [ { lmx.capabilities.theme.flavor = flavor; } ]; + chosen = name: theme [ { lmx.capabilities.theme.name = name; } ]; force = { config, ... }: { @@ -23,47 +30,52 @@ in let default = theme [ ]; in - default.flavor == "system" && default.palette == palettes.mocha && builtins.length colors == 26; + default.name == "system" + && default.palette == colorsOf "catppuccin-mocha" + && default.dark + && default.colors == default.ansi + && builtins.length roles == 18; themeTerminal = let - inherit ((theme [ ]).ansi) blue text base; + inherit ((theme [ ]).ansi) blue foreground background; in - builtins.attrNames terminal == colors + builtins.attrNames terminal == roles && builtins.all (color: color == "default" || builtins.isInt color && color >= 0 && color <= 15) ( builtins.attrValues terminal ) && blue == 4 - && text == null - && base == null - && (chosen "latte").ansi == (theme [ ]).ansi; - themeFlavors = - flavors == [ - "frappe" - "latte" - "macchiato" - "mocha" - ] - && (chosen "system").palette == palettes.mocha + && foreground == null + && background == null + && (chosen "tokyo-night").ansi == (theme [ ]).ansi; + themeNames = + builtins.length names == 10 + && (chosen "system").palette == colorsOf "catppuccin-mocha" && builtins.all ( - flavor: - (chosen flavor).palette == palettes.${flavor} && builtins.attrNames palettes.${flavor} == colors - ) flavors; + name: + let + picked = chosen name; + in + picked.palette == colorsOf name + && picked.colors == picked.palette + && picked.dark == themes.${name}.dark + && builtins.attrNames (colorsOf name) == roles + ) names; }; fails = { - unknownFlavor = { + unknownTheme = { modules = [ - { lmx.capabilities.theme.flavor = "espresso"; } + { lmx.capabilities.theme.name = "espresso"; } force ]; - message = "lmx.capabilities.theme.flavor' is not of type"; + message = "lmx.capabilities.theme.name' is not of type"; }; - conflictingFlavors = { + conflictingThemes = { modules = [ - { lmx.capabilities.theme.flavor = "latte"; } - { lmx.capabilities.theme.flavor = "mocha"; } + { lmx.capabilities.theme.name = "nord"; } + { lmx.capabilities.theme.name = "dracula"; } force ]; - message = "lmx.capabilities.theme.flavor' has conflicting definition values"; + message = "lmx.capabilities.theme.name' has conflicting definition values"; }; readOnlyPalette = { modules = [ @@ -84,12 +96,16 @@ in pkgs.runCommand "shared-theme-palette" { nativeBuildInputs = [ pkgs.jq ]; - copied = builtins.toJSON palettes; + copied = builtins.toJSON (colorsOf "catppuccin-mocha"); passAsFile = [ "copied" ]; } '' - jq -S 'del(.version) | map_values(.colors | map_values(.hex))' \ - ${pkgs.catppuccin}/palette/palette.json > source.json + jq -S '.mocha.colors | map_values(.hex) | { + background: .base, background_dark: .mantle, background_darker: .crust, + surface: .surface0, selection: .surface1, border: .surface2, muted: .overlay2, + subtle: .subtext0, foreground: .text, accent: .mauve, red: .red, orange: .peach, + yellow: .yellow, green: .green, cyan: .teal, blue: .blue, purple: .mauve, pink: .pink + }' ${pkgs.catppuccin}/palette/palette.json > source.json jq -S . "$copiedPath" > copied.json diff -u source.json copied.json touch "$out" diff --git a/catalog/_shared/theme.nix b/catalog/_shared/theme.nix index 48d73cc..4c5246b 100644 --- a/catalog/_shared/theme.nix +++ b/catalog/_shared/theme.nix @@ -1,47 +1,74 @@ # Colors of the guest, chosen once in the declaration and read by modules and the platform. # -# Identity: one theme per system. `flavor` is `system`, the terminal's own colors and background, -# or a Catppuccin flavor. `palette` is the flavor's table in palette.toml, and `ansi` the terminal -# colors in ansi.toml that stand for the same names; both are read-only. -# Merge: `flavor` holds one value. Equal definitions agree; different definitions at the same +# Identity: one theme per system. `name` is `system`, the terminal's own colors and background, +# or a table of themes.toml. `palette` holds the theme's colors by role, `dark` tells whether they +# sit on a dark background, `ansi` holds the terminal colors in ansi.toml that stand for the same +# roles, and `colors` holds what a module draws with; all four are read-only. +# Merge: `name` holds one value. Equal definitions agree; different definitions at the same # priority fail evaluation. -# Conflicts: modules read the theme and never set it. The user chooses the flavor. +# Conflicts: modules read the theme and never set it. The user chooses the theme. { config, lib, ... }: let - palettes = builtins.fromTOML (builtins.readFile ./palette.toml); + themes = builtins.fromTOML (builtins.readFile ./themes.toml); terminal = lib.mapAttrs (_: color: if color == "default" then null else color) ( builtins.fromTOML (builtins.readFile ./ansi.toml) ); - inherit (config.lmx.capabilities.theme) flavor; + inherit (config.lmx.capabilities.theme) name; + system = name == "system"; + chosen = themes.${if system then "catppuccin-mocha" else name}; + palette = removeAttrs chosen [ + "dark" + "title" + "homepage" + ]; in { options.lmx.capabilities.theme = { - flavor = lib.mkOption { - type = lib.types.enum ([ "system" ] ++ builtins.attrNames palettes); + name = lib.mkOption { + type = lib.types.enum ([ "system" ] ++ builtins.attrNames themes); default = "system"; description = '' - Colors of the guest: system, the terminal's own colors and background, or a Catppuccin - flavor: latte, frappe, macchiato or mocha. + Colors of the guest: system, the terminal's own colors and background, or a theme of + themes.toml, such as tokyo-night. ''; }; palette = lib.mkOption { type = lib.types.attrsOf lib.types.str; readOnly = true; - default = palettes.${if flavor == "system" then "mocha" else flavor}; - defaultText = lib.literalMD "the table of `flavor` in `palette.toml`, Mocha's for `system`"; + default = palette; + defaultText = lib.literalMD "the colors of `name` in `themes.toml`, catppuccin-mocha's for `system`"; description = '' - Colors of the flavor by name, such as `blue`, as `#rrggbb`. With system they are Mocha's, - for a tool that knows only explicit colors. + Colors of the theme by role, such as `accent` or `red`, as `#rrggbb`. With system they are + catppuccin-mocha's, for a tool that knows only explicit colors. ''; }; + dark = lib.mkOption { + type = lib.types.bool; + readOnly = true; + default = chosen.dark; + defaultText = lib.literalMD "`dark` of `name` in `themes.toml`, catppuccin-mocha's for `system`"; + description = "Whether the palette's colors sit on a dark background."; + }; ansi = lib.mkOption { type = lib.types.attrsOf (lib.types.nullOr (lib.types.ints.between 0 15)); readOnly = true; default = terminal; defaultText = lib.literalMD "the table in `ansi.toml`"; description = '' - The terminal color that stands for each name of `palette` with the system flavor: one of - the terminal's 16 colors, or null for its default foreground or background. + The terminal color that stands for each role of `palette` with system: one of the + terminal's 16 colors, or null for its default foreground or background. + ''; + }; + colors = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.nullOr (lib.types.either lib.types.str (lib.types.ints.between 0 15)) + ); + readOnly = true; + default = if system then terminal else palette; + defaultText = lib.literalMD "`ansi` for `system`, `palette` otherwise"; + description = '' + The color a module draws each role with: the palette's `#rrggbb`, or with system the + terminal color of `ansi`. ''; }; }; diff --git a/catalog/_shared/themes.toml b/catalog/_shared/themes.toml new file mode 100644 index 0000000..41422d1 --- /dev/null +++ b/catalog/_shared/themes.toml @@ -0,0 +1,246 @@ +# The guest's themes. Each table holds `title`, `homepage`, `dark`, true for a dark background, and +# the 18 colors that modules read by role. The values come from the theme's own Neovim colorscheme +# in the locked Nixpkgs and its published palette; a role the theme has no color for takes its +# nearest one. + +# catppuccin-nvim, colorscheme catppuccin-mocha. +[catppuccin-mocha] +title = "Catppuccin Mocha" +homepage = "https://catppuccin.com/palette/" +dark = true +background = "#1e1e2e" +background_dark = "#181825" +background_darker = "#11111b" +surface = "#313244" +selection = "#45475a" +border = "#585b70" +muted = "#9399b2" +subtle = "#a6adc8" +foreground = "#cdd6f4" +accent = "#cba6f7" +red = "#f38ba8" +orange = "#fab387" +yellow = "#f9e2af" +green = "#a6e3a1" +cyan = "#94e2d5" +blue = "#89b4fa" +purple = "#cba6f7" +pink = "#f5c2e7" + +# tokyonight.nvim, colorscheme tokyonight-night. +[tokyo-night] +title = "Tokyo Night" +homepage = "https://github.com/folke/tokyonight.nvim" +dark = true +background = "#1a1b26" +background_dark = "#16161e" +background_darker = "#0c0e14" +surface = "#292e42" +selection = "#283457" +border = "#3b4261" +muted = "#565f89" +subtle = "#a9b1d6" +foreground = "#c0caf5" +accent = "#7aa2f7" +red = "#f7768e" +orange = "#ff9e64" +yellow = "#e0af68" +green = "#9ece6a" +cyan = "#7dcfff" +blue = "#7aa2f7" +purple = "#bb9af7" +pink = "#ff007c" + +# dracula.nvim, colorscheme dracula; Dracula has no blue, and its terminal blue is its purple. +[dracula] +title = "Dracula" +homepage = "https://draculatheme.com/" +dark = true +background = "#282a36" +background_dark = "#21222c" +background_darker = "#191a21" +surface = "#44475a" +selection = "#44475a" +border = "#6272a4" +muted = "#6272a4" +subtle = "#abb2bf" +foreground = "#f8f8f2" +accent = "#bd93f9" +red = "#ff5555" +orange = "#ffb86c" +yellow = "#f1fa8c" +green = "#50fa7b" +cyan = "#8be9fd" +blue = "#bd93f9" +purple = "#bd93f9" +pink = "#ff79c6" + +# nord.nvim, colorscheme nord. Nord has no darker background or secondary text: the two darker +# backgrounds are nord0 mixed 25% and 45% with black, and subtle is half nord4, half the comment gray. +[nord] +title = "Nord" +homepage = "https://www.nordtheme.com/" +dark = true +background = "#2e3440" +background_dark = "#232730" +background_darker = "#191d23" +surface = "#3b4252" +selection = "#434c5e" +border = "#4c566a" +muted = "#616e88" +subtle = "#9da6b9" +foreground = "#d8dee9" +accent = "#88c0d0" +red = "#bf616a" +orange = "#d08770" +yellow = "#ebcb8b" +green = "#a3be8c" +cyan = "#88c0d0" +blue = "#81a1c1" +purple = "#b48ead" +pink = "#b48ead" + +# gruvbox.nvim, colorscheme gruvbox with a dark background. +[gruvbox-dark] +title = "Gruvbox Dark" +homepage = "https://github.com/morhetz/gruvbox" +dark = true +background = "#282828" +background_dark = "#1d2021" +background_darker = "#1d2021" +surface = "#3c3836" +selection = "#665c54" +border = "#504945" +muted = "#928374" +subtle = "#a89984" +foreground = "#ebdbb2" +accent = "#fabd2f" +red = "#fb4934" +orange = "#fe8019" +yellow = "#fabd2f" +green = "#b8bb26" +cyan = "#8ec07c" +blue = "#83a598" +purple = "#d3869b" +pink = "#d3869b" + +# rose-pine, colorscheme rose-pine-main. +[rose-pine] +title = "Rosé Pine" +homepage = "https://rosepinetheme.com/" +dark = true +background = "#191724" +background_dark = "#16141f" +background_darker = "#16141f" +surface = "#1f1d2e" +selection = "#403d52" +border = "#524f67" +muted = "#6e6a86" +subtle = "#908caa" +foreground = "#e0def4" +accent = "#ebbcba" +red = "#eb6f92" +orange = "#f6c177" +yellow = "#f6c177" +green = "#95b1ac" +cyan = "#9ccfd8" +blue = "#31748f" +purple = "#c4a7e7" +pink = "#ebbcba" + +# kanagawa.nvim, colorscheme kanagawa-wave. +[kanagawa-wave] +title = "Kanagawa Wave" +homepage = "https://github.com/rebelot/kanagawa.nvim" +dark = true +background = "#1f1f28" +background_dark = "#16161d" +background_darker = "#16161d" +surface = "#2a2a37" +selection = "#223249" +border = "#54546d" +muted = "#727169" +subtle = "#c8c093" +foreground = "#dcd7ba" +accent = "#7e9cd8" +red = "#e46876" +orange = "#ffa066" +yellow = "#e6c384" +green = "#98bb6c" +cyan = "#7aa89f" +blue = "#7e9cd8" +purple = "#957fb8" +pink = "#d27e99" + +# everforest, colorscheme everforest with a dark background and medium contrast. +[everforest-dark] +title = "Everforest Dark" +homepage = "https://github.com/sainnhe/everforest" +dark = true +background = "#2d353b" +background_dark = "#232a2e" +background_darker = "#232a2e" +surface = "#343f44" +selection = "#543a48" +border = "#4f585e" +muted = "#859289" +subtle = "#9da9a0" +foreground = "#d3c6aa" +accent = "#a7c080" +red = "#e67e80" +orange = "#e69875" +yellow = "#dbbc7f" +green = "#a7c080" +cyan = "#83c092" +blue = "#7fbbb3" +purple = "#d699b6" +pink = "#d699b6" + +# nvim-solarized-lua, colorscheme solarized with a light background; its selection reverses the +# text, so selection is the light tan of solarized.nvim. +[solarized-light] +title = "Solarized Light" +homepage = "https://ethanschoonover.com/solarized/" +dark = false +background = "#fdf6e3" +background_dark = "#eee8d5" +background_darker = "#eee8d5" +surface = "#eee8d5" +selection = "#d3cfc1" +border = "#93a1a1" +muted = "#93a1a1" +subtle = "#657b83" +foreground = "#586e75" +accent = "#268bd2" +red = "#dc322f" +orange = "#cb4b16" +yellow = "#b58900" +green = "#859900" +cyan = "#2aa198" +blue = "#268bd2" +purple = "#6c71c4" +pink = "#d33682" + +# github-nvim-theme, colorscheme github_light_default, with the Primer colors it is built from. +[github-light] +title = "GitHub Light" +homepage = "https://primer.style/" +dark = false +background = "#ffffff" +background_dark = "#f6f8fa" +background_darker = "#eaeef2" +surface = "#eaeef2" +selection = "#dae9f9" +border = "#d0d7de" +muted = "#6e7781" +subtle = "#656d76" +foreground = "#1f2328" +accent = "#0969da" +red = "#d1242f" +orange = "#bc4c00" +yellow = "#9a6700" +green = "#1a7f37" +cyan = "#1b7c83" +blue = "#0969da" +purple = "#8250df" +pink = "#bf3989" diff --git a/catalog/graph.json b/catalog/graph.json index 885697e..c0a39f5 100644 --- a/catalog/graph.json +++ b/catalog/graph.json @@ -3,7 +3,7 @@ { "id": "theme", "name": "`[theme]`", - "text": "One `[theme]` in `limanix.toml` colors the editor, the prompt, tmux, Lazygit, Yazi, bat, delta, Harlequin and Posting.", + "text": "One `[theme]` in `limanix.toml` colors the editor, the prompt, tmux, Lazygit, Yazi, bat, delta, Harlequin and Posting by the same roles.", "kind": null, "module": null, "with": [] @@ -251,7 +251,7 @@ { "id": "posting", "name": "Posting", - "text": "A terminal HTTP client in the Catppuccin flavor of the theme; Mocha with `system`.", + "text": "A terminal HTTP client in the colors of the theme; catppuccin-mocha's with `system`.", "kind": "modules", "module": "posting", "with": ["theme"] diff --git a/catalog/modules/harlequin/README.md b/catalog/modules/harlequin/README.md index 62c6853..e094fe2 100644 --- a/catalog/modules/harlequin/README.md +++ b/catalog/modules/harlequin/README.md @@ -53,9 +53,9 @@ profile discovery and precedence. ## Theme -The default theme is Catppuccin in the guest's flavor. Harlequin refuses -Textual's terminal theme. With the default `system` flavor it stays Catppuccin -Mocha, with its own background. +The default theme, `limanix`, draws the palette of the guest's theme with +Textual. Harlequin refuses Textual's terminal theme, so with the default +`system` theme it draws catppuccin-mocha's colors, with their own background. ## Configuration and integration diff --git a/catalog/modules/harlequin/default.nix b/catalog/modules/harlequin/default.nix index 1c8c588..ee24c09 100644 --- a/catalog/modules/harlequin/default.nix +++ b/catalog/modules/harlequin/default.nix @@ -3,14 +3,10 @@ imports = [ ./help.nix ]; environment.systemPackages = [ + # Harlequin refuses Textual's terminal theme, so the system theme draws the palette's colors. (import ./package.nix { inherit pkgs; - # Harlequin refuses Textual's terminal theme, so the system flavor keeps Mocha. - flavor = - if config.lmx.capabilities.theme.flavor == "system" then - "mocha" - else - config.lmx.capabilities.theme.flavor; + inherit (config.lmx.capabilities) theme; }) ]; } diff --git a/catalog/modules/harlequin/package.nix b/catalog/modules/harlequin/package.nix index c62e503..7f85860 100644 --- a/catalog/modules/harlequin/package.nix +++ b/catalog/modules/harlequin/package.nix @@ -1,4 +1,4 @@ -{ pkgs, flavor }: +{ pkgs, theme }: (pkgs.harlequin.override { withPostgresAdapter = true; withBigQueryAdapter = false; @@ -6,6 +6,16 @@ (previous: { postPatch = (previous.postPatch or "") + '' substituteInPlace src/harlequin/cli.py \ - --replace-fail 'DEFAULT_THEME = "harlequin"' 'DEFAULT_THEME = "catppuccin-${flavor}"' + --replace-fail 'DEFAULT_THEME = "harlequin"' 'DEFAULT_THEME = "limanix"' + substituteInPlace src/harlequin/app_base.py \ + --replace-fail 'from harlequin.colors import HARLEQUIN_TEXTUAL_THEME' \ + 'from harlequin.colors import HARLEQUIN_TEXTUAL_THEME, LIMANIX_TEXTUAL_THEME' \ + --replace-fail 'self.register_theme(HARLEQUIN_TEXTUAL_THEME)' \ + $'self.register_theme(HARLEQUIN_TEXTUAL_THEME)\n self.register_theme(LIMANIX_TEXTUAL_THEME)' + cat >> src/harlequin/colors.py <<'EOF' + + LIMANIX_TEXTUAL_THEME = TextualTheme(name="limanix", ${import ../../_shared/lib/textual.nix theme}) + VALID_THEMES.update({"limanix": LIMANIX_TEXTUAL_THEME}) + EOF ''; }) diff --git a/catalog/modules/harlequin/test.nix b/catalog/modules/harlequin/test.nix index a824d9c..ee64ef0 100644 --- a/catalog/modules/harlequin/test.nix +++ b/catalog/modules/harlequin/test.nix @@ -6,31 +6,33 @@ let helpers = import ../../_shared/test/helpers.nix { inherit evalSystem pkgs lib; }; defaultConfiguration = helpers.evaluate [ ./default.nix ]; - latte = helpers.evaluate [ + themed = helpers.evaluate [ ./default.nix - { lmx.capabilities.theme.flavor = "latte"; } + { lmx.capabilities.theme.name = "dracula"; } ]; + inherit (defaultConfiguration.config.lmx.capabilities) theme; package = - flavor: + configuration: import ./package.nix { - inherit pkgs flavor; + inherit pkgs; + inherit (configuration.config.lmx.capabilities) theme; }; in { eval.defaults = helpers.verify "the module-owned adapter and theme package is installed" ( - helpers.installedAsDeclared defaultConfiguration (package "mocha") + helpers.installedAsDeclared defaultConfiguration (package defaultConfiguration) && import ./test/check.nix { hasPackage = helpers.installed defaultConfiguration; - inherit pkgs; + inherit pkgs theme; } ) defaultConfiguration; - eval.latte = - helpers.verify "the default theme follows the guest's flavor" - (helpers.installedAsDeclared latte (package "latte")) - latte; + eval.themed = + helpers.verify "the default theme follows the guest's theme" + (helpers.installedAsDeclared themed (package themed)) + themed; run = import ./test/smoke.nix { profile = helpers.profileFor defaultConfiguration; - inherit pkgs; + inherit pkgs theme; }; - builds.harlequin = package "mocha"; + builds.harlequin = package defaultConfiguration; } diff --git a/catalog/modules/harlequin/test/check.nix b/catalog/modules/harlequin/test/check.nix index 9abebb3..0a548d2 100644 --- a/catalog/modules/harlequin/test/check.nix +++ b/catalog/modules/harlequin/test/check.nix @@ -1,7 +1,7 @@ -{ pkgs, hasPackage, ... }: -hasPackage ( - import ../package.nix { - inherit pkgs; - flavor = "mocha"; - } -) +{ + pkgs, + hasPackage, + theme, + ... +}: +hasPackage (import ../package.nix { inherit pkgs theme; }) diff --git a/catalog/modules/harlequin/test/smoke.nix b/catalog/modules/harlequin/test/smoke.nix index 02dcb69..53c979d 100644 --- a/catalog/modules/harlequin/test/smoke.nix +++ b/catalog/modules/harlequin/test/smoke.nix @@ -1,9 +1,11 @@ -{ pkgs, profile, ... }: +{ + pkgs, + profile, + theme, + ... +}: let - package = import ../package.nix { - inherit pkgs; - flavor = "mocha"; - }; + package = import ../package.nix { inherit pkgs theme; }; python = pkgs.lib.last package.propagatedBuildInputs; in { diff --git a/catalog/modules/harlequin/test/theme-smoke.py b/catalog/modules/harlequin/test/theme-smoke.py index c533872..a3611f9 100644 --- a/catalog/modules/harlequin/test/theme-smoke.py +++ b/catalog/modules/harlequin/test/theme-smoke.py @@ -22,7 +22,7 @@ def check_theme(arguments, expected): with patch.object(cli.Harlequin, "run", capture_theme): - check_theme([], "catppuccin-mocha") + check_theme([], "limanix") config = Path(os.environ["XDG_CONFIG_HOME"]) / "harlequin" / "config.toml" config.write_text( 'default_profile = "personal"\n[profiles.personal]\ntheme = "harlequin"\n' diff --git a/catalog/modules/posting/README.md b/catalog/modules/posting/README.md index 14c9ab5..a878e04 100644 --- a/catalog/modules/posting/README.md +++ b/catalog/modules/posting/README.md @@ -48,12 +48,12 @@ settings. ## Theme -The module sets `POSTING_THEME` to follow `[theme]` in `limanix.toml`. A -Catppuccin flavor uses `catppuccin-`, such as `catppuccin-latte`. +The module adds a `limanix` theme to Posting, drawn from the palette of the +guest's theme in `[theme]` of `limanix.toml`, and sets `POSTING_THEME=limanix`. Posting fails to start with Textual's terminal themes `ansi-dark` and -`ansi-light`. With the default `system` flavor it stays `catppuccin-mocha`, with -its own background. For a light terminal, add this to your existing `[env]` -table: +`ansi-light`, so with the default `system` theme it draws catppuccin-mocha's +colors, with their own background. For a light terminal, choose a light theme in +`[theme]`, or add one of Posting's own themes to your existing `[env]` table: ```toml [env] @@ -68,7 +68,7 @@ Personal settings are in `~/.config/posting/config.yaml`, following | Boundary | Contract | | -- | -- | -| Settings | `environment.variables.POSTING_THEME` defaults to `catppuccin-mocha` with the system flavor and to `catppuccin-` with a Catppuccin flavor; `[env]` and personal YAML may override it | +| Settings | `environment.variables.POSTING_THEME` defaults to `limanix`, drawn from the guest's palette; `[env]` and personal YAML may override it | | Personal state | `~/.config/posting/config.yaml` and request collections, respecting XDG configuration | | Integration | Needs no other module; collections live with your projects | | Services and capabilities | No API server or language-support declarations | diff --git a/catalog/modules/posting/default.nix b/catalog/modules/posting/default.nix index fa028b4..18b270c 100644 --- a/catalog/modules/posting/default.nix +++ b/catalog/modules/posting/default.nix @@ -5,14 +5,19 @@ ... }: let - inherit (config.lmx.capabilities.theme) flavor; + # Posting crashes on Textual's terminal themes, so the system theme draws the palette's colors. + package = pkgs.posting.overridePythonAttrs (previous: { + postPatch = (previous.postPatch or "") + '' + cat >> src/posting/themes.py <<'EOF' + + BUILTIN_THEMES["limanix"] = TextualTheme(name="limanix", ${import ../../_shared/lib/textual.nix config.lmx.capabilities.theme}) + EOF + ''; + }); in { imports = [ ./help.nix ]; - environment.systemPackages = [ pkgs.posting ]; - # Posting crashes on Textual's terminal themes; the system flavor keeps Mocha. - environment.variables.POSTING_THEME = lib.mkDefault ( - if flavor == "system" then "catppuccin-mocha" else "catppuccin-${flavor}" - ); + environment.systemPackages = [ package ]; + environment.variables.POSTING_THEME = lib.mkDefault "limanix"; } diff --git a/catalog/modules/posting/graph.toml b/catalog/modules/posting/graph.toml index d6a3485..0d8b73d 100644 --- a/catalog/modules/posting/graph.toml +++ b/catalog/modules/posting/graph.toml @@ -1,4 +1,4 @@ [posting] name = "Posting" -text = "A terminal HTTP client in the Catppuccin flavor of the theme; Mocha with `system`." +text = "A terminal HTTP client in the colors of the theme; catppuccin-mocha's with `system`." with = ["theme"] diff --git a/catalog/modules/posting/test.nix b/catalog/modules/posting/test.nix index 6b4784a..98adec3 100644 --- a/catalog/modules/posting/test.nix +++ b/catalog/modules/posting/test.nix @@ -7,24 +7,29 @@ let helpers = import ../../_shared/test/helpers.nix { inherit evalSystem pkgs lib; }; inherit (helpers) evaluate verify profileFor; defaultConfiguration = evaluate [ ./default.nix ]; - latte = evaluate [ + themed = evaluate [ ./default.nix - { lmx.capabilities.theme.flavor = "latte"; } + { lmx.capabilities.theme.name = "nord"; } ]; + posting = + configuration: + lib.findFirst ( + package: (package.pname or "") == "posting" + ) null configuration.config.environment.systemPackages; in { eval = { - defaults = verify "Posting is installed with Catppuccin Mocha by default" (import ./test/check.nix { + defaults = verify "Posting is installed with the guest's theme" (import ./test/check.nix { inherit (defaultConfiguration) config; - hasPackage = helpers.installed defaultConfiguration; - inherit pkgs; }) defaultConfiguration; - latte = verify "the Posting theme follows the guest's flavor" ( - latte.config.environment.variables.POSTING_THEME == "catppuccin-latte" - ) latte; + themed = + verify "the Posting theme follows the guest's theme" + (lib.hasInfix themed.config.lmx.capabilities.theme.palette.accent (posting themed).postPatch) + themed; }; run.commands = import ./test/smoke.nix { inherit pkgs; profile = profileFor defaultConfiguration; }; + builds.posting = posting defaultConfiguration; } diff --git a/catalog/modules/posting/test/check.nix b/catalog/modules/posting/test/check.nix index 82fa9ac..0936bcb 100644 --- a/catalog/modules/posting/test/check.nix +++ b/catalog/modules/posting/test/check.nix @@ -1,7 +1,3 @@ -{ - config, - pkgs, - hasPackage, - ... -}: -hasPackage pkgs.posting && config.environment.variables.POSTING_THEME == "catppuccin-mocha" +{ config, ... }: +builtins.any (package: (package.pname or "") == "posting") config.environment.systemPackages +&& config.environment.variables.POSTING_THEME == "limanix" diff --git a/catalog/shell/astronvim/README.md b/catalog/shell/astronvim/README.md index c4f5634..360bbeb 100644 --- a/catalog/shell/astronvim/README.md +++ b/catalog/shell/astronvim/README.md @@ -116,11 +116,27 @@ stay in the Nix store. Additional user plugins can be installed explicitly with `:Lazy install`; startup does not download missing plugins. XDG configuration, data, state, and cache directory overrides are respected. -The bundled colorscheme is Catppuccin. With the default `system` flavor its -background is transparent. The terminal's background shows through. The light or -dark background that Neovim detects in the terminal chooses Latte or Mocha for -the text. A Catppuccin flavor in `[theme]` of `limanix.toml` uses that flavor -with its own background. For another theme, assign AstroUI's `colorscheme` in a +Each theme in `[theme]` of `limanix.toml` draws the editor with its own Neovim +colorscheme from the catalog's Nixpkgs, on the theme's background: + +| Theme | Plugin | Colorscheme | +| -- | -- | -- | +| `catppuccin-mocha` | catppuccin-nvim | `catppuccin-mocha` | +| `tokyo-night` | tokyonight.nvim | `tokyonight-night` | +| `dracula` | dracula.nvim | `dracula` | +| `nord` | nord.nvim | `nord` | +| `gruvbox-dark` | gruvbox.nvim | `gruvbox` | +| `rose-pine` | rose-pine | `rose-pine-main` | +| `kanagawa-wave` | kanagawa.nvim | `kanagawa-wave` | +| `everforest-dark` | everforest | `everforest` | +| `solarized-light` | nvim-solarized-lua | `solarized` | +| `github-light` | github-nvim-theme | `github_light_default` | + +With the default `system` theme the colorscheme is Catppuccin with a transparent +background. The terminal's background shows through. The light or dark +background that Neovim detects in the terminal chooses Latte or Mocha for the +text. A theme without a colorscheme of its own draws its palette with +Catppuccin's. For another colorscheme, assign AstroUI's `colorscheme` in a personal Lazy specification: ```lua diff --git a/catalog/shell/astronvim/implementation.nix b/catalog/shell/astronvim/implementation.nix index fd4f193..9817c8b 100644 --- a/catalog/shell/astronvim/implementation.nix +++ b/catalog/shell/astronvim/implementation.nix @@ -5,7 +5,17 @@ version: lib, }: let + inherit (config.lmx.capabilities) theme; serverName = import ./server-name.nix; + native = (import ../neovim/colorschemes.nix pkgs).${theme.name} or null; + themeSettings = { + inherit (theme) name dark; + catppuccin = import ../../_shared/lib/catppuccin.nix theme.palette; + } + // lib.optionalAttrs (native != null) { + inherit (native) colorscheme; + plugin = "${pluginSources}/theme"; + }; packages = import ./packages.nix { inherit pkgs lib version; parsers = lib.unique ( @@ -31,6 +41,10 @@ let ++ lib.optional (!(packages.plugins ? "folke/lazy.nvim")) { name = "lazy.nvim"; path = pkgs.vimPlugins.lazy-nvim; + } + ++ lib.optional (native != null) { + name = "theme"; + path = native.plugin; }; sourceNames = map (entry: entry.name) sourceEntries; pluginSources = @@ -82,7 +96,7 @@ in else local paths = vim.json.decode(table.concat(vim.fn.readfile("${paths}"), "\n")) local setup = dofile("${./init.lua}") - setup(paths, ${builtins.toJSON config.lmx.capabilities.theme.flavor}) + setup(paths, vim.json.decode([==[${builtins.toJSON themeSettings}]==])) end ''; }; diff --git a/catalog/shell/astronvim/init.lua b/catalog/shell/astronvim/init.lua index 8750dff..e6eb2f1 100644 --- a/catalog/shell/astronvim/init.lua +++ b/catalog/shell/astronvim/init.lua @@ -66,7 +66,7 @@ local function use_mac_clipboard() } end -return function(paths, flavor) +return function(paths, theme) use_mac_clipboard() vim.opt.rtp:prepend(paths.lazy) vim.fn.mkdir(vim.fn.stdpath("state"), "p") @@ -84,24 +84,35 @@ return function(paths, flavor) for repository, directory in pairs(paths.plugins) do table.insert(specs, { repository, dir = directory, optional = true, build = false }) end - -- With the system flavor the terminal's background shows through, and the light or dark + -- With the system theme the terminal's background shows through, and the light or dark -- background that Neovim detects in the terminal chooses Latte or Mocha for the text. Neovim -- bundles a colorscheme of its own named catppuccin; catppuccin-nvim is the plugin's. - local system = flavor == "system" + local system = theme.name == "system" + local flavour = theme.dark and "mocha" or "latte" + if theme.colorscheme then + vim.o.background = theme.dark and "dark" or "light" + vim.list_extend(specs, { + { name = "theme", dir = theme.plugin, lazy = false, priority = 1000 }, + { "AstroNvim/astroui", opts = { colorscheme = theme.colorscheme } }, + }) + else + vim.list_extend(specs, { + { + "catppuccin/nvim", + name = "catppuccin", + dir = paths.catppuccin, + lazy = false, + priority = 1000, + opts = system and { + flavour = "auto", + background = { light = "latte", dark = "mocha" }, + transparent_background = true, + } or { flavour = flavour, color_overrides = { [flavour] = theme.catppuccin } }, + }, + { "AstroNvim/astroui", opts = { colorscheme = system and "catppuccin-nvim" or "catppuccin-" .. flavour } }, + }) + end vim.list_extend(specs, { - { - "catppuccin/nvim", - name = "catppuccin", - dir = paths.catppuccin, - lazy = false, - priority = 1000, - opts = system and { - flavour = "auto", - background = { light = "latte", dark = "mocha" }, - transparent_background = true, - } or { flavour = flavor }, - }, - { "AstroNvim/astroui", opts = { colorscheme = system and "catppuccin-nvim" or "catppuccin-" .. flavor } }, { "AstroNvim/astrocore", opts = function(_, opts) diff --git a/catalog/shell/astronvim/module.nix b/catalog/shell/astronvim/module.nix index c295985..660f0dd 100644 --- a/catalog/shell/astronvim/module.nix +++ b/catalog/shell/astronvim/module.nix @@ -33,6 +33,7 @@ in message = "astronvim: select one line"; } ]; + lmx.internal.neovim.colorscheme = false; } ] ++ map ( diff --git a/catalog/shell/astronvim/test.nix b/catalog/shell/astronvim/test.nix index 0a0712e..528467b 100644 --- a/catalog/shell/astronvim/test.nix +++ b/catalog/shell/astronvim/test.nix @@ -33,9 +33,9 @@ let entry ../../toolchains/rust/default.nix ]; - latte = evaluate [ + themed = evaluate [ entry - { lmx.capabilities.theme.flavor = "latte"; } + { lmx.capabilities.theme.name = "tokyo-night"; } ]; suppliedGopls = pkgs.writeShellScriptBin "gopls" '' test "$#" -ge 1 && test "$1" = --catalog-smoke || exit 64 @@ -120,10 +120,10 @@ let in { eval = lineTests.eval // { - theme = verify "the colorscheme follows the guest's flavor" ( - lib.hasInfix ''setup(paths, "latte")'' latte.config.programs.neovim.configure.customLuaRC - && lib.hasInfix ''setup(paths, "system")'' defaults.config.programs.neovim.configure.customLuaRC - ) latte; + theme = verify "the colorscheme follows the guest's theme" ( + lib.hasInfix ''"colorscheme":"tokyonight-night"'' themed.config.programs.neovim.configure.customLuaRC + && lib.hasInfix ''"name":"system"'' defaults.config.programs.neovim.configure.customLuaRC + ) themed; optionalProviders = verify "language providers remain optional" ( (capability defaults).tools == { } && (capability defaults).languages == { } diff --git a/catalog/shell/astronvim/test/jdtls-command.lua b/catalog/shell/astronvim/test/jdtls-command.lua index a53110d..4a3e4c0 100644 --- a/catalog/shell/astronvim/test/jdtls-command.lua +++ b/catalog/shell/astronvim/test/jdtls-command.lua @@ -19,7 +19,7 @@ local function configure(configuration) lazy = base .. "/lazy", astro = base .. "/astro", catppuccin = base .. "/catppuccin", parsers = base .. "/parsers", plugins = {}, servers = { jdtls = configuration, gopls = { cmd = other } }, - }, "system") + }, { name = "system", dark = true }) assert(vim.deep_equal(options.config.gopls.cmd, other), "non-Java server command changed") return options.config.jdtls end diff --git a/catalog/shell/astronvim/test/smoke.nix b/catalog/shell/astronvim/test/smoke.nix index e2f4caa..8baa02b 100644 --- a/catalog/shell/astronvim/test/smoke.nix +++ b/catalog/shell/astronvim/test/smoke.nix @@ -10,7 +10,7 @@ let assert(package.loaded.lazy, "bundled Lazy startup did not run") assert(vim.g.colors_name:match("^catppuccin"), "default Catppuccin theme did not load") assert(vim.api.nvim_get_hl(0, { name = "Normal" }).bg == nil, - "the system flavor hides the terminal's background: " + "the system theme hides the terminal's background: " .. vim.api.nvim_exec2("verbose highlight Normal", { output = true }).output) assert(require("lazy.core.config").plugins.AstroNvim, "AstroNvim is absent") for name, plugin in pairs(require("lazy.core.config").plugins) do diff --git a/catalog/shell/cli-tools/README.md b/catalog/shell/cli-tools/README.md index 8438d64..a6a47b6 100644 --- a/catalog/shell/cli-tools/README.md +++ b/catalog/shell/cli-tools/README.md @@ -78,9 +78,36 @@ Inside a Git repository, `git diff` and `git log -p` use delta when Git opens a pager. Interactive staging uses delta's color-only filter. The settings are written to the VM's system Git configuration; a repository or user Git configuration can override them. bat, and delta through it, highlight in the -colors of the guest's theme: with the default `system` flavor, `BAT_THEME=ansi` -uses the terminal's colors, and a Catppuccin flavor selects bat's theme of the -same name, such as `Catppuccin Mocha`. +colors of the guest's theme. With the default `system` theme, `BAT_THEME=ansi` +uses the terminal's colors. Another theme sets `BAT_THEME=limanix`, a bat theme +built from its palette, and `BAT_CACHE_PATH` to the bat cache that holds it; +delta reads the same cache, and its added and removed lines take the palette's +green and red. A personal `bat cache --build` needs a `BAT_CACHE_PATH` of your +own, for example in the `[env]` table. + +## Colors + +The other tools draw in the guest's theme too. With the default `system` theme +they keep the terminal's own colors: fzf runs with `--color=16`, btop with its +`TTY` theme, and the rest with their defaults. Another theme gives each the +roles of its palette: + +| Tool | Setting | +| -- | -- | +| fzf | `FZF_DEFAULT_OPTS` with the theme's `--color` | +| `ls`, eza, fd, dust and Zsh completion | `LS_COLORS`, generated with vivid | +| eza columns | `EZA_COLORS` | +| jq | `JQ_COLORS` | +| btop | Its default theme, `limanix`; a personal `btop.conf` keeps its own | +| Git status, branches and log decorations | `color.*` in the system Git configuration | +| `man` and `less` | `LESS_TERMCAP_*` and `GROFF_NO_SGR=1` | +| ripgrep | `RIPGREP_CONFIG_PATH` | +| tealdeer | `TEALDEER_CONFIG_DIR` | + +Your own value in the `[env]` table of `limanix.toml`, or in a startup file, +replaces any of these variables; a repository or user Git configuration replaces +the colors of Git. yq, xh and duf keep their own colors: they take no colors +that a module could set system-wide. ## Configuration and integration diff --git a/catalog/shell/cli-tools/default.nix b/catalog/shell/cli-tools/default.nix index e360a32..0e0c618 100644 --- a/catalog/shell/cli-tools/default.nix +++ b/catalog/shell/cli-tools/default.nix @@ -1,40 +1,23 @@ +{ pkgs, ... }: { - config, - pkgs, - lib, - ... -}: -let - inherit (config.lmx.capabilities.theme) flavor; -in -{ - imports = [ ./help.nix ]; + imports = [ + ./help.nix + ./theme.nix + ]; programs.git.enable = true; - # bat, and delta through it, highlight in the terminal's colors with the system flavor. - environment.variables.BAT_THEME = lib.mkDefault ( - if flavor == "system" then "ansi" else "Catppuccin ${lib.toSentenceCase flavor}" - ); - environment.systemPackages = [ pkgs.ripgrep pkgs.fd pkgs.fzf pkgs.bat pkgs.eza - pkgs.delta pkgs.jq pkgs.yq-go pkgs.xh - pkgs.btop pkgs.dust pkgs.duf pkgs.tealdeer ]; - - programs.git.config = { - core.pager = lib.mkDefault "${pkgs.delta}/bin/delta"; - interactive.diffFilter = lib.mkDefault "${pkgs.delta}/bin/delta --color-only"; - }; } diff --git a/catalog/shell/cli-tools/recolor.py b/catalog/shell/cli-tools/recolor.py new file mode 100644 index 0000000..ba2da97 --- /dev/null +++ b/catalog/shell/cli-tools/recolor.py @@ -0,0 +1,24 @@ +import json +import re +import sys + +theme, palette, colors = sys.argv[1:] +mocha = { + value["hex"].lower().lstrip("#"): name + for name, value in json.load(open(palette))["mocha"]["colors"].items() +} +target = {name: value.lstrip("#") for name, value in json.load(open(colors)).items()} + + +def replace(match): + value = match.group(1) or match.group(2) + color = target.get(mocha.get(value.lower()), value) + return "#" + color if match.group(1) else color + + +text = re.sub( + r"#([0-9A-Fa-f]{6})|(?Catppuccin Mocha", "limanix", 1)) diff --git a/catalog/shell/cli-tools/test.nix b/catalog/shell/cli-tools/test.nix index 20a632c..5e5f1a9 100644 --- a/catalog/shell/cli-tools/test.nix +++ b/catalog/shell/cli-tools/test.nix @@ -13,11 +13,14 @@ let programs.git.config.interactive.diffFilter = "cat"; } ]; - latte = helpers.evaluate [ + themed = helpers.evaluate [ ./default.nix - { lmx.capabilities.theme.flavor = "latte"; } + { lmx.capabilities.theme.name = "kanagawa-wave"; } ]; settings = record: builtins.head record.config.programs.git.config; + btop = lib.findFirst ( + package: (package.pname or "") == "btop" + ) null configuration.config.environment.systemPackages; defaults = settings configuration; userSettings = settings overridden; in @@ -35,22 +38,31 @@ in pkgs.jq pkgs.yq-go pkgs.xh - pkgs.btop pkgs.dust pkgs.duf pkgs.tealdeer - ]; + ] + && btop != null; gitDefaults = defaults.core.pager == "${pkgs.delta}/bin/delta" && defaults.interactive.diffFilter == "${pkgs.delta}/bin/delta --color-only"; gitOverrides = userSettings.core.pager == "cat" && userSettings.interactive.diffFilter == "cat"; themeColors = + let + variables = themed.config.environment.variables; + themedSettings = settings themed; + in configuration.config.environment.variables.BAT_THEME == "ansi" - && latte.config.environment.variables.BAT_THEME == "Catppuccin Latte"; + && !(configuration.config.environment.variables ? BAT_CACHE_PATH) + && variables.BAT_THEME == "limanix" + && lib.hasPrefix builtins.storeDir variables.BAT_CACHE_PATH + && themedSettings.core.pager != defaults.core.pager + && themedSettings.delta.plus-style == "syntax #2b2f2f"; }; run = import ./test/run.nix { inherit pkgs; inherit (configuration) config; profile = helpers.profileFor configuration; }; + builds.btop = btop; } diff --git a/catalog/shell/cli-tools/theme.nix b/catalog/shell/cli-tools/theme.nix new file mode 100644 index 0000000..8917a54 --- /dev/null +++ b/catalog/shell/cli-tools/theme.nix @@ -0,0 +1,305 @@ +{ + config, + pkgs, + lib, + ... +}: +let + inherit (config.lmx.capabilities) theme; + inherit (theme) palette; + system = theme.name == "system"; + catppuccin = import ../../_shared/lib/catppuccin.nix palette; + # A template written for Catppuccin Mocha, drawn in the guest's palette by role. + recolor = + name: template: + pkgs.runCommand name + { + nativeBuildInputs = [ pkgs.python3 ]; + colors = builtins.toJSON catppuccin; + passAsFile = [ "colors" ]; + } + '' + python3 ${./recolor.py} ${lib.escapeShellArg template} \ + ${pkgs.catppuccin}/palette/palette.json "$colorsPath" > "$out" + ''; + mocha = + port: + pkgs.catppuccin.override { + variant = "mocha"; + themeList = [ port ]; + }; + batCache = pkgs.runCommand "bat-theme-cache" { nativeBuildInputs = [ pkgs.bat ]; } '' + mkdir -p source/themes + ln -s ${recolor "limanix.tmTheme" "${mocha "bat"}/bat/Catppuccin Mocha.tmTheme"} source/themes/limanix.tmTheme + bat cache --build --source source --target "$out" + ''; + # delta reads bat's themes only from the bat directory of XDG_CACHE_HOME. + delta = + if system then + pkgs.delta + else + pkgs.symlinkJoin { + name = "delta-${pkgs.delta.version}"; + paths = [ pkgs.delta ]; + nativeBuildInputs = [ pkgs.makeWrapper ]; + postBuild = '' + mkdir -p "$out/share/limanix" + ln -s ${batCache} "$out/share/limanix/bat" + wrapProgram "$out/bin/delta" --set XDG_CACHE_HOME "$out/share/limanix" + ''; + }; + # btop has no setting outside the user's own btop.conf, so its default theme is patched. + btop = pkgs.btop.overrideAttrs (previous: { + postPatch = (previous.postPatch or "") + '' + substituteInPlace src/btop_config.cpp \ + --replace-fail '{"color_theme", "Default"}' '{"color_theme", "${ + if system then "TTY" else "limanix" + }"}' + ''; + postInstall = + (previous.postInstall or "") + + lib.optionalString (!system) '' + cp ${recolor "limanix.theme" "${mocha "btop"}/btop/catppuccin_mocha.theme"} "$out/share/btop/themes/limanix.theme" + ''; + }); + lsColors = pkgs.runCommand "ls-colors" { nativeBuildInputs = [ pkgs.vivid ]; } '' + vivid generate ${recolor "limanix.yml" "${pkgs.vivid.src}/themes/catppuccin-mocha.yml"} > "$out" + ''; + channels = + color: + map (start: lib.fromHexString (builtins.substring start 2 color)) [ + 1 + 3 + 5 + ]; + sgr = color: "38;2;" + lib.concatMapStringsSep ";" toString (channels color); + escape = builtins.fromJSON ''"\u001b"''; + blend = + color: amount: + "#" + + lib.concatMapStrings ( + pair: + lib.fixedWidthString 2 "0" ( + lib.toLower (lib.toHexString (builtins.floor (pair.fst * amount + pair.snd * (1 - amount) + 0.5))) + ) + ) (lib.zipLists (channels color) (channels palette.background)); + fzfColors = lib.concatStringsSep "," ( + lib.mapAttrsToList (key: color: "${key}:${color}") { + "bg+" = catppuccin.surface0; + bg = catppuccin.base; + spinner = catppuccin.rosewater; + hl = catppuccin.red; + fg = catppuccin.text; + header = catppuccin.red; + info = catppuccin.mauve; + pointer = catppuccin.rosewater; + marker = catppuccin.lavender; + "fg+" = catppuccin.text; + prompt = catppuccin.mauve; + "hl+" = catppuccin.red; + selected-bg = catppuccin.surface1; + border = catppuccin.surface0; + label = catppuccin.text; + } + ); + ezaColors = lib.concatStringsSep ":" ( + lib.mapAttrsToList (key: color: "${key}=${sgr color}") { + ur = palette.yellow; + uw = palette.red; + ux = palette.green; + ue = palette.green; + gr = palette.yellow; + gw = palette.red; + gx = palette.green; + tr = palette.yellow; + tw = palette.red; + tx = palette.green; + su = palette.purple; + sf = palette.purple; + xa = palette.muted; + sn = palette.green; + sb = palette.green; + df = palette.green; + ds = palette.green; + uu = palette.yellow; + uR = palette.red; + un = palette.subtle; + gu = palette.yellow; + gR = palette.red; + gn = palette.subtle; + lc = palette.red; + lm = palette.red; + ga = palette.green; + gm = palette.blue; + gd = palette.red; + gv = palette.yellow; + gt = palette.purple; + gi = palette.muted; + gc = palette.red; + xx = palette.muted; + da = palette.blue; + "in" = palette.purple; + bl = palette.cyan; + hd = palette.foreground; + lp = palette.cyan; + cc = palette.red; + } + ); + ripgrep = + color: + lib.concatMapStringsSep "," (start: "0x" + builtins.substring start 2 color) [ + 1 + 3 + 5 + ]; + ripgrepConfig = pkgs.writeText "ripgrep-colors" '' + --colors=path:fg:${ripgrep palette.purple} + --colors=line:fg:${ripgrep palette.green} + --colors=column:fg:${ripgrep palette.muted} + --colors=match:fg:${ripgrep palette.red} + --colors=match:style:bold + ''; + tealdeer = + color: + let + inherit (lib) elemAt; + values = channels color; + in + "{ rgb = { r = ${toString (elemAt values 0)}, g = ${toString (elemAt values 1)}, b = ${toString (elemAt values 2)} } }"; + tealdeerConfig = pkgs.writeTextDir "config.toml" '' + [style.description] + foreground = ${tealdeer palette.foreground} + + [style.command_name] + foreground = ${tealdeer palette.accent} + bold = true + + [style.example_text] + foreground = ${tealdeer palette.green} + + [style.example_code] + foreground = ${tealdeer palette.cyan} + + [style.example_variable] + foreground = ${tealdeer palette.cyan} + underline = true + ''; +in +{ + environment = { + systemPackages = [ + delta + btop + ]; + + variables = lib.mapAttrs (_: lib.mkDefault) ( + { + FZF_DEFAULT_OPTS = "--color=${if system then "16" else fzfColors}"; + } + // ( + # With the system theme bat, and delta through it, highlight in the terminal's colors. + if system then + { BAT_THEME = "ansi"; } + else + { + BAT_THEME = "limanix"; + BAT_CACHE_PATH = "${batCache}"; + EZA_COLORS = ezaColors; + JQ_COLORS = lib.concatMapStringsSep ":" (role: sgr palette.${role}) [ + "orange" + "orange" + "orange" + "orange" + "green" + "muted" + "muted" + "blue" + ]; + RIPGREP_CONFIG_PATH = "${ripgrepConfig}"; + TEALDEER_CONFIG_DIR = "${tealdeerConfig}"; + GROFF_NO_SGR = "1"; + LESS_TERMCAP_md = "${escape}[1;${sgr palette.accent}m"; + LESS_TERMCAP_me = "${escape}[0m"; + LESS_TERMCAP_us = "${escape}[4;${sgr palette.green}m"; + LESS_TERMCAP_ue = "${escape}[0m"; + LESS_TERMCAP_so = "${escape}[${sgr palette.background};48;2;${ + lib.concatMapStringsSep ";" toString (channels palette.accent) + }m"; + LESS_TERMCAP_se = "${escape}[0m"; + } + ) + ); + + extraInit = lib.mkIf (!system) '' + IFS= read -r LS_COLORS < ${lsColors} + export LS_COLORS + ''; + }; + + programs.git.config = lib.mkMerge [ + { + core.pager = lib.mkDefault "${delta}/bin/delta"; + interactive.diffFilter = lib.mkDefault "${delta}/bin/delta --color-only"; + } + (lib.mkIf (!system) { + delta = lib.mapAttrs (_: lib.mkDefault) ( + { + minus-style = "syntax ${blend palette.red 0.1}"; + minus-emph-style = "bold syntax ${blend palette.red 0.25}"; + plus-style = "syntax ${blend palette.green 0.1}"; + plus-emph-style = "bold syntax ${blend palette.green 0.25}"; + line-numbers-minus-style = palette.red; + line-numbers-plus-style = palette.green; + line-numbers-zero-style = palette.muted; + line-numbers-left-style = palette.border; + line-numbers-right-style = palette.border; + file-style = palette.foreground; + file-decoration-style = palette.border; + hunk-header-decoration-style = palette.border; + hunk-header-line-number-style = palette.subtle; + } + // (if theme.dark then { dark = true; } else { light = true; }) + ); + color = lib.mapAttrsRecursive (_: lib.mkDefault) { + branch = { + current = "${palette.accent} bold"; + local = palette.foreground; + remote = palette.green; + }; + status = { + added = palette.green; + changed = palette.yellow; + untracked = palette.red; + branch = "${palette.accent} bold"; + nobranch = palette.red; + header = palette.muted; + }; + decorate = { + HEAD = "${palette.accent} bold"; + branch = palette.green; + remoteBranch = palette.red; + tag = palette.yellow; + stash = palette.purple; + }; + diff = { + meta = palette.muted; + frag = palette.purple; + commit = palette.yellow; + old = palette.red; + new = palette.green; + }; + grep = { + filename = palette.purple; + linenumber = palette.green; + match = "${palette.red} bold"; + }; + interactive = { + prompt = palette.accent; + header = palette.muted; + help = palette.muted; + error = palette.red; + }; + }; + }) + ]; +} diff --git a/catalog/shell/lazygit/README.md b/catalog/shell/lazygit/README.md index bb8ae06..cd4bdef 100644 --- a/catalog/shell/lazygit/README.md +++ b/catalog/shell/lazygit/README.md @@ -36,12 +36,12 @@ and `?` to see the keys. Configure your commit identity as described in ## Customize -With the default `system` flavor, Lazygit keeps its own theme, which uses the -terminal's colors and background. A Catppuccin flavor in `[theme]` of -`limanix.toml` sets the colors of its palette in `/etc/xdg/lazygit/config.yml`. -Lazygit reads only the first `config.yml` it finds: a personal -`~/.config/lazygit/config.yml` replaces the managed file, with the clipboard -command and any Catppuccin colors it sets. To keep them, start from a copy: +With the default `system` theme, Lazygit keeps its own theme, which uses the +terminal's colors and background. Another theme in `[theme]` of `limanix.toml` +sets the colors of its palette in `/etc/xdg/lazygit/config.yml`. Lazygit reads +only the first `config.yml` it finds: a personal `~/.config/lazygit/config.yml` +replaces the managed file, with the clipboard command and any theme colors it +sets. To keep them, start from a copy: ```console install -D -m 644 /etc/xdg/lazygit/config.yml ~/.config/lazygit/config.yml diff --git a/catalog/shell/lazygit/default.nix b/catalog/shell/lazygit/default.nix index c39d788..51495b3 100644 --- a/catalog/shell/lazygit/default.nix +++ b/catalog/shell/lazygit/default.nix @@ -1,6 +1,6 @@ { config, lib, ... }: let - inherit (config.lmx.capabilities.theme) flavor palette; + inherit (config.lmx.capabilities.theme) name palette; in { imports = [ ./help.nix ]; @@ -9,23 +9,23 @@ in programs.lazygit = { enable = true; - # Lazygit's own theme uses the terminal's colors, which the system flavor asks for. - settings.gui = lib.mkIf (flavor != "system") { + # Lazygit's own theme uses the terminal's colors, which the system theme asks for. + settings.gui = lib.mkIf (name != "system") { theme = lib.mapAttrs (_: lib.mkDefault) { activeBorderColor = [ - palette.blue + palette.accent "bold" ]; - inactiveBorderColor = [ palette.subtext0 ]; + inactiveBorderColor = [ palette.border ]; optionsTextColor = [ palette.blue ]; - selectedLineBgColor = [ palette.surface0 ]; - cherryPickedCommitBgColor = [ palette.surface1 ]; + selectedLineBgColor = [ palette.surface ]; + cherryPickedCommitBgColor = [ palette.selection ]; cherryPickedCommitFgColor = [ palette.blue ]; unstagedChangesColor = [ palette.red ]; - defaultFgColor = [ palette.text ]; + defaultFgColor = [ palette.foreground ]; searchingActiveBorderColor = [ palette.yellow ]; }; - authorColors."*" = lib.mkDefault palette.lavender; + authorColors."*" = lib.mkDefault palette.blue; }; # The platform's pbcopy sends the text through the terminal to the Mac clipboard. settings.os.copyToClipboardCmd = lib.mkDefault "printf %s {{text}} | pbcopy"; diff --git a/catalog/shell/lazygit/test.nix b/catalog/shell/lazygit/test.nix index 9249091..3d87f0e 100644 --- a/catalog/shell/lazygit/test.nix +++ b/catalog/shell/lazygit/test.nix @@ -7,9 +7,9 @@ let helpers = import ../../_shared/test/helpers.nix { inherit evalSystem pkgs lib; }; inherit (helpers) evaluate verify profileFor; defaultConfiguration = evaluate [ ./default.nix ]; - latte = evaluate [ + themed = evaluate [ ./default.nix - { lmx.capabilities.theme.flavor = "latte"; } + { lmx.capabilities.theme.name = "gruvbox-dark"; } ]; in { @@ -22,10 +22,10 @@ in hasPackage = helpers.installed defaultConfiguration; } ) defaultConfiguration; - latte = verify "the managed colors follow the guest's flavor" (import ./test/check.nix { - inherit (latte) config; - hasPackage = helpers.installed latte; - }) latte; + themed = verify "the managed colors follow the guest's theme" (import ./test/check.nix { + inherit (themed) config; + hasPackage = helpers.installed themed; + }) themed; clipboard = verify "copies go to the Mac clipboard through the platform's pbcopy" ( defaultConfiguration.config.programs.lazygit.settings.os.copyToClipboardCmd == "printf %s {{text}} | pbcopy" diff --git a/catalog/shell/lazygit/test/check.nix b/catalog/shell/lazygit/test/check.nix index 2bf0522..65969c0 100644 --- a/catalog/shell/lazygit/test/check.nix +++ b/catalog/shell/lazygit/test/check.nix @@ -1,19 +1,19 @@ { config, hasPackage, ... }: let - inherit (config.lmx.capabilities.theme) flavor palette; + inherit (config.lmx.capabilities.theme) name palette; inherit (config.programs.lazygit) settings; expectedTheme = { activeBorderColor = [ - palette.blue + palette.accent "bold" ]; - inactiveBorderColor = [ palette.subtext0 ]; + inactiveBorderColor = [ palette.border ]; optionsTextColor = [ palette.blue ]; - selectedLineBgColor = [ palette.surface0 ]; - cherryPickedCommitBgColor = [ palette.surface1 ]; + selectedLineBgColor = [ palette.surface ]; + cherryPickedCommitBgColor = [ palette.selection ]; cherryPickedCommitFgColor = [ palette.blue ]; unstagedChangesColor = [ palette.red ]; - defaultFgColor = [ palette.text ]; + defaultFgColor = [ palette.foreground ]; searchingActiveBorderColor = [ palette.yellow ]; }; in @@ -22,8 +22,8 @@ config.programs.git.enable && hasPackage config.programs.git.package && hasPackage config.programs.lazygit.package && ( - if flavor == "system" then + if name == "system" then !(settings ? gui) else - settings.gui.theme == expectedTheme && settings.gui.authorColors."*" == palette.lavender + settings.gui.theme == expectedTheme && settings.gui.authorColors."*" == palette.blue ) diff --git a/catalog/shell/neovim/README.md b/catalog/shell/neovim/README.md index 90a79da..186611c 100644 --- a/catalog/shell/neovim/README.md +++ b/catalog/shell/neovim/README.md @@ -32,6 +32,13 @@ nvim notes.md The `vi` and `vim` aliases also launch Neovim. +With a theme other than `system` in `[theme]` of `limanix.toml`, Neovim uses the +theme's own colorscheme, the same one as +[AstroNvim](../astronvim/README.md#personal-configuration); a theme without one +draws its palette with catppuccin-nvim. Your `init.lua` or `init.vim` runs after +it and may choose another colorscheme. The `system` theme keeps Neovim's default +colorscheme. + ## Make Neovim the default editor The module leaves `EDITOR` at its NixOS default, `nano`. To make Neovim the diff --git a/catalog/shell/neovim/colorschemes.nix b/catalog/shell/neovim/colorschemes.nix new file mode 100644 index 0000000..da4517b --- /dev/null +++ b/catalog/shell/neovim/colorschemes.nix @@ -0,0 +1,42 @@ +pkgs: { + catppuccin-mocha = { + plugin = pkgs.vimPlugins.catppuccin-nvim; + colorscheme = "catppuccin-mocha"; + }; + tokyo-night = { + plugin = pkgs.vimPlugins.tokyonight-nvim; + colorscheme = "tokyonight-night"; + }; + dracula = { + plugin = pkgs.vimPlugins.dracula-nvim; + colorscheme = "dracula"; + }; + nord = { + plugin = pkgs.vimPlugins.nord-nvim; + colorscheme = "nord"; + }; + gruvbox-dark = { + plugin = pkgs.vimPlugins.gruvbox-nvim; + colorscheme = "gruvbox"; + }; + rose-pine = { + plugin = pkgs.vimPlugins.rose-pine; + colorscheme = "rose-pine-main"; + }; + kanagawa-wave = { + plugin = pkgs.vimPlugins.kanagawa-nvim; + colorscheme = "kanagawa-wave"; + }; + everforest-dark = { + plugin = pkgs.vimPlugins.everforest; + colorscheme = "everforest"; + }; + solarized-light = { + plugin = pkgs.vimPlugins.nvim-solarized-lua; + colorscheme = "solarized"; + }; + github-light = { + plugin = pkgs.vimPlugins.github-nvim-theme; + colorscheme = "github_light_default"; + }; +} diff --git a/catalog/shell/neovim/default.nix b/catalog/shell/neovim/default.nix index c0d1600..210a553 100644 --- a/catalog/shell/neovim/default.nix +++ b/catalog/shell/neovim/default.nix @@ -1,9 +1,61 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (config.lmx.capabilities) theme; + native = (import ./colorschemes.nix pkgs).${theme.name} or null; + flavour = if theme.dark then "mocha" else "latte"; + colorscheme = + if native == null then + '' + require("catppuccin").setup({ + flavour = "${flavour}", + color_overrides = { + ${flavour} = vim.json.decode([==[${builtins.toJSON (import ../../_shared/lib/catppuccin.nix theme.palette)}]==]), + }, + }) + vim.cmd.colorscheme("catppuccin-${flavour}") + '' + else + '' + vim.cmd.colorscheme("${native.colorscheme}") + ''; +in { imports = [ ./help.nix ]; - programs.neovim = { + options.lmx.internal.neovim.colorscheme = lib.mkOption { + type = lib.types.bool; + default = true; + internal = true; + visible = false; + description = "Whether plain Neovim draws the guest's theme; an editor setup with its own colorscheme turns it off."; + }; + + config.programs.neovim = { enable = true; viAlias = true; vimAlias = true; + # The wrapper starts this script through VIMINIT, so it loads the personal init file itself. + configure = lib.mkIf (config.lmx.internal.neovim.colorscheme && theme.name != "system") { + packages.limanix.start = [ + (if native == null then pkgs.vimPlugins.catppuccin-nvim else native.plugin) + ]; + customLuaRC = '' + vim.o.background = "${if theme.dark then "dark" else "light"}" + ${colorscheme} + local config = vim.fn.stdpath("config") + if vim.fn.filereadable(config .. "/init.lua") == 1 then + vim.env.MYVIMRC = config .. "/init.lua" + dofile(vim.env.MYVIMRC) + elseif vim.fn.filereadable(config .. "/init.vim") == 1 then + vim.env.MYVIMRC = config .. "/init.vim" + vim.cmd.source(vim.fn.fnameescape(vim.env.MYVIMRC)) + end + ''; + }; }; } diff --git a/catalog/shell/tmux/README.md b/catalog/shell/tmux/README.md index 68600ad..194f69e 100644 --- a/catalog/shell/tmux/README.md +++ b/catalog/shell/tmux/README.md @@ -86,10 +86,10 @@ custom module. ## Appearance -The colors come from the guest's theme. With the default `system` flavor they -are the terminal's own colors, and the status line has no background of its own: -the terminal's shows through. A Catppuccin flavor in `[theme]` of `limanix.toml` -uses its [palette](https://catppuccin.com/palette/) instead. The styles load +The colors come from the guest's theme. With the default `system` theme they are +the terminal's own colors, and the status line has no background of its own: the +terminal's shows through. Another theme in `[theme]` of `limanix.toml` draws the +status line, borders and selection in its palette instead. The styles load before the restoration plugins, which preserve continuum's status-line hook. The right side of the status line starts with the words of `lmx status --short` diff --git a/catalog/shell/tmux/default.nix b/catalog/shell/tmux/default.nix index ea98ed8..7e15bb4 100644 --- a/catalog/shell/tmux/default.nix +++ b/catalog/shell/tmux/default.nix @@ -5,8 +5,8 @@ ... }: let - inherit (config.lmx.capabilities) theme; - names = builtins.attrNames theme.palette; + inherit (config.lmx.capabilities.theme) colors; + roles = builtins.attrNames colors; # tmux's names of the terminal's 16 colors, which it also prints back. terminal = [ "black" @@ -26,16 +26,19 @@ let "brightcyan" "brightwhite" ]; - # A palette name as a tmux color: the terminal's own with the system flavor, the flavor's otherwise. + # A role as a tmux color: the terminal's own with the system theme, the palette's otherwise. color = - name: - if theme.flavor != "system" then - theme.palette.${name} - else if theme.ansi.${name} == null then + role: + let + value = colors.${role}; + in + if builtins.isString value then + value + else if value == null then "default" else - builtins.elemAt terminal theme.ansi.${name}; - styles = lib.replaceStrings (map (name: "@theme-${name}@") names) (map color names) ( + builtins.elemAt terminal value; + styles = lib.replaceStrings (map (role: "@theme-${role}@") roles) (map color roles) ( builtins.readFile ./tmux.conf ); in diff --git a/catalog/shell/tmux/test/tests.nix b/catalog/shell/tmux/test/tests.nix index 5a022ec..774c975 100644 --- a/catalog/shell/tmux/test/tests.nix +++ b/catalog/shell/tmux/test/tests.nix @@ -26,11 +26,11 @@ let }; } ]; - latte = evaluate [ + themed = evaluate [ module.path - { lmx.capabilities.theme.flavor = "latte"; } + { lmx.capabilities.theme.name = "nord"; } ]; - inherit (latte.config.lmx.capabilities.theme) palette; + inherit (themed.config.lmx.capabilities.theme) palette; text = configuration: configuration.config.environment.etc."tmux.conf".text; bindings = lib.concatMap @@ -63,8 +63,10 @@ in && lib.hasInfix "set -g mouse on" (text navigationDisabled) && lib.hasInfix "set -s set-clipboard on" (text navigationDisabled) ) navigationDisabled; - theme = verify "styles follow the guest's flavor, and the terminal's colors with system" ( - lib.hasInfix "set -g status-style 'bg=${palette.mantle},fg=${palette.text}'" (text latte) + theme = verify "styles follow the guest's theme, and the terminal's colors with system" ( + lib.hasInfix "set -g status-style 'bg=${palette.background_dark},fg=${palette.foreground}'" ( + text themed + ) && lib.hasInfix "set -g status-style 'bg=default,fg=default'" (text defaultConfiguration) && lib.hasInfix "set -g pane-active-border-style 'fg=magenta'" (text defaultConfiguration) && !lib.hasInfix "#" ( @@ -74,12 +76,12 @@ in ) ) ) - ) latte; + ) themed; segment = verify "the status line shows the words of lmx status --short" - (lib.hasInfix "#[fg=${palette.peach},bold]#(/run/current-system/sw/bin/lmx status --short)" ( - text latte + (lib.hasInfix "#[fg=${palette.orange},bold]#(/run/current-system/sw/bin/lmx status --short)" ( + text themed )) - latte; + themed; }; } diff --git a/catalog/shell/tmux/tmux.conf b/catalog/shell/tmux/tmux.conf index c9ee24f..97b0561 100644 --- a/catalog/shell/tmux/tmux.conf +++ b/catalog/shell/tmux/tmux.conf @@ -2,21 +2,21 @@ set -g mouse on set -s set-clipboard on set -as terminal-features ',xterm*:RGB:clipboard' -set -g status-style 'bg=@theme-mantle@,fg=@theme-text@' -set -g message-style 'bg=@theme-surface0@,fg=@theme-text@' -set -g message-command-style 'bg=@theme-surface0@,fg=@theme-text@' -set -g mode-style 'fg=@theme-mauve@,bg=@theme-base@,reverse' -set -g pane-border-style 'fg=@theme-surface1@' -set -g pane-active-border-style 'fg=@theme-mauve@' -set -g window-status-style 'bg=@theme-mantle@,fg=@theme-subtext0@' -set -g window-status-current-style 'fg=@theme-mauve@,bg=@theme-base@,reverse,bold' -set -g window-status-activity-style 'bg=@theme-mantle@,fg=@theme-peach@' -set -g window-status-bell-style 'bg=@theme-mantle@,fg=@theme-red@,bold' +set -g status-style 'bg=@theme-background_dark@,fg=@theme-foreground@' +set -g message-style 'bg=@theme-surface@,fg=@theme-foreground@' +set -g message-command-style 'bg=@theme-surface@,fg=@theme-foreground@' +set -g mode-style 'fg=@theme-accent@,bg=@theme-background@,reverse' +set -g pane-border-style 'fg=@theme-border@' +set -g pane-active-border-style 'fg=@theme-accent@' +set -g window-status-style 'bg=@theme-background_dark@,fg=@theme-subtle@' +set -g window-status-current-style 'fg=@theme-accent@,bg=@theme-background@,reverse,bold' +set -g window-status-activity-style 'bg=@theme-background_dark@,fg=@theme-orange@' +set -g window-status-bell-style 'bg=@theme-background_dark@,fg=@theme-red@,bold' set -g window-status-format ' #I:#W ' set -g window-status-current-format ' #I:#W ' -set -g status-left '#[fg=@theme-mauve@,bg=@theme-base@,reverse,bold] #S #[default] ' +set -g status-left '#[fg=@theme-accent@,bg=@theme-background@,reverse,bold] #S #[default] ' set -g status-left-length 40 -set -g status-right '#[fg=@theme-peach@,bold]#(/run/current-system/sw/bin/lmx status --short)#[default] #[fg=@theme-subtext0@]%a %d %b #[fg=@theme-text@]%H:%M ' +set -g status-right '#[fg=@theme-orange@,bold]#(/run/current-system/sw/bin/lmx status --short)#[default] #[fg=@theme-subtle@]%a %d %b #[fg=@theme-foreground@]%H:%M ' set -g status-right-length 80 bind -T copy-mode-vi v send-keys -X begin-selection diff --git a/catalog/shell/yazi/README.md b/catalog/shell/yazi/README.md index edbc423..6c96a86 100644 --- a/catalog/shell/yazi/README.md +++ b/catalog/shell/yazi/README.md @@ -39,13 +39,12 @@ manager without the shell directory handoff. Selecting Yazi does not enable Zsh. ## Colors and configuration -With the default `system` flavor, Yazi runs as packaged, with its own light and +With the default `system` theme, Yazi runs as packaged, with its own light and dark presets: they draw in the terminal's colors, apart from the fixed colors of file icons, and on its background, and Yazi picks the one that matches the -terminal. A Catppuccin flavor in `[theme]` of `limanix.toml` instead wraps the -upstream Yazi binary with a store-backed theme in its -[palette](https://catppuccin.com/palette/); it does not rebuild Yazi's Rust -sources to change colors. With a Catppuccin flavor: +terminal. Another theme in `[theme]` of `limanix.toml` instead wraps the +upstream Yazi binary with a store-backed theme in its palette; it does not +rebuild Yazi's Rust sources to change colors. With such a theme: | Configuration | Theme and file behavior | | -- | -- | diff --git a/catalog/shell/yazi/default.nix b/catalog/shell/yazi/default.nix index 46dced6..2bc0a03 100644 --- a/catalog/shell/yazi/default.nix +++ b/catalog/shell/yazi/default.nix @@ -6,7 +6,7 @@ }: let # Yazi's own presets use the terminal's colors and follow its light or dark background. - system = config.lmx.capabilities.theme.flavor == "system"; + system = config.lmx.capabilities.theme.name == "system"; colors = config.lmx.capabilities.theme.palette; names = [ "black" @@ -26,10 +26,10 @@ let "lightcyan" ]; values = with colors; [ - base - text - subtext0 - surface2 + background + foreground + subtle + border red red green @@ -37,13 +37,13 @@ let yellow yellow blue - mauve - mauve - sky - sky + purple + purple + cyan + cyan ]; themeText = lib.replaceStrings ((map (name: ''"${name}"'') names) ++ [ "overall = {}" ]) ( - (map (value: ''"${value}"'') values) ++ [ ''overall = { bg = "${colors.base}" }'' ] + (map (value: ''"${value}"'') values) ++ [ ''overall = { bg = "${colors.background}" }'' ] ) (builtins.readFile ./theme-default.toml); theme = builtins.fromTOML themeText; themeFile = pkgs.writeText "yazi-theme.toml" themeText; diff --git a/catalog/shell/yazi/test.nix b/catalog/shell/yazi/test.nix index bbce03b..8ad39ba 100644 --- a/catalog/shell/yazi/test.nix +++ b/catalog/shell/yazi/test.nix @@ -13,10 +13,10 @@ let lib.hasInfix "yazi-with-default-theme" ( configuration.config.programs.yazi.package.drvAttrs.buildCommand or "" ); - # The store-backed theme and its wrapper exist with a Catppuccin flavor. + # The store-backed theme and its wrapper exist with a palette theme. mochaConfiguration = evaluate [ ./default.nix - { lmx.capabilities.theme.flavor = "mocha"; } + { lmx.capabilities.theme.name = "catppuccin-mocha"; } ]; cases = import ./test/tests.nix { module.path = ./default.nix; @@ -37,7 +37,7 @@ in hasPackage = helpers.installed defaultConfiguration; } ) defaultConfiguration; - systemTheme = verify "the system flavor runs Yazi with its own presets in the terminal's colors" ( + systemTheme = verify "the system theme runs Yazi with its own presets in the terminal's colors" ( !themed defaultConfiguration && themed mochaConfiguration ) defaultConfiguration; } diff --git a/catalog/shell/yazi/test/runtime-smoke.py b/catalog/shell/yazi/test/runtime-smoke.py index a9eb2e1..497dba3 100644 --- a/catalog/shell/yazi/test/runtime-smoke.py +++ b/catalog/shell/yazi/test/runtime-smoke.py @@ -95,17 +95,17 @@ def ready(): profile = os.environ["LMX_YAZI_PROFILE"] -run("bash-default", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], profile, b"38;2;137;220;235") -run("zsh-default", os.environ["LMX_ZSH"], os.environ["LMX_ZSH_INIT"], profile, b"38;2;137;220;235") -run("latte", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], os.environ["LMX_YAZI_LATTE_PROFILE"], b"38;2;4;165;229") +run("bash-default", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], profile, b"38;2;148;226;213") +run("zsh-default", os.environ["LMX_ZSH"], os.environ["LMX_ZSH_INIT"], profile, b"38;2;148;226;213") +run("solarized", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], os.environ["LMX_YAZI_SOLARIZED_PROFILE"], b"38;2;42;161;152") run("personal", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], profile, b"38;2;255;0;0", personal="#ff0000") run("explicit", os.environ["LMX_ZSH"], os.environ["LMX_ZSH_INIT"], profile, b"38;2;0;255;0", explicit="#00ff00") run("managed", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], os.environ["LMX_YAZI_MANAGED_PROFILE"], b"38;2;18;52;86") -run("retain-cwd", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], profile, b"38;2;137;220;235", exit_key=b"Q") -run("newline-cwd", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], profile, b"38;2;137;220;235", project_name="project with spaces\n") +run("retain-cwd", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], profile, b"38;2;148;226;213", exit_key=b"Q") +run("newline-cwd", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], profile, b"38;2;148;226;213", project_name="project with spaces\n") -run("personal-keymap", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], profile, b"38;2;137;220;235", non_theme=True) +run("personal-keymap", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], profile, b"38;2;148;226;213", non_theme=True) run("managed-explicit", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], os.environ["LMX_YAZI_MANAGED_PROFILE"], b"38;2;18;52;86", explicit="#00ff00") run("managed-flavor", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], os.environ["LMX_YAZI_FLAVOR_PROFILE"], b"38;2;171;205;239") -# The system flavor draws on the terminal's own background: no 24-bit background appears. +# The system theme draws on the terminal's own background: no 24-bit background appears. run("system-default", os.environ["LMX_BASH"], os.environ["LMX_BASH_INIT"], os.environ["LMX_YAZI_SYSTEM_PROFILE"], b"project with spaces", absent=b"48;2;") diff --git a/catalog/shell/yazi/test/smoke.nix b/catalog/shell/yazi/test/smoke.nix index 4cd62cd..d1ae607 100644 --- a/catalog/shell/yazi/test/smoke.nix +++ b/catalog/shell/yazi/test/smoke.nix @@ -10,7 +10,7 @@ let managedProfile = profileFor configurations.managedOverride; flavorProfile = profileFor configurations.managedFlavor; - latteProfile = profileFor configurations.latte; + solarizedProfile = profileFor configurations.solarized; in { shellErrors = @@ -39,7 +39,7 @@ in LMX_YAZI_SYSTEM_PROFILE = systemProfile; LMX_YAZI_MANAGED_PROFILE = managedProfile; LMX_YAZI_FLAVOR_PROFILE = flavorProfile; - LMX_YAZI_LATTE_PROFILE = latteProfile; + LMX_YAZI_SOLARIZED_PROFILE = solarizedProfile; LMX_BASH = "${pkgs.bashInteractive}/bin/bash"; LMX_ZSH = "${pkgs.zsh}/bin/zsh"; LMX_BASH_INIT = pkgs.writeText "yazi-bash-init" config.programs.bash.interactiveShellInit; diff --git a/catalog/shell/yazi/test/tests.nix b/catalog/shell/yazi/test/tests.nix index fce092c..30d6f1d 100644 --- a/catalog/shell/yazi/test/tests.nix +++ b/catalog/shell/yazi/test/tests.nix @@ -28,13 +28,13 @@ let }; } ]; - latte = evaluate [ + solarized = evaluate [ module.path - { lmx.capabilities.theme.flavor = "latte"; } + { lmx.capabilities.theme.name = "solarized-light"; } ]; in { - configurations = { inherit managedOverride managedFlavor latte; }; + configurations = { inherit managedOverride managedFlavor solarized; }; evaluation = { independentShell = verify "Yazi provides shell integration without enabling Zsh" ( !defaultConfiguration.config.programs.zsh.enable diff --git a/catalog/shell/zsh/README.md b/catalog/shell/zsh/README.md index 11a54e0..336d068 100644 --- a/catalog/shell/zsh/README.md +++ b/catalog/shell/zsh/README.md @@ -51,12 +51,17 @@ Atuin records history in the VM user's data directory, normally are disabled; no account is needed. Its managed settings are in `/etc/atuin/config.toml`. Atuin reads only one `config.toml`: a personal `~/.config/atuin/config.toml` replaces the managed file, with both disabled -settings. To keep them, start from a copy: +settings. To keep them, start from a copy, with the theme it names: ```console install -D -m 644 /etc/atuin/config.toml ~/.config/atuin/config.toml +cp -r /etc/atuin/themes ~/.config/atuin/ ``` +With a theme other than `system` in `[theme]` of `limanix.toml`, the command +highlighting, the autosuggestions and Atuin's search draw in its palette; the +`system` theme leaves them in the terminal's colors. + For a project that already has an `.envrc`, inspect that file and run this inside the project directory in the VM: @@ -71,11 +76,11 @@ does not create an environment definition for the project. ## Prompt Both managed prompts use the colors of the guest's theme. With the default -`system` flavor they are the terminal's own colors. The prompt follows the theme -of the terminal on your Mac. A Catppuccin flavor in `[theme]` of `limanix.toml` -uses its palette instead. Outside tmux, both show the words of -`lmx status --short` that need attention, such as `restart`; inside tmux, tmux's -status line shows them. +`system` theme they are the terminal's own colors. The prompt follows the theme +of the terminal on your Mac. Another theme in `[theme]` of `limanix.toml` uses +its palette instead. Outside tmux, both show the words of `lmx status --short` +that need attention, such as `restart`; inside tmux, tmux's status line shows +them. | `prompt` | Behavior | | -- | -- | @@ -141,4 +146,4 @@ persistent terminal sessions. | Pristine home | The managed shell starts without creating `~/.zshrc` or opening the setup wizard | | Custom startup directory | Zsh reads personal startup files from `ZDOTDIR` when set | | You want the setup wizard | The normal `zsh-newuser-install` function remains available for explicit manual use | -| Colors look wrong with Pure | With a Catppuccin flavor, set `COLORTERM=truecolor` when the terminal supports 24-bit color, or choose colors from its 256-color palette with `zstyle`; the `system` flavor needs neither | +| Colors look wrong with Pure | `limanix shell` passes on `COLORTERM` when the terminal on the Mac sets it to `truecolor` or `24bit`. Without it, a palette theme gets the nearest of 256 colors: set `COLORTERM` in the Mac shell or in the `[env]` table, or choose colors from the 256-color palette with `zstyle`. The `system` theme needs neither | diff --git a/catalog/shell/zsh/default.nix b/catalog/shell/zsh/default.nix index 9d0baa7..2d7da58 100644 --- a/catalog/shell/zsh/default.nix +++ b/catalog/shell/zsh/default.nix @@ -22,7 +22,10 @@ let ''; in { - imports = [ ./help.nix ]; + imports = [ + ./help.nix + ./theme.nix + ]; options.lmx.zsh.prompt = lib.mkOption { type = lib.types.enum [ @@ -34,7 +37,7 @@ in description = '' Prompt of Zsh: Pure, Starship, or none, which leaves the prompt to personal startup files. Pure and Starship use the colors of the guest's theme, the terminal's own with the system - flavor, and, outside tmux, show the words of `lmx status --short`. + theme, and, outside tmux, show the words of `lmx status --short`. ''; }; diff --git a/catalog/shell/zsh/pure.nix b/catalog/shell/zsh/pure.nix index bfe27ee..6e15375 100644 --- a/catalog/shell/zsh/pure.nix +++ b/catalog/shell/zsh/pure.nix @@ -1,5 +1,5 @@ # Initialization of the Pure prompt in the colors of the guest's theme: the terminal's own colors -# with the system flavor, the flavor's palette otherwise. Outside tmux, the right prompt shows the +# with the system theme, the theme's palette otherwise. Outside tmux, the right prompt shows the # words of `lmx status --short`; inside tmux, its status line shows them. { lib, @@ -7,32 +7,24 @@ theme, }: let - system = theme.flavor == "system"; - # A palette name as a zsh color: the terminal's color number, or the flavor's #rrggbb. - color = - name: - if !system then - theme.palette.${name} - else if theme.ansi.${name} == null then - "default" - else - toString theme.ansi.${name}; + # A role as a zsh color: the terminal's color number, or the palette's #rrggbb. + color = role: if theme.colors.${role} == null then "default" else toString theme.colors.${role}; colors = lib.mapAttrs (_: color) { path = "blue"; - "git:branch" = "mauve"; + "git:branch" = "purple"; "git:branch:cached" = "red"; - "git:dirty" = "peach"; - "git:arrow" = "teal"; - "git:stash" = "teal"; + "git:dirty" = "orange"; + "git:arrow" = "cyan"; + "git:stash" = "cyan"; "git:action" = "yellow"; - host = "lavender"; - user = "lavender"; + host = "accent"; + user = "accent"; "user:root" = "red"; execution_time = "yellow"; - virtualenv = "overlay1"; + virtualenv = "muted"; "prompt:success" = "green"; "prompt:error" = "red"; - "prompt:continuation" = "overlay1"; + "prompt:continuation" = "muted"; }; in '' @@ -52,7 +44,7 @@ in _lmx_prompt_status() { local words [[ -n $TMUX ]] || words=$(/run/current-system/sw/bin/lmx status --short 2>/dev/null) - _lmx_prompt_words=''${words:+"%B%F{${color "peach"}}''${words//\%/%%}%f%b"} + _lmx_prompt_words=''${words:+"%B%F{${color "orange"}}''${words//\%/%%}%f%b"} } autoload -Uz add-zsh-hook && add-zsh-hook precmd _lmx_prompt_status setopt prompt_subst diff --git a/catalog/shell/zsh/starship.nix b/catalog/shell/zsh/starship.nix index 471ed8a..c1dd636 100644 --- a/catalog/shell/zsh/starship.nix +++ b/catalog/shell/zsh/starship.nix @@ -1,20 +1,14 @@ { lib, theme }: let - system = theme.flavor == "system"; - # The terminal's own colors by number with the system flavor; its default colors need no entry. - name = if system then "system" else "catppuccin_${theme.flavor}"; - colors = - if system then - lib.mapAttrs (_: toString) (lib.filterAttrs (_: color: color != null) theme.ansi) - else - theme.palette; + # The terminal's own colors by number with the system theme; its default colors need no entry. + colors = lib.mapAttrs (_: toString) (lib.filterAttrs (_: color: color != null) theme.colors); in lib.mapAttrsRecursive (_: lib.mkDefault) { - palette = name; - palettes.${name} = colors; + palette = theme.name; + palettes.${theme.name} = colors; directory.style = "bold blue"; - hostname.style = "bold lavender"; - git_branch.style = "bold mauve"; + hostname.style = "bold accent"; + git_branch.style = "bold purple"; git_status.style = "bold red"; cmd_duration.style = "bold yellow"; character = { @@ -27,7 +21,7 @@ lib.mapAttrsRecursive (_: lib.mkDefault) { command = ''[ -n "$TMUX" ] || exec /run/current-system/sw/bin/lmx status --short''; when = true; shell = [ "sh" ]; - style = "bold peach"; + style = "bold orange"; format = "([$output]($style) )"; }; } diff --git a/catalog/shell/zsh/test.nix b/catalog/shell/zsh/test.nix index 53ec5fc..9968b72 100644 --- a/catalog/shell/zsh/test.nix +++ b/catalog/shell/zsh/test.nix @@ -7,12 +7,12 @@ let helpers = import ../../_shared/test/helpers.nix { inherit evalSystem pkgs lib; }; inherit (helpers) evaluate verify profileFor; defaultConfiguration = evaluate [ ./default.nix ]; - # Starship starts with a Catppuccin flavor, the case of explicit colors; Pure with the default. + # Starship starts with a palette theme, the case of explicit colors; Pure with the default. starshipConfiguration = evaluate [ ./default.nix { lmx.zsh.prompt = "starship"; - lmx.capabilities.theme.flavor = "mocha"; + lmx.capabilities.theme.name = "catppuccin-mocha"; } ]; cases = import ./test/tests.nix { diff --git a/catalog/shell/zsh/test/startup-check.nix b/catalog/shell/zsh/test/startup-check.nix index 3d53084..fe781e5 100644 --- a/catalog/shell/zsh/test/startup-check.nix +++ b/catalog/shell/zsh/test/startup-check.nix @@ -2,7 +2,7 @@ let inherit (config.lmx.zsh) prompt; inherit (config.lmx.capabilities) theme; - pathColor = if theme.flavor == "system" then toString theme.ansi.blue else theme.palette.blue; + pathColor = toString theme.colors.blue; promptChecks = { pure = '' @@ -23,7 +23,7 @@ let starship = '' [[ $STARSHIP_CONFIG == /nix/store/* ]] starship print-config | grep -F 'ssh_only = false' - starship print-config | grep -F 'palette = "catppuccin_mocha"' + starship print-config | grep -F 'palette = "catppuccin-mocha"' starship module directory | grep -F '38;2;137;180;250' [[ -n $(starship module hostname) ]] starship module status --status 7 | grep -F 7 diff --git a/catalog/shell/zsh/test/tests.nix b/catalog/shell/zsh/test/tests.nix index 2270d3f..ae3e1f0 100644 --- a/catalog/shell/zsh/test/tests.nix +++ b/catalog/shell/zsh/test/tests.nix @@ -20,10 +20,10 @@ let { limanix.user.shell = pkgs.bashInteractive; } ]; starship = withPrompt "starship" { }; - starshipLatte = withPrompt "starship" { lmx.capabilities.theme.flavor = "latte"; }; - pureLatte = withPrompt "pure" { lmx.capabilities.theme.flavor = "latte"; }; + starshipThemed = withPrompt "starship" { lmx.capabilities.theme.name = "everforest-dark"; }; + pureThemed = withPrompt "pure" { lmx.capabilities.theme.name = "everforest-dark"; }; none = withPrompt "none" { }; - inherit (starshipLatte.config.programs.starship) settings; + inherit (starshipThemed.config.programs.starship) settings; commands = configuration: configuration.config.limanix.help.zsh.commands; pureInit = configuration: configuration.config.programs.zsh.promptInit; in @@ -46,21 +46,18 @@ in && lib.strings.trim (pureInit none) == "" && !(builtins.elem "starship" (commands none)) ) none; - theme = - verify "Pure and Starship follow the guest's flavor, and the terminal's colors with system" - ( - settings.palette == "catppuccin_latte" - && settings.palettes.catppuccin_latte == starshipLatte.config.lmx.capabilities.theme.palette - && lib.hasInfix "zstyle ':prompt:pure:path' color '${pureLatte.config.lmx.capabilities.theme.palette.blue}'" ( - pureInit pureLatte - ) - && lib.hasInfix "zstyle ':prompt:pure:path' color '4'" (pureInit defaultConfiguration) - && lib.hasInfix "%F{11}" (pureInit defaultConfiguration) - && starship.config.programs.starship.settings.palettes.system.blue == "4" - && starship.config.programs.starship.settings.palette == "system" - && !(starship.config.programs.starship.settings.palettes.system ? text) - ) - pureLatte; + theme = verify "Pure and Starship follow the guest's theme, and the terminal's colors with system" ( + settings.palette == "everforest-dark" + && settings.palettes.everforest-dark == starshipThemed.config.lmx.capabilities.theme.palette + && lib.hasInfix "zstyle ':prompt:pure:path' color '${pureThemed.config.lmx.capabilities.theme.palette.blue}'" ( + pureInit pureThemed + ) + && lib.hasInfix "zstyle ':prompt:pure:path' color '4'" (pureInit defaultConfiguration) + && lib.hasInfix "%F{11}" (pureInit defaultConfiguration) + && starship.config.programs.starship.settings.palettes.system.blue == "4" + && starship.config.programs.starship.settings.palette == "system" + && !(starship.config.programs.starship.settings.palettes.system ? foreground) + ) pureThemed; segment = verify "outside tmux, Pure and Starship show the words of lmx status --short" ( lib.hasPrefix ''[ -n "$TMUX" ] || '' settings.custom.lmx.command && lib.hasSuffix "lmx status --short" settings.custom.lmx.command @@ -68,6 +65,6 @@ in pureInit defaultConfiguration ) && lib.hasInfix "RPROMPT='$_lmx_prompt_words'" (pureInit defaultConfiguration) - ) starshipLatte; + ) starshipThemed; }; } diff --git a/catalog/shell/zsh/theme.nix b/catalog/shell/zsh/theme.nix new file mode 100644 index 0000000..4911ebe --- /dev/null +++ b/catalog/shell/zsh/theme.nix @@ -0,0 +1,56 @@ +{ config, lib, ... }: +let + inherit (config.lmx.capabilities) theme; + inherit (theme) palette; + color = role: "fg=${palette.${role}}"; +in +lib.mkIf (theme.name != "system") { + programs.zsh = { + autosuggestions.highlightStyle = lib.mkDefault (color "muted"); + syntaxHighlighting.styles = lib.mapAttrs (_: lib.mkDefault) { + unknown-token = "${color "red"},bold"; + reserved-word = color "purple"; + alias = color "green"; + suffix-alias = "${color "green"},underline"; + global-alias = color "cyan"; + builtin = color "green"; + function = color "green"; + command = color "green"; + precommand = "${color "green"},underline"; + hashed-command = color "green"; + autodirectory = "${color "green"},underline"; + globbing = color "blue"; + history-expansion = color "blue"; + command-substitution-delimiter = color "purple"; + process-substitution-delimiter = color "purple"; + back-quoted-argument-delimiter = color "purple"; + single-quoted-argument = color "yellow"; + double-quoted-argument = color "yellow"; + dollar-quoted-argument = color "yellow"; + rc-quote = color "cyan"; + dollar-double-quoted-argument = color "cyan"; + back-double-quoted-argument = color "cyan"; + back-dollar-quoted-argument = color "cyan"; + redirection = color "yellow"; + comment = color "muted"; + arg0 = color "green"; + }; + }; + + programs.atuin.settings.theme.name = lib.mkDefault "limanix"; + environment.etc."atuin/themes/limanix.toml".text = '' + [theme] + name = "limanix" + + [colors] + AlertInfo = "${palette.green}" + AlertWarn = "${palette.yellow}" + AlertError = "${palette.red}" + Annotation = "${palette.blue}" + Base = "${palette.foreground}" + Guidance = "${palette.subtle}" + Important = "${palette.accent}" + Title = "${palette.accent}" + Muted = "${palette.muted}" + ''; +} diff --git a/previews/catppuccin-mocha.svg b/previews/catppuccin-mocha.svg new file mode 100644 index 0000000..f96c522 --- /dev/null +++ b/previews/catppuccin-mocha.svg @@ -0,0 +1,319 @@ + +Catppuccin Mocha + + + + + + + + + + + + + + + + +1 +local +M += +{} +│ +l +r +w +x +r +w +x +r +w +x +- +2025-01-01 +12:00 +-- +docs +-> +src +2 +│ +. +r +w +- +r +-- +r +-- +259 +2025-01-01 +12:00 +- +M +init.lua +3 +-- +Greets +every +name +on +the +list. +│ +. +r +w +- +r +-- +r +-- +5 +2025-01-01 +12:00 +-- +notes.txt +4 +function +M +. +greet +( +names +, +word +) +│ +. +r +w +- +r +-- +r +-- +10 +2025-01-01 +12:00 +-- +README.md +5 +local +lines += +{} +│ +. +r +w +x +r +- +x +r +- +x +24 +2025-01-01 +12:00 +-- +run.sh +6 +for +index +, +name +in +ipairs +( +names +) +do +│ +d +r +w +x +r +- +x +r +- +x +- +2025-01-01 +12:00 +-- +src +7 +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +│ +8 +end +│ +9 +return +table +. +concat +( +lines +, +" +\n +" +) +├ +──────────────────────────────────────────────────── +10 +end +│ +11 +│ +init.lua +12 +M +. +version += +"1.0" +│ +13 +│ +4 +: +local +M += +{} +14 +return +M +│function +M +. +greet +( +names +, +word +) +~ +│ +local +lines += +{} +~ +│ +for +index +, +name +in +ipairs +( +names +) +do +~ +│ +lines +[ +index +] += +word +.. +", +" +.. +name +~ +│ +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +~ +│ +end +~ +│ +return +table +. +concat +( +lines +, +" +\n +" +) +~ +│end +~ +│ +~ +│ +M +. +version += +"1.0" +~ +│ +~ +│return +M +~ +│ +~ +│ +~ +│ +~ +│ +init.lua +1,1 +All +│ +│ +work +0:nvim +Wed +01 +Jan +12:00 + + diff --git a/previews/dracula.svg b/previews/dracula.svg new file mode 100644 index 0000000..a8aa55b --- /dev/null +++ b/previews/dracula.svg @@ -0,0 +1,309 @@ + +Dracula + + + + + + + + + + + + + + + + +1 +local +M += +{} +│ +l +r +w +x +r +w +x +r +w +x +- +2025-01-01 +12:00 +-- +docs +-> +src +2 +│ +. +r +w +- +r +-- +r +-- +259 +2025-01-01 +12:00 +- +M +init.lua +3 +-- +Greets +every +name +on +the +list. +│ +. +r +w +- +r +-- +r +-- +5 +2025-01-01 +12:00 +-- +notes.txt +4 +function +M +. +greet +( +names +, +word +) +│ +. +r +w +- +r +-- +r +-- +10 +2025-01-01 +12:00 +-- +README.md +5 +local +lines += +{} +│ +. +r +w +x +r +- +x +r +- +x +24 +2025-01-01 +12:00 +-- +run.sh +6 +for +index, +name +in +ipairs +(names) +do +│d +r +w +x +r +- +x +r +- +x +- +2025-01-01 +12:00 +-- +src +7 +lines[index] += +(word +or +"Hi" +) +.. +", +" +.. +name +│ +8 +end +│ +9 +return +table +. +concat +(lines, +" +\n +" +) +├ +──────────────────────────────────────────────────── +10 +end +│ +11 +│ +init.lua +12 +M +. +version += +"1.0" +│ +13 +│ +4 +: +local +M += +{} +14 +return +M +│function +M +. +greet +( +names +, +word +) +~ +│ +local +lines += +{} +~ +│ +for +index +, +name +in +ipairs +( +names +) +do +~ +│ +lines +[ +index +] += +word +.. +", +" +.. +name +~ +│ +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +~ +│ +end +~ +│ +return +table +. +concat +( +lines +, +" +\n +" +) +~ +│end +~ +│ +~ +│ +M +. +version += +"1.0" +~ +│ +~ +│return +M +~ +│ +~ +│ +~ +│ +~ +│ +init.lua +1,1 +All +│ +│ +work +0:nvim +Wed +01 +Jan +12:00 + + diff --git a/previews/everforest-dark.svg b/previews/everforest-dark.svg new file mode 100644 index 0000000..f59a92d --- /dev/null +++ b/previews/everforest-dark.svg @@ -0,0 +1,313 @@ + +Everforest Dark + + + + + + + + + + + + + + + + +1 +local +M += +{} +│ +l +r +w +x +r +w +x +r +w +x +- +2025-01-01 +12:00 +-- +docs +-> +src +2 +│ +. +r +w +- +r +-- +r +-- +259 +2025-01-01 +12:00 +- +M +init.lua +3 +-- +Greets +every +name +on +the +list. +│ +. +r +w +- +r +-- +r +-- +5 +2025-01-01 +12:00 +-- +notes.txt +4 +function +M +. +greet +(names +, +word) +│ +. +r +w +- +r +-- +r +-- +10 +2025-01-01 +12:00 +-- +README.md +5 +local +lines += +{} +│ +. +r +w +x +r +- +x +r +- +x +24 +2025-01-01 +12:00 +-- +run.sh +6 +for +index +, +name +in +ipairs +(names) +do +│ +d +r +w +x +r +- +x +r +- +x +- +2025-01-01 +12:00 +-- +src +7 +lines[index] += +(word +or +"Hi" +) +.. +", +" +.. +name +│ +8 +end +│ +9 +return +table +. +concat +(lines +, +" +\n +" +) +├ +──────────────────────────────────────────────────── +10 +end +│ +11 +│ +init.lua +12 +M +. +version += +"1.0" +│ +13 +│ +4 +: +local +M += +{} +14 +return +M +│ +function +M +. +greet +( +names +, +word +) +~ +│ +local +lines += +{} +~ +│ +for +index +, +name +in +ipairs +( +names +) +do +~ +│ +lines +[ +index +] += +word +.. +", +" +.. +name +~ +│ +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +~ +│ +end +~ +│ +return +table +. +concat +( +lines +, +" +\n +" +) +~ +│ +end +~ +│ +~ +│ +M +. +version += +"1.0" +~ +│ +~ +│ +return +M +~ +│ +~ +│ +~ +│ +~ +│ +init.lua +1,1 +All +│ +│ +work +0:nvim +Wed +01 +Jan +12:00 + + diff --git a/previews/github-light.svg b/previews/github-light.svg new file mode 100644 index 0000000..c7b3c57 --- /dev/null +++ b/previews/github-light.svg @@ -0,0 +1,309 @@ + +GitHub Light + + + + + + + + + + + + + + + + +1 +local +M += +{} +│ +l +r +w +x +r +w +x +r +w +x +- +2025-01-01 +12:00 +-- +docs +-> +src +2 +│ +. +r +w +- +r +-- +r +-- +259 +2025-01-01 +12:00 +- +M +init.lua +3 +-- +Greets +every +name +on +the +list. +│ +. +r +w +- +r +-- +r +-- +5 +2025-01-01 +12:00 +-- +notes.txt +4 +function +M +. +greet +(names, +word) +│ +. +r +w +- +r +-- +r +-- +10 +2025-01-01 +12:00 +-- +README.md +5 +local +lines += +{} +│ +. +r +w +x +r +- +x +r +- +x +24 +2025-01-01 +12:00 +-- +run.sh +6 +for +index, +name +in +ipairs +(names) +do +│d +r +w +x +r +- +x +r +- +x +- +2025-01-01 +12:00 +-- +src +7 +lines[index] += +(word +or +"Hi" +) +.. +", +" +.. +name +│ +8 +end +│ +9 +return +table +. +concat +(lines, +" +\n +" +) +├ +──────────────────────────────────────────────────── +10 +end +│ +11 +│ +init.lua +12 +M +. +version += +"1.0" +│ +13 +│ +4 +: +local +M += +{} +14 +return +M +│ +function +M +. +greet +( +names +, +word +) +~ +│ +local +lines += +{} +~ +│ +for +index +, +name +in +ipairs +( +names +) +do +~ +│ +lines +[ +index +] += +word +.. +", +" +.. +name +~ +│ +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +~ +│ +end +~ +│ +return +table +. +concat +( +lines +, +" +\n +" +) +~ +│ +end +~ +│ +~ +│ +M +. +version += +"1.0" +~ +│ +~ +│ +return +M +~ +│ +~ +│ +~ +│ +~ +│ +init.lua +1,1 +All +│ +│ +work +0:nvim +Wed +01 +Jan +12:00 + + diff --git a/previews/gruvbox-dark.svg b/previews/gruvbox-dark.svg new file mode 100644 index 0000000..e4c5230 --- /dev/null +++ b/previews/gruvbox-dark.svg @@ -0,0 +1,321 @@ + +Gruvbox Dark + + + + + + + + + + + + + + + + +1 +local +M += +{} +│ +l +r +w +x +r +w +x +r +w +x +- +2025-01-01 +12:00 +-- +docs +-> +src +2 +│ +. +r +w +- +r +-- +r +-- +259 +2025-01-01 +12:00 +- +M +init.lua +3 +-- +Greets +every +name +on +the +list. +│ +. +r +w +- +r +-- +r +-- +5 +2025-01-01 +12:00 +-- +notes.txt +4 +function +M +. +greet +( +names +, +word +) +│ +. +r +w +- +r +-- +r +-- +10 +2025-01-01 +12:00 +-- +README.md +5 +local +lines += +{} +│ +. +r +w +x +r +- +x +r +- +x +24 +2025-01-01 +12:00 +-- +run.sh +6 +for +index +, +name +in +ipairs +( +names +) +do +│ +d +r +w +x +r +- +x +r +- +x +- +2025-01-01 +12:00 +-- +src +7 +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +│ +8 +end +│ +9 +return +table. +concat +( +lines +, +" +\n +" +) +├ +──────────────────────────────────────────────────── +10 +end +│ +11 +│ +init.lua +12 +M +. +version += +"1.0" +│ +13 +│ +4 +: +local +M += +{} +14 +return +M +│ +function +M +. +greet +( +names +, +word +) +~ +│ +local +lines += +{} +~ +│ +for +index +, +name +in +ipairs +( +names +) +do +~ +│ +lines +[ +index +] += +word +.. +", +" +.. +name +~ +│ +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +~ +│ +end +~ +│ +return +table +. +concat +( +lines +, +" +\n +" +) +~ +│ +end +~ +│ +~ +│ +M +. +version += +"1.0" +~ +│ +~ +│ +return +M +~ +│ +~ +│ +~ +│ +~ +│ +init.lua +1,1 +All +│ +│ +work +0:nvim +Wed +01 +Jan +12:00 + + diff --git a/previews/kanagawa-wave.svg b/previews/kanagawa-wave.svg new file mode 100644 index 0000000..72af47e --- /dev/null +++ b/previews/kanagawa-wave.svg @@ -0,0 +1,335 @@ + +Kanagawa Wave + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 +local +M += +{} +│ +l +r +w +x +r +w +x +r +w +x +- +2025-01-01 +12:00 +-- +docs +-> +src +2 +│ +. +r +w +- +r +-- +r +-- +259 +2025-01-01 +12:00 +- +M +init.lua +3 +-- +Greets +every +name +on +the +list. +│ +. +r +w +- +r +-- +r +-- +5 +2025-01-01 +12:00 +-- +notes.txt +4 +function +M +. +greet +( +names +, +word +) +│ +. +r +w +- +r +-- +r +-- +10 +2025-01-01 +12:00 +-- +README.md +5 +local +lines += +{} +│ +. +r +w +x +r +- +x +r +- +x +24 +2025-01-01 +12:00 +-- +run.sh +6 +for +index +, +name +in +ipairs +( +names +) +do +│d +r +w +x +r +- +x +r +- +x +- +2025-01-01 +12:00 +-- +src +7 +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +│ +8 +end +│ +9 +return +table +. +concat +( +lines +, +" +\n +" +) +├ +──────────────────────────────────────────────────── +10 +end +│ +11 +│ +init.lua +12 +M +. +version += +"1.0" +│ +13 +│ +4 +: +local +M += +{} +14 +return +M +│ +function +M +. +greet +( +names +, +word +) +~ +│ +local +lines += +{} +~ +│ +for +index +, +name +in +ipairs +( +names +) +do +~ +│ +lines +[ +index +] += +word +.. +", +" +.. +name +~ +│ +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +~ +│ +end +~ +│ +return +table +. +concat +( +lines +, +" +\n +" +) +~ +│ +end +~ +│ +~ +│ +M +. +version += +"1.0" +~ +│ +~ +│ +return +M +~ +│ +~ +│ +~ +│ +~ +│ +init.lua +1,1 +All +│ +│ +work +0:nvim +Wed +01 +Jan +12:00 + + diff --git a/previews/nord.svg b/previews/nord.svg new file mode 100644 index 0000000..ef4d5e8 --- /dev/null +++ b/previews/nord.svg @@ -0,0 +1,322 @@ + +Nord + + + + + + + + + + + + + + + + +1 +local +M += +{} +│ +l +r +w +x +r +w +x +r +w +x +- +2025-01-01 +12:00 +-- +docs +-> +src +2 +│ +. +r +w +- +r +-- +r +-- +259 +2025-01-01 +12:00 +- +M +init.lua +3 +-- +Greets +every +name +on +the +list. +│ +. +r +w +- +r +-- +r +-- +5 +2025-01-01 +12:00 +-- +notes.txt +4 +function +M +. +greet +( +names +, +word +) +│ +. +r +w +- +r +-- +r +-- +10 +2025-01-01 +12:00 +-- +README.md +5 +local +lines += +{} +│ +. +r +w +x +r +- +x +r +- +x +24 +2025-01-01 +12:00 +-- +run.sh +6 +for +index +, +name +in +ipairs +( +names +) +do +│ +d +r +w +x +r +- +x +r +- +x +- +2025-01-01 +12:00 +-- +src +7 +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +│ +8 +end +│ +9 +return +table +. +concat +( +lines +, +" +\n +" +) +├ +──────────────────────────────────────────────────── +10 +end +│ +11 +│ +init.lua +12 +M +. +version += +"1.0" +│ +13 +│ +4 +: +local +M += +{} +14 +return +M +│ +function +M +. +greet +( +names +, +word +) +~ +│ +local +lines += +{} +~ +│ +for +index +, +name +in +ipairs +( +names +) +do +~ +│ +lines +[ +index +] += +word +.. +", +" +.. +name +~ +│ +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +~ +│ +end +~ +│ +return +table +. +concat +( +lines +, +" +\n +" +) +~ +│ +end +~ +│ +~ +│ +M +. +version += +"1.0" +~ +│ +~ +│ +return +M +~ +│ +~ +│ +~ +│ +~ +│ +init.lua +1,1 +All +│ +│ +work +0:nvim +Wed +01 +Jan +12:00 + + diff --git a/previews/rose-pine.svg b/previews/rose-pine.svg new file mode 100644 index 0000000..418dfdb --- /dev/null +++ b/previews/rose-pine.svg @@ -0,0 +1,321 @@ + +Rosé Pine + + + + + + + + + + + + + + + + +1 +local +M += +{} +│l +r +w +x +r +w +x +r +w +x +- +2025-01-01 +12:00 +-- +docs +-> +src +2 +│ +. +r +w +- +r +-- +r +-- +259 +2025-01-01 +12:00 +- +M +init.lua +3 +-- +Greets +every +name +on +the +list. +│ +. +r +w +- +r +-- +r +-- +5 +2025-01-01 +12:00 +-- +notes.txt +4 +function +M +. +greet +( +names +, +word +) +│ +. +r +w +- +r +-- +r +-- +10 +2025-01-01 +12:00 +-- +README.md +5 +local +lines += +{} +│ +. +r +w +x +r +- +x +r +- +x +24 +2025-01-01 +12:00 +-- +run.sh +6 +for +index +, +name +in +ipairs +( +names +) +do +│ +d +r +w +x +r +- +x +r +- +x +- +2025-01-01 +12:00 +-- +src +7 +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +│ +8 +end +│ +9 +return +table +. +concat +( +lines +, +" +\n +" +) +├ +──────────────────────────────────────────────────── +10 +end +│ +11 +│ +init.lua +12 +M +. +version += +"1.0" +│ +13 +│ +4 +: +local +M += +{} +14 +return +M +│ +function +M +. +greet +( +names +, +word +) +~ +│ +local +lines += +{} +~ +│ +for +index +, +name +in +ipairs +( +names +) +do +~ +│ +lines +[ +index +] += +word +.. +", +" +.. +name +~ +│ +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +~ +│ +end +~ +│ +return +table +. +concat +( +lines +, +" +\n +" +) +~ +│ +end +~ +│ +~ +│ +M +. +version += +"1.0" +~ +│ +~ +│ +return +M +~ +│ +~ +│ +~ +│ +~ +│ +init.lua +1,1 +All +│ +│ +work +0:nvim +Wed +01 +Jan +12:00 + + diff --git a/previews/solarized-light.svg b/previews/solarized-light.svg new file mode 100644 index 0000000..3d609ed --- /dev/null +++ b/previews/solarized-light.svg @@ -0,0 +1,335 @@ + +Solarized Light + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 +local +M += +{} +│ +l +r +w +x +r +w +x +r +w +x +- +2025-01-01 +12:00 +-- +docs +-> +src +2 +│ +. +r +w +- +r +-- +r +-- +259 +2025-01-01 +12:00 +- +M +init.lua +3 +-- +Greets +every +name +on +the +list. +│ +. +r +w +- +r +-- +r +-- +5 +2025-01-01 +12:00 +-- +notes.txt +4 +function +M +. +greet +( +names +, +word +) +│ +. +r +w +- +r +-- +r +-- +10 +2025-01-01 +12:00 +-- +README.md +5 +local +lines += +{} +│ +. +r +w +x +r +- +x +r +- +x +24 +2025-01-01 +12:00 +-- +run.sh +6 +for +index +, +name +in +ipairs +( +names +) +do +│d +r +w +x +r +- +x +r +- +x +- +2025-01-01 +12:00 +-- +src +7 +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +│ +8 +end +│ +9 +return +table +. +concat +( +lines +, +" +\n +" +) +├ +──────────────────────────────────────────────────── +10 +end +│ +11 +│ +init.lua +12 +M +. +version += +"1.0" +│ +13 +│ +4 +: +local +M += +{} +14 +return +M +│ +function +M +. +greet +( +names +, +word +) +~ +│ +local +lines += +{} +~ +│ +for +index +, +name +in +ipairs +( +names +) +do +~ +│ +lines +[ +index +] += +word +.. +", +" +.. +name +~ +│ +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +~ +│ +end +~ +│ +return +table +. +concat +( +lines +, +" +\n +" +) +~ +│ +end +~ +│ +~ +│ +M +. +version += +"1.0" +~ +│ +~ +│ +return +M +~ +│ +~ +│ +~ +│ +~ +│ +init.lua +1,1 +All +│ +│ +work +0:nvim +Wed +01 +Jan +12:00 + + diff --git a/previews/themes.json b/previews/themes.json new file mode 100644 index 0000000..4910cba --- /dev/null +++ b/previews/themes.json @@ -0,0 +1,262 @@ +[ + { + "name": "catppuccin-mocha", + "title": "Catppuccin Mocha", + "homepage": "https://catppuccin.com/palette/", + "dark": true, + "palette": { + "accent": "#cba6f7", + "background": "#1e1e2e", + "background_dark": "#181825", + "background_darker": "#11111b", + "blue": "#89b4fa", + "border": "#585b70", + "cyan": "#94e2d5", + "foreground": "#cdd6f4", + "green": "#a6e3a1", + "muted": "#9399b2", + "orange": "#fab387", + "pink": "#f5c2e7", + "purple": "#cba6f7", + "red": "#f38ba8", + "selection": "#45475a", + "subtle": "#a6adc8", + "surface": "#313244", + "yellow": "#f9e2af" + } + }, + { + "name": "dracula", + "title": "Dracula", + "homepage": "https://draculatheme.com/", + "dark": true, + "palette": { + "accent": "#bd93f9", + "background": "#282a36", + "background_dark": "#21222c", + "background_darker": "#191a21", + "blue": "#bd93f9", + "border": "#6272a4", + "cyan": "#8be9fd", + "foreground": "#f8f8f2", + "green": "#50fa7b", + "muted": "#6272a4", + "orange": "#ffb86c", + "pink": "#ff79c6", + "purple": "#bd93f9", + "red": "#ff5555", + "selection": "#44475a", + "subtle": "#abb2bf", + "surface": "#44475a", + "yellow": "#f1fa8c" + } + }, + { + "name": "everforest-dark", + "title": "Everforest Dark", + "homepage": "https://github.com/sainnhe/everforest", + "dark": true, + "palette": { + "accent": "#a7c080", + "background": "#2d353b", + "background_dark": "#232a2e", + "background_darker": "#232a2e", + "blue": "#7fbbb3", + "border": "#4f585e", + "cyan": "#83c092", + "foreground": "#d3c6aa", + "green": "#a7c080", + "muted": "#859289", + "orange": "#e69875", + "pink": "#d699b6", + "purple": "#d699b6", + "red": "#e67e80", + "selection": "#543a48", + "subtle": "#9da9a0", + "surface": "#343f44", + "yellow": "#dbbc7f" + } + }, + { + "name": "github-light", + "title": "GitHub Light", + "homepage": "https://primer.style/", + "dark": false, + "palette": { + "accent": "#0969da", + "background": "#ffffff", + "background_dark": "#f6f8fa", + "background_darker": "#eaeef2", + "blue": "#0969da", + "border": "#d0d7de", + "cyan": "#1b7c83", + "foreground": "#1f2328", + "green": "#1a7f37", + "muted": "#6e7781", + "orange": "#bc4c00", + "pink": "#bf3989", + "purple": "#8250df", + "red": "#d1242f", + "selection": "#dae9f9", + "subtle": "#656d76", + "surface": "#eaeef2", + "yellow": "#9a6700" + } + }, + { + "name": "gruvbox-dark", + "title": "Gruvbox Dark", + "homepage": "https://github.com/morhetz/gruvbox", + "dark": true, + "palette": { + "accent": "#fabd2f", + "background": "#282828", + "background_dark": "#1d2021", + "background_darker": "#1d2021", + "blue": "#83a598", + "border": "#504945", + "cyan": "#8ec07c", + "foreground": "#ebdbb2", + "green": "#b8bb26", + "muted": "#928374", + "orange": "#fe8019", + "pink": "#d3869b", + "purple": "#d3869b", + "red": "#fb4934", + "selection": "#665c54", + "subtle": "#a89984", + "surface": "#3c3836", + "yellow": "#fabd2f" + } + }, + { + "name": "kanagawa-wave", + "title": "Kanagawa Wave", + "homepage": "https://github.com/rebelot/kanagawa.nvim", + "dark": true, + "palette": { + "accent": "#7e9cd8", + "background": "#1f1f28", + "background_dark": "#16161d", + "background_darker": "#16161d", + "blue": "#7e9cd8", + "border": "#54546d", + "cyan": "#7aa89f", + "foreground": "#dcd7ba", + "green": "#98bb6c", + "muted": "#727169", + "orange": "#ffa066", + "pink": "#d27e99", + "purple": "#957fb8", + "red": "#e46876", + "selection": "#223249", + "subtle": "#c8c093", + "surface": "#2a2a37", + "yellow": "#e6c384" + } + }, + { + "name": "nord", + "title": "Nord", + "homepage": "https://www.nordtheme.com/", + "dark": true, + "palette": { + "accent": "#88c0d0", + "background": "#2e3440", + "background_dark": "#232730", + "background_darker": "#191d23", + "blue": "#81a1c1", + "border": "#4c566a", + "cyan": "#88c0d0", + "foreground": "#d8dee9", + "green": "#a3be8c", + "muted": "#616e88", + "orange": "#d08770", + "pink": "#b48ead", + "purple": "#b48ead", + "red": "#bf616a", + "selection": "#434c5e", + "subtle": "#9da6b9", + "surface": "#3b4252", + "yellow": "#ebcb8b" + } + }, + { + "name": "rose-pine", + "title": "Ros\u00e9 Pine", + "homepage": "https://rosepinetheme.com/", + "dark": true, + "palette": { + "accent": "#ebbcba", + "background": "#191724", + "background_dark": "#16141f", + "background_darker": "#16141f", + "blue": "#31748f", + "border": "#524f67", + "cyan": "#9ccfd8", + "foreground": "#e0def4", + "green": "#95b1ac", + "muted": "#6e6a86", + "orange": "#f6c177", + "pink": "#ebbcba", + "purple": "#c4a7e7", + "red": "#eb6f92", + "selection": "#403d52", + "subtle": "#908caa", + "surface": "#1f1d2e", + "yellow": "#f6c177" + } + }, + { + "name": "solarized-light", + "title": "Solarized Light", + "homepage": "https://ethanschoonover.com/solarized/", + "dark": false, + "palette": { + "accent": "#268bd2", + "background": "#fdf6e3", + "background_dark": "#eee8d5", + "background_darker": "#eee8d5", + "blue": "#268bd2", + "border": "#93a1a1", + "cyan": "#2aa198", + "foreground": "#586e75", + "green": "#859900", + "muted": "#93a1a1", + "orange": "#cb4b16", + "pink": "#d33682", + "purple": "#6c71c4", + "red": "#dc322f", + "selection": "#d3cfc1", + "subtle": "#657b83", + "surface": "#eee8d5", + "yellow": "#b58900" + } + }, + { + "name": "tokyo-night", + "title": "Tokyo Night", + "homepage": "https://github.com/folke/tokyonight.nvim", + "dark": true, + "palette": { + "accent": "#7aa2f7", + "background": "#1a1b26", + "background_dark": "#16161e", + "background_darker": "#0c0e14", + "blue": "#7aa2f7", + "border": "#3b4261", + "cyan": "#7dcfff", + "foreground": "#c0caf5", + "green": "#9ece6a", + "muted": "#565f89", + "orange": "#ff9e64", + "pink": "#ff007c", + "purple": "#bb9af7", + "red": "#f7768e", + "selection": "#283457", + "subtle": "#a9b1d6", + "surface": "#292e42", + "yellow": "#e0af68" + } + } +] diff --git a/previews/tokyo-night.svg b/previews/tokyo-night.svg new file mode 100644 index 0000000..759d839 --- /dev/null +++ b/previews/tokyo-night.svg @@ -0,0 +1,321 @@ + +Tokyo Night + + + + + + + + + + + + + + + + +1 +local +M += +{} +│ +l +r +w +x +r +w +x +r +w +x +- +2025-01-01 +12:00 +-- +docs +-> +src +2 +│ +. +r +w +- +r +-- +r +-- +259 +2025-01-01 +12:00 +- +M +init.lua +3 +-- +Greets +every +name +on +the +list. +│ +. +r +w +- +r +-- +r +-- +5 +2025-01-01 +12:00 +-- +notes.txt +4 +function +M +. +greet +( +names +, +word +) +│ +. +r +w +- +r +-- +r +-- +10 +2025-01-01 +12:00 +-- +README.md +5 +local +lines += +{} +│ +. +r +w +x +r +- +x +r +- +x +24 +2025-01-01 +12:00 +-- +run.sh +6 +for +index +, +name +in +ipairs +( +names +) +do +│d +r +w +x +r +- +x +r +- +x +- +2025-01-01 +12:00 +-- +src +7 +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +│ +8 +end +│ +9 +return +table +. +concat +( +lines +, +" +\n +" +) +├ +──────────────────────────────────────────────────── +10 +end +│ +11 +│ +init.lua +12 +M +. +version += +"1.0" +│ +13 +│ +4 +: +local +M += +{} +14 +return +M +│ +function +M +. +greet +( +names +, +word +) +~ +│ +local +lines += +{} +~ +│ +for +index +, +name +in +ipairs +( +names +) +do +~ +│ +lines +[ +index +] += +word +.. +", +" +.. +name +~ +│ +lines +[ +index +] += +( +word +or +"Hi" +) +.. +", +" +.. +name +~ +│ +end +~ +│ +return +table +. +concat +( +lines +, +" +\n +" +) +~ +│ +end +~ +│ +~ +│ +M +. +version += +"1.0" +~ +│ +~ +│ +return +M +~ +│ +~ +│ +~ +│ +~ +│ +init.lua +1,1 +All +│ +│ +work +0:nvim +Wed +01 +Jan +12:00 + + diff --git a/tools/harness/__main__.py b/tools/harness/__main__.py index fa2ca05..3bb12b7 100644 --- a/tools/harness/__main__.py +++ b/tools/harness/__main__.py @@ -8,7 +8,7 @@ if __package__ in (None, ""): sys.path.insert(0, str(Path(__file__).resolve().parents[1])) -from harness import REPOSITORY, cache, checks, drift, graph, plan, release, update +from harness import REPOSITORY, cache, checks, drift, graph, plan, previews, release, update def parser() -> argparse.ArgumentParser: @@ -52,6 +52,7 @@ def parser() -> argparse.ArgumentParser: reports.add_argument("--repository", default=os.environ.get("GITHUB_REPOSITORY", "")) commands.add_parser("graph", help="Write catalog/graph.json from the graph.toml files") + commands.add_parser("previews", help="Write previews/, the theme previews of the site") return root @@ -100,6 +101,8 @@ def run(arguments: argparse.Namespace) -> int: drift.report(arguments.repository) case "graph": graph.write() + case "previews": + previews.write() return 0 diff --git a/tools/harness/previews.py b/tools/harness/previews.py new file mode 100644 index 0000000..4f388b8 --- /dev/null +++ b/tools/harness/previews.py @@ -0,0 +1,40 @@ +"""Write previews/, the theme previews that the site shows. + +Nix builds tools/previews.nix, which draws the scene of every theme; this command copies the +result into previews/ and gives the files the owner of the repository. +""" + +import os +import shutil +import subprocess +from pathlib import Path + +from harness import REPOSITORY, nix + +SOURCE = REPOSITORY / "tools" / "previews.nix" + + +def write(repository: Path = REPOSITORY) -> Path: + built = subprocess.run( + [ + "nix", + *nix.FLAGS, + "build", + "--impure", + "--no-link", + "--print-out-paths", + "--file", + SOURCE, + ], + check=True, + stdout=subprocess.PIPE, + text=True, + ).stdout.strip() + target = repository / "previews" + shutil.rmtree(target, ignore_errors=True) + shutil.copytree(built, target) + owner = repository.stat() + for path in [target, *target.iterdir()]: + path.chmod(0o755 if path.is_dir() else 0o644) + os.chown(path, owner.st_uid, owner.st_gid) + return target diff --git a/tools/previews.nix b/tools/previews.nix new file mode 100644 index 0000000..2780f8c --- /dev/null +++ b/tools/previews.nix @@ -0,0 +1,57 @@ +# The preview of each theme that the site shows: tmux, Neovim, eza and delta from the catalog, +# drawn with the theme in a terminal and saved as SVG, with the themes' palettes as JSON. +let + nixpkgs = import ../checks/nixpkgs.nix; + system = builtins.currentSystem; + pkgs = import nixpkgs { inherit system; }; + inherit (pkgs) lib; + themes = builtins.fromTOML (builtins.readFile ../catalog/_shared/themes.toml); + scene = + name: + let + inherit + ( + (import ../checks/nixos.nix { + inherit nixpkgs system; + userName = "preview"; + modules = [ + ../catalog/shell/tmux/default.nix + ../catalog/shell/neovim/default.nix + ../catalog/shell/cli-tools/default.nix + { lmx.capabilities.theme.name = name; } + ]; + }) + ) + config + ; + delta = lib.findFirst ( + package: lib.hasPrefix "delta-" (package.name or "") + ) pkgs.delta config.environment.systemPackages; + in + { + inherit name; + tmux = config.environment.etc."tmux.conf".source; + environment = config.system.build.setEnvironment; + gitconfig = config.environment.etc."gitconfig".source; + neovim = "${config.programs.neovim.finalPackage}/bin/nvim"; + delta = "${delta}/bin/delta"; + palette = config.lmx.capabilities.theme.palette; + inherit (themes.${name}) title homepage dark; + }; +in +pkgs.runCommand "theme-previews" + { + nativeBuildInputs = [ + (pkgs.python3.withPackages (python: [ python.pyte ])) + pkgs.tmux + pkgs.git + pkgs.eza + pkgs.ncurses + ]; + scenes = builtins.toJSON (map scene (builtins.attrNames themes)); + passAsFile = [ "scenes" ]; + } + '' + export TERMINFO_DIRS=${pkgs.ncurses}/share/terminfo + python3 ${./previews.py} "$scenesPath" "$out" + '' diff --git a/tools/previews.py b/tools/previews.py new file mode 100644 index 0000000..c8b66dd --- /dev/null +++ b/tools/previews.py @@ -0,0 +1,255 @@ +"""Draw each theme's preview: a tmux window with Neovim, eza and delta, saved as SVG. + +The scene runs the catalog's own configuration of each tool in a pseudo-terminal; pyte keeps the +screen that tmux draws, and the SVG paints it cell by cell in the theme's colors. +""" + +import fcntl +import json +import os +import pty +import re +import select +import shlex +import struct +import subprocess +import sys +import tempfile +import termios +import time +from html import escape +from pathlib import Path +from typing import Any + +import pyte + +COLUMNS, ROWS = 120, 32 +WIDTH, HEIGHT, FONT, PADDING = 8.4, 18.0, 14, 14 +FONTS = ( + "'JetBrains Mono Variable', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace" +) +SAMPLE = """local M = {} + +-- Greets every name on the list. +function M.greet(names, word) + local lines = {} + for index, name in ipairs(names) do + lines[index] = word .. ", " .. name + end + return table.concat(lines, "\\n") +end + +return M +""" +TERMINAL = { + "black": "background_darker", + "red": "red", + "green": "green", + "brown": "yellow", + "blue": "blue", + "magenta": "purple", + "cyan": "cyan", + "white": "subtle", + "brightblack": "muted", + "brightred": "red", + "brightgreen": "green", + "brightbrown": "yellow", + "brightblue": "blue", + "brightmagenta": "pink", + "brightcyan": "cyan", + "brightwhite": "foreground", +} + + +class Screen(pyte.Screen): + def report_device_status(self, mode: int, **_: Any) -> None: + pass + + +def project(directory: Path) -> None: + (directory / "src").mkdir() + (directory / "init.lua").write_text(SAMPLE) + (directory / "README.md").write_text("# Greeter\n") + (directory / "notes.txt").write_text("todo\n") + (directory / "run.sh").write_text("#!/bin/sh\nnvim init.lua\n") + (directory / "run.sh").chmod(0o755) + (directory / "src" / "util.lua").write_text("return {}\n") + (directory / "docs").symlink_to("src") + git = ["git", "-c", "user.name=LimaNix", "-c", "user.email=preview@limanix.dev"] + subprocess.run([*git, "init", "-q"], cwd=directory, check=True) + subprocess.run([*git, "add", "."], cwd=directory, check=True) + subprocess.run([*git, "commit", "-qm", "greeter"], cwd=directory, check=True) + changed = SAMPLE.replace("= word ..", '= (word or "Hi") ..').replace( + "return M", 'M.version = "1.0"\n\nreturn M' + ) + (directory / "init.lua").write_text(changed) + for path in directory.rglob("*"): + if ".git" not in path.parts: + os.utime(path, (1735732800, 1735732800), follow_symlinks=False) + + +def configuration(source: str, target: Path) -> None: + text = Path(source).read_text() + text = text.replace("#(/run/current-system/sw/bin/lmx status --short)", "") + text = text.replace("%a %d %b", "Wed 01 Jan").replace("%H:%M", "12:00") + target.write_text(text) + + +def capture(scene: dict[str, Any], home: Path) -> pyte.Screen: + directory = home / "greeter" + directory.mkdir() + project(directory) + (home / ".gitconfig").write_text(Path(scene["gitconfig"]).read_text()) + configuration(scene["tmux"], home / "tmux.conf") + tools = ":".join(str(Path(tool).parent) for tool in (scene["neovim"], scene["delta"])) + prefix = ( + f". {shlex.quote(scene['environment'])}; " + f"export PATH={tools}:{os.environ['PATH']}; cd {directory}; " + ) + environment = { + **os.environ, + "HOME": str(home), + "TMUX_TMPDIR": str(home), + "TERM": "xterm-256color", + "COLORTERM": "truecolor", + "LANG": "C.UTF-8", + } + commands = { + "editor": "nvim -c 'set number' init.lua", + "files": "eza -l --git --no-user --time-style=long-iso; exec sleep 600", + "diff": "git --no-pager diff | delta --paging=never; exec sleep 600", + } + shell = {name: prefix + command for name, command in commands.items()} + tmux = ["tmux", "-u", "-L", "preview", "-f", str(home / "tmux.conf")] + size = ["-x", str(COLUMNS), "-y", str(ROWS - 1)] + for command in ( + ["new-session", "-d", "-s", "work", "-n", "nvim", *size, shell["editor"]], + ["split-window", "-h", "-l", "52", shell["files"]], + ["split-window", "-v", "-l", "22", shell["diff"]], + ["select-pane", "-t", "work:0.0"], + ): + subprocess.run([*tmux, *command], env=environment, check=True) + screen = Screen(COLUMNS, ROWS) + stream = pyte.ByteStream(screen) + pid, descriptor = pty.fork() + if pid == 0: + fcntl.ioctl(0, termios.TIOCSWINSZ, struct.pack("HHHH", ROWS, COLUMNS, 0, 0)) + os.execvpe(tmux[0], [*tmux, "attach", "-t", "work"], environment) + os.set_blocking(descriptor, False) + deadline = time.monotonic() + 60 + quiet = time.monotonic() + try: + while time.monotonic() < deadline: + ready, _, _ = select.select([descriptor], [], [], 0.2) + if ready: + try: + data = os.read(descriptor, 65536) + except OSError: + break + if data: + stream.feed(data) + quiet = time.monotonic() + text = "\n".join(screen.display) + settled = time.monotonic() - quiet > 2 + if settled and all(marker in text for marker in ("M.greet", "run.sh", "= word ..")): + break + else: + lines = "\n".join(screen.display) + raise SystemExit(f"{scene['name']}: the scene did not settle:\n{lines}") + finally: + subprocess.run([*tmux, "kill-server"], env=environment, check=False) + os.close(descriptor) + os.waitpid(pid, 0) + return screen + + +def paint(color: str, palette: dict[str, str], default: str) -> str: + if color == "default": + return default + if color in TERMINAL: + return palette[TERMINAL[color]] + return "#" + color.lower() + + +def svg(screen: pyte.Screen, scene: dict[str, Any]) -> str: + palette = scene["palette"] + width = COLUMNS * WIDTH + 2 * PADDING + height = ROWS * HEIGHT + 2 * PADDING + shapes: list[str] = [] + texts: list[str] = [] + for row in range(ROWS): + line = screen.buffer[row] + cells = [] + for column in range(COLUMNS): + cell = line[column] + foreground = paint(cell.fg, palette, palette["foreground"]) + background = paint(cell.bg, palette, palette["background"]) + if cell.reverse: + foreground, background = background, foreground + cells.append( + (cell.data or " ", foreground, background, cell.bold, cell.italics, cell.underscore) + ) + y = PADDING + row * HEIGHT + column = 0 + while column < COLUMNS: + background = cells[column][2] + end = column + while end < COLUMNS and cells[end][2] == background: + end += 1 + if background != palette["background"]: + shapes.append( + f'' + ) + column = end + column = 0 + while column < COLUMNS: + style = cells[column][1:2] + cells[column][3:] + end = column + while end < COLUMNS and cells[end][1:2] + cells[end][3:] == style: + end += 1 + run = "".join(cell[0] for cell in cells[column:end]) + attributes = f'fill="{style[0]}"' + if style[1]: + attributes += ' font-weight="700"' + if style[2]: + attributes += ' font-style="italic"' + if style[3]: + attributes += ' text-decoration="underline"' + # One text per word: SVG viewers collapse runs of spaces. + for word in re.finditer(r"\S+", run): + texts.append( + f'{escape(word.group())}' + ) + column = end + title = escape(scene["title"]) + return ( + '\n' + f"{title}\n" + f'\n' + + "\n".join(shapes) + + f'\n\n' + + "\n".join(texts) + + "\n\n\n" + ) + + +def main() -> None: + scenes = json.loads(Path(sys.argv[1]).read_text()) + output = Path(sys.argv[2]) + output.mkdir() + themes = [] + for scene in sorted(scenes, key=lambda item: item["title"]): + with tempfile.TemporaryDirectory() as home: + screen = capture(scene, Path(home)) + (output / f"{scene['name']}.svg").write_text(svg(screen, scene)) + keys = ("name", "title", "homepage", "dark", "palette") + themes.append({key: scene[key] for key in keys}) + (output / "themes.json").write_text(json.dumps(themes, indent=2) + "\n") + + +if __name__ == "__main__": + main() diff --git a/tools/tests/fixtures/failure.err b/tools/tests/fixtures/failure.err index ce7c760..72350ce 100644 --- a/tools/tests/fixtures/failure.err +++ b/tools/tests/fixtures/failure.err @@ -13,9 +13,9 @@ error: | ^ 294| } - … while evaluating the option `lmx.capabilities.theme.flavor': + … while evaluating the option `lmx.capabilities.theme.name': (stack trace truncated; use '--show-trace' to show the full, detailed trace) - error: A definition for option `lmx.capabilities.theme.flavor' is not of type `one of "system", "frappe", "latte", "macchiato", "mocha"'. Definition values: + error: A definition for option `lmx.capabilities.theme.name' is not of type `one of "system", "catppuccin-mocha", "dracula", "everforest-dark", "github-light", "gruvbox-dark", "kanagawa-wave", "nord", "rose-pine", "solarized-light", "tokyo-night"'. Definition values: - In `/workspace/checks/nixos.nix': "espresso" diff --git a/tools/tests/test_nix.py b/tools/tests/test_nix.py index d26a246..16a3d37 100644 --- a/tools/tests/test_nix.py +++ b/tools/tests/test_nix.py @@ -10,7 +10,7 @@ def test_primary_error_keeps_the_last_error_block_without_source_lines() -> None: diagnostic = nix.primary_error((FIXTURES / "failure.err").read_text()) assert diagnostic.startswith( - " error: A definition for option `lmx.capabilities.theme.flavor'" + " error: A definition for option `lmx.capabilities.theme.name'" ) assert "espresso" in diagnostic assert "while evaluating" not in diagnostic