From a6ab121d12140136549c53901b7880f0c8af29bc Mon Sep 17 00:00:00 2001 From: Akianonymus Date: Fri, 30 Jul 2021 22:41:56 +0530 Subject: [PATCH] Implement theme switcher using telescope picker ask if want to change default theme, change value in user_config.lua load it as a telescope extension live preview of themes Co-authored-by: Galen Rowell --- lua/plugins/telescope.lua | 3 + lua/telescope/_extensions/themes.lua | 185 +++++++++++++++++++++++++++ lua/theme.lua | 4 +- lua/utils.lua | 96 ++++++++++++++ 4 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 lua/telescope/_extensions/themes.lua create mode 100644 lua/utils.lua diff --git a/lua/plugins/telescope.lua b/lua/plugins/telescope.lua index 915689f..80ba398 100644 --- a/lua/plugins/telescope.lua +++ b/lua/plugins/telescope.lua @@ -67,6 +67,9 @@ telescope.setup( } ) +-- load the theme_switcher extension +require("telescope").load_extension("themes") + if not pcall( function() diff --git a/lua/telescope/_extensions/themes.lua b/lua/telescope/_extensions/themes.lua new file mode 100644 index 0000000..e2aaa6e --- /dev/null +++ b/lua/telescope/_extensions/themes.lua @@ -0,0 +1,185 @@ +-- This file can be loaded as a telescope extension +local M = {} + +-- reload themes without restarting vim +-- if no theme name given then reload the current theme +M.reload_theme = function(theme_name) + local reload_plugin = require("utils").reload_plugin + + -- if theme name is empty or nil, then reload the current theme + if (theme_name == nil or theme_name == "") then + theme_name = vim.g.nvchad_theme + end + + if not pcall(require, "themes/" .. theme_name) then + error("No such theme ( " .. theme_name .. " )") + end + + vim.g.nvchad_theme = theme_name + + -- reload the base16 theme + local ok, base16 = pcall(require, "base16") + if not ok then + error("Error: Cannot load base16 plugin!") + end + base16(base16.themes[theme_name], true) + + reload_plugin { + "highlights", + "plugins.bufferline", + "galaxyline", + "plugins.statusline" + } + + -- now send the provider info to actual refresh + require("galaxyline.provider").async_load_providers:send() + + return true + -- open a buffer and close it to reload the statusline + -- vim.cmd("new|bwipeout") + -- commented out here as it will not work with telescope picker +end + +-- Custom theme picker +-- Most of the code is copied from telescope colorscheme plugin, mostly for preview creation +M.theme_switcher = function(opts) + local pickers, finders, previewers, actions, action_state, utils, conf + if pcall(require, "telescope") then + pickers = require "telescope.pickers" + finders = require "telescope.finders" + previewers = require "telescope.previewers" + + actions = require "telescope.actions" + action_state = require "telescope.actions.state" + utils = require "telescope.utils" + conf = require("telescope.config").values + else + error "Cannot find telescope!" + end + + local local_utils = require "utils" + local reload_theme = M.reload_theme + + -- get a table of available themes + local themes = local_utils.list_themes() + if next(themes) ~= nil then + -- save this to use it for later to restore if theme not changed + local current_theme = vim.g.nvchad_theme + local new_theme = "" + local change = false + + -- buffer number and name + local bufnr = vim.api.nvim_get_current_buf() + local bufname = vim.api.nvim_buf_get_name(bufnr) + + local previewer + + -- in case its not a normal buffer + if vim.fn.buflisted(bufnr) ~= 1 then + local deleted = false + local function del_win(win_id) + if win_id and vim.api.nvim_win_is_valid(win_id) then + utils.buf_delete(vim.api.nvim_win_get_buf(win_id)) + pcall(vim.api.nvim_win_close, win_id, true) + end + end + + previewer = + previewers.new { + preview_fn = function(_, entry, status) + if not deleted then + deleted = true + del_win(status.preview_win) + del_win(status.preview_border_win) + end + reload_theme(entry.value) + end + } + else + -- show current buffer content in previewer + previewer = + previewers.new_buffer_previewer { + get_buffer_by_name = function() + return bufname + end, + define_preview = function(self, entry) + if vim.loop.fs_stat(bufname) then + conf.buffer_previewer_maker(bufname, self.state.bufnr, {bufname = self.state.bufname}) + else + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, lines) + end + reload_theme(entry.value) + end + } + end + + local picker = + pickers.new( + { + prompt_title = "Set NvChad color", + finder = finders.new_table(themes), + previewer = previewer, + sorter = conf.generic_sorter(opts), + attach_mappings = function() + actions.select_default:replace( + -- if a entry is selected, change current_theme to that + function(prompt_bufnr) + local selection = action_state.get_selected_entry() + new_theme = selection.value + change = true + actions.close(prompt_bufnr) + end + ) + return true + end + } + ) + + -- rewrite picker.close_windows + local close_windows = picker.close_windows + picker.close_windows = function(status) + close_windows(status) + -- now apply the theme, if success, then ask for default theme change + local final_theme + if change then + final_theme = new_theme + else + final_theme = current_theme + end + + if reload_theme(final_theme) then + if change then + -- ask for confirmation to set as default theme + local ans = string.lower(vim.fn.input("Set " .. new_theme .. " as default theme ? [y/N] ")) == "y" + if ans then + local_utils.change_theme(current_theme, final_theme) + else + -- will be used in restoring nvchad theme var + final_theme = current_theme + end + end + -- open a buffer and close it to reload the statusline + vim.cmd("new|bwipeout") + end + -- set nvchad_theme global var + vim.g.nvchad_theme = current_theme + end + -- launch the telescope picker + picker:find() + else + print("No themes found in " .. themes_folder) + end +end + +-- register theme swticher as themes to telescope +local present, telescope = pcall(require, "telescope") +if present then + return telescope.register_extension { + exports = { + themes = M.theme_switcher + } + } +else + error "Cannot find telescope!" +end diff --git a/lua/theme.lua b/lua/theme.lua index bf9fea2..236f379 100644 --- a/lua/theme.lua +++ b/lua/theme.lua @@ -1,9 +1,9 @@ local chad_theme = require("user_config").ui.theme vim.g.nvchad_theme = chad_theme -local present2, base16 = pcall(require, "base16") +local present, base16 = pcall(require, "base16") -if present2 then +if present then base16(base16.themes[chad_theme], true) require "highlights" return true diff --git a/lua/utils.lua b/lua/utils.lua new file mode 100644 index 0000000..e86b895 --- /dev/null +++ b/lua/utils.lua @@ -0,0 +1,96 @@ +local M = {} + +-- reload a plugin ( will try to load even if not loaded) +-- can take a string or list ( table ) +M.reload_plugin = function(plugins) + local function _reload_plugin(plugin) + local loaded = package.loaded[plugin] + if loaded then + package.loaded[plugin] = nil + end + if not pcall(require, plugin) then + error("Error: Cannot load " .. plugin .. " plugin!") + end + end + + if type(plugins) == "string" then + _reload_plugin(plugins) + elseif type(plugins) == "table" then + for _, plugin in ipairs(plugins) do + _reload_plugin(plugin) + end + end +end + +-- return a table of available themes +M.list_themes = function(return_type) + local themes = {} + -- folder where theme files are stored + local themes_folder = vim.fn.stdpath("config") .. "/lua/themes" + -- list all the contents of the folder and filter out files with .lua extension, then append to themes table + local fd = vim.loop.fs_scandir(themes_folder) + if fd then + while true do + local name, typ = vim.loop.fs_scandir_next(fd) + if name == nil then + break + end + if typ ~= "directory" and string.find(name, ".lua") then + -- return the table values as keys if specified + if return_type == "keys_as_value" then + themes[vim.fn.fnamemodify(name, ":r")] = true + else + table.insert(themes, vim.fn.fnamemodify(name, ":r")) + end + end + end + end + return themes +end + +-- 1st arg - r or w +-- 2nd arg - file path +-- 3rd arg - content if 1st arg is w +-- return file data on read, nothing on write +M.file = function(mode, filepath, content) + local data + local fd = assert(vim.loop.fs_open(filepath, mode, 438)) + local stat = assert(vim.loop.fs_fstat(fd)) + if stat.type ~= "file" then + data = false + else + if mode == "r" then + data = assert(vim.loop.fs_read(fd, stat.size, 0)) + else + assert(vim.loop.fs_write(fd, content, 0)) + data = true + end + end + assert(vim.loop.fs_close(fd)) + return data +end + +-- 1st arg as current theme, 2nd as new theme +M.change_theme = function(current_theme, new_theme) + if current_theme == nil or new_theme == nil then + error "Provide current and new theme name" + end + if current_theme == new_theme then + return + end + + local file = vim.fn.stdpath("config") .. "/lua/user_config.lua" + -- store in data variable + local data = assert(M.file("r", file)) + local find = "theme = .?" .. current_theme .. ".?" + local replace = 'theme = "' .. new_theme .. '"' + local content = string.gsub(data, find, replace) + -- see if the find string exists in file + if content == data then + error("Cannot change default theme with " .. new_theme .. ", edit " .. file .. " manually") + else + assert(M.file("w", file, content)) + end +end + +return M