Browse Source

Rewrite plugin manager

master
Adam Pippin 2 years ago
parent
commit
d452bcb1a5
  1. 5
      README.md
  2. 24
      doc/nukevim.txt
  3. 55
      lua/config/plugins/development.lua
  4. 2
      lua/config/plugins/editing.lua
  5. 12
      lua/config/plugins/lang-php.lua
  6. 2
      lua/config/plugins/lib.lua
  7. 13
      lua/config/plugins/ui.lua
  8. 2
      lua/module.lua
  9. 2
      lua/module/keymap.lua
  10. 251
      lua/module/plugins.lua
  11. 4
      lua/modules.lua
  12. 2
      lua/nukevim.lua
  13. 12
      lua/plugin/vim_plug.lua

5
README.md

@ -7,3 +7,8 @@ should be fairly adaptable (if poorly implemented) to anything you want it to
do.
For further information, check out the [vim help file](doc/nukevim.txt).
## TODO
* Go through and declare all the variables `local` because I misunderstood lua
variable scoping rules.

24
doc/nukevim.txt

@ -98,10 +98,12 @@ The leader key is, out of the box, the spacebar. Press it and wait briefly and
a similar legend should pop up to show the actions available.
Some actions are only available when specific file types are loaded (e.g.,
phpunit actions only display when you have a php file open). Several tools are
phpunit actions only display when you have a php file open).
Several tools are
only available when the associated program is found in your path (e.g., git
tools are only available if git is found). If something seems missing, check
the install section and see if installing a dependency would help.
tools are only available if git is found). Run `:checkhealth` to see what's
going on with all of that.
Good luck!
@ -631,14 +633,15 @@ Each plugin supports a several properties, all except `name` are optional:
* `enable`: if set and not `true`, the plugin will be skipped
* `name`: the name of the plugin, generally a github reference (`user/plugin`)
* `config`: passed through as-is to both `vim-plug` and the plugin itself, some
useful options available from vim-plug:
* `opts`: used to control plugin loading options, some available options are:
* `branch`: override the branch to check out from git
* `on`: delay loading the plugin until one of the specified commands is called
* `do`: command to run after install/update
* `requires`: a list of programs that should be available in your path in order
for this plugin to be enabled (to, e.g., disable a docker plugin if docker
isn't installed)
* `requires`: contains sub-properties:
* `provider`: a list of vim providers (e.g., 'python3') that must be present
* `binary`: a list of executables that must be available on your path
* `plugin`: a list of other plugins that must have been successfully initialized
* `config`: passed through to the plugin itself as configuration
* `keys`: a list of key mappings in the format expected by the keymap module
(see |NukeVimModuleKeymap|) that should be registered if this plugin is enabled
@ -647,6 +650,11 @@ In addition to the plugin itself, if a file is present at
system to allow you to provide additional customization or initialization
logic. See |NukeVimArchPlugin| for more information.
If any of a plugin's requirements are not met it will simply not be loaded. This
should allow NukeVim to simply adapt to whatever is available at the time. If
any expected functionality is missing, running `:checkhealth` should report
why a plugin wasn't loaded under the plugin module section.
--------------------------------------------------------------------------------
5.5 Settings *NukeVimModuleSettings*

55
lua/config/plugins/development.lua

@ -13,6 +13,7 @@ return {
{
name = 'vim-vdebug/vdebug',
opts = { requires = { provider = { 'python3' } } },
keys = {
{ mode = 'n', key = { '<leader>', 'd', 'd' }, group = true, label = 'debugger' },
{ mode = 'v', key = { '<leader>', 'd', 'd' }, group = true, label = 'debugger' },
@ -54,9 +55,9 @@ return {
--
{
name = 'neoclide/coc.nvim',
-- Only enable this plugin if npm is installed
requires = { 'npm' },
config = {
opts = {
-- Only enable if npm is installed
requires = { binary = { 'npm' } },
branch = 'master',
['do'] = 'npm install'
},
@ -73,11 +74,10 @@ return {
--
{
name = 'preservim/tagbar',
requires = { 'ctags' },
config = { ['on'] = { 'TagbarToggle', 'TagbarOpen', 'TagbarOpenAutoClose' } },
-- keys = {
-- { mode = 'n', key = { '<leader>', 't' }, map = ':TagbarOpenAutoClose<CR>', label = 'Show File Structure' }
-- }
opts = {
requires = { binary = { 'ctags' } },
['on'] = { 'TagbarToggle', 'TagbarOpen', 'TagbarOpenAutoClose' }
},
keys = {
{ mode = 'n', key = { '<A-;>' }, map = ':TagbarOpenAutoClose<CR>', label = 'show tagbar' },
{ mode = 'i', key = { '<A-;>' }, map = '<ESC>:TagbarOpenAutoClose<CR>', label = 'show tagbar' },
@ -102,7 +102,7 @@ return {
-- Trouble - Show a todo list and list of errors from the lsp
--
{ name = 'kyazdani42/nvim-web-devicons' },
{ name = 'folke/trouble.nvim', config = { branch = 'main' } },
{ name = 'folke/trouble.nvim', opts = { branch = 'main' } },
----------------------------------------------------------------------------
-- Todo Comments
@ -111,7 +111,7 @@ return {
-- project-wide
{
name = 'folke/todo-comments.nvim',
config = { branch = "main" },
opts = { branch = "main" },
keys = {
{ mode = 'n', key = { '<leader>', 'w', 't' }, map = ':TodoTrouble<CR>', label = "todos list" },
{ mode = 'n', key = { '<leader>', 'w', 'T' }, map = ':TodoTelescope<CR>', label = "todos search" },
@ -147,9 +147,11 @@ return {
--
{
name = 'tpope/vim-fugitive',
-- Only enable if git is installed
requires = { 'git' },
config = { ['on'] = { 'G', 'Gdiffsplit', 'GDelete' } },
opts = {
-- Only enable if git is installed
requires = { binary = { 'git' } },
['on'] = { 'G', 'Gdiffsplit', 'GDelete' }
},
-- Put a bunch of stuff in the <leader> menu
keys = {
{ mode = 'n', key = { '<leader>', 'g', 'b' }, map = ':G blame<CR>', label = 'blame' },
@ -175,7 +177,9 @@ return {
--
{
name = 'junegunn/gv.vim',
requires = { 'git' },
opts = {
requires = { binary = { 'git' } },
},
keys = {
{ mode = 'n', key = { '<leader>', 'g', 'g' }, map = ':GV<CR>', label = 'graph' }
}
@ -189,7 +193,9 @@ return {
--
{
name = 'airblade/vim-gitgutter',
requires = { 'git' },
opts = {
requires = { binary = { 'git' } },
},
keys = {
{ mode = 'n', key = { '<leader>', 'g', 'r' }, map = '<Plug>(GitGutterPrevHunk)', label = 'previous hunk' },
{ mode = 'n', key = { '<leader>', 'g', 'f' }, map = '<Plug>(GitGutterNextHunk)', label = 'next hunk' },
@ -205,10 +211,10 @@ return {
--
{
name = 'apzelos/blamer.nvim',
requires = { 'git' },
-- config = {
opts = {
requires = { binary = { 'git' } },
-- ['on'] = { 'BlamerToggle' }
-- },
},
keys = {
{ mode = 'n', key = { '<leader>', 'g', 'w' }, map = '<Cmd>BlamerToggle<CR>', label = 'toggle inline blame' },
}
@ -229,8 +235,10 @@ return {
{
enable = false,
name = 'kkvh/vim-docker-tools',
requires = { 'docker' },
config = { ['on'] = { 'DockerToolsOpen', 'DockerToolsToggle' } },
opts = {
requires = { binary = { 'docker' } },
['on'] = { 'DockerToolsOpen', 'DockerToolsToggle' }
},
keys = {
{ mode = 'n', key = { '<leader>', 'd', 'c' }, map = ':below new<CR>:DockerToolsOpen<CR>', label = 'docker containers' }
}
@ -245,8 +253,11 @@ return {
{
enable = true,
name = 'skanehira/denops-docker.vim',
config = { branch = 'main', ['on'] = { 'DockerContainers', 'DockerImages' } },
requires = { 'deno', 'docker' },
opts = {
requires = { binary = { 'deno', 'docker' }, plugin = { 'vim-denops/denops.vim' } },
branch = 'main',
['on'] = { 'DockerContainers', 'DockerImages' }
},
keys = {
{ mode = 'n', key = { '<leader>', 'd', 'c' }, map = ':below new<CR>:DockerContainers<CR>8<C-w>_', options = { silent = true }, label = 'docker containers' },
{ mode = 'n', key = { '<leader>', 'd', 'i' }, map = ':below new<CR>:DockerImages<CR>8<C-w>_', options = { silent = true }, label = 'docker images' },

2
lua/config/plugins/editing.lua

@ -18,7 +18,7 @@ return {
--
{
name = 'ggandor/lightspeed.nvim',
config = { branch = 'main' },
opts = { branch = 'main' },
keys = {
{ mode = 'n', key = { 's' }, virtual = true, label = "smart jump forward" },
{ mode = 'n', key = { 'S' }, virtual = true, label = "smart jump backward" },

12
lua/config/plugins/lang-php.lua

@ -21,8 +21,8 @@ return {
--
{
name = 'vim-php/phpctags',
requires = { 'ctags', 'php', 'composer' },
config = {
opts = {
requires = { binary = { 'ctags', 'php', 'composer' } },
['do'] = 'composer install'
}
},
@ -35,9 +35,11 @@ return {
--
{
name = 'vim-php/vim-composer',
requires = { 'php', 'composer' },
-- Only load this plugin if a composer function is called
config = { ['on'] = { 'ComposerInstall', 'ComposerUpdate', 'ComposerJSON' } },
opts = {
requires = { binary = { 'php', 'composer' } },
-- Only load this plugin if a composer function is called
['on'] = { 'ComposerInstall', 'ComposerUpdate', 'ComposerJSON' }
},
keys = {
{ filetype = 'php', mode = 'n', key = { '<leader>', 'l', 'c' }, group = true, label = 'composer' },
{ filetype = 'php', mode = 'n', key = { '<leader>', 'l', 'c', 'i' }, map = ':ComposerInstall<CR>', label = 'composer install' },

2
lua/config/plugins/lib.lua

@ -7,7 +7,7 @@ return {
----------------------------------------------------------------------------
-- Denops - Provides for deno/js-based plugins
--
{ name = 'vim-denops/denops.vim', requires = { 'deno' }, config = { branch = 'main' } },
{ name = 'vim-denops/denops.vim', opts = { requires = { binary = { 'deno' } }, branch = 'main' } },
----------------------------------------------------------------------------
-- Asyncrun - Run shell commands and stream their output into a window

13
lua/config/plugins/ui.lua

@ -32,7 +32,7 @@ return {
-- TODO: Actually configure this because the default options are quite noisy.
--
{ name = 'vim-airline/vim-airline' },
{ name = 'vim-airline/vim-airline-themes' },
{ name = 'vim-airline/vim-airline-themes', opt = { requires = { plugin = { 'vim-airline/vim-airline' } } } },
----------------------------------------------------------------------------
-- Fuzzy find files
@ -48,7 +48,7 @@ return {
--
{
name = 'ctrlpvim/ctrlp.vim',
config = {
opts = {
-- Only load this plugin once one of these vim commands is triggered
['on'] = { 'CtrlP', 'CtrlPMixed', 'CtrlPMRU' }
},
@ -66,7 +66,7 @@ return {
--
{
name = 'akinsho/toggleterm.nvim',
config = { branch = 'main' },
opts = { branch = 'main' },
keys = {
-- { mode = 'n', key = { '<C-t>' }, map = '<cmd>exe "v:count" + tabpagenr() . "ToggleTerm"<CR>', label = "open terminal", options = { silent = true } },
-- { mode = 't', key = { '<C-t>' }, map = '<cmd>exe "v:count" + tabpagenr() . "ToggleTerm"<CR>', label = "close terminal", options = { silent = true } },
@ -97,6 +97,9 @@ return {
--
{
name = 'nvim-telescope/telescope.nvim',
opts = {
requires = { plugin = { 'nvim-lua/plenary.nvim' } }
},
keys = {
{ mode = 'n', key = { '<leader>', 'g', 'z' }, map = ':lua require("telescope.builtin").git_branches()<cr>', label = 'checkout' },
}
@ -122,7 +125,7 @@ return {
-- { name = 'sunjon/Shade.nvim', config = { opacity = 75 } },
-- This is a fork that, at least at the time, basically just had all the PRs
-- merged.
{ name = 'jghauser/shade.nvim', config = { opacity = 60 , branch = "main" } },
{ name = 'jghauser/shade.nvim', opts = { branch = 'main' }, config = { opacity = 60 } },
----------------------------------------------------------------------------
-- Easier buffer moving
@ -131,7 +134,7 @@ return {
--
{
name = 'sindrets/winshift.nvim',
config = {
opts = {
branch = 'main',
['on'] = { 'WinShift' }
},

2
lua/module.lua

@ -55,7 +55,7 @@ end
-- }
-- }
function Module:health()
return nil
return { messages = { } }
end
return Module

2
lua/module/keymap.lua

@ -9,7 +9,7 @@ end
function M:register()
if (self.config.ui.enable) then
self.plugins:add('folke/which-key.nvim', { branch = "main" }, {
self.plugins:add('folke/which-key.nvim', { branch = "main" }, {}, {
{ mode = 'n', key = { '?', '?', '?' }, map = ":lua nukevim.modules:get('keymap'):show()<CR>", hidden = true },
{ mode = 'i', key = { '?', '?', '?' }, map = "<cmd> lua nukevim.modules:get('keymap'):show()<CR>", hidden = true },
{ mode = 'v', key = { '?', '?', '?' }, map = "<cmd> lua nukevim.modules:get('keymap'):show()<CR>", hidden = true },

251
lua/module/plugins.lua

@ -4,118 +4,221 @@ local package_manager = require('plugin/vim_plug')
local file_exists = require('lib/file_exists')
local directory_exists = require('lib/directory_exists')
local has_command = require('lib/has_command')
local merge = require('lib/merge')
function M:initialize()
self.path = self.config.path or "~/.config/nvim/plugins/"
self.auto_install = self.config.auto_install or true
self.auto_cleanup = self.config.auto_cleanup or true
self.need_install = false
self.plugins = {}
self.keymap = nukevim.modules:get('keymap')
end
-- Add a new plugin to the plugin system
function M:add(name, opts, config, keys, enable)
if (enable == nil) then enable = true end
plugin = { initialized = false, enable = enable, health = { }, name = name, opts = opts or {}, config = config or {}, keys = keys or {} }
table.insert(self.plugins, plugin)
return plugin
end
-- Check whether a plugin is registered and available in the plugin system
function M:has(name)
for idx, plugin in pairs(self.plugins) do
if (plugin.name == name) then
return plugin.initialized
end
end
return false
end
-- Register all plugins from the config with ourself
function M:register()
for idx, plugin in pairs(self.config.plugins) do
reg_plugin = self:add(plugin.name, plugin.config or nil, plugin.keys or nil, plugin.requires or nil)
reg_plugin.enable = plugin.enable
self:add(plugin.name, plugin.opts, plugin.config, plugin.keys, plugin.enable)
end
end
-- Actually initialize all the plugins
function M:commit()
package_manager:initialize(self.path)
package_manager:enter()
for idx, plugin in pairs(self.plugins) do
-- Check that this plugin is enabled, dependencies are satisfied, etc
should_initialize = self:shouldInitializePlugin(plugin)
-- HACK: Should have a better way to detect this
if (plugin.enable == false and plugin.enable_diag_message == 'not installed' and self.auto_install) then
should_initialize = true
end
if (should_initialize) then
-- Add to the plugin manager
package_manager:add(plugin.name, plugin.config or nil)
-- Register any key bindings it specifies
if (plugin.keys ~= nil) then
for idx, key in ipairs(plugin.keys) do
self.keymap:add(key)
end
package_manager:enter();
-- build out our temporary array of plugins
-- instead of building out a proper dependency tree, we just iterate through
-- removing plugins as they're instantiated or determined to not be instantiable (lol)
-- and we have nothing left or don't change the size of the list
plugins = {}
for idx,plugin in pairs(self.plugins) do
table.insert(plugins, plugin)
end
last_size = #plugins
while true do
for idx=#plugins,1,-1 do
plugin = plugins[idx]
plugin_enabled, plugin_enabled_messages = self:isPluginEnabled(plugin)
plugin_installed, plugin_installed_messages = self:isPluginInstalled(plugin)
plugin_required_providers, plugin_providers_messages = self:checkPluginRequiredProviders(plugin)
plugin_required_binaries, plugin_binaries_messages = self:checkPluginRequiredBinaries(plugin)
plugin_required_plugins, plugin_plugins_messages = self:checkPluginRequiredPlugins(plugin)
if (plugin_enabled) then
plugin.health = merge({plugin_enabled_messages, plugin_installed_messages, plugin_providers_messages, plugin_binaries_messages, plugin_plugins_messages})
else
plugin.health = plugin_enabled_messages
end
-- If the plugin has a lua module in nukevim, load and register that
if (file_exists(vim.env.HOME .. '/.config/nvim/lua/plugin/' .. plugin.name:gsub('%.', '-') .. '.lua')) then
local plugin_module = require('plugin/' .. plugin.name:gsub('%.', '-')):new()
plugin_module:configure(plugin.config)
nukevim.modules:addInstance(plugin.name, plugin_module)
-- If everything passed, then we're good
-- If the only thing failing is that the plugin isn't installed _and_
-- we have auto_install enabled, then register it and mark needs_install
-- so we can just install it after.
if (plugin_enabled and (plugin_installed or self.auto_install) and plugin_required_providers and plugin_required_binaries and plugin_required_plugins) then
self:enablePlugin(plugin)
if (not plugin_installed) then self.needs_install = true end
table.remove(plugins, idx)
-- Otherwise if any of these conditions occur then we know the plugin
-- will never be enabled so we can explicitly disable and remove it.
elseif (
(not plugin_enabled) or
(not plugin_installed) or
(not plugin_required_providers) or
(not plugin_required_binaries)
) then
plugin.enable = false
table.remove(plugins, idx)
else
-- Otherwise, the only thing that could have failed is a required
-- plugin, so leave it in for next pass to see if the dependency
-- is now satisfied.
end
end
if (#plugins == last_size) then
-- we didn't do anything this pass, give up
break
end
last_size = #plugins
end
package_manager:exit()
end
function M:shouldInitializePlugin(plugin)
if (plugin.enable ~= nil and plugin.enable ~= true) then
plugin.enable = false
plugin.enable_diag_class = 'warn'
plugin.enable_diag_message = 'explicitly disabled'
return false
end
-- Once we determine a plugin should be loaded, this actually does it
function M:enablePlugin(plugin)
package_manager:add(plugin.name, plugin.opts)
if (plugin.requires ~= nil) then
for i=1,#plugin.requires do
if (not has_command(plugin.requires[i])) then
plugin.enable = false
plugin.enable_diag_class = 'error'
plugin.enable_diag_message = ('missing dependency: %s'):format(plugin.requires[i])
return false
end
-- Register key bindings
if (plugin.keys ~= nil) then
for idx, key in ipairs(plugin.keys) do
self.keymap:add(key)
end
end
name = plugin.name:match('/(.*)')
if (not directory_exists(self.path .. '/' .. name)) then
plugin.enable = false
plugin.enable_diag_class = 'error'
plugin.enable_diag_message = 'not installed'
plugin.needs_install = true
return false
-- If the plugin has a lua module in nukevim, load and register that
if (file_exists(vim.env.HOME .. '/.config/nvim/lua/plugin/' .. plugin.name:gsub('%.', '-') .. '.lua')) then
local plugin_module = require('plugin/' .. plugin.name:gsub('%.', '-')):new()
plugin_module:configure(plugin.config)
nukevim.modules:addInstance(plugin.name, plugin_module)
end
plugin.enable = true
plugin.enable_diag_class = 'ok'
plugin.enable_diag_message = 'all checks passed';
return true
plugin.initialized = true
end
function M:boot()
if (self.auto_install and package_manager:hasPendingPackages()) then
package_manager:install()
-- Check whether a plugin is supposed to be enabled
function M:isPluginEnabled(plugin)
if (plugin.enable == nil or plugin.enable == true) then
return true, { }
else
for idx, plugin in pairs(self.plugins) do
if (plugin.needs_install == true) then
package_manager:install()
break
end
end
return false, { { 'info', 'explicitly disabled' } }
end
end
if (self.auto_cleanup) then
package_manager:cleanup()
-- Check whether a plugin is installed
function M:isPluginInstalled(plugin)
if (package_manager:installed(plugin.name)) then
return true, { }
else
return false, { { 'error', 'not installed' } }
end
end
function M:add(name, config, keys, requires)
plugin = { name = name, config = config, keys = keys, requires = requires }
table.insert(self.plugins, plugin)
return plugin
-- Check whether all of a plugin's declared providers are available
function M:checkPluginRequiredProviders(plugin)
if (plugin.opts.requires == nil or plugin.opts.requires.provider == nil) then
return true, { }
end
messages = {}
for idx,provider in ipairs(plugin.opts.requires.provider) do
if (vim.fn.has(provider) == 0) then
table.insert(messages, { 'error', ('missing provider: %s'):format(provider) })
end
end
if (#messages == 0) then
return true, { }
else
return false, messages
end
end
function M:has(name)
for idx, plugin in pairs(self.plugins) do
if (plugin.name == name) then
return plugin.enable == nil or plugin.enable == true
-- Check whether all of a plugin's declared system binaries are available
function M:checkPluginRequiredBinaries(plugin)
if (plugin.opts.requires == nil or plugin.opts.requires.binary == nil) then
return true, { }
end
messages = {}
for idx,binary in ipairs(plugin.opts.requires.binary) do
if (not has_command(binary)) then
table.insert(messages, { 'error', ('missing binary: %s'):format(binary) })
end
end
return false
if (#messages == 0) then
return true, { }
else
return false, messages
end
end
-- Check whether all of a plugin's dependencies on other plugins are satisfied
function M:checkPluginRequiredPlugins(plugin)
if (plugin.opts.requires == nil or plugin.opts.requires.plugin == nil) then
return true, { }
end
messages = {}
for idx, plugin in ipairs(plugin.opts.requires.plugin) do
if (not self:has(plugin)) then
table.insert(messages, { 'error', ('missing required plugin: %s'):format(plugin) })
end
for i, p in pairs(self.plugins) do
if (p.name == plugin and p.enable == false) then
table.insert(messages, { 'error', ('missing required plugin: %s'):format(plugin) })
end
end
end
if (#messages == 0) then
return true, { }
else
return false, messages
end
end
function M:boot()
if (self.auto_install and (package_manager:hasPendingPackages() or self.needs_install)) then
self:install()
end
if (self.auto_cleanup) then
self:cleanup()
end
end
function M:install()
@ -130,7 +233,13 @@ function M:health()
messages = {}
for idx, plugin in pairs(self.plugins) do
table.insert(messages, { plugin.enable_diag_class, ('%s: %s'):format(plugin.name, plugin.enable_diag_message) })
if (#plugin.health == 0) then
table.insert(messages, { 'ok', ('%s: %s'):format(plugin.name, 'all checks passed') })
else
for i, msg in pairs(plugin.health) do
table.insert(messages, { msg[1], ('%s: %s'):format(plugin.name, msg[2]) })
end
end
end
return {

4
lua/modules.lua

@ -21,6 +21,10 @@ function Modules:addInstance(name, instance, config)
self.modules[name] = { instance = instance, config = config }
end
function Modules:has(name)
return self.modules[name] ~= nil
end
function Modules:get(name)
if (self.modules[name] == nil) then
print("MODULE NOT LOADED: "..name)

2
lua/nukevim.lua

@ -61,7 +61,7 @@ function NukeVim:health()
for name, module in pairs(self.modules.modules) do
check = module.instance:health()
if (check ~= nil) then
if (check ~= nil and check.messages ~= nil and #check.messages ~= 0) then
health.report_start(('Module: %s'):format(name))
for idx,message in ipairs(check.messages) do

12
lua/plugin/vim_plug.lua

@ -1,6 +1,6 @@
local M = {}
M.path = nil
local directory_exists = require('lib/directory_exists')
function M:initialize(path)
self.path = path
@ -15,7 +15,10 @@ function M:exit()
end
function M:add(name, config)
if (type(config) == "nil") then
-- TODO: we're getting passed through the "opts" from the plugin config
-- this should pull out only the values we need and put them in the format
-- vim-plug expects
if (type(config) == "nil" or #config == 0) then
vim.fn['plug#'](name)
else
vim.fn['plug#'](name, config)
@ -34,4 +37,9 @@ end
function M:cleanup()
end
function M:installed(plugin)
name = plugin:match('/(.*)')
return directory_exists(self.path .. '/' .. name)
end
return M

Loading…
Cancel
Save