Thank you for considering contributing to the Vim Plugin Manager project! This document outlines the process for contributing to this project and helps ensure a smooth collaboration experience.
- Code of Conduct
- Getting Started
- Development Workflow
- Pull Request Process
- Coding Standards
- Architecture Overview
- Testing
- Documentation
- Issue Reporting
This project adheres to a code of conduct that expects all participants to be respectful, inclusive, and considerate. By participating, you are expected to uphold this code. Please report unacceptable behavior to gke@6admin.io.
The project targets Linux only (Debian, Ubuntu, Arch, Gentoo, RHEL/AlmaLinux/Rocky). Windows and macOS are not supported.
The minimum Vim version is 8.2 (with +job and +channel). This floor
is set by RHEL 9 / AlmaLinux 9 / Rocky 9, which ship Vim 8.2.2637, and will
not be raised without a concrete reason. The codebase uses no Vim 9.0+
features; a vim9script migration is explicitly deferred (dominant cost is
git/network I/O, not script execution). Do not change the guard
if v:version < 802 in plugin/plugin_manager.vim.
- Fork the repository on GitHub.
- Clone your fork locally:
git clone https://github.com/yourusername/vim-plugin-manager.git cd vim-plugin-manager - Add the upstream repository as a remote:
git remote add upstream https://github.com/username/vim-plugin-manager.git
- Create a branch for your work:
git checkout -b feature/your-feature-name
The project uses GitHub Flow + tags: a single protected branch (main)
and release tags. There is no develop branch and no maintenance branches
(vX.Y): fixes always target the current release.
main: stable code, protected by a ruleset (required CI checks, no force push, conversations must be resolved). Maintainers bypass this protection.- External contributors open pull requests from their fork.
- Maintainers work on local
feature/*,fix/*,chore/*,hotfix/*branches and merge them with--no-ffto preserve branch topology. - Releases are tags on
main(vX.Y.Z), see Releases.
- Fork the repository on GitHub, clone your fork and add upstream:
git clone https://github.com/yourusername/vim-plugin-manager.git cd vim-plugin-manager git remote add upstream https://github.com/log0u7/vim-plugin-manager.git - Branch from the latest upstream
main(one branch per topic, no long-lived branches):git fetch upstream git checkout -b fix/my-bug upstream/main # or feature/... docs/... chore/... - Make your changes, following the coding standards and the TDD guidance.
- Test your changes (see Testing).
- Commit with Conventional Commits:
Format:
git commit -m "feat: add support for XYZ"type(scope): subject(scope is optional). Valid types:feat:,fix:,docs:,test:,refactor:,style:,chore:,ci:,build:. Recommended scopes:core,async,ui,git,cmd,api,github,gitlab,deps. - Push to your fork and open a pull request (see
Pull Request Process):
git push origin fix/my-bug
- Rebase on
upstream/mainif asked; the maintainer squash-merges the PR (one commit per PR, PR title used as commit message, branch deleted automatically).
- Branch from
main:git checkout -b feature/my-feature main. - Commit in granular Conventional Commits.
- Merge back with
--no-ffto preserve branch topology:git checkout main git merge --no-ff feature/my-feature
- Push directly (maintainers bypass the ruleset) and release with
make tag VERSION=vX.Y.Zwhen appropriate.
Tests are written test-first whenever practical:
- Red: write the failing Vader test first. For a bug fix, the test must reproduce the bug (regression test) before touching the code.
- Green: write the minimal change that makes the test pass.
- Refactor: clean up with the tests staying green.
When this is not practical (pure docs, CI plumbing, UI cosmetics, smoke
fixtures), say so in the PR's "How was this tested?" section. Prefer tests
that run offline (local git fixtures, no network): see tests/update.vader
and tests/pin.vader for the fixture patterns.
Releases are automated via .github/workflows/release.yml:
- Update
CHANGELOG.mdwith notes for the new version (the single source of truth for versioning). - Commit and push to
main:git commit -m "chore: bump to vx.y.z" git push - Tag the release and push the tag:
Pushing a
git tag vX.Y.Z git push origin vX.Y.Z
vX.Y.Ztag to GitHub triggers the release workflow, which buildsvim-plugin-manager-vX.Y.Z.tar.gzviamake archiveand publishes a GitHub Release with the asset and auto-generated release notes.
Note: Per-file Version: headers are not maintained. The canonical version
is the Git tag and the CHANGELOG entry.
- Open the PR from your fork branch; fill out the pull request template completely (Summary, Type of change, Related issue, How was this tested, Checklist).
- Link any relevant issues using GitHub keywords (e.g., "Fixes #123").
- Ensure all CI checks are green (
ci-greenaggregates the whole suite) - the ruleset onmainblocks the merge until they are. - Wait for a maintainer review; address or justify findings (push to the same branch, stale conversations get dismissed).
- All review threads must be resolved. The maintainer squash-merges:
your PR becomes a single commit on
main(use the PR title as the Conventional Commit message), and your branch is deleted automatically.
GitHub Actions versions are kept current by Dependabot (weekly, grouped). The bot only prepares the PRs - they are handled human-in-the-loop: a maintainer reviews, approves, then squash-merges. There is no auto-merge anywhere in this repository. Review the workflow diff (action refs and permissions) with the same care as any code change.
- Follow existing code style and structure.
- For Vimscript:
- Use 2-space indentation.
- Keep lines under 100 characters when possible.
- Use snake_case for functions and variables.
- Prefix internal functions with
s:. - Prefix plugin-specific functions with
plugin_manager#. - Document functions with comments.
- Use Vim script's native idioms.
The plugin is designed using a modular architecture that follows the principles of separation of concerns and single responsibility. Understanding this architecture will help you contribute effectively.
The project is organized into several key components:
-
Plugin Entry Point
plugin/plugin_manager.vim: Defines commands, initializes global variables, and provides the main entry point function.
-
Command Dispatcher
autoload/plugin_manager/cmd.vim: Parses command arguments and dispatches to specialized command modules.
-
Public API Façade
autoload/plugin_manager/api.vim: Provides a unified API for all plugin operations.
-
Core Functionality
autoload/plugin_manager/core.vim: Error handling foundation (throw,handle_error,parse_error).autoload/plugin_manager/core/log.vim: Log management (debug,trace,view,clear, rotation).autoload/plugin_manager/core/cache.vim: Update check cache (read,write, TTL).autoload/plugin_manager/core/util.vim: Paths, config, URL parsing, filesystem, plugin options.autoload/plugin_manager/git.vim: Abstracts all Git operations and submodule management. Injectsgit -C <dir>for directory scoping (git-only; usecore/util.vim'srun_in_dirfor arbitrary shell commands).autoload/plugin_manager/async.vim: Provides non-blocking async operations using Vim's job/channel API.autoload/plugin_manager/ui.vim: Handles user interface, sidebar rendering, and progress indication.
-
Command Modules
autoload/plugin_manager/cmd/*.vim: Contains implementation of specific commands:add.vim: Plugin installation logic.remove.vim: Plugin removal operations.list.vim: Plugin listing and status reporting.update.vim: Plugin update operations.backup.vim: Configuration backup operations.restore.vim: Plugin restoration operations.helptags.vim: Helptags generation.reload.vim: Plugin reloading operations.status.vim: Plugin status reporting.declare.vim: Declarative plugin configuration.remote.vim: Remote repository management.
-
Utility Files
ftplugin/pluginmanager.vim: Sets buffer configuration and key mappings.syntax/pluginmanager.vim: Defines syntax highlighting for the plugin interface.
graph TD
subgraph User["User input"]
PM[":PluginManager (cmd)"]
PMR[":PluginManagerRemote (url)"]
DECL[":Plugin / :PluginBegin / :PluginEnd"]
SB["Sidebar keys (q l u s S c b r R ?)"]
end
subgraph Dispatch["Dispatch and API"]
DISP["cmd.vim - cmd#dispatch()"]
API["api.vim - public facade"]
end
subgraph Commands["Command modules - cmd/*.vim"]
CMDS["add - remove - update - status - check
list - backup - restore - helptags - reload
declare - remote"]
end
subgraph Services["Core services"]
GIT["git.vim - Git and submodule ops"]
ASYNC["async.vim - Vim job/channel queue"]
UI["ui.vim - sidebar, spinners, glyphs"]
end
subgraph Ext["External"]
SUB["Git submodules / .gitmodules"]
JOBS["Vim job/channel processes"]
BUF["pluginmanager buffer (filetype + syntax)"]
end
subgraph Foundation["Foundation - used by every active layer"]
CORE["core.vim - errors (throw, handle_error, parse_error)"]
LOG["core/log.vim - log management"]
CACHE["core/cache.vim - update check cache"]
UTIL["core/util.vim - paths, config, URL parsing, options"]
end
classDef foundation fill:#f4f0ff,stroke:#6b46c1,stroke-width:2px
class CORE,LOG,CACHE,UTIL foundation
PM --> DISP
SB --> DISP
PMR --> API
DECL --> API
DISP --> API
API --> CMDS
CMDS --> GIT
CMDS --> ASYNC
CMDS --> UI
GIT --> SUB
ASYNC --> JOBS
UI --> BUF
DISP -.->|"core#throw / handle_error"| CORE
CMDS -.->|"core#throw / handle_error / util#get_config"| CORE
GIT -.->|"core#throw / log#write"| CORE
ASYNC -.->|"log#write / log#debug"| LOG
UI -.->|"util#get_config"| UTIL
Dotted arrows indicate dependency on core.vim and its sub-modules;
every active layer uses them but they are not part of the primary data flow.
:PluginManagerRemote bypasses the dispatcher and calls api#add_remote
directly - it is a dedicated command, not a sub-command of :PluginManager.
sequenceDiagram
actor User
participant D as cmd#dispatch
participant A as api.vim
participant U as update.vim
participant As as async.vim
participant G as git.vim
participant UI as ui.vim
User->>D: :PluginManager update [name]
D->>A: api#update(name)
A->>U: update#execute(name)
U->>UI: open_header / start_operation per module
loop for each module
U->>As: async#start_job("git fetch origin")
end
Note over As: Vim job/channel queue<br/>(max_concurrent_jobs slots)
loop on each fetch callback
As-->>U: on_fetch_complete(result)
U->>G: collect_status_local(path)
G-->>U: has_updates / behind / branch
alt has updates
U->>As: async#start_job("git pull")
As-->>U: on_update_complete(result)
U->>G: head_changed(path, before)
U->>UI: complete_operation "ok"
U->>U: helptags (silent)
else up-to-date or custom branch
U->>UI: complete_operation "skip" / "info"
end
end
U->>G: git commit -am "Update Modules" (if auto_commit)
U->>UI: footer with summary
- User commands are processed through
:PluginManagerwhich callsplugin_manager#cmd#dispatch(). - The dispatcher parses arguments and routes to the appropriate command module.
- Command modules implement specific operations using the core functionality.
- UI feedback is provided through the UI module.
- Git operations are abstracted through the Git module.
- Asynchronous operations are handled through the Async module.
Errors follow a structured, 4-field format:
PM_ERROR:component:CODE:messagefor internal errors, whereCODEis one of the component-specific codes defined ins:error_typesincore.vim.- Raise errors with
plugin_manager#core#throw(component, code, message)rather than a barethrow 'PM_ERROR:...'. - Catch and present them with
plugin_manager#core#handle_error(v:exception, component). - The Core module provides utilities for creating, handling, and formatting errors.
- UI error display is handled through the UI module.
When adding new features:
- Determine the Appropriate Module: New functionality should be placed in the most relevant module, or create a new one if needed.
- Follow the API Pattern:
- Internal functions should be prefixed with
s:. - Public functions should follow the naming pattern
plugin_manager#modulename#functionname().
- Internal functions should be prefixed with
- Use Core Utilities: Leverage existing utilities from the Core, Git, UI, and Async modules.
- Add Command Implementation: Place new commands in the cmd/ directory.
- Update API: Add API functions to api.vim for new commands.
- Add Documentation: Update help docs and README.md with new functionality.
- Update Command Handling: Update the command dispatcher in cmd.vim.
The plugin uses global configuration variables defined in plugin/plugin_manager.vim, and accessed via plugin_manager#core#util#get_config():
g:plugin_manager_vim_dir: Base directory for Vim configuration.g:plugin_manager_plugins_dir: Directory for storing plugins.g:plugin_manager_start_dir: Directory for auto-loaded plugins.g:plugin_manager_opt_dir: Directory for optional (lazy-loaded) plugins.g:plugin_manager_vimrc_path: Path to vimrc file.g:plugin_manager_sidebar_width: Width of the sidebar UI.g:plugin_manager_default_git_host: Default Git host for short plugin names.g:plugin_manager_fancy_ui: Controls whether to use Unicode symbols in the UI.g:plugin_manager_enable_logging: Enable/disable error logging.g:plugin_manager_max_log_size: Maximum log file size before rotation.g:plugin_manager_log_history_count: Number of log files to keep in rotation.g:plugin_manager_spinner_style: Style for spinners in async operations.g:plugin_manager_pull_strategy: Git pull strategy for updates (ff-only,merge,rebase).g:plugin_manager_auto_commit_on_update: Auto commit after successful updates.g:plugin_manager_max_concurrent_jobs: Maximum concurrent async jobs.g:plugin_manager_job_timeout: Default timeout (seconds) for async jobs.g:plugin_manager_debug_mode: Enable additional debug information.g:plugin_manager_trace_commands: Log all git commands to the debug log.g:plugin_manager_check_on_startup: Check for plugin updates onVimEnter(opt-in, default off).g:plugin_manager_check_interval: Hours between background update checks (cache TTL).g:plugin_manager_auto_update: Auto-install available updates on startup (opt-in, default off).
When adding new configuration options, follow this pattern and provide sensible defaults.
The project uses Vader for automated
tests, run in CI on both GitHub Actions (.github/workflows/test.yml) and
GitLab CI (.gitlab-ci.yml).
Run the suite locally:
# Interactive (local, with TUI)
make test
# Headless (same output as CI, clean plain text)
make test-civader.vim is cloned automatically at the pinned SHA on first run. There is also a real async smoke test that runs Vim under a pty:
make test-async # Requires util-linux 'script' (standard on Linux)vader.vim is cloned automatically at the pinned SHA on first run. To use an
existing clone at a custom path: make test-ci VADER_DIR=./vader.vim. Clean
artifacts with make clean.
The test target runs vim -Nu .vaderrc.vim -c 'Vader! tests/*.vader'
(interactive terminal). The test-ci target runs the same via vim -es
(headless/ex mode) and produces clean plain-text output.
The CI matrix runs the Vader suite on:
| Distribution | Vim version | Notes |
|---|---|---|
| AlmaLinux 9 | 8.2.2637 | RHEL 9 proxy - exercises the real 8.2 floor |
| AlmaLinux 10 | 9.1.083 | RHEL 10 proxy |
| Debian 12 Bookworm | 9.0.1378 | Debian oldstable |
| Debian 13 Trixie | 9.1.1230 | Debian stable |
| Ubuntu 24.04 LTS | 9.1.0016 | |
| Ubuntu 26.04 LTS | 9.1.2141 | |
| Arch Linux | 9.2.0735 | Rolling, the ceiling |
| Gentoo | 9.1.1652 | Non-blocking (allow_failure: true) |
VimScript can be linted locally with vim-vint
if desired (it is no longer run in CI). The configuration lives in
.vintrc.yaml at the repository root. Only correctness policies (undefined
variables, set nocompatible) are enabled as errors; style policies are left
off to avoid conflict with the project's existing conventions.
To run vint locally (requires Python):
pip install vim-vint
vint -e autoload/ plugin/ ftplugin/ syntax/When adding new features or fixing bugs:
- Follow the TDD guidance: failing test first, minimal implementation, then refactor.
- Add or update Vader tests under
tests/. Prefer tests that do not require network access (mock with local fixtures). - Verify your changes work correctly in Vim 8.2+ on Linux (Neovim and Windows are not supported).
- Test all related functionality to ensure no regressions.
- Ensure the suite passes (
make test-ci) before opening a PR.
- Update documentation for any changed functionality.
- Document new features in:
- The README.md file
- The plugin's help documentation (doc/plugin_manager.txt)
- Code comments for functions
Documentation should be clear, concise, and include examples where appropriate.
Bugs and feature requests go through the issue templates (forms):
- Bug report: description, reproduction steps, expected/actual behavior,
Vim version, distro, git version, manager version,
PM_ERROR:log lines. - Feature request: problem to solve, proposed solution, alternatives, and whether you want to implement it yourself.
Please do not open blank issues; the forms capture everything a maintainer needs to triage quickly.
Understanding the project's complete structure will help you contribute effectively:
.
├── autoload/
│ └── plugin_manager/
│ ├── api.vim # Public API facade
│ ├── async.vim # Non-blocking job queue (Vim job/channel)
│ ├── cmd.vim # Command dispatcher and completion
│ ├── core.vim # Errors, logging, paths, config, URL parsing
│ ├── git.vim # Git and submodule operations
│ ├── ui.vim # Sidebar rendering, spinners, glyphs
│ └── cmd/ # Command implementations
│ ├── add.vim # Plugin installation
│ ├── backup.vim # Configuration backup
│ ├── check.vim # Update detection and notifications
│ ├── declare.vim # Declarative plugin configuration
│ ├── helptags.vim # Help tag generation
│ ├── list.vim # Plugin listing
│ ├── reload.vim # Plugin reload
│ ├── remote.vim # Remote repository management
│ ├── remove.vim # Plugin removal
│ ├── restore.vim # Plugin restoration from .gitmodules
│ ├── status.vim # Plugin status reporting
│ └── update.vim # Plugin update
├── doc/ # Vim help documentation
│ └── plugin_manager.txt # :help plugin-manager
├── ftplugin/ # Filetype plugin
│ └── pluginmanager.vim # Buffer settings and key mappings
├── plugin/ # Plugin entry point
│ └── plugin_manager.vim # Config defaults and command definitions
├── syntax/ # Syntax highlighting
│ └── pluginmanager.vim # Sidebar syntax highlighting
├── tests/ # Vader test suite
│ ├── async.vader # Async jobs: shell_argv, queue, sync fallback
│ ├── backup.vader # Backup: commit and push to remote
│ ├── basic.vader # Plugin load, commands, default config
│ ├── cache.vader # Update-check cache read/write/TTL
│ ├── check.vader # Update detection and silent mode
│ ├── core.vader # Core: URL parsing, options, errors
│ ├── declare.vader # Declarative Plugin/Begin/End blocks
│ ├── dispatch.vader # Command dispatch and tab completion
│ ├── gitmodules.vader # .gitmodules parsing and module lookup
│ ├── health.vader # Health check statuses (characterized)
│ ├── perf.vader # collect_status_local subprocess budget
│ ├── remove.vader # Plugin removal and ambiguity guard
│ ├── restore.vader # Submodule restoration from .gitmodules
│ ├── status.vader # Status block rendering
│ ├── ui.vader # Sidebar rendering, operations, glyphs
│ └── update.vader # Update flows and stash safety
├── .github/workflows/test.yml # GitHub Actions CI
├── .gitlab-ci.yml # GitLab CI
├── AGENTS.md # Guidance for AI agents and tooling
├── CHANGELOG.md # History of changes and versions
├── CONTRIBUTING.md # Contribution guidelines (this file)
├── LICENSE # MIT license
├── Makefile # Build, test, and version management
└── README.md # Project overview and usage
By contributing to this project, you agree that your contributions will be licensed under the same MIT License that covers the project.
Thank you for contributing to Vim Plugin Manager! Your efforts help make this project better for everyone.