Skip to content

🦎 Dynamically adapt your skin the environment you're in. Set conditional rules for when a colorscheme should be applied in NeoVim.

License

Notifications You must be signed in to change notification settings

uhs-robert/color-chameleon.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

52 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

logo

color-chameleon.nvim

Dynamically adapt your skin to the environment you're in, like a chameleon.

🦎 Overview

color-chameleon.nvim lets you set conditional rules for when a colorscheme should be applied.

These rules are evaluated in order from top-to-bottom; the first matching rule wins.

Rules are triggered on VimEnter, DirChanged, BufReadPost, BufNewFile, BufEnter, and TermOpen events.

color-chameleon-demo.mp4
✨ What's New / 🚨 Breaking Changes
🚨 v0.1: Launch Party

✨ FEATURES: New features go here

This is the initial launch. New features will go here in subsequent releases. Feel free to continue onto the next section.

🚨 BREAKING CHANGE: Breaking changes go here

Nothing broken yet! Check back later.

✨ Features

  • Context-Aware Colorschemes: Automatically switch colorschemes based on:
    • Working directory (local vs remote/mounted filesystems)
    • Environment variables (colorterm, sudo, TMUX, custom vars)
    • Buffer properties (filetype, buffer type)
    • Custom functions/conditions (any logic you can write)
  • Flexible Logic: Combine conditions with AND logic, use arrays for OR logic
  • Smart Switching: Preserves your previous colorscheme when leaving special contexts
  • Buffer-Aware: Considers both global working directory and individual buffer paths
  • Simple Configuration: Express your workflow through intuitive conditional rules
  • Lightweight: Minimal performance impact

πŸ“¦ Installation

Install the theme with your preferred package manager, such as folke/lazy.nvim:

{
  "uhs-robert/color-chameleon.nvim",
  lazy = false,
  priority = 900,
  config = function()
    require("color-chameleon").setup({
      rules = {
        { path = "~/mnt/", colorscheme = "gruvbox" },
      },
      default = "oasis"
    })
  end
}

Or via packer.nvim:

use {
  "uhs-robert/color-chameleon.nvim",
  config = function()
    require("color-chameleon").setup({
      rules = {
        { path = "~/mnt/", colorscheme = "gruvbox" },
      },
      default = "oasis"
    })
  end
}

πŸ‘¨β€βš–οΈ Rule Structure

Each rule can have the following fields:

  • colorscheme (required: string) - The colorscheme to apply when this rule matches.
  • background (optional: string) - Background setting to apply ("light" or "dark"). Applied before the colorscheme.
  • path (optional: string or array) - Directory path(s) to match (e.g., "~/mnt/", "~/work/client-a/", {"~/work/client-a, "~/work/client-b/"}. Use an array to match any of multiple paths.
  • buftype (optional: string or array) - Buffer type(s) to match (e.g., "terminal", "help", or {"quickfix", "nofile"}). Use an array to match any of multiple buffer types.
  • filetype (optional: string or array) - Buffer filetype(s) to match (e.g., "markdown", "python", or {"lua", "vim"}). Use an array to match any of multiple filetypes.
  • env (optional: table) - Environment variable conditions to check.
    • Key: environment variable name
    • Value:
      • true = check if variable exists
      • false = check if variable doesn't exist
      • string = check if variable equals this exact value
  • condition (optional: function) - Custom function that receives the current working directory and returns a boolean.

Important

Rules are evaluated in order. The first matching rule wins.

All conditions in a rule must match (AND logic). For example, a rule with both path and filetype will only match if the current directory matches the path AND the buffer filetype matches.

Arrays within a field use OR logic. For example, filetype = {"json", "yaml"} matches if the filetype is "json" OR "yaml".

Refer to Usage below for examples of each and Use Cases for real world examples.

πŸš€ Usage

The power is in your hands; create rules that match your unique workflow.

Below are practical examples organized from simple to advanced. Start with the basics, then explore the combinations that fit your needs.

πŸ–ŒοΈ Basic Configuration

Switch theme when in a specific directory:

require("color-chameleon").setup({
  rules = {
    { path = "~/mnt/", colorscheme = "oasis-mirage" }, -- check out oasis.nvim for a cool colorscheme pack!
  },
  default = "oasis", -- Optional: colorscheme when no rules match
})

🎨 Advanced Examples

πŸ—ƒοΈ Multiple Directories
Different themes for different directories:
require("color-chameleon").setup({
  rules = {
    { path = "~/work/", colorscheme = "tokyonight" },
    { path = "~/personal/", colorscheme = "catppuccin" },
    { path = "~/mnt/", colorscheme = "oasis-mirage" },
  },
})
🌦️ Environment-Based Switching

Change themes based on any vim.env variable. These are variables set by Neovim's startup environment (vim.env).

require("color-chameleon").setup({
  rules = {
    { colorscheme = "gruvbox", env = { COLORTERM = "truecolor" } }, -- Applies when truecolor is supported
    { colorscheme = "tokyonight", env = { TMUX = true } }, -- Applies when in TMUX
    { path = "~/work/", colorscheme = "catppuccin", env = { WORK_ENV = "production" } }, -- Applies when path AND env are both true
  },
})

Boolean values like true|false simply check for existence while string values check exact matches like so:

rules = {
  -- Check if variable exists
  { colorscheme = "gruvbox", env = { TMUX = true } },
  -- Check if variable doesn't exist
  { colorscheme = "tokyonight", env = { TMUX = false } },
  -- Check for exact value
  { colorscheme = "nord", env = { NODE_ENV = "production" } },
  -- Multiple conditions (all must match)
  { colorscheme = "catppuccin", env = { TMUX = true, COLORTERM = "truecolor" } },
}

Use :ChameleonEnv to see the ENV variables being used in your current environment.

[!IMPORTANT]

These variables are inherited when Neovim launches and do not change during runtime.

πŸ“„ Buffer Properties (Filetype & Buftype)

Change theme based on buffer properties like filetype or buffer type:

require("color-chameleon").setup({
  rules = {
    -- Simple filetype matching
    { filetype = "markdown", colorscheme = "nord" },
    { filetype = "python", colorscheme = "gruvbox" },

    -- Buffer type matching
    -- NOTE: buffers which open a split like "help" are ignored
    { buftype = "terminal", colorscheme = "tokyonight" },

    -- Combine path + filetype (both must match)
    {
      path = "~/notes/",
      filetype = "markdown",
      colorscheme = "nord"
    },

    -- Combine path + buftype
    {
      path = "~/work/",
      buftype = "terminal",
      colorscheme = "gruvbox"
    },
  },
})

Common filetypes: markdown, python, lua, javascript, typescript, rust, go, etc.

Common buftypes:

  • "" (empty string) - Normal file buffers
  • "terminal" - Terminal buffers
  • "help" - Help documentation
  • "quickfix" - Quickfix/location lists
  • "nofile" - Scratch buffers

Refer to buftype for all.

πŸ”€ OR Logic with Arrays

You can use arrays to match any of multiple values (OR logic). This works for path, filetype, and buftype:

require("color-chameleon").setup({
  rules = {
    -- Match ANY of these filetypes
    { filetype = {"json", "yaml", "toml", "xml"}, colorscheme = "nord" },

    -- Match ANY of these paths
    { path = {"~/work/client-a/", "~/work/client-b/"}, colorscheme = "gruvbox" },

    -- Match ANY of these buffer types
    { buftype = {"terminal", "nofile"}, colorscheme = "catppuccin" },

    -- Combine: Match ANY filetype AND a specific path (both conditions must match)
    {
      path = "~/notes/",
      filetype = {"markdown", "text", "org"},
      colorscheme = "tokyonight"
    },

    -- Multiple arrays: Match ANY path AND ANY filetype
    {
      path = {"~/projects/rust/", "~/projects/go/"},
      filetype = {"rust", "go"},
      colorscheme = "gruvbox"
    },
  },
})

How it works:

  • Arrays within a field use OR logic: match any value
  • Multiple fields use AND logic: all must match
πŸŒ“ Background Setting (Light/Dark Mode)

Set the background explicitly when applying a colorscheme. Useful for themes that adapt to light/dark backgrounds:

require("color-chameleon").setup({
  rules = {
    -- Use gruvbox dark for remote systems
    { path = "~/mnt/", colorscheme = "gruvbox", background = "dark" },

    -- Use light background for reading documentation
    { filetype = {"markdown", "text"}, colorscheme = "everforest", background = "light" },

    -- Dark background for terminal buffers
    { buftype = "terminal", colorscheme = "tokyonight", background = "dark" },
  },
  default = "nord",
  background = "dark",  -- Global default background
})

[!NOTE]

The background setting is applied before the colorscheme, allowing themes to adapt accordingly.

🧩 Custom Conditions
Use custom functions for any logic you can imagine. The `condition` function receives the current working directory and should return a boolean:
require("color-chameleon").setup({
  rules = {
    -- Check for a specific file in the directory
    {
      path = "~/projects/",
      colorscheme = "nord",
      condition = function(cwd)
        return vim.fn.filereadable(cwd .. "/.use-nord-theme") == 1
      end
    },
    -- Use a different theme during night hours
    {
      colorscheme = "oasis-night",
      background = "dark",
      condition = function()
        local hour = tonumber(os.date("%H"))
        return hour >= 20 or hour < 6
      end
    },
  },
})
🀞 Combine It All
Combine multiple conditions for powerful context-aware theming. All conditions in a rule must match:
local uid = (vim.uv or vim.loop).getuid()
local is_sudoedit = vim.env.SUDOEDIT == "1" -- This requires your shell's config to export a flag like: SUDO_EDITOR="env SUDOEDIT=1 /usr/bin/nvim"
local is_root = is_sudoedit or uid == 0

require("color-chameleon").setup({
  rules = {
     -- Custom condition for root context
    { colorscheme = "oasis-sol", condition = function() return is_root end },

    -- Single condition for mount directory (remote files SSHFS)
    { path = "~/mnt/", colorscheme = "oasis-mirage" },

    -- Combine path + environment
    { path = "~/work/", env = { NODE_ENV = "production" }, colorscheme = "tokyonight" },

    -- Combine path + filetype
    { path = "~/notes/", filetype = "markdown", colorscheme = "catppuccin" },

    -- ALL conditions combined: path + env + filetype + buftype + custom function
    -- This rule only matches when ALL of these are true:
    {
      path = "~/projects/sensitive/",           -- In sensitive projects directory
      env = { COLORTERM = "truecolor" },        -- AND full terminal color support
      filetype = {"lua", "md"},                 -- AND editing a Lua or Md file
      buftype = "",                             -- AND it's a normal file (not terminal/help/etc)
      condition = function(cwd)                 -- AND a .secure marker file exists
        return vim.fn.filereadable(cwd .. "/.secure") == 1
      end,
      colorscheme = "gruvbox"
    },

  },
  default = "oasis", -- Default theme for normal contexts
})

Tip

Now that you know how the plugin works, check out Use Cases below for some real world examples.

πŸ”§ API Commands

Color Chameleon provides the following commands:

  • :ChameleonStatus - Show current status and configuration
  • :ChameleonToggle - Toggle camouflage mode on/off
  • :ChameleonEnable - Enable camouflage mode
  • :ChameleonDisable - Disable camouflage mode
  • :ChameleonEnv - Show your current vim.env variables
  • :ChameleonInspect - Inspect the current buffer and evaluate rule matches
  • :ChameleonReload - Reload configuration and reapply rules
  • :ChameleonDebug - Toggle debug mode
Lua API
local chameleon = require("color-chameleon")

-- Enable/disable programmatically
chameleon.toggle()
chameleon.enable()
chameleon.disable()

-- Check status
chameleon.status()

-- Run to inspect buffer/window properties and to see what rules match in current context
chameleon.inspect()

-- List environment variables
chameleon.env()

-- Reload configuration
chameleon.reload()

-- Toggle debug mode
chameleon.debug()

🎹 Keymapping

Default keybindings, when enabled, under <leader>C (fully customizable):

Keymap Command Description
<leader>Cc :ChameleonToggle Toggle camouflage mode
<leader>Cv :ChameleonEnv Show environment variables
<leader>Cs :ChameleonStatus Show chameleon status
<leader>Cd :ChameleonDebug Toggle debug mode
<leader>Cr :ChameleonReload Reload configuration
<leader>Ci :ChameleonInspect Inspect context and rule matching

Refer to configuration below on how to disable or customize.

βš™οΈ Configuration

🍦 Default Options

require("color-chameleon").setup({
  enabled = true,    -- Set to to false to disable this plugin
  debug = false,     -- Set to true to enable debug logging
  rules = {},        -- Array of rule tables (see examples above)
  default = nil,     -- Default theme when no rules match (nil = restore to state at init)
                     -- Example: { colorscheme = "oasis-lagoon", background = "dark" } | "oasis-lagoon"
  keymaps = true,    -- Set to false to disable, or pass a table to customize:
  -- keymaps = {
  --   lead_prefix = "<leader>C",  -- Default prefix (default: "<leader>C")
  --   keymaps = {                 -- Override individual keys
  --     toggle = "<leader>Cc",
  --     env = "<leader>Cv",
  --     status = "<leader>Cs",
  --     debug = "<leader>Cd",
  --     reload = "<leader>Cr",
  --     inspect = "<leader>Ci"
  --   },
  -- },
})

πŸ’Ό Use Cases

Real-world scenarios to inspire your workflow:

πŸ“‘ Distinguish Local vs Remote Work

Instantly know when you're working on remote or mounted filesystems:

rules = {
  { path = "~/mnt/", colorscheme = "gruvbox" },
  { path = "/mnt/", colorscheme = "gruvbox" },
}
πŸ“ Different Themes for Different Projects

Visually separate work, personal, and client projects:

rules = {
  { path = "~/work/client-a/", colorscheme = "tokyonight" },
  { path = "~/work/client-b/", colorscheme = "nord" },
  { path = "~/personal/", colorscheme = "catppuccin" },
}
πŸ•΅οΈβ€β™‚οΈ Warning Theme for Elevated Privileges

Use a high-visibility theme when editing as root:

local uid = (vim.uv or vim.loop).getuid()
local is_sudoedit = vim.env.SUDOEDIT == "1" -- This requires your shell's config to export a flag like: SUDO_EDITOR="env SUDOEDIT=1 /usr/bin/nvim"
local is_root = is_sudoedit or uid == 0

rules = {
  { colorscheme = "gruvbox", condition = function() return is_root end },
}
πŸ•‘οΈ Time-Based Themes

Automatically switch between light and dark themes based on time of day.

rules = {
  {
    colorscheme = "catppuccin-latte",
    background = "light",
    condition = function()
      local hour = tonumber(os.date("%H"))
      return hour >= 6 and hour < 18  -- 6 AM to 6 PM
    end
  },
  { colorscheme = "catppuccin-mocha", background = "dark" },  -- Fallback for night
}

This is just a simple time of day example using static hours.

A truly dynamic system based on actual location can be found here.

πŸ¦€ Project Type Detection

Automatically apply themes based on the type of project you're working on:

rules = {
  -- Rust projects
  {
    colorscheme = "nord",
    condition = function(cwd)
      return vim.fn.filereadable(cwd .. "/Cargo.toml") == 1
    end
  },
  -- Node.js/JavaScript projects
  {
    colorscheme = "catppuccin",
    condition = function(cwd)
      return vim.fn.filereadable(cwd .. "/package.json") == 1
    end
  },
  -- Python projects
  {
    colorscheme = "tokyonight",
    condition = function(cwd)
      return vim.fn.filereadable(cwd .. "/pyproject.toml") == 1
        or vim.fn.filereadable(cwd .. "/setup.py") == 1
    end
  },
}
🌿 Git Branch-Based Themes

Use different themes for production branches vs development:

rules = {
  {
    colorscheme = "gruvbox",  -- High-contrast theme for production
    condition = function()
      local handle = io.popen("git branch --show-current 2>/dev/null")
      if handle then
        local branch = handle:read("*a"):gsub("%s+", "")
        handle:close()
        return branch == "main" or branch == "master"
      end
      return false
    end
  },
}
πŸ“„ Filetype-Specific Themes

Apply specific themes for configuration files or special filetypes:

rules = {
  -- Match multiple filetypes with an array (recommended)
  { filetype = {"json", "yaml", "toml", "xml"}, colorscheme = "onedark" },

  -- Or use individual rules
  { filetype = "json", colorscheme = "onedark" },
  { filetype = "yaml", colorscheme = "onedark" },
  { filetype = "toml", colorscheme = "onedark" },
  { filetype = "xml", colorscheme = "onedark" },

  -- Or use a condition for complex logic
  {
    colorscheme = "onedark",
    condition = function()
      local ft = vim.bo.filetype
      return ft == "json" or ft == "yaml" or ft == "toml" or ft == "conf"
    end
  },
}
πŸ” Diff Mode & Read-Only Files

Use high-contrast themes when reviewing diffs or editing read-only files:

rules = {
  -- High contrast for diff mode
  {
    colorscheme = "gruvbox",
    condition = function()
      return vim.wo.diff
    end
  },
  -- Distinct theme for read-only files
  {
    colorscheme = "nord",
    condition = function()
      return vim.bo.readonly or not vim.bo.modifiable
    end
  },
}
πŸ’» Terminal Buffer Detection

Switch themes when working with terminal buffers (or any buffer type):

rules = {
  -- Simple buffer type matching
  { buftype = "terminal", colorscheme = "tokyonight" },

  -- Combine with path for project-specific terminal themes
  {
    path = "~/work/",
    buftype = "terminal",
    colorscheme = "gruvbox"
  },

  -- Normal file buffers only (exclude special buffers)
  { buftype = "", colorscheme = "catppuccin" },
}

Tip

You may also want to check out the rule recipes for submissions from users.

πŸ™ Acknowledgments

Inspired by my other plugin oasis.nvim which has... a lot of themes to pick from!

With so much variety, why not visually distinguish between your different working contexts in NeoVim?

About

🦎 Dynamically adapt your skin the environment you're in. Set conditional rules for when a colorscheme should be applied in NeoVim.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

No packages published

Languages