feat: initial implementation
A first working, publishable version of colorsaver.nvim. This plugin connects to the ColorScheme event to detect changes to the colorscheme. Any time that changes, it saves the colorscheme's name to a local file. In addition to that, it can watch that local file for changes. This way, when one `nvim` process changes the colorscheme, all other instances will automatically reload the colorscheme.
This commit is contained in:
10 files changed
+835
No files matched your search
+66
@@ -0,0 +1,66 @@
|
||||
#--------------------------------------------------#
|
||||
# The following was generated with gitignore.nvim: #
|
||||
#--------------------------------------------------#
|
||||
# Gitignore for the following technologies: Lua, Vim
|
||||
|
||||
# Compiled Lua sources
|
||||
luac.out
|
||||
|
||||
# luarocks build files
|
||||
*.src.rock
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.os
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
*.def
|
||||
*.exp
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
!*.svg # comment out if you don't need vector files
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
Sessionx.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
*~
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
indent_type = "Spaces"
|
||||
indent_width = 2
|
||||
column_width = 100
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright © 2023 Stephen Waits <steve@waits.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,158 @@
|
||||
# colorsaver.nvim
|
||||
|
||||
colorsaver.nvim is a Neovim plugin designed to save and load your preferred
|
||||
colorscheme persistently across sessions. With colorsaver, you can ensure your
|
||||
aesthetic preferences are maintained automatically. Additionally, it can
|
||||
automatically load colorscheme changes across all running instances of Neovim,
|
||||
providing a consistent user experience.
|
||||
|
||||
## Features
|
||||
|
||||
- **Persistent Colorscheme**: Save your selected colorscheme and have it load
|
||||
automatically on startup.
|
||||
- **Automatic Updates**: Watch for changes and automatically apply them across
|
||||
instances of Neovim.
|
||||
|
||||
## Installation
|
||||
|
||||
Install colorsaver using your favorite package manager.
|
||||
|
||||
### [lazy](https://github.com/folke/lazy.nvim) (recommended)
|
||||
|
||||
```lua
|
||||
{
|
||||
"https://git.sr.ht/~swaits/colorsaver.nvim",
|
||||
lazy = true,
|
||||
event = "VimEnter",
|
||||
opts = {
|
||||
-- your options here
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Or, using [LazyVim](https://www.lazyvim.org/), which is built on top of
|
||||
[lazy](https://github.com/folke/lazy.nvim), one example configuration might
|
||||
look like this:
|
||||
|
||||
```lua
|
||||
{
|
||||
-- tell LazyVim to stop messing with colorschemes
|
||||
{ "catppuccin/nvim", name = "catppuccin", enabled = false },
|
||||
{ "folke/tokyonight.nvim", enabled = false },
|
||||
{
|
||||
"LazyVim/LazyVim",
|
||||
opts = {
|
||||
colorscheme = "default", -- just some random built-in scheme
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
-- load/save our last used colorscheme automatically
|
||||
{
|
||||
"https://git.sr.ht/~swaits/colorsaver.nvim",
|
||||
event = "VimEnter",
|
||||
opts = {},
|
||||
dependencies = {
|
||||
-- load colorschemes as a dependency of colorsaver
|
||||
{ "AlexvZyl/nordic.nvim" },
|
||||
{ "EdenEast/nightfox.nvim" },
|
||||
{ "Shatur/neovim-ayu" },
|
||||
{ "rebelot/kanagawa.nvim" },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### [pckr](https://github.com/lewis6991/pckr.nvim)
|
||||
|
||||
```lua
|
||||
{
|
||||
"https://git.sr.ht/~swaits/colorsaver.nvim",
|
||||
config = function()
|
||||
require("colorsaver").setup({
|
||||
-- your options here
|
||||
})
|
||||
end
|
||||
}
|
||||
```
|
||||
|
||||
### [vim-plug](https://github.com/junegunn/vim-plug)
|
||||
|
||||
```vim
|
||||
Plug 'https://git.sr.ht/~swaits/colorsaver.nvim'
|
||||
lua require("colorsaver").setup({
|
||||
-- your options here
|
||||
})
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
To configure colorsaver, pass a table with your preferred settings to the
|
||||
`setup` function. Here is the default configuration:
|
||||
|
||||
```lua
|
||||
{
|
||||
-- log_level: Sets the logging level for the module's output.
|
||||
-- Acceptable values are "debug", "info", "warn", "error".
|
||||
log_level = "warn",
|
||||
|
||||
-- debounce_ms: Sets the debounce time in milliseconds for file watching.
|
||||
-- Accepts any integer greater than or equal to 50. If experiencing issues,
|
||||
-- consider increasing this value.
|
||||
debounce_ms = 100,
|
||||
|
||||
-- filename: The name of the file where the colorscheme will be saved.
|
||||
-- Note that the file is always stored in the "data" directory, which is
|
||||
-- usually ~/.local/share/nvim/
|
||||
filename = "colorsaver",
|
||||
|
||||
-- auto_load: If true, any colorscheme changes from one instance of nvim
|
||||
-- will be automatically loaded by all other instances of nvim.
|
||||
auto_load = true,
|
||||
}
|
||||
```
|
||||
|
||||
### File Watching
|
||||
|
||||
If `auto_load` is enabled, colorsaver will watch for changes in the saved
|
||||
colorscheme file and apply them to all running instances of Neovim.
|
||||
|
||||
### Logging
|
||||
|
||||
colorsaver features an integrated logging system. You can specify the log level
|
||||
in the configuration options. For troubleshooting, set the `log_level` to
|
||||
`"debug"` to receive verbose output.
|
||||
|
||||
## Usage
|
||||
|
||||
Once colorsaver is installed and configured, it works automatically. Your
|
||||
current colorscheme will be saved upon change and reloaded when Neovim starts
|
||||
or when any Neovim instance changes it (when `auto_load` is `true`).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions to colorsaver are welcome! Please feel free to fork the
|
||||
repository, make your changes, and submit a pull request.
|
||||
|
||||
## MIT License
|
||||
|
||||
Copyright © 2023 [Stephen Waits](https://swaits.com/) <steve@waits.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,103 @@
|
||||
-- autocommands.lua
|
||||
--- AutocommandGroup module for managing groups of autocommands in Neovim.
|
||||
-- This module encapsulates the creation and management of autocommand groups and their definitions.
|
||||
local AutocommandGroup = {}
|
||||
AutocommandGroup.__index = AutocommandGroup
|
||||
|
||||
--- Constructor for creating a new AutocommandGroup.
|
||||
-- @param name string The name of the autocommand group.
|
||||
-- @param definitions table A table of autocommand definitions.
|
||||
-- @return table A new instance of AutocommandGroup.
|
||||
function AutocommandGroup.new(name, definitions)
|
||||
-- Validate that 'name' is provided and is a string.
|
||||
assert(type(name) == "string", "Autocommand group name must be a string")
|
||||
|
||||
-- Create an instance of AutocommandGroup and set its properties.
|
||||
local self = setmetatable({}, AutocommandGroup)
|
||||
self.name = name
|
||||
self.definitions = definitions or {}
|
||||
-- Create a new autocommand group in Neovim, clearing any existing autocommands in the group.
|
||||
self.group_id = vim.api.nvim_create_augroup(name, { clear = true })
|
||||
-- Set or update the autocommand definitions for the group.
|
||||
self:set_definitions(self.definitions)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets or updates the autocommand definitions for the group.
|
||||
-- @param definitions table The autocommand definitions to set or update.
|
||||
function AutocommandGroup:set_definitions(definitions)
|
||||
-- Validate that 'definitions' is a table.
|
||||
assert(type(definitions) == "table", "Autocommand definitions must be a table")
|
||||
|
||||
-- Clear existing autocommands from the group to prepare for new definitions.
|
||||
vim.api.nvim_clear_autocmds({ group = self.group_id })
|
||||
|
||||
-- Iterate over each definition and create an autocommand.
|
||||
for _, def in ipairs(definitions) do
|
||||
-- Perform checks to ensure the definition structure is valid.
|
||||
assert(def.events, "Autocommand definition is missing 'events' key")
|
||||
assert(
|
||||
type(def.events) == "string" or type(def.events) == "table",
|
||||
"'events' must be a string or a table"
|
||||
)
|
||||
assert(def.patterns, "Autocommand definition is missing 'patterns' key")
|
||||
assert(
|
||||
type(def.patterns) == "string" or type(def.patterns) == "table",
|
||||
"'patterns' must be a string or a table"
|
||||
)
|
||||
assert(
|
||||
def.callback or def.command,
|
||||
"Autocommand definition must have either 'callback' or 'command'"
|
||||
)
|
||||
|
||||
-- Check for 'callback' and 'command' specifics and validate their types.
|
||||
if def.callback then
|
||||
assert(type(def.callback) == "function", "'callback' must be a function")
|
||||
end
|
||||
|
||||
if def.command then
|
||||
assert(type(def.command) == "string", "'command' must be a string")
|
||||
end
|
||||
|
||||
-- Validate optional keys if they are present.
|
||||
if def.once then
|
||||
assert(type(def.once) == "boolean", "'once' must be a boolean")
|
||||
end
|
||||
|
||||
if def.nested then
|
||||
assert(type(def.nested) == "boolean", "'nested' must be a boolean")
|
||||
end
|
||||
|
||||
-- Create the autocommand in Neovim with the provided definition.
|
||||
vim.api.nvim_create_autocmd(def.events, {
|
||||
group = self.group_id,
|
||||
pattern = def.patterns,
|
||||
callback = def.callback,
|
||||
command = def.command,
|
||||
once = def.once,
|
||||
nested = def.nested,
|
||||
})
|
||||
end
|
||||
|
||||
-- Update the stored definitions.
|
||||
self.definitions = definitions
|
||||
end
|
||||
|
||||
--- Clears the autocommand group, effectively disabling all its autocommands.
|
||||
function AutocommandGroup:clear()
|
||||
-- Clear all autocommands associated with the group in Neovim.
|
||||
vim.api.nvim_clear_autocmds({ group = self.group_id })
|
||||
end
|
||||
|
||||
--- Restores the autocommand group using the stored definitions.
|
||||
function AutocommandGroup:restore()
|
||||
-- Reapply the stored autocommand definitions.
|
||||
self:set_definitions(self.definitions)
|
||||
end
|
||||
|
||||
-- Module table for exporting the constructor.
|
||||
local M = {
|
||||
new = AutocommandGroup.new,
|
||||
}
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,98 @@
|
||||
-- config.lua
|
||||
--- Configuration module for handling user settings and defaults.
|
||||
-- This module provides validation and fallbacks for user-specified configuration.
|
||||
local M = {}
|
||||
|
||||
--- Define default configuration values with inline documentation.
|
||||
-- These defaults are used as a fallback if the user does not provide these settings or if they are invalid.
|
||||
local defaults = {
|
||||
--- log_level: Sets the logging level for the module's output.
|
||||
-- @field log_level Acceptable values are "debug", "info", "warn", "error".
|
||||
log_level = "warn",
|
||||
|
||||
--- debounce_ms: Sets the debounce time in milliseconds for file watching.
|
||||
-- @field debounce_ms Accepts any integer greater than or equal to 50. If experiencing issues, consider increasing this value.
|
||||
debounce_ms = 100,
|
||||
|
||||
--- filename: The name of the file where the colorscheme will be saved.
|
||||
-- @field filename Note that the file is always stored in the "data" directory, which is usually ~/.local/share/nvim/
|
||||
filename = "colorsaver",
|
||||
|
||||
--- auto_load: If true, any colorscheme changes from one instance of nvim will be automatically loaded by all other instances of nvim.
|
||||
-- @field auto_load
|
||||
auto_load = true,
|
||||
}
|
||||
|
||||
--- Validates the logging level.
|
||||
-- @param level string The log level to validate.
|
||||
-- @return boolean Whether the log level is valid.
|
||||
local function is_log_level_valid(level)
|
||||
local valid_levels = { debug = true, info = true, warn = true, error = true }
|
||||
return valid_levels[level:lower()] ~= nil
|
||||
end
|
||||
|
||||
--- Validates the debounce time.
|
||||
-- @param ms number The debounce time in milliseconds to validate.
|
||||
-- @return boolean Whether the debounce time is valid.
|
||||
local function is_debounce_ms_valid(ms)
|
||||
return type(ms) == "number" and ms >= 50
|
||||
end
|
||||
|
||||
--- Validates the filename.
|
||||
-- @param name string The filename to validate.
|
||||
-- @return boolean Whether the filename is valid.
|
||||
local function is_filename_valid(name)
|
||||
return type(name) == "string" and name ~= ""
|
||||
end
|
||||
|
||||
--- Validates the auto_load value.
|
||||
-- @param value boolean The auto_load value to validate.
|
||||
-- @return boolean Whether the auto_load is valid.
|
||||
local function is_auto_load_valid(value)
|
||||
return type(value) == "boolean"
|
||||
end
|
||||
|
||||
--- Checks for invalid keys in the user configuration and logs a warning for each.
|
||||
-- @param user_config table The user configuration to check.
|
||||
local function check_for_invalid_keys(user_config)
|
||||
for key, _ in pairs(user_config) do
|
||||
if defaults[key] == nil then
|
||||
log:warn("Ignoring invalid configuration key '" .. key .. "'.")
|
||||
user_config[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Merges user configuration with defaults and validates the result.
|
||||
-- @param user_config table The user-specified configuration.
|
||||
-- @param logger table The logger instance to be used for logging messages.
|
||||
-- @return table The validated and merged configuration.
|
||||
function M.setup(user_config, logger)
|
||||
-- Store the logger after ensuring it's provided.
|
||||
assert(logger, "Logger instance must be provided for configuration setup.")
|
||||
log = logger
|
||||
|
||||
-- Check if the provided user_config is a table.
|
||||
if user_config ~= nil and type(user_config) ~= "table" then
|
||||
log:error("Provided user_config must be a table")
|
||||
return
|
||||
end
|
||||
|
||||
-- Remove invalid keys from the user configuration.
|
||||
check_for_invalid_keys(user_config or {})
|
||||
|
||||
-- Deeply merge the defaults with the user configuration, prioritizing user settings.
|
||||
local config = vim.tbl_deep_extend("force", {}, defaults, user_config or {})
|
||||
|
||||
-- Validate and apply individual configuration settings.
|
||||
config.log_level = is_log_level_valid(config.log_level) and config.log_level or defaults.log_level
|
||||
config.debounce_ms = is_debounce_ms_valid(config.debounce_ms) and config.debounce_ms
|
||||
or defaults.debounce_ms
|
||||
config.filename = is_filename_valid(config.filename) and config.filename or defaults.filename
|
||||
config.auto_load = is_auto_load_valid(config.auto_load) and config.auto_load or defaults.auto_load
|
||||
|
||||
-- Return the validated and potentially corrected configuration.
|
||||
return config
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,50 @@
|
||||
-- file_watcher.lua
|
||||
--- FileWatcher module for watching file system events.
|
||||
-- This module provides the ability to watch for changes in a specified file and execute a callback when changes occur.
|
||||
local FileWatcher = {}
|
||||
FileWatcher.__index = FileWatcher
|
||||
|
||||
--- Constructor for the FileWatcher object.
|
||||
-- @param path string The file system path to watch.
|
||||
-- @param callback function The callback function to execute when a change is detected.
|
||||
-- @return table The constructed FileWatcher object.
|
||||
function FileWatcher.new(path, callback)
|
||||
-- Validate the input parameters to ensure correct types.
|
||||
assert(type(path) == "string", "Path must be a string")
|
||||
assert(type(callback) == "function", "Callback must be a function")
|
||||
|
||||
-- Set up the FileWatcher object.
|
||||
local self = setmetatable({}, FileWatcher)
|
||||
self.path = path
|
||||
self.callback = callback
|
||||
-- Initialize the file system watcher with libuv.
|
||||
self.fs_watcher = vim.loop.new_fs_event()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Starts watching the file system event.
|
||||
-- This method activates the file watcher, which will invoke the callback on file changes.
|
||||
function FileWatcher:start()
|
||||
-- Ensure the file system watcher starts monitoring for changes.
|
||||
-- Empty options table and vim.schedule_wrap to ensure the callback is called in the main loop.
|
||||
self.fs_watcher:start(self.path, {}, vim.schedule_wrap(self.callback))
|
||||
end
|
||||
|
||||
--- Stops watching the file system event.
|
||||
-- This method deactivates the file watcher, preventing the callback from being invoked.
|
||||
function FileWatcher:stop()
|
||||
-- If the watcher is active, stop it.
|
||||
if self.fs_watcher then
|
||||
self.fs_watcher:stop()
|
||||
end
|
||||
end
|
||||
|
||||
--- Restarts watching the file system event.
|
||||
-- This method stops and then restarts the file watcher.
|
||||
function FileWatcher:restart()
|
||||
-- Stop and then start the watcher to refresh the monitoring.
|
||||
self:stop()
|
||||
self:start()
|
||||
end
|
||||
|
||||
return FileWatcher
|
||||
@@ -0,0 +1,219 @@
|
||||
local M = {}
|
||||
|
||||
-- load modules
|
||||
local autocommands = require("colorsaver.autocommands")
|
||||
local config = require("colorsaver.config")
|
||||
local file_watcher = require("colorsaver.file_watcher")
|
||||
local logger = require("colorsaver.logger")
|
||||
local utils = require("colorsaver.utils")
|
||||
|
||||
-- our module local data (and these are just placeholder values, they're overwritten below)
|
||||
local autocmd_group = nil
|
||||
local color_file_path = vim.fn.stdpath("data") .. "/colorsaver"
|
||||
local colorfile_watcher = nil
|
||||
local log = logger.new("warn", "colorsaver")
|
||||
|
||||
--- save_colorscheme(new_scheme)
|
||||
--
|
||||
-- This function saves a new colorscheme to a file. If no new_scheme is provided, it saves the default colorscheme.
|
||||
--
|
||||
-- Parameters:
|
||||
-- - new_scheme (string): The name of the new colorscheme to save.
|
||||
--
|
||||
-- Returns:
|
||||
-- - success (boolean): true if the colorscheme was saved successfully, false otherwise.
|
||||
--
|
||||
-- Raises:
|
||||
-- - None
|
||||
--
|
||||
-- Example usage:
|
||||
-- save_colorscheme("my_colorscheme")
|
||||
--
|
||||
local function save_colorscheme(new_scheme)
|
||||
local colorscheme_name = new_scheme
|
||||
if not vim.tbl_contains(vim.fn.getcompletion("", "color"), colorscheme_name) then
|
||||
log:warn("Didn't find " .. colorscheme_name .. " in the list of colorschemes")
|
||||
return false
|
||||
end
|
||||
|
||||
local file, err = io.open(color_file_path, "w")
|
||||
if not file then
|
||||
log:error("Failed to open " .. color_file_path .. " for writing: " .. err)
|
||||
return false
|
||||
end
|
||||
|
||||
local success, write_err = pcall(function()
|
||||
file:write(colorscheme_name)
|
||||
end)
|
||||
|
||||
local close_ok, close_err = pcall(function()
|
||||
file:close()
|
||||
end)
|
||||
|
||||
if not success then
|
||||
log:error("Failed to write colorscheme to " .. color_file_path .. ": " .. write_err)
|
||||
return false
|
||||
elseif not close_ok then
|
||||
log:error("Failed to close the file " .. color_file_path .. ": " .. close_err)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- load_colorscheme()
|
||||
--
|
||||
-- This function loads a colorscheme for the current session in Neovim. It first
|
||||
-- attempts to read the colorscheme from a specified file path. If the file exists
|
||||
-- and is not empty, the content of the file is used as the colorscheme.
|
||||
--
|
||||
-- Parameters:
|
||||
-- None
|
||||
--
|
||||
-- Returns:
|
||||
-- success (boolean): true if the colorscheme was loaded successfully, false otherwise.
|
||||
--
|
||||
-- Raises:
|
||||
-- None
|
||||
--
|
||||
-- Example usage:
|
||||
-- load_colorscheme()
|
||||
--
|
||||
local function load_colorscheme()
|
||||
local colorscheme = M.default -- Fallback to default colorscheme
|
||||
|
||||
local file, err = io.open(color_file_path, "r")
|
||||
if not file then
|
||||
log:warn("Failed to open " .. color_file_path .. " for reading: " .. err)
|
||||
return false
|
||||
end
|
||||
|
||||
local content, read_err = file:read("*a")
|
||||
if read_err then
|
||||
log:error("Failed to read from " .. color_file_path .. ": " .. read_err)
|
||||
file:close()
|
||||
return false
|
||||
end
|
||||
|
||||
local close_ok, close_err = file:close()
|
||||
if not close_ok then
|
||||
log:error("Failed to close " .. color_file_path .. ": " .. close_err)
|
||||
return false
|
||||
end
|
||||
|
||||
colorscheme = content ~= "" and content or colorscheme
|
||||
|
||||
if not (colorscheme and colorscheme ~= "") then
|
||||
log:error("Invalid colorscheme name found in " .. color_file_path .. ".")
|
||||
return false
|
||||
end
|
||||
|
||||
-- disable ColorScheme autocmd so we don't get into a bad loop; this is
|
||||
-- because we're about to call `colorscheme` which will trigger ColorScheme
|
||||
-- and cause us to write the file again, thus retriggering the
|
||||
-- load_colorscheme function since the file changed... bad loop
|
||||
if autocmd_group then
|
||||
autocmd_group:clear()
|
||||
end
|
||||
|
||||
log:info("Switching to '" .. colorscheme .. "'")
|
||||
local ok, cmd_err = pcall(vim.api.nvim_command, "colorscheme " .. colorscheme)
|
||||
if not ok then
|
||||
log:error("Failed to apply colorscheme '" .. colorscheme .. "': " .. cmd_err)
|
||||
return false
|
||||
end
|
||||
|
||||
-- and re-enable the autocommands
|
||||
if autocmd_group then
|
||||
autocmd_group:restore()
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Callback function for file change events.
|
||||
--
|
||||
-- This function is called when a file change event is triggered. It handles
|
||||
-- the event by reloading the colorscheme from the updated file. If there is an
|
||||
-- error, it logs the error message and stops the event.
|
||||
--
|
||||
-- Parameters:
|
||||
-- - err (string|nil) The error message, if any. If there is no error, this parameter is nil.
|
||||
-- - filename (string) The name of the file that triggered the event.
|
||||
-- - events (table) A table containing information about the events that occurred.
|
||||
--
|
||||
-- Returns:
|
||||
-- None
|
||||
--
|
||||
-- Raises:
|
||||
-- None
|
||||
--
|
||||
local function on_change(err, filename)
|
||||
vim.schedule(function()
|
||||
if err then
|
||||
-- If there's an error, notify and stop the event
|
||||
log:error("Error watching file " .. tostring(filename) .. ": " .. tostring(err))
|
||||
return
|
||||
end
|
||||
|
||||
-- Reload the colorscheme from the updated file
|
||||
load_colorscheme()
|
||||
end)
|
||||
end
|
||||
|
||||
--- Sets up the ColorSaver module with the provided options.
|
||||
--
|
||||
-- Parameters:
|
||||
-- - opts (table): A table containing the options for configuring the ColorSaver module. The following keys are supported:
|
||||
-- - filename (string): The name of the file where the colorscheme will be saved.
|
||||
-- - log_level (string): The log level for the ColorSaver module.
|
||||
-- - debounce_ms (number): The debounce time in milliseconds for saving the colorscheme.
|
||||
-- - auto_load (boolean): Whether to enable autoloading of the colorscheme file.
|
||||
--
|
||||
-- Returns:
|
||||
-- None
|
||||
--
|
||||
-- Raises:
|
||||
-- None
|
||||
--
|
||||
-- This function sets up the ColorSaver module by performing the following steps:
|
||||
-- 1. Extracts and validates the options provided.
|
||||
-- 2. Sets up local data, including the file path for saving the colorscheme and a logger instance.
|
||||
-- 3. Loads the colorscheme at startup.
|
||||
-- 4. Captures the new colorscheme and saves it on the "ColorScheme" event.
|
||||
-- 5. Sets up autoloading of the colorscheme file if enabled.
|
||||
--
|
||||
function M.setup(opts)
|
||||
-- Extract options and validate them
|
||||
M.config = config.setup(opts, log)
|
||||
|
||||
-- Set up some local data
|
||||
color_file_path = vim.fn.stdpath("data") .. "/" .. M.config.filename
|
||||
log = logger.new(M.config.log_level, "colorsaver")
|
||||
|
||||
-- Load the colorscheme at startup.
|
||||
load_colorscheme()
|
||||
|
||||
-- Capture the new colorscheme and save it on the ColorScheme event.
|
||||
local debounced_save_colorscheme = utils.debounce(save_colorscheme, M.config.debounce_ms)
|
||||
autocmd_group = autocommands.new("ColorSaverGroup", {
|
||||
{
|
||||
events = { "ColorScheme" },
|
||||
patterns = { "*" },
|
||||
callback = function(info)
|
||||
vim.schedule(function()
|
||||
debounced_save_colorscheme(info.match) -- info.match is the colorscheme parameter (i.e. name)
|
||||
end)
|
||||
end,
|
||||
},
|
||||
})
|
||||
|
||||
-- Setup autoloading if enabled
|
||||
if M.config.auto_load then
|
||||
local debounced_on_change = utils.debounce(on_change, M.config.debounce_ms)
|
||||
colorfile_watcher = file_watcher.new(color_file_path, debounced_on_change)
|
||||
colorfile_watcher:start()
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,65 @@
|
||||
--- Logger module for neovim that provides a simple logging interface.
|
||||
-- @module Logger
|
||||
local Logger = {}
|
||||
Logger.__index = Logger
|
||||
|
||||
--- Log levels with corresponding neovim's vim.log.levels.
|
||||
local log_levels = {
|
||||
debug = vim.log.levels.DEBUG,
|
||||
info = vim.log.levels.INFO,
|
||||
warn = vim.log.levels.WARN,
|
||||
error = vim.log.levels.ERROR,
|
||||
}
|
||||
|
||||
--- Constructor for Logger object.
|
||||
-- @param min_level The minimum log level to output messages for.
|
||||
-- @param prefix A prefix to prepend to all log messages.
|
||||
-- @return A new Logger instance with specified level and prefix.
|
||||
function Logger.new(min_level, prefix)
|
||||
local self = setmetatable({}, Logger)
|
||||
self.min_level = log_levels[min_level] or log_levels.info -- Fallback to info level if unspecified
|
||||
self.prefix = prefix or "Logger" -- Default prefix if none provided
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the minimum log level.
|
||||
-- Only log messages at this level or higher will be output.
|
||||
-- @param min_level The minimum log level string ("debug", "info", "warn", "error").
|
||||
function Logger:set_level(min_level)
|
||||
self.min_level = log_levels[min_level] or self.min_level -- Keep current level if the new level is invalid
|
||||
end
|
||||
|
||||
--- Internal function to process the log message.
|
||||
-- @param level The log level of the message.
|
||||
-- @param message The log message.
|
||||
function Logger:_log(level, message)
|
||||
if level >= self.min_level then
|
||||
vim.notify(self.prefix .. ": " .. message, level)
|
||||
end
|
||||
end
|
||||
|
||||
--- Logs a message at DEBUG level.
|
||||
-- @param message The message to log.
|
||||
function Logger:debug(message)
|
||||
self:_log(log_levels.debug, "DEBUG: " .. message)
|
||||
end
|
||||
|
||||
--- Logs a message at INFO level.
|
||||
-- @param message The message to log.
|
||||
function Logger:info(message)
|
||||
self:_log(log_levels.info, "INFO: " .. message)
|
||||
end
|
||||
|
||||
--- Logs a message at WARN level.
|
||||
-- @param message The message to log.
|
||||
function Logger:warn(message)
|
||||
self:_log(log_levels.warn, "WARN: " .. message)
|
||||
end
|
||||
|
||||
--- Logs a message at ERROR level.
|
||||
-- @param message The message to log.
|
||||
function Logger:error(message)
|
||||
self:_log(log_levels.error, "ERROR: " .. message)
|
||||
end
|
||||
|
||||
return Logger
|
||||
@@ -0,0 +1,52 @@
|
||||
--- Utility functions for debouncing and validating colorschemes.
|
||||
-- @module util
|
||||
local M = {}
|
||||
|
||||
--- Debounces a function call. This is useful to prevent a function from
|
||||
-- being called too frequently, delaying its execution until after a certain
|
||||
-- amount of time has elapsed since the last time it was invoked.
|
||||
-- @param func function The function to debounce.
|
||||
-- @param delay number The time delay in milliseconds.
|
||||
-- @return function A new debounced function.
|
||||
function M.debounce(func, delay)
|
||||
assert(type(func) == "function", "Invalid argument 'func': function expected")
|
||||
assert(type(delay) == "number", "Invalid argument 'delay': number expected")
|
||||
|
||||
local timer = nil
|
||||
return function(...)
|
||||
local args = { ... } -- Capture any arguments passed to the function.
|
||||
|
||||
if timer then
|
||||
timer:stop() -- Stop any existing timer.
|
||||
timer:close() -- Clean up the timer resources.
|
||||
end
|
||||
|
||||
-- Create a new timer.
|
||||
timer = vim.loop.new_timer()
|
||||
|
||||
-- Start the timer with the specified delay.
|
||||
timer:start(
|
||||
delay,
|
||||
0,
|
||||
vim.schedule_wrap(function()
|
||||
func(unpack(args)) -- Call the original function with the captured arguments.
|
||||
timer:stop() -- Stop the timer.
|
||||
timer:close() -- Clean up the timer resources.
|
||||
timer = nil -- Clear the timer variable.
|
||||
end)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
--- Validates whether the provided colorscheme name is available in Neovim.
|
||||
-- This function checks against the list of valid colorschemes that Neovim knows about.
|
||||
-- @param colorscheme string The colorscheme name to validate.
|
||||
-- @return boolean True if the colorscheme is valid, false otherwise.
|
||||
function M.is_colorscheme_valid(colorscheme)
|
||||
assert(type(colorscheme) == "string", "Invalid argument 'colorscheme': string expected")
|
||||
|
||||
-- Check if the colorscheme exists in the list of completions for "color".
|
||||
return vim.tbl_contains(vim.fn.getcompletion("", "color"), colorscheme)
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in new issue
Block a user