Browse Source

refact(core): Split command / autocommand utility functions into their own services.

my-config
connorgmeean 2 years ago
parent
commit
f58f3e8b1c
  1. 32
      lua/doom/core/modules.lua
  2. 9
      lua/doom/modules/core/reloader/init.lua
  3. 144
      lua/doom/services/autocommands.lua
  4. 101
      lua/doom/services/commands.lua
  5. 126
      lua/doom/utils/init.lua

32
lua/doom/core/modules.lua

@ -109,6 +109,8 @@ modules.start = function()
end
local keymaps_service = require("doom.services.keymaps")
local commands_service = require("doom.services.commands")
local autocmds_service = require("doom.services.autocommands")
--- Applies commands, autocommands, packages from enabled modules (`modules.lua`).
modules.load_modules = function()
@ -155,19 +157,9 @@ modules.load_modules = function()
-- Set/unset frozen packer dependencies
if type(spec.commit) == "table" then
local last_commit = nil
for version, commit in pairs(spec.commit) do
if version == "latest" then
version = utils.nvim_latest_supported
end
if vim.fn.has(version) == 1 then
last_commit = commit
else
break
end
end
spec.commit = last_commit
-- Commit can be a table of values, where the keys indicate
-- which neovim version is required.
spec.commit = utils.pick_compatible_field(spec.commit)
end
if not doom.freeze_dependencies then
@ -183,12 +175,14 @@ modules.load_modules = function()
if module.autocmds then
local autocmds = type(module.autocmds) == "function" and module.autocmds()
or module.autocmds
utils.make_augroup(module_name, autocmds)
for _, autocmd_spec in ipairs(autocmds) do
autocmds_service.set(autocmd_spec[1], autocmd_spec[2], autocmd_spec[3], autocmd_spec)
end
end
if module.cmds then
for _, cmd_spec in ipairs(module.cmds) do
utils.make_cmd(cmd_spec[1], cmd_spec[2], cmd_spec)
commands_service.set(cmd_spec[1], cmd_spec[2], cmd_spec[3] or cmd_spec.opts)
end
end
@ -213,15 +207,13 @@ modules.handle_user_config = function()
-- Handle extra user cmds
for _, cmd_spec in pairs(doom.cmds) do
utils.make_cmd(cmd_spec[1], cmd_spec[2], cmd_spec)
commands_service.set(cmd_spec[1], cmd_spec[2], cmd_spec[3] or cmd_spec.opts)
end
-- Handle extra user autocmds
local autocmds = {}
for _, cmd_spec in pairs(doom.autocmds) do
table.insert(autocmds, cmd_spec)
for _, autocmd_spec in pairs(doom.autocmds) do
autocmds_service.set(autocmd_spec[1], autocmd_spec[2], autocmd_spec[3], autocmd_spec)
end
utils.make_augroup("user", autocmds)
-- Handle extra user keybinds
for _, keybinds in ipairs(doom.binds) do

9
lua/doom/modules/core/reloader/init.lua

@ -92,10 +92,11 @@ reloader._reload_doom = function()
end, doom.packages)
-- Reset State
if _doom and _doom.cmd_funcs then
_doom.cmd_funcs = {}
end
doom.packages = {}
local commands_service = require("doom.services.commands")
commands_service.del_all()
print('deleting autocommands')
local autocmds_service = require("doom.services.autocommands")
autocmds_service.del_all()
-- Unload doom.modules/doom.core lua files
for k, _ in pairs(package.loaded) do

144
lua/doom/services/autocommands.lua

@ -0,0 +1,144 @@
--- AutoCommands Service,
--- Provides functions to wrap neovims APIs to set and remove autocmds
--- Acts as a compatibility layer between different API versions.
--- Manages references to all commands to be cleared for :DoomReload
-- TYPES
--- @class AutoCommandArgs
--- @field args string Args parsed to command (if any)
--- @field fargs string[] Args split by unescaped whitespace (if any)
--- @field line1 number Starting line of the command range
--- @field line2 number Final line of the command range
--- @field count number Any count supplied (if any)
--- @class SetAutoCommandOptions
--- @field nested boolean|nil
--- @field once boolean|nil
--- IMPLEMENTATIONS
--- Wraps the nvim functionality to handle different neovim versions.
local utils = require("doom.utils")
-- Data to be stored globally so it can be accessed from the nvim-0.5 implementation
local data = _G._doom_autocmds_service_data
or {
-- Stores data relating to the auto command so they can be deleted on neovim < 0.8
autocmd_signatures = {},
-- Stores the lua function handlers for nvim version < 0.8
autocmd_actions = {},
-- Stores created autocommand ids from vim.api.nvim_create_autocmd (or custom shim in the v0.5 version)
autocmd_ids = {},
}
_G._doom_autocmds_service_data = data
-- Store all autocommands inside of an augroup for doom-nvim
if vim.fn.has("nvim-0.8") then
vim.api.nvim_create_augroup("DoomAutoCommands", { clear = true })
else
vim.cmd([[
augroup DoomAutoCommands
autocmd!
augroup END
]])
end
local set_autocmd_implementations = {
["nvim-0.5"] = function(event, pattern, action, opts)
local cmd_string = "autocmd! "
cmd_string = cmd_string .. ("%s %s "):format(event, pattern)
local uid = utils.unique_index()
data.autocmd_ids[uid] = true
data.autocmd_signatures = cmd_string
if opts.nested then
cmd_string = cmd_string .. "++nested "
end
if opts.once then
cmd_string = cmd_string .. "++once "
end
if type(action) == "string" then
cmd_string = cmd_string .. action .. " "
else
data.autocmd_actions[uid] = action
cmd_string = cmd_string .. (":lua _doom_autocmds_service_data.autocmd_actions[%d]()"):format(uid)
end
vim.cmd(cmd_string)
return uid
end,
["latest"] = function(event, pattern, action, opts)
local merged_opts = vim.tbl_extend('keep', opts, {
callback = action,
pattern = pattern,
group = "DoomAutoCommands"
})
local id = vim.api.nvim_create_autocmd(event, merged_opts)
data.autocmd_ids[id] = true
data.autocmd_signatures[id] = ("%s %s"):format(event, pattern)
return id
end,
}
local set_autocmd_fn = utils.pick_compatible_field(set_autocmd_implementations)
local del_autocmd_implementations = {
["nvim-0.5"] = function(id)
local delete_signature = data.autocmd_signatures[id]
if delete_signature then
vim.cmd(delete_signature)
end
end,
["latest"] = function(id)
vim.api.nvim_del_autocmd(id)
end,
}
local del_autocmd_fn = utils.pick_compatible_field(del_autocmd_implementations)
local del_all_autocmd_implementations = {
["nvim-0.5"] = function()
vim.cmd([[
augroup DoomAutoCommands
autocmd!
augroup END
]])
end,
["latest"] = function()
vim.api.nvim_create_augroup("DoomAutoCommands", { clear = true })
end,
}
local del_all_autocmd_fn = utils.pick_compatible_field(del_all_autocmd_implementations)
-- API
local autocmds_service = {}
--- Set a neovim autocmd
---@param event string Name of autocmd
---@param pattern string Pattern to match autocommand with
---@param action string|function(AutoCommandArgs)
---@param opts SetAutoCommandOptions|nil
---@return number ID of autocommand, used to delete it later on
autocmds_service.set = function(event, pattern, action, opts)
local resolved_opts = opts or {}
local stripped_opts = {
nested = resolved_opts.nested or false,
once = resolved_opts.once or false,
}
return set_autocmd_fn(event, pattern, action, stripped_opts)
end
--- Deletes an autocommand from a given id
---@param id number ID of autocommand to delete
autocmds_service.del = function(id)
del_autocmd_fn(id)
data.autocmd_ids[id] = nil
data.autocmd_signatures[id] = nil
data.autocmd_actions[id] = nil
end
autocmds_service.del_all = function()
del_all_autocmd_fn()
end
return autocmds_service

101
lua/doom/services/commands.lua

@ -0,0 +1,101 @@
--- Commands Service,
--- Provides functions to wrap neovims APIs to set and remove commands
--- Acts as a compatibility layer between different API versions.
--- Manages references to all commands to be cleared for :DoomReload
-- TYPES
--- @class CommandArgs
--- @field args string Args parsed to command (if any)
--- @field fargs string[] Args split by unescaped whitespace (if any)
--- @field line1 number Starting line of the command range
--- @field line2 number Final line of the command range
--- @field count number Any count supplied (if any)
--- @class SetCommandOptions
--- @field nargs number|'*'|nil Number of expected arguments for the command. '*' for variable.
--- IMPLEMENTATIONS
--- Wraps the nvim functionality to handle different neovim versions.
local utils = require("doom.utils")
-- Data to be stored globally so it can be accessed from the nvim-0.5 implementation
local data = _G._doom_commands_service_data or {
command_actions = {},
}
_G._doom_commands_service_data = data
local set_command_implementations = {
["nvim-0.5"] = function(name, command, opts)
-- Build the command constructor
local cmd_string = "command! "
if opts and opts.nargs ~= nil then
cmd_string = cmd_string .. ("-nargs=%s "):format(opts.nargs)
end
if opts and opts.completion ~= nil then
cmd_string = cmd_string .. ("-complete=%s "):format(table.concat(opts.complete, ","))
end
cmd_string = cmd_string .. " " .. name .. " "
if type(command) == "string" then
cmd_string = cmd_string .. command .. " "
else
local uid = utils.unique_index()
data.command_actions[uid] = command
cmd_string = cmd_string .. ("lua _doom_commands_service_data.command_actions[%d]"):format(uid)
if opts.nargs ~= nil then
cmd_string = cmd_string .. "(<f-args>)"
else
cmd_string = cmd_string .. "()"
end
end
vim.cmd(cmd_string)
end,
["nvim-0.8"] = function(name, command, opts)
vim.api.nvim_create_user_command(name, command, opts)
end,
}
local set_command_fn = utils.pick_compatible_field(set_command_implementations)
local del_command_implementations = {
["nvim-0.5"] = function(name)
vim.cmd(("delcommand %s"):format(name))
end,
["nvim-0.8"] = function(name)
vim.api.nvim_del_user_command(name)
end,
}
local del_command_fn = utils.pick_compatible_field(del_command_implementations)
-- API
local commands_service = {}
--- List of all commands set so they can be deleted by `commands.del_all()`
--- @type table<string,boolean|nil>
commands_service.stored_names = {}
--- Set a neovim command
---@param name string Name of command
---@param command string|function(CommandArgs)
---@param opts SetCommandOptions|nil
commands_service.set = function(name, command, opts)
commands_service.stored_names[name] = true
set_command_fn(name, command, opts or {})
end
commands_service.del = function(name)
commands_service.stored_names[name] = nil
del_command_fn(name)
end
commands_service.del_all = function()
for name, _ in pairs(commands_service.stored_names) do
if name then
del_command_fn(name)
end
end
commands_service.stored_names = {}
end
return commands_service

126
lua/doom/utils/init.lua

@ -11,7 +11,7 @@ utils.version = {
}
--- Currently supported version of neovim for this build of doom-nvim
utils.nvim_latest_supported = 'nvim-0.8'
utils.nvim_latest_supported = "nvim-0.8"
utils.doom_version =
string.format("%d.%d.%d", utils.version.major, utils.version.minor, utils.version.patch)
@ -21,6 +21,7 @@ utils.find_config = function(filename)
local function get_filepath(dir)
return table.concat({ dir, filename }, system.sep)
end
local path = get_filepath(system.doom_configs_root)
if fs.file_exists(path) then
return path
@ -74,85 +75,6 @@ utils.safe_require = function(path)
end
end
--- Stores a function in a global table, returns a string to execute the function
-- @param fn function
-- @return string
utils.commandify_function = function(fn, has_arguments)
if not _G._doom then
_G._doom = {}
end
if not _doom.cmd_funcs then
_doom.cmd_funcs = {}
end
-- Nobody is going to need more than a million of these, right?
local unique_number = utils.unique_index()
_doom.cmd_funcs[unique_number] = fn
if has_arguments then
return ("lua _doom.cmd_funcs[%d](<f-args>)"):format(unique_number)
else
return ("lua _doom.cmd_funcs[%d]()"):format(unique_number)
end
end
-- @type MakeCmdOptions
-- @field nargs string|number|nil
-- @field complete string[]|nil
--- Creates a new command that can be executed from the neovim command line
-- @param cmd_name string The name of the command, i.e. `:DoomReload`
-- @param action string|function The action to execute when the cmd is entered.
-- @param opts MakeCmdOptions
utils.make_cmd = function(cmd_name, action, opts)
local cmd_string = "command! "
if opts and opts.nargs ~= nil then
cmd_string = cmd_string .. ("-nargs=%s "):format(opts.nargs)
end
if opts and opts.completion ~= nil then
cmd_string = cmd_string .. ("-complete=%s "):format(table.concat(opts.complete, ","))
end
cmd_string = cmd_string .. " " .. cmd_name .. " "
cmd_string = type(action) == "function" and cmd_string .. utils.commandify_function(action, opts and opts.nargs ~= nil) or cmd_string .. action
vim.cmd(cmd_string)
end
utils.make_autocmd = function(event, pattern, action, group, nested, once)
local cmd = "autocmd "
if group then
cmd = cmd .. group .. " "
end
cmd = cmd .. event .. " "
cmd = cmd .. pattern .. " "
if nested then
cmd = cmd .. "++nested "
end
if once then
cmd = cmd .. "++once "
end
cmd = type(action) == "function" and cmd .. utils.commandify_function(action) or cmd .. action
vim.cmd(cmd)
end
utils.make_augroup = function(group_name, cmds, existing_group)
if not existing_group then
vim.cmd("augroup " .. group_name)
vim.cmd("autocmd!")
end
for _, cmd in ipairs(cmds) do
utils.make_autocmd(cmd[1], cmd[2], cmd[3], existing_group and group_name, cmd.nested, cmd.once)
end
if not existing_group then
vim.cmd("augroup END")
end
end
utils.get_sysname = function()
return vim.loop.os_uname().sysname
end
@ -286,4 +208,48 @@ utils.iter_string_at = function(str, sep)
return string.gmatch(str, "([^" .. sep .. "]+)")
end
--- Picks a field from a table by checking the keys if it's compatible
---@generic T
---@param compatibility_table table<string,T>
---@return T
---@example
---```lua
---local val = utils.pick_compatible_field({
--- ['nvim-0.5'] = 'this will be picked',
--- ['nvim-9.9'] = 'version too high, wont be picked'
---})
---print(val) -- > 'this will be picked'
---```
utils.pick_compatible_field = function(compatibility_table)
-- Sort the keys in order of neovim version
local sorted = vim.tbl_keys(compatibility_table)
table.sort(sorted, function(a, b)
return a < b
end)
-- Need "latest" to be last as it is a catch all for the default behaviour
if sorted[1] == "latest" then
table.remove(sorted, 1)
table.insert(sorted, "latest")
end
-- Find the last key that is compatible with this neovim version
local last_field = nil
for _, version in ipairs(sorted) do
local field = compatibility_table[version]
local ver = version == "latest" and utils.nvim_latest_supported or version
if vim.fn.has(ver) == 1 then
last_field = field
else
break
end
end
-- Must always return a value.
if last_field == nil then
error("Error getting compatible field.")
end
return last_field
end
return utils

Loading…
Cancel
Save