Why Do I Need This
I have always implemented my own way of having colorschemes, earlier I used to do something like this
1local highlights = require('themes.highlights')
2
3local function setup(opts)
4  if opts == nil then opts = { theme = "nirvana" } end
5  local colors = require('themes.colorschemes.' .. opts.theme).get_colors()
6  highlights.highlight_all(colors, opts)
7end
8
9return { setup = setup }The main thing here is the line local colors = require('themes.colorschemes.' .. opts.theme).get_colors(). Observe that we have to require the theme file everytime. Instead of doing this everytime we can compile the colors into a cache file so that it can be called faster the next time. This is what we will try to implement in this blog. At the end I will also teach you how to make a custom telescope prompt for selecting your custom themes
Setup
In this setup we will create some files required for this. Make a directory .config/nvim/lua/themes. Throughout this blog, all the files would be in this directory
init.lua
01// init.lua
02vim.g.currentTheme = "onedark"
03
04local M = {}
05
06local function hexToRgb(c)
07  c = string.lower(c)
08  return { tonumber(c:sub(2, 3), 16), tonumber(c:sub(4, 5), 16), tonumber(c:sub(6, 7), 16) }
09end
10
11-- some functions to make light and dark colors
12
13M.blend = function(foreground, background, alpha)
14  alpha = type(alpha) == "string" and (tonumber(alpha, 16) / 0xff) or alpha
15  local bg = hexToRgb(background)
16  local fg = hexToRgb(foreground)
17
18  local blendChannel = function(i)
19    local ret = (alpha * fg[i] + ((1 - alpha) * bg[i]))
20    return math.floor(math.min(math.max(0, ret), 255) + 0.5)
21  end
22
23  return string.format("#%02x%02x%02x", blendChannel(1), blendChannel(2), blendChannel(3))
24end
25
26M.darken = function(hex, bg, amount)
27  return M.blend(hex, bg, amount)
28end
29
30M.lighten = function(hex, fg, amount)
31  return M.blend(hex, fg, amount)
32end
33
34return MExplaination of the blend function:
- It takes in three parametres:
- foreground : 
string- in the form of hex - background : 
string- also in the form of hex - alpha : 
float- between 0 and 1, representing the blending alpha or transparency level. 
 - foreground : 
 - The purpose of this function is to blend the foreground color over the background color with the given transparency level (alpha).
 - If alpha is a string, it assumes it's a hexadecimal color string and converts it to a numeric value by dividing it by 
0xff (255). If alpha is a number, it uses it as is. - It then converts both the foreground and background colors from hexadecimal format to 
RGBformat using a helper function calledhexToRgbwhich is responsible for converting a hexadecimal color string to anRGBcolor representation. - The function defines another inner function called 
blendChannel(i)which blends the color channels (red, green, and blue) individually based on the alpha value and returns the blended channel value. It ensures that the resulting channel value is clamped between 0 and 255 and rounds it to the nearest integer. - Finally, the function constructs a new hexadecimal color string using the blended 
RGBcolor channels and returns it. 
Finally we will also have a function to fetch the themes colors for setting the highlights
1M.getCurrentTheme = function()
2  local path = "themes.schemes." .. vim.g.currentTheme
3  local theme = require(path).get_colors()
4  return theme
5endscheme
let us make a file at lua/themes/schemes/onedark.lua
01// themes/schemes/onedark.lua
02local M = {}
03
04function M.get_colors()
05  return {
06    background = "#181b21",
07    black = "#181b21",
08    darker = '#111418',
09    foreground = "#dcdee6",
10
11    cursor = "#dcdee6",
12    comment = '#79818f',
13
14    contrast = '#1c1f26',
15    cursorline = '#1c1f26',
16
17    color0 = "#272b33",
18    color1 = "#c75f68",
19    color2 = "#60ae7f",
20    color3 = "#cb795f",
21    color4 = "#7095db",
22    color5 = "#b475c6",
23    color6 = "#63b0b9",
24    color7 = "#abb2bf",
25    color8 = "#2d3139",
26    color9 = "#e0626c",
27    color10 = "#6bbd8c",
28    color11 = "#d9846a",
29    color12 = "#7ca0e3",
30    color13 = "#bf75d4",
31    color14 = "#6ec0cb",
32    color15 = "#abb2bf",
33    comment_light = "#9096a1",
34  }
35end
36
37return Mto get more colorschemes, check out my repo
highlights
for highlights, we will create folder lua/themes/hls. In later sections, we will create a function that will loop through all the files in the repo, so you can name files in it however you want. But each file should return a table with highlights. For example for the dashboard -
01// themes/hls/alpha.lua
02local themes = require("themes")
03local colors = themes.getCurrentTheme()
04
05return {
06  AlphaHeader = { fg = colors.color4, bg = colors.background },
07  AlphaLabel = { fg = colors.color7, bg = colors.background },
08  AlphaIcon = { fg = colors.color5, bold = true, },
09  AlphaKeyPrefix = { fg = colors.color1, bg = themes.darken(colors.color1, colors.background, 0.04) },
10  AlphaMessage = { fg = colors.color2, bg = colors.background },
11  AlphaFooter = { fg = colors.comment, bg = colors.background },
12}Instead of manually typing out all the highlights, get them all from here
implementation
now let us actually start implementing what we were here for, colorscheme caching. All of this code is in themes/init.lua
01// themes/init.lua 
02
03-- simple helper functions
04M.mergeTb         = function(...)
05  return vim.tbl_deep_extend("force", ...)
06end
07
08M.loadTb          = function(g)
09  g = require("themes.hls." .. g)
10  return g
11endExplaination:
- 
M.mergeTb: This function merges multiple tables into one usingvim.tbl_deep_extend("force", ...). It takes any number of tables as arguments and returns a single merged table. - 
M.loadTb: This function loads a highlights table from a file. 
making cache
this function creates the cache and saves the file at ~/.local/share/nvim/colors_data/
01// themes/init.lua 
02
03vim.g.theme_cache = vim.fn.stdpath "data" .. "/colors_data/"
04
05M.tableToStr      = function(tb)
06  local result = ""
07
08  for hlgroupName, hlgroup_vals in pairs(tb) do
09    local hlname = "'" .. hlgroupName .. "',"
10    local opts = ""
11
12    for optName, optVal in pairs(hlgroup_vals) do
13      local valueInStr = ((type(optVal)) == "boolean" or type(optVal) == "number") and tostring(optVal)
14          or '"' .. optVal .. '"'
15      opts = opts .. optName .. "=" .. valueInStr .. ","
16    end
17
18    result = result .. "vim.api.nvim_set_hl(0," .. hlname .. "{" .. opts .. "})"
19  end
20
21  return result
22end
23
24M.toCache         = function(filename, tb)
25  local lines = "return string.dump(function()" .. M.tableToStr(tb) .. "end, true)"
26  local file = io.open(vim.g.theme_cache .. filename, "wb")
27
28  if file then
29    ---@diagnostic disable-next-line: deprecated
30    file:write(loadstring(lines)())
31    file:close()
32  end
33endExplaination:
- 
M.tableToStr: This function converts a table of color scheme definitions into a string that can be written to a Lua file. It iterates over the table and its sub-tables, converting the data into a format suitable for use withvim.api.nvim_set_hl. - 
M.toCache: This function takes a filename and a table of color scheme definitions, converts the table into a Lua code string usingM.tableToStr, and writes this code to a cache file. The resulting file is written in binary mode to the path specified byvim.g.theme_cache. 
applying
01// themes/init.lua 
02local hl_files = vim.fn.stdpath "config" .. "/lua/themes/hls"
03
04M.compile         = function()
05  if not vim.loop.fs_stat(vim.g.theme_cache) then
06    vim.fn.mkdir(vim.g.theme_cache, "p")
07  end
08
09  for _, file in ipairs(vim.fn.readdir(hl_files)) do
10    local filename = vim.fn.fnamemodify(file, ":r")
11    M.toCache(filename, M.loadTb(filename))
12  end
13end
14
15M.load            = function()
16  M.compile()
17  for _, file in ipairs(vim.fn.readdir(vim.g.theme_cache)) do
18    dofile(vim.g.theme_cache .. file)
19  end
20endExplaination:
- 
M.compile: This function compiles color scheme definitions into cache files. It checks if the cache directory specified invim.g.theme_cacheexists and creates it if it doesn't. It then iterates through a list of color scheme files, converts them to Lua code usingM.toCache, and writes them to cache files. - 
M.load: This function loads cached color scheme definitions into Neovim. It first compiles the color schemes usingM.compileand then iterates through the cache files in the cache directory, usingdofileto load each one into Neovim 
This is it basically, now the
last step
create a file ~/.config/nvim/colors/onedark.lua
1// colors/onedark.lua
2vim.g.currentTheme = "onedark"
3require("plenary.reload").reload_module "themes"
4require("themes").load()and now just run this command:
1:colorscheme onedarkbonus - telescope picker
now if you have multiple schemes in themes/schemes, we can create a telescope picker for just them. For this you will first need to install Telescope
01// themes/picker.lua 
02local pickers      = require "telescope.pickers"
03local finders      = require "telescope.finders"
04local actions      = require "telescope.actions"
05local action_state = require "telescope.actions.state"
06local conf         = require("telescope.config").values
07
08local M            = {}
09
10-- this code generates the list of all schemes in .config/nvim/colors
11local schemes      = {}
12local files        = vim.fn.stdpath "config" .. "/colors/"
13for _, file in ipairs(vim.fn.readdir(files)) do
14  local f = vim.fn.fnamemodify(file, ":r")
15  table.insert(schemes, f)
16end
17
18M.setup = function(opts)
19  opts = opts or {}
20  -- create a new picker
21  pickers.new(opts, {
22    prompt_title = "Kolorschemes",
23    finder = finders.new_table {
24      results = schemes -- add all the schemes to the table
25    },
26    sorter = conf.generic_sorter(opts),
27    attach_mappings = function(buffer)
28      actions.select_default:replace(function()
29        local theme = action_state.get_selected_entry().value -- get the selected value
30        -- apply the scheme
31        vim.g.currentTheme = theme
32        vim.cmd("colorscheme " .. theme)
33        actions.close(buffer)
34      end)
35      return true
36    end,
37  }):find()
38end
39
40return MYou can run this by
1:lua require("themes.pick").setup()Voila, we are done!
