aboutsummaryrefslogtreecommitdiff
path: root/common/nvim/lua/setup
diff options
context:
space:
mode:
Diffstat (limited to 'common/nvim/lua/setup')
-rwxr-xr-xcommon/nvim/lua/setup/compat.lua104
-rwxr-xr-xcommon/nvim/lua/setup/manager.lua811
-rwxr-xr-xcommon/nvim/lua/setup/plugins.lua609
3 files changed, 1524 insertions, 0 deletions
diff --git a/common/nvim/lua/setup/compat.lua b/common/nvim/lua/setup/compat.lua
new file mode 100755
index 0000000..ef90444
--- /dev/null
+++ b/common/nvim/lua/setup/compat.lua
@@ -0,0 +1,104 @@
+-- setup/compat.lua
+-- Automatically patches deprecated APIs based on Neovim version
+
+-- Version check helper
+local function has_version(major, minor, patch)
+ local v = vim.version()
+ patch = patch or 0
+ return v.major > major
+ or (v.major == major and v.minor > minor)
+ or (v.major == major and v.minor == minor and v.patch >= patch)
+end
+
+-- === GLOBAL PATCHES === --
+
+-- Neovim 0.10+: vim.islist replaces deprecated vim.tbl_islist
+if has_version(0, 10) then
+ if vim.tbl_islist == nil then
+ vim.tbl_islist = vim.islist
+ end
+end
+
+-- Neovim 0.12+: vim.tbl_flatten removed → shim using vim.iter
+if has_version(0, 12) then
+ vim.tbl_flatten = function(t)
+ return vim.iter(t):flatten():totable()
+ end
+end
+
+-- === DEPRECATION SHIMS (0.13 / 1.0) === --
+
+-- client.is_stopped → client:is_stopped()
+if has_version(0, 13) then
+ local mt = getmetatable(vim.lsp._client or {})
+ if mt and mt.__index and mt.__index.is_stopped then
+ mt.__index.is_stopped = function(client, ...)
+ return client:is_stopped(...)
+ end
+ end
+end
+
+-- client.request → client:request()
+if has_version(0, 13) then
+ local mt = getmetatable(vim.lsp._client or {})
+ if mt and mt.__index and mt.__index.request then
+ mt.__index.request = function(client, ...)
+ return client:request(...)
+ end
+ end
+end
+
+-- vim.validate{tbl} → vim.validate(tbl)
+if has_version(1, 0) then
+ if type(vim.validate) == "function" then
+ local old_validate = vim.validate
+ vim.validate = function(arg)
+ -- Handle both forms for backward compatibility
+ if type(arg) == "table" then
+ return old_validate(arg)
+ else
+ return old_validate{ arg }
+ end
+ end
+ end
+end
+
+-- Deprecated: vim.lsp.get_active_clients (moved in 0.11+)
+if has_version(0, 11) then
+ if vim.lsp.get_active_clients == nil then
+ vim.lsp.get_active_clients = function(...)
+ return vim.lsp.get_clients(...)
+ end
+ end
+end
+
+-- Deprecated: vim.diagnostic.setqflist / setloclist (moved in 0.11+)
+if has_version(0, 11) then
+ if vim.diagnostic.setqflist == nil then
+ vim.diagnostic.setqflist = function(diags, opts)
+ return vim.diagnostic.toqflist(diags, opts)
+ end
+ end
+ if vim.diagnostic.setloclist == nil then
+ vim.diagnostic.setloclist = function(diags, opts)
+ return vim.diagnostic.toloclist(diags, opts)
+ end
+ end
+end
+
+-- Deprecated: vim.lsp.buf.formatting/formatting_sync (removed in 0.8+)
+if has_version(0, 8) then
+ if vim.lsp.buf.formatting == nil then
+ vim.lsp.buf.formatting = function(opts)
+ return vim.lsp.buf.format(opts)
+ end
+ end
+ if vim.lsp.buf.formatting_sync == nil then
+ vim.lsp.buf.formatting_sync = function(opts, timeout_ms)
+ return vim.lsp.buf.format(vim.tbl_extend("force", opts or {}, { timeout_ms = timeout_ms }))
+ end
+ end
+end
+
+-- Return something to satisfy require()
+return true
diff --git a/common/nvim/lua/setup/manager.lua b/common/nvim/lua/setup/manager.lua
new file mode 100755
index 0000000..9cf1d14
--- /dev/null
+++ b/common/nvim/lua/setup/manager.lua
@@ -0,0 +1,811 @@
+-- manager.lua
+
+local M = {}
+
+-- State tracking
+local state = {
+ manager_invoked = nil,
+ initialized = false,
+ bootstrap_completed = {},
+}
+
+-- Path constants
+local PATHS = {
+ lazy = vim.fn.stdpath("data") .. "/lazy/lazy.nvim",
+ packer = vim.fn.stdpath("data") .. "/site/pack/packer/start/packer.nvim",
+ packer_dir = vim.fn.stdpath("data") .. "/site/pack/packer/start",
+ builtin_dir = vim.fn.stdpath("data") .. "/nvim/site/pack/core/opt",
+}
+
+-- Utility functions
+local function safe_require(module)
+ local ok, result = pcall(require, module)
+ return ok and result or nil
+end
+
+local function notify(msg, level)
+ vim.notify("[Manager] " .. msg, level or vim.log.levels.INFO)
+end
+
+local function execute_git_command(cmd, _)
+ -- Use vim.fn.system instead of os.execute for better cross-platform support and error handling
+ local result = vim.fn.system(cmd)
+ return vim.v.shell_error == 0
+end
+
+local function get_nvim_version()
+ local version = vim.version()
+ if version then
+ return version.major, version.minor, version.patch
+ end
+
+ -- Fallback for older versions
+ local version_str = vim.fn.execute("version"):match("NVIM v(%d+%.%d+%.%d+)")
+ if version_str then
+ local major, minor, patch = version_str:match("(%d+)%.(%d+)%.(%d+)")
+ return tonumber(major), tonumber(minor), tonumber(patch)
+ end
+ return 0, 0, 0
+end
+
+local function has_builtin_manager()
+ local major, minor = get_nvim_version()
+ return major > 0 or (major == 0 and minor >= 12)
+end
+
+-- CRITICAL FIX: This function is essential to prevent runtime conflicts.
+-- It removes the specified manager's directory from the runtimepath.
+local function cleanup_manager(manager_name)
+ if manager_name == "packer" then
+ -- Reset packer state and remove from rtp
+ local packer = safe_require("packer")
+ if packer then
+ pcall(packer.reset)
+ end
+ -- Remove the entire packer directory from rtp
+ local packer_rtp = vim.fn.glob(PATHS.packer_dir)
+ if packer_rtp then
+ local rtp_items = vim.split(vim.o.rtp, ",")
+ local new_rtp_items = {}
+ for _, item in ipairs(rtp_items) do
+ if item ~= packer_rtp then
+ table.insert(new_rtp_items, item)
+ end
+ end
+ vim.o.rtp = table.concat(new_rtp_items, ",")
+ end
+ elseif manager_name == "lazy" then
+ -- Lazy.nvim clears its state on each run, but we can remove it from rtp for good measure
+ local lazy_rtp = vim.fn.glob(PATHS.lazy)
+ if lazy_rtp then
+ local rtp_items = vim.split(vim.o.rtp, ",")
+ local new_rtp_items = {}
+ for _, item in ipairs(rtp_items) do
+ if item ~= lazy_rtp then
+ table.insert(new_rtp_items, item)
+ end
+ end
+ vim.o.rtp = table.concat(new_rtp_items, ",")
+ end
+ elseif manager_name == "builtin" then
+ -- Built-in manager is handled by vim.opt.packpath and doesn't need manual cleanup from rtp
+ -- unless we want to disable its packages, which isn't the goal here.
+ end
+end
+
+-- IMPROVED: Use vim.g for persistence instead of file system
+local function save_manager_choice(manager_name)
+ vim.g.nvim_manager_choice = manager_name
+ -- Also save to data directory as a simple text file for true persistence across sessions
+ local data_dir = vim.fn.stdpath("data")
+ local choice_file = data_dir .. "/.manager_choice"
+ local file = io.open(choice_file, "w")
+ if file then
+ file:write(manager_name)
+ file:close()
+ end
+end
+
+local function load_manager_choice()
+ -- First check vim.g (current session)
+ if vim.g.nvim_manager_choice then
+ return vim.g.nvim_manager_choice
+ end
+
+ -- Then check persistent file
+ local data_dir = vim.fn.stdpath("data")
+ local choice_file = data_dir .. "/.manager_choice"
+ local file = io.open(choice_file, "r")
+ if file then
+ local choice = file:read("*a"):gsub("%s+", "") -- trim whitespace
+ file:close()
+ if choice and choice ~= "" then
+ vim.g.nvim_manager_choice = choice -- cache in session
+ return choice
+ end
+ end
+
+ return nil
+end
+
+--- Packer Manager Implementation
+--
+-- Handles cloning, setup, and configuration of Packer.nvim.
+local Packer = {}
+
+function Packer.bootstrap()
+ if state.bootstrap_completed.packer then
+ return true
+ end
+
+ local fn = vim.fn
+ if fn.isdirectory(PATHS.packer_dir) == 0 then
+ fn.mkdir(PATHS.packer_dir, "p")
+ end
+
+ if fn.empty(fn.glob(PATHS.packer)) > 0 then
+ local is_windows = vim.loop.os_uname().version:match("Windows")
+ local git_cmd
+
+ if is_windows then
+ git_cmd = string.format(
+ 'git clone --depth=1 https://github.com/wbthomason/packer.nvim "%s" >nul 2>&1',
+ PATHS.packer
+ )
+ else
+ git_cmd = string.format(
+ 'env -i PATH="%s" HOME="%s" git clone --depth=1 --quiet https://github.com/wbthomason/packer.nvim %q >/dev/null 2>&1',
+ os.getenv("PATH") or "/usr/bin:/bin",
+ os.getenv("HOME") or "/tmp",
+ PATHS.packer
+ )
+ end
+
+ if not execute_git_command(git_cmd, "Failed to clone packer.nvim") then
+ return false
+ end
+ end
+
+ state.bootstrap_completed.packer = true
+ return true
+end
+
+function Packer.setup()
+ if not Packer.bootstrap() then
+ return false
+ end
+
+ -- Ensure packer.nvim is in the runtime path
+ vim.cmd("packadd packer.nvim")
+
+ local packer = safe_require("packer")
+ if not packer then
+ notify("Failed to load packer.nvim", vim.log.levels.ERROR)
+ return false
+ end
+
+ -- Reset any existing configuration from a previous run
+ pcall(packer.reset)
+
+ packer.init({
+ auto_reload_compiled = true,
+ display = {
+ open_fn = function()
+ return require("packer.util").float({ border = "rounded" })
+ end,
+ },
+ luarocks = {
+ python_cmd = 'python3'
+ },
+ })
+
+ local plugins = safe_require("setup.plugins")
+ if not plugins then
+ notify("Failed to load plugins configuration", vim.log.levels.ERROR)
+ return false
+ end
+
+ packer.startup(function(use)
+ use "wbthomason/packer.nvim"
+ for _, plugin in ipairs(plugins) do
+ -- CHECK FOR EXCLUDE HERE - Packer support for exclude option
+ if plugin.exclude and vim.tbl_contains(plugin.exclude, "packer") then
+ --notify("Excluding plugin for packer: " .. (plugin.name or plugin.as or plugin[1] or "unknown"), vim.log.levels.INFO)
+ goto continue
+ end
+
+ -- Packer doesn't have a lazy option, so we ensure all plugins are loaded eagerly
+ -- by clearing any lazy-loading keys from the plugins table.
+ local packer_plugin = vim.deepcopy(plugin)
+ packer_plugin.event = nil
+ packer_plugin.keys = nil
+ packer_plugin.cmd = nil
+ packer_plugin.ft = nil
+ packer_plugin.lazy = nil
+ packer_plugin.exclude = nil -- Remove exclude from the actual plugin spec
+ use(packer_plugin)
+ ::continue::
+ end
+ end)
+
+ return true
+end
+
+function Packer.is_available()
+ return vim.fn.isdirectory(PATHS.packer) == 1
+end
+
+--- Lazy.nvim Manager Implementation
+--
+local Lazy = {}
+
+function Lazy.bootstrap()
+ if state.bootstrap_completed.lazy then
+ return true
+ end
+
+ -- Check if lazy.nvim is already cloned
+ if not vim.loop.fs_stat(PATHS.lazy) then
+ local is_windows = vim.loop.os_uname().version:match("Windows")
+ local git_cmd
+
+ if is_windows then
+ git_cmd = string.format(
+ 'git clone --filter=blob:none --branch=stable https://github.com/folke/lazy.nvim.git "%s" >nul 2>&1',
+ PATHS.lazy
+ )
+ else
+ git_cmd = string.format(
+ 'env -i PATH="%s" HOME="%s" git clone --filter=blob:none --branch=stable --quiet https://github.com/folke/lazy.nvim.git %q >/dev/null 2>&1',
+ os.getenv("PATH") or "/usr/bin:/bin",
+ os.getenv("HOME") or "/tmp",
+ PATHS.lazy
+ )
+ end
+
+ if not execute_git_command(git_cmd, "Failed to clone lazy.nvim") then
+ return false
+ end
+ end
+
+ state.bootstrap_completed.lazy = true
+ return true
+end
+
+function Lazy.setup()
+ if not Lazy.bootstrap() then
+ return false
+ end
+
+ -- Ensure lazy.nvim is in the runtime path before requiring it
+ vim.opt.rtp:prepend(PATHS.lazy)
+
+ local lazy = safe_require("lazy")
+ if not lazy then
+ notify("Failed to load lazy.nvim", vim.log.levels.ERROR)
+ return false
+ end
+
+ -- FIX: Correctly require plugins and set up lazy.nvim
+ local plugins = safe_require("setup.plugins")
+ if not plugins then
+ notify("Failed to load plugins configuration", vim.log.levels.ERROR)
+ return false
+ end
+
+ -- Filter out excluded plugins for Lazy
+ local filtered_plugins = {}
+ for _, plugin in ipairs(plugins) do
+ -- CHECK FOR EXCLUDE HERE - Lazy support for exclude option
+ if plugin.exclude and vim.tbl_contains(plugin.exclude, "lazy") then
+ --notify("Excluding plugin for lazy: " .. (plugin.name or plugin[1] or "unknown"), vim.log.levels.INFO)
+ else
+ local lazy_plugin = vim.deepcopy(plugin)
+ lazy_plugin.exclude = nil -- Remove exclude from the actual plugin spec
+ table.insert(filtered_plugins, lazy_plugin)
+ end
+ end
+
+ -- Setup Lazy.nvim with the correct options
+ lazy.setup(filtered_plugins, {
+ {
+ import = "plugins",
+ },
+ defaults = { lazy = false }, -- Set plugins to be lazy-loaded by default
+ install = { missing = true }, -- CRITICAL FIX: This ensures missing plugins are installed
+ ui = {
+ border = "rounded",
+ },
+ performance = {
+ rtp = {
+ disabled_plugins = {
+ "gzip", "matchit", "matchparen", "netrwPlugin",
+ "tarPlugin", "tohtml", "tutor", "zipPlugin",
+ },
+ },
+ },
+ })
+
+ return true
+end
+
+function Lazy.is_available()
+ return vim.loop.fs_stat(PATHS.lazy) ~= nil
+end
+
+--- Built-in manager implementation (Neovim 0.12+)
+--
+local Builtin = {}
+
+function Builtin.bootstrap()
+ if not has_builtin_manager() then
+ --notify("Built-in package manager not available in this Neovim version", vim.log.levels.WARN)
+ return false
+ end
+
+ state.bootstrap_completed.builtin = true
+ return true
+end
+
+function Builtin.setup()
+ if not has_builtin_manager() then
+ --notify("Built-in package manager not available in this Neovim version", vim.log.levels.WARN)
+ return false
+ end
+
+ local plugins = safe_require("setup.plugins")
+ if not plugins then
+ notify("Failed to load plugins configuration", vim.log.levels.ERROR)
+ return false
+ end
+
+ -- Convert plugins to builtin manager format
+ local builtin_specs = {}
+ for _, plugin in ipairs(plugins) do
+ -- CHECK FOR EXCLUDE HERE
+ if plugin.exclude and vim.tbl_contains(plugin.exclude, "builtin") then
+ --notify("Excluding plugin for builtin: " .. (plugin.name or plugin[1] or "unknown"), vim.log.levels.INFO)
+ goto continue
+ end
+ local spec = {}
+
+ if type(plugin) == "string" then
+ -- Handle string format like "user/repo"
+ if plugin:match("^[%w%-_%.]+/[%w%-_%.]+$") then
+ -- It's a GitHub shorthand
+ spec.src = "https://github.com/" .. plugin
+ spec.name = plugin:match("/([%w%-_%.]+)$") -- Extract repo name
+ else
+ -- It's already a full URL
+ spec.src = plugin
+ end
+ elseif type(plugin) == "table" then
+ -- Handle table format
+ if plugin[1] and type(plugin[1]) == "string" then
+ -- Format like {"user/repo", ...}
+ if plugin[1]:match("^[%w%-_%.]+/[%w%-_%.]+$") then
+ spec.src = "https://github.com/" .. plugin[1]
+ spec.name = plugin[1]:match("/([%w%-_%.]+)$")
+ else
+ spec.src = plugin[1]
+ end
+
+ -- Copy other properties
+ for k, v in pairs(plugin) do
+ if type(k) == "string" then
+ spec[k] = v
+ end
+ end
+ elseif plugin.src then
+ spec.src = plugin.src
+ for k, v in pairs(plugin) do
+ if k ~= "src" then
+ spec[k] = v
+ end
+ end
+ elseif plugin.url then
+ spec.src = plugin.url
+ for k, v in pairs(plugin) do
+ if k ~= "url" then
+ spec[k] = v
+ end
+ end
+ else
+ notify("Invalid plugin specification for built-in manager: " .. vim.inspect(plugin), vim.log.levels.WARN)
+ goto continue
+ end
+
+ -- Handle name override
+ if plugin.name then
+ spec.name = plugin.name
+ elseif plugin.as then
+ spec.name = plugin.as
+ elseif not spec.name and spec.src then
+ -- Extract name from URL if not specified
+ spec.name = spec.src:match("/([%w%-_%.]+)%.git$") or spec.src:match("/([%w%-_%.]+)$") or spec.src
+ end
+
+ -- Handle version
+ if plugin.version then
+ spec.version = plugin.version
+ end
+
+ -- Remove keys that builtin manager doesn't understand
+ spec.lazy = nil
+ spec.event = nil
+ spec.keys = nil
+ spec.cmd = nil
+ spec.ft = nil
+ spec.dependencies = nil
+ spec.config = nil
+ spec.build = nil
+ spec.run = nil
+ spec.priority = nil
+ spec.as = nil
+ spec.url = nil
+ spec.exclude = nil
+ spec[1] = nil -- Remove positional argument
+ end
+
+ if spec.src then
+ table.insert(builtin_specs, spec)
+ end
+ ::continue::
+ end
+
+ -- Debug: Show what we're about to install
+ --notify(string.format("Installing %d plugins with built-in manager", #builtin_specs), vim.log.levels.INFO)
+
+ -- CRITICAL FIX: Call vim.pack.add with the specs directly, not wrapped in array
+ if #builtin_specs > 0 then
+ local ok, err = pcall(vim.pack.add, builtin_specs)
+ if not ok then
+ notify("Failed to add plugins: " .. tostring(err), vim.log.levels.ERROR)
+ return false
+ end
+
+ --notify("Plugins added successfully. Use :Pack to install/update them.", vim.log.levels.INFO)
+ else
+ notify("No valid plugins found for built-in manager", vim.log.levels.WARN)
+ end
+
+ -- Create user commands for convenience - FIXED COMMAND NAMES
+ vim.api.nvim_create_user_command("Package", function(opts)
+ local subcommand = opts.fargs[1] or "update"
+ local names = vim.list_slice(opts.fargs, 2)
+
+ if subcommand == "add" then
+ -- For add, we need to re-run setup to add new plugins
+ --notify("Re-running builtin manager setup to add new plugins...")
+ Builtin.setup()
+ elseif subcommand == "update" then
+ if #names == 0 then
+ names = nil -- Update all plugins
+ end
+ vim.pack.update(names)
+ elseif subcommand == "status" then
+ local plugins = vim.pack.get()
+ print(string.format("Built-in manager: %d plugins managed", #plugins))
+ for _, plugin in ipairs(plugins) do
+ local status = plugin.active and "active" or "inactive"
+ print(string.format(" %s (%s): %s", plugin.spec.name, status, plugin.path))
+ end
+ else
+ -- Default behavior - treat as update
+ if subcommand then
+ table.insert(names, 1, subcommand)
+ end
+ if #names == 0 then
+ names = nil
+ end
+ vim.pack.update(names)
+ end
+ end, {
+ nargs = "*",
+ complete = function(arglead, cmdline, cursorpos)
+ local args = vim.split(cmdline, "%s+")
+ if #args <= 2 then
+ -- Complete subcommands
+ local subcommands = { "add", "update", "status" }
+ local matches = {}
+ for _, cmd in ipairs(subcommands) do
+ if cmd:find("^" .. arglead) then
+ table.insert(matches, cmd)
+ end
+ end
+ return matches
+ else
+ -- Complete plugin names
+ local plugins = vim.pack.get()
+ local names = {}
+ for _, plugin in ipairs(plugins) do
+ if plugin.spec.name:find("^" .. arglead) then
+ table.insert(names, plugin.spec.name)
+ end
+ end
+ return names
+ end
+ end,
+ desc = "Manage plugins with built-in manager. Usage: :Pack [add|update|status] [plugin_names...]"
+ })
+
+ ---- Keep the old command for backwards compatibility
+ --vim.api.nvim_create_user_command("PackageStatus", function()
+ -- vim.cmd("Pack status")
+ --end, {
+ -- nargs = 0,
+ -- desc = "Show status of plugins managed by built-in manager (deprecated, use :Pack status)"
+ --})
+
+ return true
+end
+
+function Builtin.is_available()
+ return has_builtin_manager()
+end
+
+--- Manager registry
+--
+local MANAGERS = {
+ packer = Packer,
+ lazy = Lazy,
+ builtin = Builtin,
+}
+
+--- Core management functions
+--
+local function activate_manager(manager_name)
+ local manager = MANAGERS[manager_name]
+ if not manager then
+ notify("Unknown manager: " .. manager_name, vim.log.levels.ERROR)
+ return false
+ end
+
+ -- Cleanup the old manager before activating the new one to prevent runtime conflicts.
+ if state.manager_invoked and state.manager_invoked ~= manager_name then
+ cleanup_manager(state.manager_invoked)
+ end
+
+ if not manager.bootstrap() then
+ return false
+ end
+
+ local ok = manager.setup()
+ if ok then
+ state.manager_invoked = manager_name
+ -- CRITICAL FIX: Persist the manager choice after successful setup
+ save_manager_choice(manager_name)
+ end
+ return ok
+end
+
+--- Auto-detection and command setup
+--
+local function setup_auto_detection()
+ -- Autocmd to activate Packer when Packer commands are used
+ vim.api.nvim_create_autocmd("CmdUndefined", {
+ pattern = "Packer*",
+ callback = function(event)
+ if state.manager_invoked ~= "packer" then
+ local ok = activate_manager("packer")
+ if ok then
+ -- Re-execute the original command after setup
+ vim.cmd(event.match)
+ end
+ end
+ end,
+ desc = "Auto-activate Packer when Packer commands are used"
+ })
+
+ -- Autocmd to activate Lazy when Lazy commands are used
+ vim.api.nvim_create_autocmd("CmdUndefined", {
+ pattern = "Lazy*",
+ callback = function(event)
+ if state.manager_invoked ~= "lazy" then
+ local ok = activate_manager("lazy")
+ if ok then
+ -- CRITICAL FIX: Use vim.schedule to defer the command execution
+ -- This ensures Lazy's setup is complete before running the command.
+ vim.schedule(function()
+ pcall(vim.cmd, event.match)
+ end)
+ end
+ end
+ end,
+ desc = "Auto-activate Lazy and re-execute command"
+ })
+
+ vim.api.nvim_create_autocmd("CmdUndefined", {
+ pattern = "Package*",
+ callback = function(event)
+ if state.manager_invoked ~= "builtin" and has_builtin_manager() then
+ local ok = activate_manager("builtin")
+ if ok then
+ vim.cmd(event.match)
+ end
+ end
+ end,
+ desc = "Auto-activate built-in manager when Pack commands are used"
+ })
+end
+
+--- Public API
+--
+function M.setup()
+ if state.initialized then
+ return
+ end
+
+ -- Initial bootstrap attempt for all managers to see what's available
+ for name, manager in pairs(MANAGERS) do
+ -- CRITICAL FIX: Always bootstrap, but don't set up yet
+ pcall(manager.bootstrap)
+ end
+
+ -- CRITICAL FIX: Check for a previously saved choice
+ local persistent_choice = load_manager_choice()
+ if persistent_choice and MANAGERS[persistent_choice] then
+ -- If a choice exists, immediately activate that manager for this session
+ activate_manager(persistent_choice)
+ else
+ -- If no choice exists, set up the autocmds to wait for a command
+ setup_auto_detection()
+ end
+
+ state.initialized = true
+end
+
+function M.use_manager(manager_name)
+ if not state.initialized then
+ M.setup()
+ end
+
+ local available = M.available_managers()
+ if not vim.tbl_contains(available, manager_name) then
+ notify(string.format("Manager '%s' is not available. Available: %s",
+ manager_name, table.concat(available, ", ")), vim.log.levels.WARN)
+ return false
+ end
+
+ return activate_manager(manager_name)
+end
+
+function M.available_managers()
+ local managers = {}
+ for name, manager in pairs(MANAGERS) do
+ if manager.is_available() then
+ table.insert(managers, name)
+ end
+ end
+ return managers
+end
+
+function M.current_manager()
+ return state.manager_invoked
+end
+
+function M.status()
+ local info = {
+ initialized = state.initialized,
+ current_manager = state.manager_invoked,
+ available_managers = M.available_managers(),
+ bootstrap_completed = state.bootstrap_completed,
+ }
+
+ print("=== Neovim Plugin Manager Status ===")
+ print(string.format("Initialized: %s", tostring(info.initialized)))
+ print(string.format("Current Manager: %s", info.current_manager or "None"))
+ print(string.format("Available Managers: %s", table.concat(info.available_managers, ", ")))
+
+ -- FIX: Properly format the Neovim version
+ local major, minor, patch = get_nvim_version()
+ print(string.format("Neovim Version: %d.%d.%d", major, minor, patch))
+ print(string.format("Built-in Support: %s", tostring(has_builtin_manager())))
+
+ return info
+end
+
+-- FIX: Added M.get_nvim_version function to the public API
+function M.get_nvim_version()
+ local major, minor, patch = get_nvim_version()
+ return { major = major, minor = minor, patch = patch }
+end
+
+function M.reset_nvim()
+ vim.ui.input({
+ prompt = "Are you sure you want to reset Neovim? This will delete all data, state, cache, and plugins. (y/N): "
+ }, function(input)
+ if input and input:lower() == "y" then
+ local fn = vim.fn
+ local is_windows = vim.loop.os_uname().version:match("Windows")
+
+ local paths_to_remove = {
+ fn.stdpath("data"),
+ fn.stdpath("state"),
+ fn.stdpath("cache"),
+ fn.stdpath("config") .. "/plugin",
+ }
+
+ local cmd = ""
+ if is_windows then
+ local paths_quoted = {}
+ for _, path in ipairs(paths_to_remove) do
+ table.insert(paths_quoted, string.format('"%s"', path))
+ end
+ cmd = "powershell -Command \"Remove-Item " ..
+ table.concat(paths_quoted, ", ") .. " -Recurse -Force -ErrorAction SilentlyContinue\""
+ else
+ local paths_quoted = {}
+ for _, path in ipairs(paths_to_remove) do
+ table.insert(paths_quoted, vim.fn.shellescape(path))
+ end
+ cmd = "rm -rf " .. table.concat(paths_quoted, " ")
+ end
+
+ notify("Resetting Neovim... Please restart after this operation.")
+
+ vim.defer_fn(function()
+ local result = os.execute(cmd)
+ if result ~= 0 then
+ notify("Reset command may have failed. You might need to delete directories manually.", vim.log.levels.WARN)
+ else
+ notify("Reset completed successfully. Please restart Neovim.")
+ end
+ end, 100)
+ else
+ notify("Reset cancelled.")
+ end
+ end)
+end
+
+-- Clear manager choice function
+function M.clear_choice()
+ vim.g.nvim_manager_choice = nil
+ local data_dir = vim.fn.stdpath("data")
+ local choice_file = data_dir .. "/.manager_choice"
+ os.remove(choice_file)
+ notify("Manager choice cleared. Next command will determine the manager.")
+end
+
+vim.api.nvim_create_user_command("Reset", function()
+ M.reset_nvim()
+end, {
+ nargs = 0,
+ desc = "Reset Neovim's data, state, cache, and plugin directories"
+})
+
+local function manager_command(opts)
+ local subcommand = opts.fargs[1]
+
+ if subcommand == "status" then
+ M.status()
+ elseif subcommand == "packer" or subcommand == "Packer" then
+ M.use_manager("packer")
+ elseif subcommand == "lazy" or subcommand == "Lazy" then
+ M.use_manager("lazy")
+ elseif subcommand == "builtin" or subcommand == "built-in" or subcommand == "Builtin" or subcommand == "Built-in" then
+ M.use_manager("builtin")
+ elseif subcommand == "clear" then
+ M.clear_choice()
+ else
+ print("Unknown subcommand. Try 'status', 'packer', 'lazy', 'builtin' or 'clear'.")
+ end
+end
+
+vim.api.nvim_create_user_command("Manager", manager_command, {
+ nargs = "+",
+ complete = function(arglead)
+ local subcommands = { "status", "packer", "Packer", "lazy", "Lazy", "builtin", "built-in", "Builtin", "Built-in",
+ "clear" }
+ local result = {}
+ for _, subcommand in ipairs(subcommands) do
+ if subcommand:find("^" .. arglead, 1) then
+ table.insert(result, subcommand)
+ end
+ end
+ return result
+ end,
+ desc = "Manage plugins. Subcommands: status, packer, lazy, builtin, clear"
+})
+
+return M
diff --git a/common/nvim/lua/setup/plugins.lua b/common/nvim/lua/setup/plugins.lua
new file mode 100755
index 0000000..0fb0886
--- /dev/null
+++ b/common/nvim/lua/setup/plugins.lua
@@ -0,0 +1,609 @@
+-- plugins.lua
+
+-- Helper to compare current Neovim version
+local function version_at_least(minor, major)
+ local v = vim.version()
+ major = major or 0
+ return v.major > major or (v.major == major and v.minor >= minor)
+end
+
+local function version_below(minor, major)
+ local v = vim.version()
+ major = major or 0
+ return v.major < major or (v.major == major and v.minor < minor)
+end
+
+-- Normalize version input: number -> {0, number}, table -> itself
+local function parse_version(ver)
+ if type(ver) == "number" then return 0, ver end
+ if type(ver) == "table" then return ver[1] or 0, ver[2] or 0 end
+ return 0, 0
+end
+
+-- Determine if plugin should be loaded based on version
+local function should_load_plugin(min_version, max_version)
+ local min_major, min_minor = parse_version(min_version)
+ local max_major, max_minor = parse_version(max_version)
+
+ local ok_min = not min_version or version_at_least(min_minor, min_major)
+ local ok_max = not max_version or version_below(max_minor, max_major)
+
+ return ok_min and ok_max
+end
+
+-- Helper to check if a table contains a specific value
+local function contains(table, val)
+ for _, v in ipairs(table) do
+ if v == val then
+ return true
+ end
+ end
+ return false
+end
+
+-- The master list of plugins with all potential options.
+-- Keys like 'lazy', 'event', 'keys', 'dependencies' are for Lazy.nvim.
+-- Keys like 'config', 'run', 'build' are for all managers.
+local universal_plugins = {
+ -- Core
+ { "nvim-lua/plenary.nvim", lazy = true },
+ { "lewis6991/impatient.nvim" },
+
+ {
+ "nvim-treesitter/nvim-treesitter",
+ min_version = 9,
+ event = "BufReadPre",
+ },
+ { "nvim-treesitter/nvim-treesitter-textobjects", dependencies = { "nvim-treesitter/nvim-treesitter" } },
+ { "nvim-treesitter/playground", cmd = "TSPlaygroundToggle" },
+
+ -- LSP
+ { "nvimtools/none-ls.nvim", event = "BufReadPre" },
+ { "neovim/nvim-lspconfig", min_version = { 0, 9 }, event = "BufReadPre" },
+ {
+ "mason-org/mason.nvim",
+ min_version = 10,
+ cmd = "Mason",
+ event = "BufReadPre",
+ },
+ { "mason-org/mason-lspconfig.nvim", dependencies = { "mason-org/mason.nvim" } },
+ {
+ "whoissethdaniel/mason-tool-installer.nvim",
+ dependencies = { "mason-org/mason.nvim" },
+ event = "BufReadPre",
+ },
+ {
+ "https://git.sr.ht/~whynothugo/lsp_lines.nvim",
+ name = 'lsp_lines.nvim',
+ config = function()
+ require("lsp_lines").setup()
+ vim.diagnostic.config({
+ virtual_text = false,
+ })
+ end,
+ event = "LspAttach",
+ },
+ { "rmagatti/goto-preview", event = "LspAttach" },
+
+ -- Linters/Formatters
+ { "mhartington/formatter.nvim", event = "BufReadPre" },
+ {
+ "jay-babu/mason-null-ls.nvim",
+ event = "BufReadPre",
+ },
+
+ -- Completion
+ { "hrsh7th/nvim-cmp", event = "InsertEnter", exclude = { "builtin" } },
+ { "hrsh7th/cmp-nvim-lsp", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "hrsh7th/cmp-buffer", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "hrsh7th/cmp-path", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "hrsh7th/cmp-cmdline", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "petertriho/cmp-git", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "tamago324/cmp-zsh", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "f3fora/cmp-spell", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "hrsh7th/cmp-calc", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "saadparwaiz1/cmp_luasnip", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "hrsh7th/cmp-nvim-lsp-signature-help", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "rcarriga/cmp-dap", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "micangl/cmp-vimtex", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+
+ -- Snippets
+ { "L3MON4D3/LuaSnip", event = "InsertEnter" },
+ { "rafamadriz/friendly-snippets", dependencies = { "L3MON4D3/LuaSnip" } },
+
+ -- Git
+ { "tpope/vim-fugitive", cmd = { "G", "Git", "Gdiffsplit" }, event = "VeryLazy" },
+ { "kdheepak/lazygit.nvim", cmd = "LazyGit", keys = "<leader>gg" },
+ { "lewis6991/gitsigns.nvim", min_version = 11, dependencies = { "nvim-lua/plenary.nvim" }, event = "BufReadPre" },
+
+ -- UI/UX & Enhancements
+ { "rcarriga/nvim-notify", lazy = false },
+ {
+ "nvim-tree/nvim-tree.lua",
+ --cmd = { "NvimTreeToggle", "NvimTreeFocus", "NvimTreeFindFile" },
+ --keys = { "<C-n>", "<leader>e" },
+ lazy = false,
+ dependencies = { "nvim-tree/nvim-web-devicons"},
+ config = function()
+ require("plugins.nvim-tree").setup()
+ end,
+ },
+ { "ThePrimeagen/harpoon", keys = { "<leader>h" } },
+ { "airblade/vim-rooter", event = "BufEnter" },
+ { "ibhagwan/fzf-lua", cmd = "FzfLua" },
+
+ --- **Telescope** ---
+ {
+ "nvim-telescope/telescope.nvim",
+ dependencies = { "nvim-lua/plenary.nvim" },
+ config = function()
+ require("plugins.telescope").setup()
+ end,
+ },
+ {
+ "nvim-telescope/telescope-fzf-native.nvim",
+ build = "make",
+ cond = function()
+ return vim.fn.executable("make") == 1
+ end,
+ dependencies = { "nvim-telescope/telescope.nvim" },
+ },
+ { "nvim-telescope/telescope-live-grep-args.nvim", dependencies = { "nvim-telescope/telescope.nvim" } },
+ { "nvim-telescope/telescope-ui-select.nvim", dependencies = { "nvim-telescope/telescope.nvim" } },
+ { "nvim-telescope/telescope-project.nvim", dependencies = { "nvim-telescope/telescope.nvim" } },
+ { "nvim-telescope/telescope-media-files.nvim", dependencies = { "nvim-telescope/telescope.nvim" } },
+ { "nvim-telescope/telescope-file-browser.nvim", dependencies = { "nvim-telescope/telescope.nvim" } },
+ { "nvim-telescope/telescope-symbols.nvim", dependencies = { "nvim-telescope/telescope.nvim" } },
+ { "nvim-telescope/telescope-dap.nvim", dependencies = { "nvim-telescope/telescope.nvim" } },
+ { "axkirillov/telescope-changed-files", dependencies = { "nvim-telescope/telescope.nvim" } },
+ { "smartpde/telescope-recent-files", dependencies = { "nvim-telescope/telescope.nvim" } },
+ --- End Telescope ---
+
+ -- Neovim UX
+ { "folke/neodev.nvim", ft = "lua" },
+ {
+ "numToStr/Navigator.nvim",
+ lazy = false,
+ config = function()
+ require("Navigator").setup()
+ end,
+ },
+ { "tpope/vim-eunuch", cmd = { "Rename", "Delete", "Mkdir" } },
+ { "tpope/vim-unimpaired", lazy = true, event = "VeryLazy" },
+ { "kylechui/nvim-surround", event = "VeryLazy" },
+ {
+ "mbbill/undotree",
+ cmd = "UndotreeToggle",
+ keys = "<leader>u",
+ event = "BufReadPre"
+ },
+ {
+ "myusuf3/numbers.vim",
+ event = "BufReadPost",
+ config = function()
+ vim.cmd("let g:numbers_exclude = ['dashboard']")
+ end,
+ },
+ { "windwp/nvim-autopairs", event = "InsertEnter" },
+ { "numToStr/Comment.nvim", keys = { "gc", "gb" }, event = "VeryLazy" },
+ { "akinsho/toggleterm.nvim", cmd = { "ToggleTerm", "TermExec" } },
+ { "tweekmonster/startuptime.vim", cmd = "StartupTime" },
+ { "qpkorr/vim-bufkill", cmd = { "BD", "BUN" } },
+ {
+ "ggandor/leap.nvim",
+ keys = { "s", "S" },
+ event = "VeryLazy",
+ config = function()
+ require("leap").add_default_mappings()
+ end,
+ },
+ {
+ "ggandor/flit.nvim",
+ keys = { "f", "F", "t", "T" },
+ event = "VeryLazy",
+ config = function()
+ require("flit").setup()
+ end,
+ },
+ {
+ "folke/which-key.nvim",
+ min_version = { 0, 10 },
+ event = "VeryLazy",
+ --keys = "<leader>",
+ config = function()
+ require("which-key").setup()
+ end,
+ },
+ { "folke/zen-mode.nvim", cmd = "ZenMode" },
+ { "romainl/vim-cool", event = "VeryLazy" },
+ { "antoinemadec/FixCursorHold.nvim", lazy = true },
+ { "folke/trouble.nvim", cmd = { "Trouble", "TroubleToggle" } },
+
+ -- Colorschemes & Visuals (load immediately)
+ { "nyoom-engineering/oxocarbon.nvim", lazy = false, priority = 1000 },
+ { "bluz71/vim-nightfly-guicolors", lazy = false, priority = 1000 },
+ { "ayu-theme/ayu-vim", lazy = false, priority = 1000 },
+ { "joshdick/onedark.vim", lazy = false, priority = 1000 },
+ { "NTBBloodbath/doom-one.nvim", lazy = false, priority = 1000 },
+ { "nyngwang/nvimgelion", lazy = false, priority = 1000 },
+ { "projekt0n/github-nvim-theme", lazy = false, priority = 1000 },
+ { "folke/tokyonight.nvim", lazy = false, priority = 1000 },
+ { "ribru17/bamboo.nvim", lazy = false, priority = 1000 },
+
+ -- UI Enhancements
+ --{ "kyazdani42/nvim-web-devicons", lazy = true },
+ { "nvim-tree/nvim-web-devicons", lazy = true },
+ { "onsails/lspkind-nvim", dependencies = { "hrsh7th/nvim-cmp" }, exclude = { "builtin" } },
+ { "kevinhwang91/nvim-ufo", dependencies = { "kevinhwang91/promise-async" }, event = "BufReadPre" },
+ {
+ "luukvbaal/statuscol.nvim",
+ event = "WinNew",
+ config = function()
+ local builtin = require("statuscol.builtin")
+ require("statuscol").setup({
+ relculright = true,
+ segments = {
+ { text = { builtin.foldfunc }, click = "v:lua.ScFa" },
+ { text = { "%s" }, click = "v:lua.ScSa" },
+ { text = { builtin.lnumfunc, " " }, click = "v:lua.ScLa" },
+ },
+ })
+ end,
+ },
+ { "lukas-reineke/indent-blankline.nvim", event = "BufReadPre" },
+ {
+ "glepnir/dashboard-nvim",
+ dependencies = { "nvim-tree/nvim-web-devicons" },
+ cmd = "Dashboard",
+ },
+ { "karb94/neoscroll.nvim", event = "BufReadPre" },
+ { "MunifTanjim/prettier.nvim", event = "BufWritePre" },
+ {
+ "norcalli/nvim-colorizer.lua",
+ cmd = { "ColorizerToggle", "ColorizerAttachToBuffer" },
+ config = function()
+ require("colorizer").setup({
+ user_default_options = {
+ RGB = true,
+ RRGGBB = true,
+ names = false,
+ RRGGBBAA = false,
+ css = false,
+ css_fn = true,
+ mode = "foreground",
+ },
+ })
+ end,
+ },
+ { "MunifTanjim/nui.nvim" },
+ { "metakirby5/codi.vim", cmd = "Codi" },
+ {
+ "kosayoda/nvim-lightbulb",
+ dependencies = { "antoinemadec/FixCursorHold.nvim" },
+ event = "LspAttach",
+ },
+ { "SmiteshP/nvim-navic", event = "LspAttach" },
+ {
+ "rebelot/heirline.nvim",
+ event = "VeryLazy",
+ dependencies = {
+ "nvim-tree/nvim-web-devicons", -- For file icons
+ "lewis6991/gitsigns.nvim", -- For git status
+ },
+ config = function()
+ -- Ensure gitsigns is loaded before Heirline
+ if package.loaded["gitsigns"] == nil then
+ require("gitsigns").setup()
+ end
+ local ok, heirline = pcall(require, "plugins.heirline")
+ if ok and heirline then
+ heirline.setup()
+ else
+ vim.notify("Failed to load Heirline configuration", vim.log.levels.ERROR)
+ end
+ end,
+ init = function()
+ -- Set up the statusline to use Heirline once it's loaded
+ vim.opt.statusline = "%{%v:lua.require'heirline'.eval_statusline()%}"
+ vim.opt.winbar = "%{%v:lua.require'heirline'.eval_winbar()%}"
+ vim.opt.tabline = "%{%v:lua.require'heirline'.eval_tabline()%}"
+ end,
+ },
+ {
+ "samodostal/image.nvim",
+ config = function()
+ require("image").setup({})
+ end,
+ ft = { "markdown" },
+ },
+
+ -- Language Specific
+ { "simrat39/rust-tools.nvim", ft = "rust" },
+ {
+ "saecki/crates.nvim",
+ dependencies = { "nvim-lua/plenary.nvim" },
+ config = function()
+ require("crates").setup()
+ end,
+ ft = "rust",
+ },
+ {
+ "akinsho/flutter-tools.nvim",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ "stevearc/dressing.nvim",
+ },
+ config = function()
+ require("flutter-tools").setup({
+ debugger = {
+ enabled = true,
+ run_via_dap = true,
+ },
+ })
+ end,
+ ft = "dart",
+ },
+ {
+ "iamcco/markdown-preview.nvim",
+ build = "cd app && npm install",
+ ft = "markdown",
+ config = function()
+ vim.g.mkdp_filetypes = { "markdown" }
+ vim.cmd("let g:mkdp_auto_close = 0")
+ end,
+ cmd = "MarkdownPreview",
+ },
+ {
+ "ellisonleao/glow.nvim",
+ config = function()
+ local glow_path = vim.fn.executable("~/.local/bin/glow") == 1 and "~/.local/bin/glow" or "/usr/bin/glow"
+ require("glow").setup({
+ style = "dark",
+ glow_path = glow_path,
+ })
+ end,
+ ft = "markdown",
+ },
+
+ -- Debugging
+ { "mfussenegger/nvim-dap", event = "VeryLazy" },
+ { "rcarriga/nvim-dap-ui", dependencies = { "mfussenegger/nvim-dap" }, cmd = "DapUI" },
+ { "theHamsta/nvim-dap-virtual-text", dependencies = { "mfussenegger/nvim-dap" } },
+ { "gabrielpoca/replacer.nvim", cmd = "Replacer" },
+ { "jayp0521/mason-nvim-dap.nvim", dependencies = { "mason-org/mason.nvim" } },
+
+ -- Misc
+ { "rmagatti/auto-session", event = "VimEnter" },
+ { "tpope/vim-sleuth", lazy = true },
+ { "michaelb/sniprun", build = "bash ./install.sh", cmd = "SnipRun" },
+ { "stevearc/overseer.nvim", cmd = "Overseer" },
+ {
+ "nvim-neotest/neotest",
+ dependencies = {
+ "nvim-neotest/neotest-python",
+ "nvim-neotest/neotest-plenary",
+ "nvim-neotest/neotest-vim-test",
+ "nvim-neotest/nvim-nio",
+ },
+ cmd = "Neotest",
+ },
+ { "kawre/leetcode.nvim", cmd = "Leetcode" },
+ { "m4xshen/hardtime.nvim", lazy = true },
+
+ -- LaTeX
+ { "lervag/vimtex", ft = "tex" },
+}
+
+-- Helper function to detect current manager
+local function detect_current_manager()
+ -- Check if we're currently using lazy (by checking if lazy module exists)
+ if package.loaded["lazy"] or package.loaded["lazy.core.util"] then
+ return "lazy"
+ end
+
+ -- Check if we're currently using packer
+ if package.loaded["packer"] then
+ return "packer"
+ end
+
+ -- Check for builtin manager
+ if vim.plugins and vim.plugins.spec then
+ return "builtin"
+ end
+
+ return "unknown"
+end
+
+local function format_for_lazy(plugin)
+ -- Lazy.nvim's format is the closest to our universal format, so we can
+ -- largely just copy the table, with some specific adjustments.
+ local new_plugin = vim.deepcopy(plugin)
+
+ -- Lazy.nvim uses `dependencies` key for dependencies, not `requires`
+ if new_plugin.requires then
+ new_plugin.dependencies = new_plugin.requires
+ new_plugin.requires = nil
+ end
+
+ -- Change 'as' to 'name' for lazy
+ if new_plugin.as then
+ new_plugin.name = new_plugin.as
+ new_plugin.as = nil
+ end
+
+ -- Change 'run' to 'build' for lazy
+ if new_plugin.run then
+ new_plugin.build = new_plugin.run
+ new_plugin.run = nil
+ end
+
+ return new_plugin
+end
+
+local function format_for_packer(plugin)
+ -- For Packer, we need to remove lazy-loading keys to force eager loading
+ local new_plugin = vim.deepcopy(plugin)
+
+ -- Convert dependencies back to requires for packer
+ if new_plugin.dependencies then
+ new_plugin.requires = new_plugin.dependencies
+ new_plugin.dependencies = nil
+ end
+
+ -- Convert name back to as for packer
+ if new_plugin.name then
+ new_plugin.as = new_plugin.name
+ new_plugin.name = nil
+ end
+
+ -- Convert build back to run for packer
+ if new_plugin.build then
+ new_plugin.run = new_plugin.build
+ new_plugin.build = nil
+ end
+
+ -- Remove lazy-loading keys to force eager loading in packer
+ new_plugin.event = nil
+ new_plugin.keys = nil
+ new_plugin.cmd = nil
+ new_plugin.ft = nil
+ new_plugin.lazy = nil
+
+ return new_plugin
+end
+
+local function format_for_builtin(plugin)
+ -- This function is now simplified, as the main loop handles flattening
+ local new_plugin = vim.deepcopy(plugin)
+
+ -- Convert GitHub shorthand to full URL if needed
+ if type(new_plugin) == "string" then
+ if new_plugin:match("^[%w%-_%.]+/[%w%-_%.]+$") then
+ return {
+ src = "https://github.com/" .. new_plugin,
+ name = new_plugin:match("/([%w%-_%.]+)$")
+ }
+ else
+ return { src = new_plugin }
+ end
+ end
+
+ -- Handle table format
+ if new_plugin[1] and type(new_plugin[1]) == "string" then
+ local repo = new_plugin[1]
+ if repo:match("^[%w%-_%.]+/[%w%-_%.]+$") then
+ new_plugin.src = "https://github.com/" .. repo
+ new_plugin.name = new_plugin.name or new_plugin.as or repo:match("/([%w%-_%.]+)$")
+ else
+ new_plugin.src = repo
+ end
+ new_plugin[1] = nil -- Remove positional argument
+ end
+
+ -- Convert url to src if present
+ if new_plugin.url then
+ new_plugin.src = new_plugin.url
+ new_plugin.url = nil
+ end
+
+ -- Convert 'as' to 'name'
+ if new_plugin.as then
+ new_plugin.name = new_plugin.as
+ new_plugin.as = nil
+ end
+
+ -- Only keep the keys that vim.pack uses: src, name, and version
+ new_plugin.dependencies = nil
+ new_plugin.config = nil
+ new_plugin.build = nil
+ new_plugin.run = nil
+ new_plugin.cond = nil
+ new_plugin.min_version = nil
+ new_plugin.max_version = nil
+ new_plugin.lazy = nil
+ new_plugin.priority = nil
+ new_plugin.event = nil
+ new_plugin.keys = nil
+ new_plugin.cmd = nil
+ new_plugin.ft = nil
+ new_plugin.requires = nil
+
+ return new_plugin
+end
+
+-- Detect which manager is currently active and format plugins accordingly
+local current_manager = detect_current_manager()
+local plugins_to_process = {}
+local processed_plugins = {} -- Use a set to avoid duplicates
+
+-- Flatten the plugin list for the builtin manager
+if current_manager == "builtin" then
+ local function get_plugin_name(plugin)
+ if type(plugin) == "string" then
+ return plugin:match("/([%w%-_%.]+)$") or plugin
+ elseif type(plugin) == "table" then
+ -- Get name from 'name', 'as', or from the src/url
+ return plugin.name or plugin.as or (type(plugin[1]) == "string" and plugin[1]:match("/([%w%-_%.]+)$")) or
+ plugin.url:match("/([%w%-_%.]+)$")
+ end
+ end
+
+ local function add_to_process(plugin)
+ local name = get_plugin_name(plugin)
+ if name and not processed_plugins[name] then
+ table.insert(plugins_to_process, plugin)
+ processed_plugins[name] = true
+ end
+ end
+
+ for _, plugin in ipairs(universal_plugins) do
+ add_to_process(plugin)
+ if plugin.dependencies then
+ for _, dep in ipairs(plugin.dependencies) do
+ add_to_process(dep)
+ end
+ end
+ if plugin.requires then
+ for _, req in ipairs(plugin.requires) do
+ add_to_process(req)
+ end
+ end
+ end
+else
+ plugins_to_process = universal_plugins
+end
+
+local finalized_plugins = {}
+
+for _, plugin in ipairs(plugins_to_process) do
+ local cond_ok = true
+
+ -- Check for the new 'exclude' option first
+ if plugin.exclude and contains(plugin.exclude, current_manager) then
+ cond_ok = false
+ end
+
+ if cond_ok and (plugin.min_version or plugin.max_version) then
+ cond_ok = should_load_plugin(plugin.min_version, plugin.max_version)
+ end
+ if cond_ok and plugin.cond then
+ cond_ok = plugin.cond()
+ end
+
+ if cond_ok then
+ local new_plugin
+ if current_manager == "lazy" then
+ new_plugin = format_for_lazy(plugin)
+ elseif current_manager == "packer" then
+ new_plugin = format_for_packer(plugin)
+ elseif current_manager == "builtin" then
+ new_plugin = format_for_builtin(plugin)
+ else
+ -- Default to lazy format if manager is unknown
+ new_plugin = format_for_lazy(plugin)
+ end
+ table.insert(finalized_plugins, new_plugin)
+ end
+end
+
+return finalized_plugins