dvpm is a plugin manager for Vim and Neovim, powered by
denops.vim.
- Vim / Neovim start up very fast (all plugins are loaded lazily)!
- You can write all Vim / Neovim settings in TypeScript
Neovim (~/.config/nvim/init.lua or ~/AppData/Local/nvim/init.lua)
local denops = vim.fn.expand("~/.cache/nvim/dvpm/github.com/vim-denops/denops.vim")
if not vim.loop.fs_stat(denops) then
vim.fn.system({ "git", "clone", "https://github.com/vim-denops/denops.vim", denops })
end
vim.opt.runtimepath:prepend(denops)Vim (~/.vimrc or ~/_vimrc)
let s:denops = expand("~/.cache/vim/dvpm/github.com/vim-denops/denops.vim")
if !isdirectory(s:denops)
execute 'silent! !git clone https://github.com/vim-denops/denops.vim ' .. s:denops
endif
execute 'set runtimepath^=' . substitute(fnamemodify(s:denops, ':p') , '[/\\]$', '', '')If you use denops.vim v8 or later, specify workspace and add dependencies.
Place deno.json at:
~/.config/nvim/deno.json(Neovim, Mac/Linux)~/AppData/Local/nvim/deno.json(Neovim, Windows)~/.vim/deno.json(Vim, Mac/Linux)~/vimfiles/deno.json(Vim, Windows)
{
"workspace": ["./denops/config"]
}cd ./denops/config
deno add jsr:@denops/std jsr:@yukimemi/dvpmPlace at ~/.config/nvim/denops/config/main.ts (Neovim) or ~/.vim/denops/config/main.ts (Vim):
Example main.ts
import type { Denops, Entrypoint } from "@denops/std";
import * as fn from "@denops/std/function";
import * as vars from "@denops/std/variable";
import { execute } from "@denops/std/helper";
import { Dvpm } from "@yukimemi/dvpm";
export const main: Entrypoint = async (denops: Denops) => {
const base_path = (await fn.has(denops, "nvim")) ? "~/.cache/nvim/dvpm" : "~/.cache/vim/dvpm";
const base = (await fn.expand(denops, base_path)) as string;
const dvpm = await Dvpm.begin(denops, { base });
// GitHub shorthand or full URL
await dvpm.add({ url: "yukimemi/autocursor.vim" });
await dvpm.add({ url: "https://notgithub.com/some/other/plugin" });
// With branch/rev
await dvpm.add({ url: "neoclide/coc.nvim", rev: "release" });
// Execute at startup (regardless of lazy loading)
await dvpm.add({
url: "thinca/vim-quickrun",
lazy: { keys: { lhs: "<leader>r", rhs: "<cmd>QuickRun<cr>" } },
init: async ({ denops }) => {
await vars.g.set(denops, "quickrun_no_default_key_mappings", 1);
},
});
// Run before/after sourcing
await dvpm.add({
url: "folke/which-key.nvim",
after: async ({ denops }) => {
await execute(denops, `lua require("which-key").setup()`);
},
});
// Load from file
await dvpm.add({
url: "rcarriga/nvim-notify",
beforeFile: "~/.config/nvim/rc/before/nvim-notify.lua",
afterFile: "~/.config/nvim/rc/after/nvim-notify.lua",
});
// Disable a plugin
await dvpm.add({ url: "yukimemi/hitori.vim", enabled: false });
// Disable conditionally
await dvpm.add({
url: "editorconfig/editorconfig-vim",
enabled: async ({ denops }) => !(await fn.has(denops, "nvim")),
});
// With dependencies
await dvpm.add({ url: "lambdalisue/askpass.vim" });
await dvpm.add({ url: "lambdalisue/guise.vim" });
await dvpm.add({
url: "lambdalisue/gin.vim",
dependencies: ["lambdalisue/askpass.vim", "lambdalisue/guise.vim"],
});
await dvpm.end();
};See dotfiles for more complex examples.
public static async begin(denops: Denops, dvpmOption: DvpmOption): Promise<Dvpm>DvpmOption type
export type DvpmOption = {
base: string; // Base path for git clone.
cache?: string; // Cache file path. See `Cache setting`.
profiles?: string[]; // Active profiles. See `Profile setting`.
concurrency?: number; // Concurrent processes. Default: 8.
notify?: boolean; // Use vim.notify for logs. Default: false. (Neovim only)
logarg?: string[]; // git log args for :DvpmUpdate output. Default: [].
health?: boolean; // Enable :checkhealth support. Default: false. (Neovim only)
profile?: boolean; // Enable performance profiling. Default: false. See :DvpmProfile.
clean?: Bool; // Clean local changes before update. Default: false.
};public async end(): Promise<void>Adds plugins to runtimepath and sources plugin/*.vim and plugin/*.lua.
public async add(plug: Plug): Promise<void>Plug / Lazy / KeyMap / Bool types
export type Plug = {
url: string; // GitHub `username/repo` or full git URL.
name?: string; // Plugin name (auto-calculated if omitted).
dst?: string; // Custom clone path (for local development).
rev?: string; // Git branch or revision.
depth?: number; // Clone depth (shallow clone).
enabled?: Bool; // Enable/disable. Default: true.
profiles?: string[]; // Enable only when DvpmOption.profiles includes one of these.
clone?: Bool; // Whether to git clone/update. Defaults to true when enabled, false when disabled.
clean?: Bool; // Clean local changes before update. Default: false.
dependencies?: string[]; // Plugin URLs that must be loaded first.
init?: ({ denops, info }) => Promise<void>; // Run at startup before runtimepath is set (always, ignores lazy).
before?: ({ denops, info }) => Promise<void>; // Run after the plugin is added to runtimepath, before sourcing plugin/*.vim.
after?: ({ denops, info }) => Promise<void>; // Run after the plugin is added to runtimepath and sourcing plugin/*.vim.
initFile?: string; // Vim/Lua file to source at startup before runtimepath is set (always, ignores lazy).
beforeFile?: string; // File to source after the plugin is added to runtimepath, before sourcing plugin/*.vim.
afterFile?: string; // File to source after the plugin is added to runtimepath and sourcing plugin/*.vim.
build?: ({ denops, info }) => Promise<void>; // Run after install or update (even if no changes). Check info.isInstalled / info.isUpdated.
cache?: {
enabled?: Bool;
before?: string;
after?: string;
beforeFile?: string;
afterFile?: string;
};
lazy?: Lazy;
};
export type Lazy = {
enabled?: Bool; // Default: false.
cmd?: string | Command | (string | Command)[];
event?: string | string[];
ft?: string | string[];
keys?: string | string[] | KeyMap | KeyMap[];
colorscheme?: string | string[];
};
export type Command = {
name: string;
complete?: string; // Default: "file".
};
export type KeyMap = {
lhs: string;
rhs?: string; // If omitted, proxy mapping is unmapped after loading.
mode?: string | string[]; // Default: "n".
noremap?: boolean; // Default: true.
silent?: boolean; // Default: true.
nowait?: boolean; // Default: false.
expr?: boolean; // Default: false.
desc?: string;
};
export type Bool =
| boolean
| (({ denops, info }: { denops: Denops; info: PlugInfo }) => Promise<boolean>);PlugInfo is similar to Plug but with all values resolved (e.g. enabled is always a boolean).
It also has the following read-only status fields available in callbacks:
| Field | Type | Description |
|---|---|---|
isLoaded |
boolean |
Whether the plugin has been added to runtimepath in this session |
isInstalled |
boolean |
Whether the plugin was cloned (first install) in this session |
isUpdated |
boolean |
Whether the plugin was updated (git pull) in this session |
isCache |
boolean |
Whether the plugin is loaded via cache |
elaps |
number |
Elapsed time for loading (ms) |
Note:
buildis called after every update even if nothing changed, so always checkinfo.isInstalled || info.isUpdatedbefore running heavy build steps.
public async cache(arg: { script: string; path: string }): Promise<boolean>Cache a script to a file. Returns true if the cache was written. Useful for writing startup-time Vim/Lua snippets.
public list(): Plugin[]Returns the list of all registered plugins.
:DvpmUpdate [url] " Update all plugins, or only the specified one.
:DvpmList " Show plugin list in dvpm://list buffer.
:checkhealth dvpm " Health check. (Neovim only, requires health: true)
:DvpmCheckHealth " Health check in dvpm://checkhealth buffer. (Vim / Neovim)
:DvpmProfile " Show plugin performance profile. (requires profile: true):DvpmProfile requires profile: true in Dvpm.begin. Each row shows time per loading phase:
| column | description |
|---|---|
total |
Total time charged to this plugin |
init |
Time in the init / initFile hook |
before |
Time in the before / beforeFile hook |
load |
runtimepath + source + denops plugin loading |
after |
Time in the after / afterFile hook |
build |
Time in the build hook (first install only) |
Enable cache to load plugins before VimEnter (faster startup).
Example
const cache = (await fn.expand(
denops,
(await fn.has(denops, "nvim"))
? "~/.config/nvim/plugin/dvpm_plugin_cache.vim"
: "~/.vim/plugin/dvpm_plugin_cache.vim", // ~/vimfiles/plugin/dvpm_plugin_cache.vim on Windows
)) as string;
const dvpm = await Dvpm.begin(denops, { base, cache });
// Simple: just set cache.enabled
await dvpm.add({ url: "tani/vim-artemis", cache: { enabled: true } });
// With before/after scripts
await dvpm.add({
url: "rcarriga/nvim-notify",
enabled: async ({ denops }) => await fn.has(denops, "nvim"),
cache: {
after: `
lua << EOB
require("notify").setup({ stages = "slide" })
vim.notify = require("notify")
EOB
`,
// Or use a separate file:
// afterFile: "~/.config/nvim/rc/after/notify.lua",
},
});The cache file is auto-generated on first run and loaded on subsequent starts.
All plugins managed by dvpm are inherently lazy (loaded after denops.vim itself). Explicit lazy settings are useful to keep runtimepath short and load plugins only when needed.
Important: Since dvpm starts after Vim's initial startup, early triggers may be missed. For example, ft: "html" will not fire if you open an HTML file directly from the command line.
Examples
// Load on command
await dvpm.add({ url: "lambdalisue/gin.vim", lazy: { cmd: "Gin" } });
// Load on event
await dvpm.add({ url: "tweekmonster/startuptime.vim", lazy: { event: "VimEnter" } });
// Load on filetype
await dvpm.add({ url: "othree/html5.vim", lazy: { ft: "html" } });
// Load on colorscheme
await dvpm.add({ url: "folke/tokyonight.nvim", lazy: { colorscheme: "tokyonight" } });
// Load on keymap
await dvpm.add({
url: "mbbill/undotree",
lazy: { keys: { lhs: "<leader>u", rhs: "<cmd>UndotreeToggle<cr>" } },
});
// Load on keys (unmap proxy after load — useful for text objects / operator plugins)
await dvpm.add({ url: "kana/vim-textobj-user" });
await dvpm.add({
url: "kana/vim-textobj-entire",
dependencies: ["kana/vim-textobj-user"],
lazy: {
keys: [
{ lhs: "ie", mode: ["x", "o"] },
{ lhs: "ae", mode: ["x", "o"] },
],
},
});
// Library plugin (lazy, loaded when a dependent plugin is triggered)
await dvpm.add({ url: "nvim-lua/plenary.nvim", lazy: { enabled: true } });
await dvpm.add({
url: "nvim-telescope/telescope.nvim",
lazy: { cmd: "Telescope" },
dependencies: ["nvim-lua/plenary.nvim"],
});
// Manual load in init hook
await dvpm.add({
url: "junegunn/fzf.vim",
lazy: { enabled: true },
init: async ({ denops }) => {
if (Deno.env.get("ENABLE_FZF")) {
await dvpm.load("junegunn/fzf.vim");
}
},
});init/initFile— always at startup (Dvpm.end()), before runtimepath is set (ignores lazy)before/beforeFile— after the plugin is added toruntimepath, before sourcingplugin/*.vimafter/afterFile— after the plugin is added toruntimepathand sourcingplugin/*.vimbuild— after install or update
Available autocmd events
DvpmBeginPre/DvpmBeginPostDvpmEndPre/DvpmEndPostDvpmInstallPre/DvpmInstallPostDvpmUpdatePre/DvpmUpdatePostDvpmCacheUpdated
DvpmPluginLoadPre:{pluginName}/DvpmPluginLoadPost:{pluginName}DvpmPluginInstallPre:{pluginName}/DvpmPluginInstallPost:{pluginName}DvpmPluginUpdatePre:{pluginName}/DvpmPluginUpdatePost:{pluginName}
{pluginName} is the name property of PlugInfo. Wildcards are supported.
import * as autocmd from "@denops/std/autocmd";
await autocmd.define(denops, "User", "DvpmCacheUpdated", "echo 'dvpm cache updated !'");
await autocmd.define(
denops,
"User",
"DvpmPluginLoadPost:*",
`echom "Loaded: " . substitute(expand("<amatch>"), "^DvpmPluginLoadPost:", "", "")`,
);Restrict which plugins are enabled via profiles:
const dvpm = await Dvpm.begin(denops, {
base,
profiles: ["minimal", "default"], // only these profiles are active
});
await dvpm.add({ url: "yukimemi/chronicle.vim", profiles: ["minimal"] }); // enabled
await dvpm.add({ url: "yukimemi/silentsaver.vim", profiles: ["default"] }); // enabled
await dvpm.add({ url: "yukimemi/autocursor.vim", profiles: ["full"] }); // disabledA plugin is enabled if any of its profiles entries match the active profiles.
If profiles is not set on a plugin, it is always enabled.
dvpm uses @std/log for logging:
import { setup, handlers } from "@std/log";
setup({
handlers: { console: new handlers.ConsoleHandler("DEBUG") },
loggers: { dvpm: { level: "DEBUG", handlers: ["console"] } },
});