Auto-load themes for neovim
This PR adds an automatic reload feature for NeoVim themes when you change your Omarchy theme.
https://github.com/user-attachments/assets/bc552ab4-6733-4687-90d2-2127504fbae1
- It uses a new LazyVim starter repo that includes the required config for this solution to work.
Omarchy lazyvim starter repo (feel free to fork this repo into your own github and switch the git clone command in install/development/nvim.sh)
- I also needed to add a specific flavour for the regular Catppuccin theme (mocha) for this to work as intended.
- Finally, I added a line in the omarchy-theme-set that triggers the load of the new theme in NeoVim.
How does the automatic reload feature work?:
In order to enable the automatic reload feature, each theme in Omarchy must have a neovim.lua file that defines the themes like this:
-- .config/omarchy/themes/gruvbox/neovim.lua
return {
{ "morhetz/gruvbox", lazy = false },
{
"LazyVim/LazyVim",
opts = {
colorscheme = "gruvbox",
},
},
}
Additionally, we must make sure that LazyVim is configured to load every theme on startup. This is done by setting the spec and install options in the LazyVim configuration.
-- .config/nvim/lua/config/lazy.lua
spec = {
-- add LazyVim and import its plugins
{ "LazyVim/LazyVim", import = "lazyvim.plugins" },
-- import/override with your plugins
{ import = "plugins" },
-- import themes on load
{ "ellisonleao/gruvbox.nvim", lazy =false },
{ "catppuccin/nvim", name = "catppuccin", lazy = false },
{ "folke/tokyonight.nvim", lazy = false },
{ "neanias/everforest-nvim", lazy = false },
{ "rebelot/kanagawa.nvim", lazy = false },
{ "tahayvr/matteblack.nvim", lazy = false },
{ "EdenEast/nightfox.nvim", lazy = false },
{ "rose-pine/neovim", name = "rose-pine", lazy = false },
},
install = { colorscheme = { "catppuccin-latte", "catppuccin-mocha", "tokyonight", "habamax", "gruvbox", "everforest", "kanagawa", "nordfox", "matteblack", "rose-pine-dawn" },
Then, we need a function to reload the theme based on Omarchy's current theme symlink target. This function will check if the theme has changed and apply the new theme without restarting Neovim.
-- .config/nvim/lua/init.lua
-- Variable to store the last known theme symlink target
local last_theme_target = nil
-- Function to reload the theme based on Omarchy's current theme
function ReloadTheme()
local theme_dir = vim.fn.resolve(vim.fn.expand('~/.config/omarchy/current/theme'))
local theme_config = theme_dir .. '/neovim.lua'
-- Check if the theme directory has changed
if theme_dir == last_theme_target then
return -- No change, skip reload
end
if vim.fn.filereadable(theme_config) == 1 then
local ok, result = pcall(dofile, theme_config)
if not ok then
print("Error loading theme: " .. result)
return
end
-- Check if result is a table
if type(result) == "table" then
-- Look for LazyVim opts with colorscheme
for _, entry in ipairs(result) do
if entry[1] == "LazyVim/LazyVim" and entry.opts and entry.opts.colorscheme then
local ok, err = pcall(vim.cmd, 'colorscheme ' .. entry.opts.colorscheme)
if not ok then
print("Error applying colorscheme: " .. err)
return
end
vim.cmd('redraw!')
last_theme_target = theme_dir -- Update last known target
return
end
end
print("No valid colorscheme found in " .. theme_config)
else
print("Invalid theme config format in " .. theme_config)
end
else
print("Theme config not found: " .. theme_config)
end
end
-- Load the theme on startup
ReloadTheme()
How does Neovim know when to reload the theme?:
We can use a signal handler that listens for the SIGUSR1 signal. When this signal is received, it will trigger the ReloadTheme function.
-- .config/nvim/lua/init.lua
-- Reload theme on SIGUSR1 signal
vim.api.nvim_create_autocmd('User', {
pattern = 'OmarchyThemeReload',
callback = function()
ReloadTheme()
end,
})
-- Set up SIGUSR1 handler using vim.loop
local uv = vim.loop
local signal = uv.new_signal()
uv.signal_start(signal, "sigusr1", function()
vim.schedule(function()
vim.cmd('doautocmd User OmarchyThemeReload')
end)
end)
and finally, we need to send the SIGUSR1 signal to Neovim when the theme is changed. This can be done using a shell command or a script that changes the theme symlink and sends the signal to Neovim.
# omarchy-theme-set.sh
# Send SIGUSR1 signal to Neovim
pkill -SIGUSR1 nvim 2>/dev/null || true
```
Awesome! Been looking for this. I'm wondering if there isn't a way for us to upstream the function to do live theme changes to LazyVim directly? I'd rather not maintain a fork if we can get away with not doing that. Alternatively, we could put that function into our own LazyVim checkout, but still basing it on their package. Might that be possible?
I'll investigate.
@dhh We are really just changing two files in the starter: .config/nvim/lua/config/lazy.lua .config/nvim/lua/init.lua
what if we have these two modified files in a lazyvim folder in our repo: config/lazyvim
then copy them as part of the nvim.sh script ( install/development/nvim.sh ) to replace the default files cloned from the official starter.
` cp ~/.local/share/omarchy/config/lazyvim/init.lua ~/.config/nvim/lua/
cp ~/.local/share/omarchy/config/lazyvim/lazy.lua ~/.config/nvim/lua/config/ `
Sound better?
I changed the starter repo back to the original, and implemented the copy method mentioned above.
Sorry - I'm not super familiar with omarchy's internals, but I had a few questions:
- Any particular reason this is added to the lazyvim folder instead of the nvim folder in config? A lot of this looks like it could be loaded via a
plugin/file in that folder - I would recommend having a way for users to turn this off, something like checking
vim.g.omarchy_autochange_themeor something, so that users can disable in their configs if they don't want this behavior - perhaps better than trying to load and install all the colorschemes here, you could potentially just try pcall'ing the colorscheme command to the name of the omarchy colorscheme and see if it's installed (possibly, with a way of installing default themes as well for omarchy)?
Based on comments from @tjdevries I refactored the code to separate the function and the themes into an Omarchy folder in nvim/lua. So we're not touching LazyVim files anymore.
Thanks TJ for the review. As for the installation of colorschemes, It's the only way [I found] to get a theme refresh to work without hiccups. The user now doesn't have to manually initiate an install or load for a colorscheme (at least for the default themes).
@dhh this separation of code to omarchy's own folder also helps us figure out a way to add other themes to the lua/omarchy/init.lua file when the user installs third party themes.
Let me know what you think.
How does this work if the user installs an extra theme? Then it does just what we're doing now the first time? But after that it'll work?
How does this work if the user installs an extra theme? Then it does just what we're doing now the first time? But after that it'll work?
The nvim theme & colorscheme name must be added to the list in the lua/omarchy/init.lua file (we can automate this in the install theme script). Once it's there, Lazy will install it on first launch and it'll work after that.
The only issue is that if the user is using a theme and then modifying/ovverriding the values, it's not gonna use those modifications, it'll just use the base colorscheme.
Hmm, wish we could make it work like before where it could delegate to the theme nvim, if one is present. And if not present, we can assume that the theme is present in the preconfigured list. I like really way to sed an existing file with extra theme installs in and out if we can avoid it.
Something I played with a couple of days ago was that you can trigger changing themes by just require("tokyonight").load({ style = "storm" }) or require("matteblack").colorscheme(). If it's placed in your init.lua it'll flip to that theme at launch, and if it's added to a plugin file, it'll flip when it's updated due to LazyVim already having auto reloading on config changes.
There are some issues that I haven't worked out in here but in my head, there's some combination we should be able to achieve of like a symlinked .lua in the theme file that should be able to trigger the built-in LazyVim reloader and then we don't have to carry responsibility for so much of this. I believe the config will even install missing plugins if they're added in a detected config change, or if not, maybe there's a way we can have the plugin file send the lua command to trigger it.
The install of the theme can be handled completely from the plugin file as the install line in the config file is actually just fallback to try to install those themes from the Lazy repo.
Again...not fully baked by any stretch and I'm sure something @tahayvr has explored but wanted to get some thoughts and findings out of my head that I think could be helpful here before I forgot them.
Ok, I take it back! Seems like it is actually using the overrides in the neovim.lua file in themes. You can look at the ristretto theme mini-icons for testing.
The only consideration is that the themes must specify a colorscheme like so:
{
"LazyVim/LazyVim",
opts = {
colorscheme = "bamboo",
},
},
I've added this to the 2 new themes and Catppuccin. We can put a comment in the neovim.lua files that notes this for anyone trying to make themes.
@dhh and @ryanrhughes can you test this solution and let me know if it works on your end?
It's been a bumpy journey but "Per aspera ad astra".
This is super nice, but it doesn't seem entirely bullet proof yet. I tried switching from Kanagawa to Jade and got this weird grey border:
It's supposed to look like this:
I bet because the Kanagawa theme sets something that Jade then doesn't unset. I wonder if there's a way to reset everything between changes?
Also, with the current setup, what's the story on extra themes? Do they just install the theme they need on the next start?
Thanks @dhh for testing. This can be fixed by adding a vim.cmd("hi clear") before line 27 where we set the new colorscheme. I'll add this to the pr when I get a chance today.
As for new themes, I need to check how the omarchy-theme-install script is right now after the recent changes. My guess is that I need to add a script (omarchy-theme-neovim-add) that greps the theme's neovim.lua file for the repo name and colorscheme. Then adds it to our omarchy/init.lua file using gawk.
Once it's in the list in omarchy/init.lua, it'll work.
This basically is like a user has installed multiple themes and can switch between them from within neovim because they are already installed, loaded, and available. We are just automating the install/load/switching.
Awesome! Thanks for pursuing this all the way. It is annoying that you have to restart neovim right now to pickup the new theme. This is going to be much nicer 👌
I spent some time with this and tried a bit of a different take building on top of this concept to give us some options to look at.
The one thing I really don't like about this current setup is the requirement to maintain sort of a registry of themes in Neovim so I wanted to see what it'd take to eliminate that entirely. I've managed to find a way to remove all of that and just add logic to:
- Detect whether the theme we're calling isn't installed, in which case one instance installs while the others wait until that's finished and refresh themselves.
- Detect whether the theme is installed, but just not loaded, in which case we load the theme and call the colorscheme set.
- Call colorscheme set successfully because the theme is already loaded and ready to go.
In the event the theme is installed and just not called in a plugin, it still sticks around unless you run a Lazy Clean event. After that, it'd have to re-clone it.
https://github.com/basecamp/omarchy/compare/dev...ryanrhughes:omarchy:reload-neovim-themes
Sticking with the registry may be the simpler path in the end given the constraints we have in the current Lazy setup but figured it was worth a good swing for the learnings.
There is also some way we could possibly make that registry just a bunch of symlinks by having something like neovim.lua that has the them plugin definition, and neovim-active.lua that activates that theme specifically. When we install a theme, we just symlink all neovim.lua to ~/.config/nvim/plugins/{theme_name}.lua so they'll all trigger to be installed based on that vs having to maintain an init.lua file with all of them. neovim-active.lua effectively acts like our current one. Happy to flesh out an experiment of this idea if we think it has legs.
Regardless, we'll probably want to pull this bit over as just setting the colorscheme, as @dhh noticed doesn't fully apply our theme since we have an after plugin that runs. We need to re-run that after the theme is updated.
https://github.com/ryanrhughes/omarchy/blob/d81f0894f73b07bfcfa18b9ce7cd684dee8af207/config/nvim/lua/plugins/omarchy-theme.lua#L5-L9
Added a vim.cmd("hi clear") that clears all the highlight settings before appying the new colorscheme. I don't see the gray gutter that @dhh shows in his screenshots anymore.
Also added a new omarchy-theme-neovim-add script to the theme-install command. I tested it on some of the "extra themes" in the manual.
@ryanrhughes I'm going to look into your suggestions (much appreciated), but curious if you are still having style issues with the new updates to this PR.
I added a function that checks for the light.mode file in theme folders and sets background = light based on that. No need to specify background in the theme files anymore.
@dhh I think we're ready! The only thing to add in the Manual is:
Themes have to specify the colorscheme in their neovim.lua file.
{
"LazyVim/LazyVim",
opts = {
colorscheme = "bamboo",
},
}
@tahayvr this is super close in my opinion. I left a few comments in the code and a few below that I observed. Loving how this is shaping up. SO much nicer to not have to restart Neovim all the time!
Small Artifact Remaining
There's one very small artifact that isn't getting changed I noticed. It's really only noticeable when going from light to dark so like Latte to Matte Black or vice versa. Would be nice to find how to clobber this one too.
Install.colorscheme
It looks like we still need to remove all of the install.colorscheme logic here. I believe the naming of it has caused some confusion here but the install.colorscheme doesn't have anything to do with installing colorschemes and rather is the initial color scheme it chooses for running the installer -- like when you open Neovim and the installer immediately pops up. It'll always choose the first one it finds so adding them all to a list just gives us more to maintain with no net benefit. If anything, we should be ensuring that the only one in this list is the currently active theme so you don't see a slight flash if the installer comes up when you open Nvim.
I suppose we can keep it if we'd like but it just seems like something that can go wrong that offers no benefit.
This does not impact whether or not the plugin and colorscheme itself is actually loaded and available -- that is handled by the plugin definition.
Ref: https://lazy.folke.io/configuration
install = {
-- install missing plugins on startup. This doesn't increase startup time.
missing = true,
-- try to load one of these colorschemes when starting an installation during startup
colorscheme = { "habamax" },
},
Error on Load
If we load Nvim with a theme that has been installed before this update, we get this error and the theme doesn't load at all. This is a bit odd since it meets the spec but appears that there's something causing it to not load. Reinstalling the theme causes it to work fine. Ref: https://github.com/perfektnacht/omarchy-wasteland-theme
Error when Switching
Likely related to the above but if I switched to a theme that was installed before, this error comes up but the theme does seem to apply correctly. We may just need to do some error handling or something. Ref: https://github.com/bjarneo/omarchy-ash-theme
Error applying colorscheme: vim/_editor.lua:0: nvim_exec2()[1]..User Autocommands for "OmarchyThemeReload"..script nvim_exec2() called at User Autocommands for "OmarchyThemeReload":0, line 1: Vim(colorscheme):E185: Cannot find color scheme 'ash'
Parsing Error
In installed the new Azure Glow theme and encountered a few errors. I believe they're driven in part by how that theme is implementing Neovim colorsheme but maybe something we should try to catch if we can or consider how we fallback gracefully in this situation.
Ref: https://github.com/Hydradevx/omarchy-azure-glow-theme/blob/main/neovim.lua
It added this to my init.lua which obviously causes Lazy to be mad when it tries to load. It's also worth noting that the theme does not apply which I find strange since the colorscheme definition in the plugin should be doing the work so this theme should technically still work since it has the color definitions in the colorscheme that should be being set.
Migrating Existing Installed Themes
With the errors above, it raises the question; can we / should we handle some of this via migration? It seems plausible that a lot of people will have installed themes and feels bad to add a feature that doesn't work unless you reinstall all of your themes. Could also be totally fair to say that we only support this out of the box for core themes but if it's easy enough to add a migration to do a onetime parsing of everything that isn't a symlink, I'd be for that.
We definitely need to do a migration to existing extra themes that have been installed. Or have a way where there's a fallback where it just doesn't live change when that happens.
We definitely need to do a migration to existing extra themes that have been installed. Or have a way where there's a fallback where it just doesn't live change when that happens.
I am close to having something ready for the migration. Right now, if a theme is not initialized in our /.config/nvim/lua/omarchy/init.lua, the live change fails and neovim just stays on the last theme.
As for the themes like Azure Glow (mentioned above by @ryanrhughes ) that do not follow our standard configuration for neovim.lua, It simply is not gonna work. I will add a validation step in the omarchy-theme-neovim-add that will skip these and not add a wrong entry in our init.lua file.
When it doesn't work, what happens? Just don't want to brick a system.
I added validation to omarchy-theme-sync-nvim (new name) which run as part of omarchy-theme-install and checks the neovim.lua file if it's compatible or not.
- If it has a repo/name and colorscheme specified it'll add the theme to
/.config/nvim/lua/omarchy/init.lua:
- If it does not have those values specified (like the Azure Glow theme) it'll skip the sync so live theme loading will not work with that theme. Theme still installs though:
@dhh I also added a migration script that goes through all the installed themes and adds the missing ones to /.config/nvim/lua/omarchy/init.lua
@dhh I'm gonna test the updated code on a fresh machine when the new ISO install is successful. Curious to know how your tests go.
Fixed the conflict caused by the new file structure.
Why we need this ? I don’t want my Neovim to be bloated, just stick with one colorscheme.
There's no meaningful bloat by having some themes installed but not loaded. Totally worth it for fast theme switching.
Would it be possible to make a neovim plugin out of this? I prefer to have my own neovim config and I think other people would like this too.
it's been a while and this thread has become too long, so here's a summary of where we are right now:
- We add an
omarchyfolder in our Neovim config folder that contains:
- a
config/nvim/lua/omarchy/init.luafile that lists the theme & colorscheme names for LazyVim to load. - a
config/nvim/lua/omarchy/theme_reload.luathat has the logic for the theme switch. This is where we get the signal for theme changes, recognize the new theme, clear the previous colors, set the light or dark background and finally, set the new colorscheme.
- We add
config/nvim/lua/plugins/omarchy-themes.luathat tells makes thisomarchyfolder act like a plugin (sort of) - We add a line init neovim's main
init.luathat tells neovim this plugin exists and required.
-- Setup theme reloader
require("omarchy.theme_reload").setup()
- We have a script called
bbin/omarchy-theme-sync-nvimthat can parse neovim.lua files in theme folders and adds the plugin name and colorscheme toconfig/nvim/lua/omarchy/init.lua
And finaly:
- We change the system theme
neovim.luafiles to matrch our standard format. - We have 2 migration files:
- Applies the changes above to neovim config folder and theme files.
- Goes through the existing themes that are installed and parses for theme names and colorschemes and adds them to the
config/nvim/lua/omarchy/init.luafile. Similar to our omarchy-theme-sync-nvim script.
If you have tested this and have issues, let me know so I can investigate.
How omarchy-theme-install output looks now:
I really appreciate how far you've pushed on this, Taha, but this is an enormous amount of code. This shouldn't be this hard. The reason it is is that we're trying to make all these code-dumps that just happen to change colors work. I think we need to go back to the drawing board. Come up with a unified format for changing nvim colors, then build a single plugin that can take this specification, live reload changes, and take the single color definition as an input.