diff --git a/src/content/git/auto-theme.nvim.mdx b/src/content/git/auto-theme.nvim.mdx index 7173bfd..1cc0776 100644 --- a/src/content/git/auto-theme.nvim.mdx +++ b/src/content/git/auto-theme.nvim.mdx @@ -1,14 +1,29 @@ --- title: "auto-theme.nvim" slug: "auto-theme.nvim" -date: "11/28/2025" +date: "28/11/2025" --- # the problem -I toggle between light and dark mode at at around 17:00 every day. Resetting my environment and theme across all my applications was a pain. +I toggle between light and dark mode at at around 17:00 every day. Resetting my environment and theme across all my applications was a pain. I built auto-theme.nvim to update my neovim theme automatically. -I use my color scheme [midnight.nvim](/git/midnight.nvim.html) on macOS and linux with these applications: +## the solution + +I spawn a OS-specific "watcher" (python process) to listen, in non-polling manner, for theme updates: + +- linux: I integrate with D-Bus, parsing and filtering messages for settings changes to the system appearance. +- macOS: I integrate with the Apple toolkit AppKit to do the same thing--wait for a theme changed notification to be pushed out to the running app object and print accordingly. + +Stdout is continually parsed from the job and the appropriate theme is set. + +# the interesting problem + +OK, cool. Now, how can I implement automatic theme-switching for _all_ programs I use? + +> I use my color scheme [midnight.nvim](/git/midnight.nvim.html) + +I most commonly use the following applications: 1. [neovim](https://neovim.io/) 2. [ghostty](https://ghostty.org/) @@ -21,30 +36,86 @@ I use my color scheme [midnight.nvim](/git/midnight.nvim.html) on macOS and linu 9. [ripgrep](https://github.com/BurntSushi/ripgrep) 10. [zsh](https://www.zsh.org/) -# criteria for solution +> All of the code can be found in my [dotfiles](https://github.com/barrett-ruth/dots). The implementations are scattered and I provide no guarantee that files will not be moved. Unfortunately, that makes this post a wall of text. + +## success criteria I run or trigger _one command_--every application updates automatically. The feasability of this depends on the underlying support software has for dynamic reloading of configuration. In many cases this is not possible. -# the solution +## the solution -As of November 28, 2025, I've created [this script](https://github.com/barrett-ruth/dots/blob/main/scripts/theme) which is bound by a [karabiner](https://karabiner-elements.pqrs.org/) and [keyd](https://github.com/rvaiya/keyd) binding for macOS and linux, respectively. +As of November 28, 2025, I've created [this script](https://github.com/barrett-ruth/dots/blob/main/scripts/theme) which is bound by a [karabiner](https://karabiner-elements.pqrs.org/) and [keyd](https://github.com/rvaiya/keyd) binding for macOS and linux, respectively, for quick access. -## successes +### successes -1. neovim: I use my non-polling, macOS/linux-supporting automatic theme switcher [auto-theme.nvim](https://github.com/barrett-ruth/auto-theme.nvim) +1. neovim: [auto-theme.nvim](https://github.com/barrett-ruth/auto-theme.nvim) 2. ghostty: Ghostty supports [light and dark themes based on the system appearance](https://github.com/tmux/tmux/wiki)--easy. -3. sioyek: Any changes to user configuration are automatically reloaded--a script updates user preferences in-place +3. sioyek: Any changes to user configuration are automatically reloaded--my script updates the program's settings file `prefs_user.config` in-place 4. ungoogled-chromium: I folded and used the default system theme which automatically reads and updates according to the system environment -5. swaywm (linux): sway reads from a symlink'ed theme file `sed`ed, updated and reloaded by the script -6. rofi: `config.rasi` also reads from a symlink'ed theme files that is updated and reloaded by the script -7. tmux: `tmux.conf` reads from symlink'ed theme files that are automatically reloaded with `source-file` +5. swaywm (linux): sway reads from a symlink'ed theme file updated by the scripts; `swaymsg reload` triggers an instant window-manager-wide reload +6. rofi: the config file, `config.rasi`, derives its theme from a symlink'ed file updated and reloaded by the script +7. tmux: similarly to rofi, the config `tmux.conf` reads from symlink'ed theme files that are automatically reloaded with `source-file`. I also refresh the UI with `refresh-client`: -## failures +```sh +ln -sf "$XDG_CONFIG_HOME/tmux/themes/$theme.tmux" "$XDG_CONFIG_HOME/tmux/themes/theme.tmux" +tmux source-file "$XDG_CONFIG_HOME/tmux/themes/$theme.tmux" +tmux refresh-client -S 2>/dev/null || true +``` + +### failures Unfortunately, the following programs I've found nearly impossible to dynamically reload: 8. fzf: Overwriting fzf's themes, from the interactive shell `fzf` binary to `fzf-{cd,file}-widget` to integration with [fzf-lua](https://github.com/ibhagwan/fzf-lua/), I found this potentially doable but just _way too complex_. Feel free to investigate yourself--I'm going with the default theme. 9. ripgrep: I use the default theme. The ripgrep global configuration file does not support environment variables, exterminating the option to provide a `${THEME}`-based path in the global configuration file. 10. zsh: it's impossible to update `$THEME` across all existing shells (simply a limit of posix). However, all affected _programs_ will read the proper `$THEME`--I'm fine compromising here. + +### UPD: 8, 9 30/11/2025 + +After some _extreme_ amounts of finagling I'm now able to automatically update fzf and ripgrep themes both in the shell (after re-rendering the prompt\*) and in new fzf-lua instances. I consider this a 99% win. + +I do it with the following strategy for: + +a) cli programs + +Since, according to \#10 above, it is impossible to update all running shells, one way I can come to auto-updating program themes is in between terminal prompts. To get around this, I added a zsh precmd hook to my `zshrc` for both programs which checks if a symlink has been updated: + +- fzf: If `$XDG_CONFIG_HOME/fzf/themes/theme` is updated, re-export `$FZF_DEFAULT_OPTS` to include the new colors: + +```zsh +_fzf_theme_precmd() { + local theme_file="$XDG_CONFIG_HOME/fzf/themes/theme" + local theme_target=$(readlink "$theme_file" 2>/dev/null) || return + typeset -g _FZF_THEME_TARGET + test "$theme_target" = "$_FZF_THEME_TARGET" && return + _FZF_THEME_TARGET="$theme_target" + test -r "$theme_file" && export FZF_DEFAULT_OPTS="$(<"$theme_file") $FZF_OPTS" +} +add-zsh-hook precmd _fzf_theme_precmd +``` + +- ripgrep: If `$XDG_CONFIG_HOME/rg/config` is updated, re-build the `$RIPGREP_CONFIG_PATH` file as a concatentation of the _new_ theme and a separete _base_ configuration file and re-export the environment variable. + +Any and all shells, after re-rendering their terminal prompts, will see proper colors for all fzf and ripgrep commands. + +b) neovim + +This is a bit trickier. How can a running neovim process automatically update its internal colors used by fzf-lua? There are two aspects of this problem: + +1. Finding and interacting with all existing Neovim instances: RPCs with remote neovim features provide the ability to remotely probe neovim instances. + +I use an RPC to trigger the update (see \#2 below). Of course, this requires automatically configuring a socket for each neovim instance to listen on. I use the process id, unique to the neovim instance--any 1:1 mapping from neovim instance to socket identifier will do: + +```lua +local socket_path = ('/tmp/nvim-%d.sock'):format(vim.fn.getpid()) +vim.fn.serverstart(socket_path) +``` + +Neovim instances can be found by just listing `/tmp/nvim-*.sock`. + +2. Re-configuring fzf-lua: fzf-lua does not support "dynamic" reconfiguration but you can re-initialize the plugin with `require('fzf-lua').setup(opts)`. + +- fzf: I expose a function for RPC calls `fzf_theme.reload_colors()` which re-initializes the fzf environment. Special care must be taken to store and pass down the _initial_ fzf-lua configuration and update it with the new environments colors. +- ripgrep: automatically re-reads from `$RIPGREP_CONFIG_PATH`, a symlink updated by my theme script