Skip to content

yukimemi/dvpm

Repository files navigation

dvpm - Denops Vim/Neovim Plugin Manager !

DeepWiki

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

Requirement

Setup

1. Bootstrap denops.vim

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') , '[/\\]$', '', '')

2. Configure deno.json

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/dvpm

3. Write main.ts

Place 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.

API

Dvpm.begin

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.
};

Dvpm.end

public async end(): Promise<void>

Adds plugins to runtimepath and sources plugin/*.vim and plugin/*.lua.

Dvpm.add

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: build is called after every update even if nothing changed, so always check info.isInstalled || info.isUpdated before running heavy build steps.

Dvpm.cache

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.

Dvpm.list

public list(): Plugin[]

Returns the list of all registered plugins.

Commands

: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)

Cache setting

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.

Lazy Loading

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");
    }
  },
});

Hook execution order

  1. init / initFile — always at startup (Dvpm.end()), before runtimepath is set (ignores lazy)
  2. before / beforeFile — after the plugin is added to runtimepath, before sourcing plugin/*.vim
  3. after / afterFile — after the plugin is added to runtimepath and sourcing plugin/*.vim
  4. build — after install or update

Autocmd

Available autocmd events

Lifecycle

  • DvpmBeginPre / DvpmBeginPost
  • DvpmEndPre / DvpmEndPost
  • DvpmInstallPre / DvpmInstallPost
  • DvpmUpdatePre / DvpmUpdatePost
  • DvpmCacheUpdated

Per-plugin

  • 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:", "", "")`,
);

Profile setting

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"] });     // disabled

A 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.

Debug logging

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"] } },
});

About

dvpm - Denops Vim/Neovim Plugin Manager

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages