Skip to content

Repository files navigation

macos-appearance.nvim

English | 简体中文

Automatically synchronize Neovim with the current macOS light or dark appearance.

The plugin watches ~/Library/Preferences/.GlobalPreferences.plist via vim.uv.new_fs_event() and periodically polls the current appearance as a fallback. It syncs once during setup, then reacts to both file events and the poll timer to ensure timely detection.

Requirements

  • macOS
  • Neovim 0.10+

Installation

{
  "pzehrel/macos-appearance.nvim",
  event = "UIEnter",
  config = function()
    require("macos-appearance").setup {
      callback = function(appearance)
        -- your theme-switching logic here
      end,
    }
  end,
}

callback receives "dark" or "light" whenever the system appearance changes (and once during startup). It can be a plain function or an adapter table { apply = fun(appearance), reset? = fun() }.

For advanced use, skip callback and listen to User MacosAppearanceChanged with data = { appearance = "dark" | "light" }.

Behavior

During setup() the plugin:

  1. Detects the current macOS appearance.
  2. Calls callback (if set) and fires User MacosAppearanceChanged.
  3. Starts a file watcher on the plist and a periodic poll timer.
  4. Registers cleanup on VimLeavePre.

The plugin never writes to chadrc.lua or any other config file. File events are debounced at 100 ms (configurable).

Note

macOS writes appearance changes to the plist through cfprefsd, which may defer the actual disk write for several seconds. The file watcher alone only catches the disk event. To bridge the gap the plugin runs a lightweight poll (default every 2 s) that queries the current appearance via defaults read. This ensures the theme switches promptly even when cfprefsd delays the write. Set poll_interval_ms = 0 to disable polling and rely solely on file events.

Options

Option Default Description
debounce_ms 100 Debounce window for file events
retry_ms 250 Retry delay when the plist is unwatchable
poll_interval_ms 2000 Polling interval (0 = disable)
debug false Enable INFO-level diagnostic messages
notify true Show informational messages
path ~/Library/Preferences/.GlobalPreferences.plist Override the watched file
callback nil Called on appearance change

API

local ma = require "macos-appearance"

ma.get()   -- "dark" | "light"
ma.sync()  -- detect now and fire callback / event
ma.start() -- start the file watcher
ma.stop()  -- stop and release handles

setup() calls sync() then start(). Repeated calls to setup() are safe.

NvChad adapter

A built-in adapter for NvChad Base46 is included. Configure theme_toggle in chadrc.lua (the first theme is light, the second dark):

-- chadrc.lua
M.base46 = {
  theme = "tokyodark",
  theme_toggle = { "flexoki-light", "tokyodark" },
}

Then pass the adapter as the callback:

{
  "pzehrel/macos-appearance.nvim",
  event = "UIEnter",
  config = function()
    require("macos-appearance").setup {
      callback = require("macos-appearance.adapters.nvchad"),
    }
  end,
}

UIEnter ensures Base46 is initialized before the first sync.

The adapter exposes apply(appearance) and reset() for direct use:

local nvchad = require("macos-appearance.adapters.nvchad")
nvchad.apply("dark")  -- switch to dark theme
nvchad.reset()        -- clear internal state before re-setup

Development

make check   # format, lint, test

Tests run in headless Neovim without touching the real preferences plist.

License

MIT

About

Automatically sync Neovim colors with macOS light/dark appearance. Framework-agnostic callback API with built-in NvChad adapter.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages