Drill 04: Plugin Development
Module structure, lazy.nvim plugin specs, and configuration patterns.
| Reference code for understanding domus-nvim structure. |
Run This Drill
bash ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/examples/lua-drills/04-plugins.sh
Drill Script
#!/bin/bash
# LUA DRILL 04: PLUGIN DEVELOPMENT
# Topics: Module structure, lazy.nvim patterns, configuration
echo "=================================================================="
echo " LUA DRILL 04: PLUGIN DEVELOPMENT "
echo "=================================================================="
echo ""
echo "Understanding Neovim plugin/config structure"
echo ""
# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.1: MODULE STRUCTURE"
echo "How require() works in Neovim"
echo "------------------------------------------------------------------"
echo ""
cat << 'NVIMEOF'
-- Neovim looks for modules in runtimepath/lua/
-- ~/.config/nvim/lua/mymodule.lua
-- Can be required as:
require('mymodule')
-- ~/.config/nvim/lua/mymodule/init.lua
-- Can also be required as:
require('mymodule')
-- ~/.config/nvim/lua/mymodule/submodule.lua
-- Required as:
require('mymodule.submodule')
-- TYPICAL DOMUS-NVIM STRUCTURE:
-- lua/
-- βββ domus/
-- β βββ init.lua -- require('domus')
-- β βββ options.lua -- require('domus.options')
-- β βββ keymaps.lua -- require('domus.keymaps')
-- β βββ plugins/
-- β βββ init.lua -- require('domus.plugins')
-- β βββ specs/ -- plugin specs for lazy.nvim
-- β β βββ editor.lua
-- β β βββ ui.lua
-- β β βββ lsp.lua
-- β βββ config/ -- plugin configurations
-- β βββ lsp/
-- β β βββ init.lua
-- β βββ treesitter.lua
-- In init.lua:
require('domus') -- loads lua/domus/init.lua
NVIMEOF
echo ""
# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.2: LAZY.NVIM PLUGIN SPECS"
echo "The modern plugin manager"
echo "------------------------------------------------------------------"
echo ""
cat << 'NVIMEOF'
-- ~/.config/nvim/lua/domus/plugins/specs/editor.lua
return {
-- Simple plugin (just install)
"tpope/vim-sleuth",
-- Plugin with configuration
{
"folke/which-key.nvim",
event = "VeryLazy", -- lazy load
config = function()
require("which-key").setup({})
end,
},
-- Plugin with opts (auto-calls setup)
{
"numToStr/Comment.nvim",
event = {"BufReadPre", "BufNewFile"},
opts = {
-- passed to require('Comment').setup(opts)
},
},
-- Plugin with dependencies
{
"nvim-telescope/telescope.nvim",
branch = "0.1.x",
dependencies = {
"nvim-lua/plenary.nvim",
{"nvim-telescope/telescope-fzf-native.nvim", build = "make"},
},
config = function()
local telescope = require("telescope")
telescope.setup({})
telescope.load_extension("fzf")
end,
},
-- Conditional loading
{
"epwalsh/obsidian.nvim",
cond = vim.fn.isdirectory("/data/data/com.termux") == 0, -- disable on Termux
ft = "markdown",
opts = {
workspaces = {{name = "notes", path = "~/notes"}},
},
},
-- Plugin with keymaps
{
"lewis6991/gitsigns.nvim",
event = {"BufReadPre", "BufNewFile"},
opts = {
on_attach = function(bufnr)
local gs = package.loaded.gitsigns
local keymap = vim.keymap.set
keymap('n', ']h', gs.next_hunk, {buffer = bufnr, desc = 'Next hunk'})
keymap('n', '[h', gs.prev_hunk, {buffer = bufnr, desc = 'Prev hunk'})
end
},
},
}
NVIMEOF
echo ""
# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.3: CONFIG FUNCTION PATTERNS"
echo "How to configure plugins"
echo "------------------------------------------------------------------"
echo ""
cat << 'NVIMEOF'
-- Pattern 1: Inline config
{
"plugin/name",
config = function()
require("plugin").setup({
option1 = true,
option2 = "value",
})
end,
}
-- Pattern 2: opts (cleaner for simple config)
{
"plugin/name",
opts = {
option1 = true,
option2 = "value",
},
}
-- Pattern 3: External config file
{
"plugin/name",
config = function()
require("domus.plugins.config.plugin")
end,
}
-- Then in lua/domus/plugins/config/plugin.lua:
local M = {}
M.setup = function()
require("plugin").setup({
option1 = true,
})
end
M.setup()
return M
-- Pattern 4: opts function (access to plugin defaults)
{
"plugin/name",
opts = function(_, opts)
opts.option1 = true
opts.custom = {nested = true}
return opts
end,
}
NVIMEOF
echo ""
# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.4: LAZY LOADING"
echo "Load plugins only when needed"
echo "------------------------------------------------------------------"
echo ""
cat << 'NVIMEOF'
-- Load on events
{
"plugin/name",
event = "VeryLazy", -- after UI loads
event = "BufReadPre", -- before reading buffer
event = {"BufReadPre", "BufNewFile"},
event = "InsertEnter", -- when entering insert mode
}
-- Load on filetype
{
"rust-lang/rust.vim",
ft = "rust",
}
-- Load on command
{
"tpope/vim-fugitive",
cmd = {"Git", "Gstatus", "Gblame"},
}
-- Load on keymap
{
"nvim-telescope/telescope.nvim",
keys = {
{"<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find files"},
{"<leader>fg", "<cmd>Telescope live_grep<cr>", desc = "Live grep"},
},
}
-- Load as dependency (loaded when parent loads)
{
"hrsh7th/nvim-cmp",
dependencies = {
"hrsh7th/cmp-nvim-lsp", -- loaded with nvim-cmp
"hrsh7th/cmp-buffer",
},
}
-- Conditional (don't load at all if false)
{
"plugin/name",
cond = vim.fn.has("gui_running") == 1,
cond = function() return vim.fn.executable("node") == 1 end,
}
NVIMEOF
echo ""
# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 4.5: WRITING A SIMPLE PLUGIN"
echo "Module that provides functionality"
echo "------------------------------------------------------------------"
echo ""
cat << 'NVIMEOF'
-- lua/domus/plugins/config/custom/init.lua
local M = {}
M.defaults = {
prefix = "[INFO]",
log_level = "info",
}
M.options = {}
M.setup = function(opts)
M.options = vim.tbl_deep_extend("force", M.defaults, opts or {})
end
M.log = function(msg)
print(M.options.prefix .. " " .. msg)
end
M.get_stats = function()
return {
buffers = #vim.api.nvim_list_bufs(),
windows = #vim.api.nvim_list_wins(),
cwd = vim.fn.getcwd(),
}
end
-- Create commands
vim.api.nvim_create_user_command("MyLog", function(opts)
M.log(opts.args)
end, {nargs = 1})
vim.api.nvim_create_user_command("MyStats", function()
print(vim.inspect(M.get_stats()))
end, {})
return M
-- Usage:
-- require('domus.plugins.config.custom').setup({prefix = "[DEBUG]"})
-- require('domus.plugins.config.custom').log("Hello!")
-- :MyLog test message
-- :MyStats
NVIMEOF
echo ""
# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "YOUR TURN - TRY THESE:"
echo "------------------------------------------------------------------"
echo ""
echo "1. Check lazy.nvim loaded plugins:"
echo " :lua print(vim.inspect(require('lazy').plugins()))"
echo ""
echo "2. Reload a module:"
echo " :lua package.loaded['mymodule'] = nil; require('mymodule')"
echo ""
echo "3. Create simple command:"
echo " :lua vim.api.nvim_create_user_command('Test', function() print('Works!') end, {})"
echo " :Test"
echo ""
echo "------------------------------------------------------------------"
echo "KEY TAKEAWAYS:"
echo "1. Modules in lua/ dir, require('name') or require('dir.name')"
echo "2. lazy.nvim: opts = {} auto-calls setup(opts)"
echo "3. Use events/ft/cmd/keys for lazy loading"
echo "4. cond = false to skip loading entirely"
echo "5. Return M pattern for module exports"
echo "------------------------------------------------------------------"