diff --git a/lua/doom/core/config.lua b/lua/doom/core/config.lua index 71af2646f..7d5ce16db 100644 --- a/lua/doom/core/config.lua +++ b/lua/doom/core/config.lua @@ -63,40 +63,59 @@ config.load = function() local enabled_modules = require("doom.core.modules").enabled_modules profiler.start("framework|import modules") - -- Iterate over each module and save it to the doom global object - for section_name, section_modules in pairs(enabled_modules) do - for _, module_name in pairs(section_modules) do - -- If the section is `user` resolves from `lua/user/modules` - local profiler_message = ("modules|import `%s.%s`"):format(section_name, module_name) - profiler.start(profiler_message) - local search_paths = { - ("user.modules.%s.%s"):format(section_name, module_name), - ("doom.modules.%s.%s"):format(section_name, module_name), - } - - local ok, result - for _, path in ipairs(search_paths) do - ok, result = xpcall(require, debug.traceback, path) - if ok then - break + + -- Combine enabled modules (`modules.lua`) with core modules. + require("doom.utils.modules").traverse_enabled( + enabled_modules, + function(node, stack) + if type(node) == "string" then + local t_path = vim.tbl_map(function(stack_node) + return type(stack_node.key) == "string" and stack_node.key or stack_node.node + end, stack) + + local path_module = table.concat(t_path, ".") + + local profiler_message = ("modules|import `%s`"):format(path_module) + profiler.start(profiler_message) + + -- If the section is `user` resolves from `lua/user/modules` + local search_paths = { + ("user.modules.%s"):format(path_module), + ("doom.modules.%s"):format(path_module), + } + + local ok, result + for _, path in ipairs(search_paths) do + ok, result = xpcall(require, debug.traceback, path) + if ok then + break + end end - end - if ok then - doom[section_name][module_name] = result - else - local log = require("doom.utils.logging") - log.error( - string.format( - "There was an error loading module '%s.%s'. Traceback:\n%s", - section_name, - module_name, - result + + if ok then + -- Add string tag so that we can easilly target modules with more + -- traversers, ie. in `core/modules` when traversing `doom.modules` + result.type = "doom_module_single" + utils.get_set_table_path(doom.modules, t_path, result) + else + local log = require("doom.utils.logging") + log.error( + string.format( + "There was an error loading module '%s'. Traceback:\n%s", + path_module, + result + ) ) - ) + end + + profiler.stop(profiler_message) end - profiler.stop(profiler_message) end - end + , { debug = doom.logging == "trace" or doom.logging == "debug" } + ) + + + profiler.stop("framework|import modules") profiler.start("framework|config.lua (user)") diff --git a/lua/doom/core/modules.lua b/lua/doom/core/modules.lua index 0bb24e682..f3b4dcafe 100644 --- a/lua/doom/core/modules.lua +++ b/lua/doom/core/modules.lua @@ -38,13 +38,16 @@ local autocmds_service = require("doom.services.autocommands") --- Applies commands, autocommands, packages from enabled modules (`modules.lua`). modules.load_modules = function() local logger = require("doom.utils.logging") + -- Handle the Modules - for section_name, _ in pairs(doom.modules) do - for module_name, module in pairs(doom.modules[section_name]) do - if type(module) ~= "table" then - print(("Error on module %s type is %s val is %s"):format(module_name, type(module), module)) - end - local profile_msg = ("modules|init `%s.%s`"):format(section_name, module_name) + require("doom.utils.modules").traverse_loaded(doom.modules, function(node, stack) + if node.type then + local module = node + local t_path = vim.tbl_map(function(stack_node) + return type(stack_node.key) == "string" and stack_node.key + end, stack) + local path_module = table.concat(t_path, ".") + local profile_msg = ("modules|init `%s`"):format(path_module) profiler.start(profile_msg) -- Flag to continue enabling module @@ -53,17 +56,13 @@ modules.load_modules = function() -- Check module has necessary dependencies if module.requires_modules then for _, dependent_module in ipairs(module.requires_modules) do - local dep_section_name, dep_module_name = unpack(vim.split(dependent_module, "%.")) - - if not doom.modules[dep_section_name][dep_module_name] then + if not utils.get_set_table_path(doom.modules, vim.split(dependent_module, "%.")) then should_enable_module = false logger.error( - ('Doom module "%s.%s" depends on a module that is not enabled "%s.%s". Please enable the %s module.'):format( - section_name, - module_name, - dep_section_name, - dep_module_name, - dep_module_name + ('Doom module "%s" depends on a module that is not enabled "%s". Please enable the %s module.'):format( + path_module, + dependent_module, + dependent_module ) ) end @@ -88,8 +87,9 @@ modules.load_modules = function() spec.commit = utils.pick_compatible_field(spec.commit) end - -- Only pin dependencies if doom.freeze_dependencies is true - spec.lock = spec.commit and doom.freeze_dependencies + if not doom.freeze_dependencies then + spec.commit = nil + end -- Save module spec to be initialised later table.insert(doom.packages, spec) @@ -117,9 +117,11 @@ modules.load_modules = function() ) end end + profiler.stop(profile_msg) end - end + end, { debug = doom.logging == "trace" or doom.logging == "debug" }) + end --- Applies user's commands, autocommands, packages from `use_*` helper functions. diff --git a/lua/doom/modules/core/nest/init.lua b/lua/doom/modules/core/nest/init.lua index 75a4b806e..c8ec640f0 100644 --- a/lua/doom/modules/core/nest/init.lua +++ b/lua/doom/modules/core/nest/init.lua @@ -137,13 +137,18 @@ mapper.configs["nvim-mapper"] = function() local profiler = require("doom.services.profiler") local count = 0 - for section_name, _ in pairs(doom.modules) do - for module_name, module in pairs(doom[section_name]) do + require("doom.utils.modules").traverse_loaded(doom.modules, function(node, stack) + if node.type then + local module = node + local t_path = vim.tbl_map(function(stack_node) + return type(stack_node.key) == "string" and stack_node.key + end, stack) + local path_module = table.concat(t_path, ".") if module.binds then count = count + 1 vim.defer_fn(function() -- table.insert(all_keymaps, type(module.binds) == "function" and module.binds() or module.binds) - local profiler_msg = ("keymaps(async)|module: %s.%s"):format(section_name, module_name) + local profiler_msg = ("keymaps(async)|module: %s"):format(path_module) profiler.start(profiler_msg) keymaps_service.applyKeymaps( type(module.binds) == "function" and module.binds() or module.binds, @@ -154,7 +159,8 @@ mapper.configs["nvim-mapper"] = function() end, count) end end - end + end) + end return mapper diff --git a/lua/doom/modules/features/whichkey/init.lua b/lua/doom/modules/features/whichkey/init.lua index 4c0db98ff..2422763f3 100644 --- a/lua/doom/modules/features/whichkey/init.lua +++ b/lua/doom/modules/features/whichkey/init.lua @@ -121,18 +121,24 @@ whichkey.configs["which-key.nvim"] = function() local keymaps_service = require("doom.services.keymaps") local whichkey_integration = get_whichkey_integration() - for section_name, _ in pairs(doom.modules) do - for _, module in pairs(doom[section_name]) do - if module and module.binds then - -- table.insert(all_keymaps, type(module.binds) == "function" and module.binds() or module.binds) - keymaps_service.applyKeymaps( - type(module.binds) == "function" and module.binds() or module.binds, - nil, - { whichkey_integration } - ) + + require("doom.utils.modules").traverse_loaded( + doom.modules, + function(node, stack) + if node.type then + local module = node + if module.binds then + keymaps_service.applyKeymaps( + type(module.binds) == "function" and module.binds() or module.binds, + nil, + { whichkey_integration } + ) + end end end - end + --, { debug = doom.settings.logging == "trace" or doom.settings.logging == "debug" } + ) + -- Add user keymaps to whichkey user keymaps if doom.binds and #doom.binds >= 1 then diff --git a/lua/doom/services/traverser.lua b/lua/doom/services/traverser.lua new file mode 100644 index 000000000..2f04cc868 --- /dev/null +++ b/lua/doom/services/traverser.lua @@ -0,0 +1,75 @@ +-- +-- TRAVERSER +-- + +-- TODO: More documentation + +-- Default debugger print +local default_debug_node = function(node, stack) + local parent = stack[#stack] + local indent_str = string.rep("--", #stack) + local indent_cap = type(node) == "table" and "+" or ">" + print( + ("default: %s%s %s"):format( + indent_str, + indent_cap, + type(node) == "table" and parent.key or node + ) + ) +end + +-- Default debug levels +local default_log_levels = + { debug = doom.logging == "trace" or doom.logging == "debug" } + +local tree_traverser = { + build = function(builder_opts) + local traverser = builder_opts.traverser + local debug_node = builder_opts.debug_node or default_debug_node + local stack = {} + local result = {} + + -- Traverse out, pops from stack, adds to result + local traverse_out = function() + table.remove(stack, #stack) + end + + -- Error does not add to result or anything + local err = function(message) + table.remove(stack, #stack) + local path = vim.tbl_map(function(stack_node) + return "[" .. vim.inspect(stack_node.key) .. "]" + end, stack) + print(("%s\n Occursed at key `%s`."):format(message, table.concat(path, ""))) + table.remove(result, #result) + end + + local traverse_in + traverse_in = function(key, node) + table.insert(stack, { key = key, node = node }) + table.insert(result, { node = node, stack = vim.deepcopy(stack) }) + traverser(node, stack, traverse_in, traverse_out, err) + end + + return function(tree, handler, opts) + result = {} -- Reset result + if opts == nil then + opts = default_log_levels + end + + traverser(tree, stack, traverse_in, traverse_out, err) + + if opts.debug and debug_node then + for _, value in ipairs(result) do + debug_node(value.node, value.stack) + end + end + + for _, value in ipairs(result) do + handler(value.node, value.stack) + end + end + end, +} + +return tree_traverser diff --git a/lua/doom/utils/init.lua b/lua/doom/utils/init.lua index 13dcf2b2e..7d759ac20 100644 --- a/lua/doom/utils/init.lua +++ b/lua/doom/utils/init.lua @@ -139,13 +139,23 @@ utils.get_diagnostic_count = function(bufnr, severity) end --- Check if the given plugin is disabled in doom-nvim/modules.lua +--- +--- You only need to supply one single arg if `section == `. +--- --- @param section string The module section, e.g. features --- @param plugin string The module identifier, e.g. statusline --- @return boolean utils.is_module_enabled = function(section, plugin) local modules = require("doom.core.modules").enabled_modules - return modules[section] and vim.tbl_contains(modules[section], plugin) + if type(section) == "table" then + local tp = section + local name = table.remove(tp, #tp) + local subsec = utils.get_set_table_path(modules, tp) + return vim.tbl_contains(subsec, name) + else + return modules[section] and vim.tbl_contains(modules[section], plugin) + end end --- Rounds a number, optionally to the nearest decimal place @@ -231,5 +241,45 @@ utils.left_pad = function(str, length, char) return res, res ~= str end +-- Get or Set a table path list. +-- +-- Used with recursive module structures so that you can check if eg. a deep +-- path exists or if you want to create/set data to a deep path. +-- +-- if no data supplies -> returns table path node or false if not exists +-- +---@param head table The table to which you want target +---@param tp table Path that you wish to check in head +---@param data any If supplied, attaches this data to tp tip, eg. `{"a", "b"} -> b = data` +utils.get_set_table_path = function(head, tp, data) + if not head or not tp then + return false + end + local last = #tp + for i, p in ipairs(tp) do + if i ~= last then + if head[p] == nil then + if not data then + -- if a nil occurs, this means the path does no exist >> return + return false + end + head[p] = {} + end + head = head[p] + else + if data then + if type(data) == "function" then + data(head[p]) + else + head[p] = data + end + else + -- print(vim.inspect(head), p) + return head[p] + end + end + end +end + return utils diff --git a/lua/doom/utils/modules.lua b/lua/doom/utils/modules.lua new file mode 100644 index 000000000..d2aa521aa --- /dev/null +++ b/lua/doom/utils/modules.lua @@ -0,0 +1,62 @@ +local traverser = require("doom.services.traverser") + +-- +-- This file hosts some common recurring traversers in doom. +-- + +local M = {} + +-- Designed to travers `modules.lua` file +M.traverse_enabled = traverser.build({ + -- Builds the traversal function defining how we should move through the tree + -- @param node any The node itself + -- @param next function(node: any) Traverse into the traverse_in node, adding the node to the stack + -- @param traverse_out function() Traverses back a depth, pops the value from the stack + -- @param err function(message: string) Traverses back a depth, this value is skipped by the handler (see below) + traverser = function(node, stack, traverse_in, traverse_out, err) + local parent = stack[#stack] + local parent_is_section = ( + parent == nil or (type(parent.key) == "string" and type(parent.node) == "table") + ) + if type(node) == "table" and parent_is_section then + if vim.tbl_count(node) == 0 then + traverse_out() -- Handle case if a table is empty. + else + for key, value in pairs(node) do + traverse_in(key, value) -- Traverse into next layer. + end + traverse_out() -- Travel back up when a sub table has been completed. + end + elseif type(node) == "string" and not parent_is_section then + traverse_out() -- This is a leaf, traverse back a layer. + else + err( + ("doom-nvim: Error traversing doom modules in `modules.lua`, unexpected value `%s`."):format( + vim.inspect(node) + ) + ) -- Traverse back a layer but do not pass this value to the handler function. + end + end, +}) + +M.traverse_loaded = traverser.build({ + traverser = function(node, _, traverse_in, traverse_out, _) + if node.type == "doom_module_single" then + traverse_out() + else + for key, value in pairs(node) do + traverse_in(key, value) -- Traverse into next layer. + end + traverse_out() -- Travel back up when a sub table has been completed. + end + -- else + -- err( + -- ("doom-nvim: Error traversing `doom.modules`, unexpected value `%s`."):format( + -- vim.inspect(node) + -- ) + -- ) -- Traverse back a layer but do not pass this value to the handler function. + -- end + end +}) + +return M