From 770bdcf3a24e521cd2f67206e53c913bd686f13b Mon Sep 17 00:00:00 2001 From: MDietermann Date: Tue, 14 Apr 2026 13:14:47 +0200 Subject: [PATCH] feat(theming): integrate NvChad chadrc rewrite into Apply --- internal/modules/neovim/neovim.go | 38 ++++++++++++++++++++++++++ internal/modules/neovim/neovim_test.go | 30 ++++++++++++++++++++ internal/theming/theming.go | 10 +++++++ internal/theming/theming_test.go | 31 +++++++++++++++++++++ 4 files changed, 109 insertions(+) diff --git a/internal/modules/neovim/neovim.go b/internal/modules/neovim/neovim.go index e61c51f..0f89cca 100644 --- a/internal/modules/neovim/neovim.go +++ b/internal/modules/neovim/neovim.go @@ -88,6 +88,44 @@ func (m Module) resolvedTheme() string { return "gruvbox" } +// WriteChadrc renders chadrc.lua for the given NvChad theme name and writes +// it to ~/.config/nvim/lua/chadrc.lua. If the file does not already exist +// and the surrounding nvim config dir does not exist, the function returns +// nil without writing anything (the user hasn't installed neovim yet). +// +// Exported so the theming package can re-render chadrc on theme switches +// without re-running the full Module pipeline. +func WriteChadrc(nvChadThemeName string) error { + home, err := os.UserHomeDir() + if err != nil { + return err + } + cfg := filepath.Join(home, ".config", "nvim") + chadrcPath := filepath.Join(cfg, "lua", "chadrc.lua") + + // If chadrc doesn't exist, only write when the parent dir exists. A + // fresh system where nvim was never installed should be a no-op. + if _, err := os.Stat(chadrcPath); err != nil { + if _, err := os.Stat(cfg); err != nil { + return nil + } + } + + tmplBytes, err := assets.ReadFile("assets/chadrc.lua.tmpl") + if err != nil { + return err + } + tmpl, err := template.New("chadrc").Parse(string(tmplBytes)) + if err != nil { + return err + } + var buf bytes.Buffer + if err := tmpl.Execute(&buf, struct{ NvChadTheme string }{NvChadTheme: nvChadThemeName}); err != nil { + return err + } + return platform.WriteUserFile(chadrcPath, buf.Bytes(), 0o644) +} + func (m Module) renderChadrc() ([]byte, error) { tmplBytes, err := assets.ReadFile("assets/chadrc.lua.tmpl") if err != nil { diff --git a/internal/modules/neovim/neovim_test.go b/internal/modules/neovim/neovim_test.go index 6ad7510..862d107 100644 --- a/internal/modules/neovim/neovim_test.go +++ b/internal/modules/neovim/neovim_test.go @@ -177,4 +177,34 @@ func TestUnknownThemeFallsBackToFirst(t *testing.T) { } } +func TestWriteChadrcWritesFile(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + cfg := filepath.Join(home, ".config", "nvim") + if err := os.MkdirAll(cfg, 0o755); err != nil { + t.Fatal(err) + } + if err := WriteChadrc("dracula"); err != nil { + t.Fatal(err) + } + data, err := os.ReadFile(filepath.Join(cfg, "lua", "chadrc.lua")) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(data), `theme = "dracula"`) { + t.Fatalf("theme not applied: %s", data) + } +} + +func TestWriteChadrcNoopWithoutConfigDir(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + if err := WriteChadrc("gruvbox"); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(filepath.Join(home, ".config", "nvim", "lua", "chadrc.lua")); !os.IsNotExist(err) { + t.Fatal("chadrc should not be created on a bare system") + } +} + var _ module.Module = Module{} diff --git a/internal/theming/theming.go b/internal/theming/theming.go index b1369b4..cf5493a 100644 --- a/internal/theming/theming.go +++ b/internal/theming/theming.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/MDietermann/ArduinoArchSetup/internal/config" + "github.com/MDietermann/ArduinoArchSetup/internal/modules/neovim" "github.com/MDietermann/ArduinoArchSetup/internal/platform" "gopkg.in/yaml.v3" ) @@ -144,6 +145,15 @@ func Apply(cfg config.Config, themeID string) error { if err := updateOverlayActive(th.ID); err != nil { return fmt.Errorf("update overlay: %w", err) } + + if err := neovim.WriteChadrc(th.NvChadTheme); err != nil { + return fmt.Errorf("update chadrc: %w", err) + } + + // Clear NvChad's base46 theme cache so the new theme activates on next + // nvim launch. Errors ignored — the cache may not exist. + _ = os.RemoveAll(filepath.Join(home, ".cache", "nvim", "base46")) + _ = os.RemoveAll(filepath.Join(home, ".local", "share", "nvim", "lazy", "base46", "lua", "base46", "themes")) return nil } diff --git a/internal/theming/theming_test.go b/internal/theming/theming_test.go index 0e5f02c..d018b6d 100644 --- a/internal/theming/theming_test.go +++ b/internal/theming/theming_test.go @@ -117,3 +117,34 @@ func TestApplyPreservesOtherOverlayKeys(t *testing.T) { t.Fatalf("active theme missing after preserve: %s", data) } } + +func TestApplyRewritesChadrcIfPresent(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + nvimCfg := filepath.Join(home, ".config", "nvim", "lua") + if err := os.MkdirAll(nvimCfg, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(nvimCfg, "chadrc.lua"), + []byte(`M.base46 = { theme = "onedark" }`), 0o644); err != nil { + t.Fatal(err) + } + if err := Apply(config.Config{}, "gruvbox"); err != nil { + t.Fatal(err) + } + data, _ := os.ReadFile(filepath.Join(nvimCfg, "chadrc.lua")) + if !strings.Contains(string(data), `theme = "gruvbox"`) { + t.Fatalf("chadrc not rewritten: %s", data) + } + if strings.Contains(string(data), `theme = "onedark"`) { + t.Fatalf("old theme still present: %s", data) + } +} + +func TestApplyNoChadrcIsFine(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + if err := Apply(config.Config{}, "gruvbox"); err != nil { + t.Fatal(err) + } +}