# python-mode: a Python IDE layer inside Vim, now linting with Ruff

> python-mode turns Vim into a Python editing environment with linting, breakpoints, refactoring and completion. It is a plugin for people who already live in Vim, and the 0.15.0 move to Ruff changed what you have to install.

**python-mode/python-mode** — Vim python-mode. PyLint, Rope, Pydoc, breakpoints from box.

- Repository: https://github.com/python-mode/python-mode
- Stars: 5,466 · Forks: 757
- Language: Vim Script
- License: LGPL-3.0
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/python-mode-python-mode

## What python-mode is for, and who it is not for

python-mode is a Vim plugin whose stated goal is to convert Vim into a Python IDE. The README lists the target capabilities plainly: syntax highlighting, virtualenv support, running the current file with <leader>r, adding and removing breakpoints with <leader>b, Python-specific motions and operators such as ]] and vaC, improved folding, running checkers through :PymodeLint, autofixing PEP8 errors with :PymodeLintAuto, documentation lookup with <leader>K, refactoring, intellisense completion and go-to-definition with <C-c>g.

The audience is narrow and the README does not pretend otherwise. The pitch is that traditional IDEs expose only a subset of Vim's editing model, so a Python developer who has already internalised Vim keeps Vim and adds Python tooling on top. If you are not a Vim user, none of this is a reason to switch.

The project also says it needs contributors, and the README carries that line near the top. That is worth reading as a statement about maintenance capacity rather than as marketing. The last push to the repository was on 2026-05-24, so the codebase is not abandoned, but the only release listed is 0.7.0b from 2013-12-01, which means the changelog and the develop branch are where the real history lives, not the release page.

## How the plugin is put together: submodules, Vim runtime paths and Ruff

The architecture visible in the repository is a conventional Vim plugin layout: after/, autoload/, ftplugin/, plugin/, syntax/ and doc/ directories, plus a pymode/ directory holding the Python side. The README states the project is 96.1% written in Python, which is unusual for a Vim plugin and explains why a Python interpreter inside Vim is a hard requirement rather than a nicety.

Third-party Python libraries are not vendored. Since 2017-11-19 the project uses git submodules, and the README instructs you to run `git submodule update --init --recursive` inside the plugin folder. The current tree keeps three submodules: rope, pytoolconfig and tomli. Rope is the refactoring engine behind the code-refactoring features; the other two are dependency plumbing.

The most consequential design change is the 0.15.0 decision to use Ruff for linting and formatting. According to the README, Ruff replaced seven legacy submodules (pyflakes, pycodestyle, mccabe, pylint, pydocstyle, pylama, autopep8), taking the submodule count from 13 to 3 and reducing repository size. The trade-off is explicit: linting is no longer self-contained. Ruff is installed separately with `pip install ruff`, so the plugin's behaviour now depends on a tool you manage outside your plugin manager. The README also references `./scripts/verify_ruff_installation.sh` for checking that installation.

## Installing python-mode and running your first lint

The README documents several installation routes. With vim8's native package support, the plugin goes under a pack directory and must be cloned recursively so the submodules arrive with it. The README notes that Windows users need to add `-c core.symlinks=true`.

```bash
cd ~/.vim/pack/python-mode/start
git clone --recurse-submodules https://github.com/python-mode/python-mode.git
cd python-mode
```

If you use vim-plug instead, the README gives a lazy-loading line that only activates the plugin for Python buffers and pins the develop branch. Note that a plugin manager cloning this way still leaves you responsible for the submodules.

```vim
Plug 'python-mode/python-mode', { 'for': 'python', 'branch': 'develop' }
```

Before anything works, two conditions have to hold. Vim must be 7.3 or newer with +python3 support, and the README adds that `--with-features=big` is needed if you want `g:pymode_lint_signs`. Filetype plugin and filetype indent must also be enabled, since the plugin's ftplugin and indent files are what wire Python buffers to its behaviour.

Ruff is a separate install and is required for linting and formatting. The README's verification step is a script in the repository.

```bash
pip install ruff
./scripts/verify_ruff_installation.sh
```

With a Python file open, `:PymodeLint` runs the checker and `:PymodeLintAuto` applies autofixes. For documentation on any of the mappings, `:help pymode` is the in-editor reference the README points at.

## The Ruff migration is the real upgrade cost

Upgrading across the 0.15.0 boundary is not a plugin-manager refresh. Seven linters and formatters used to arrive with the plugin; now one external binary does that work, and the README directs readers to MIGRATION_GUIDE.md for the details. If your workflow depended on a specific pylint check or a pydocstyle rule that Ruff does not implement the same way, the plugin will not warn you. The diagnostics simply change.

The practical consequence is that two version axes now move independently. Your plugin checkout can be pinned to develop while the Ruff binary on your PATH is whatever pip last installed, and the lint output you see is a product of both. The README does not document a pinned Ruff version, so nothing in the documentation says which Ruff release the current code was tested against. Teams that care about reproducible lint results should treat the Ruff version as part of their environment definition and check MIGRATION_GUIDE.md before moving.

There is a second, quieter cost: the recursive clone requirement. Skip `--recurse-submodules` and you get a plugin directory that looks complete but is missing rope, which is what the refactoring commands rely on. The README calls this out for new users specifically, which suggests it has bitten people.

## What python-mode does not do

The plugin does not bundle a language server and the README makes no claim that it does. Completion and go-to-definition come from the plugin's own machinery rather than from an LSP client, so if you expect the same completion quality and protocol-level diagnostics you get from a dedicated LSP setup, that expectation is not supported by the documentation.

Python 2 is gone. The README states that from 2019-12-14 python-mode dropped python2 support and that anyone who still needs it should look at the last-py2-support branch or tag. That is a dead end for maintenance, not a supported configuration.

The supported interpreter list is also specific rather than a range: 3.10.13, 3.11.9, 3.12.4 and 3.13.0. Those exact versions appear in both the feature list and the Docker testing environment. Nothing in the README says a 3.12.1 interpreter is tested, so anyone on a distro Python that is not one of those four should verify linting and completion themselves before rolling it out across a team.

Finally, the plugin assumes a Vim built with +python3. On distributions that ship vim-tiny or a Vim compiled without Python support, no amount of plugin configuration helps. The Docker setup in the repository installs vim-nox for exactly this reason.

## python-mode versus an LSP-based Vim setup

The obvious alternative for a Vim user today is a language server client plus a Python language server, which inverts the design. python-mode embeds Python tooling and a refactoring engine into the plugin itself and drives them from Vim script and Python; an LSP setup keeps the editor thin and puts analysis in a separate process that speaks a standard protocol. The difference shows up in what you can swap. With LSP you change servers without touching your editor configuration. With python-mode, the 0.15.0 migration demonstrates the opposite: replacing the linters meant changing the plugin's internals, and the project had to ship a migration guide and drop seven submodules to do it.

The LSP route also decouples diagnostics from the plugin's release cycle, which matters given that python-mode's only listed release is from 2013 and development happens on the develop branch. What python-mode keeps that a generic LSP client does not automatically give you is the Python-specific editing layer: the motions and operators such as ]] and vaC, the folding rules, the indentation, and mappings like <leader>r and <leader>b. Those are Vim-side features, and they are the part of this plugin least likely to be reproduced by installing a server.

A reasonable read: if your complaint is diagnostics and completion, a language server addresses it more directly. If your complaint is that Vim does not feel like a Python editor, that is the gap python-mode was written to fill.

## Licence, testing and what the repository expects of contributors

python-mode is licensed under LGPL-3.0, and the repository carries the COPYING file at the top level. LGPL is a copyleft licence with a linking-oriented exception, which matters here because the plugin loads Python libraries such as rope. Whether your particular distribution or embedding arrangement satisfies the licence terms is a question for your own legal review; the README does not discuss it. What can be said from the repository is only that the licence identifier is LGPL-3.0 and the full text ships in COPYING.

For contributors, the testing story is Docker-based and documented in README-Docker.md. The repository provides docker-compose.yml with two services: python-mode-tests, which runs the test suite by default, and python-mode-dev, which drops into an interactive bash shell with the same image. The Dockerfile takes a PYTHON_VERSION build argument and installs vim-nox, git, curl, coverage and ruff, then symlinks the plugin into a Vim pack directory and installs Vader.vim for the test framework.

The README gives two entry points for running tests, one for a single version and one for the full matrix.

```bash
./scripts/user/run-tests-docker.sh 3.11
./scripts/user/test-all-python-versions.sh
```

Running the default with no argument uses Python 3.13.0, per the README. Note that the compose file's own default for PYTHON_VERSION is 3.11, so the script and the compose default do not agree; the script is the documented interface.

## Conclusion

Adopt python-mode if you already work in Vim, want Python-specific motions, breakpoints and linting in the same window, and are willing to keep Ruff installed outside the plugin. Do not adopt it if you need an editor that ships its own language server, or if you are stuck on Python 2: the project dropped python2 support on 2019-12-14 and points at the last-py2-support branch. Before committing, verify two things on your own machine: that `vim --version` reports +python3, and that `:PymodeLint` runs without errors after `pip install ruff`.

## FAQ

### How do I install python-mode in Vim?

Clone it recursively into a Vim package directory or add it through a plugin manager such as vim-plug, then make sure filetype plugin and filetype indent are enabled. The README stresses the recursive flag because the plugin relies on git submodules such as rope.

### Does python-mode still support Python 2?

No. The README states that from 2019-12-14 python-mode dropped python2 support, and that anyone who still needs it should look for the last-py2-support branch or tag.

### What do I need to install besides the plugin for linting?

Ruff. From version 0.15.0 the plugin uses Ruff for linting and formatting, replacing pyflakes, pycodestyle, mccabe, pylint, pydocstyle, pylama and autopep8, and the README says to install it with pip install ruff.

### Which Python versions does python-mode support?

The README lists 3.10.13, 3.11.9, 3.12.4 and 3.13.0, and the Docker testing environment uses the same set with 3.13.0 as the default.

### What Vim version does python-mode require?

Vim 7.3 or newer with +python3 support. The README adds that a Vim built with --with-features=big is needed if you want g:pymode_lint_signs.

## Sources

- [Issues](https://github.com/python-mode/python-mode/issues)
- [License: LGPL-3.0](https://github.com/python-mode/python-mode/blob/develop/LICENSE)
- [python-mode/python-mode on GitHub](https://github.com/python-mode/python-mode)
- [README](https://github.com/python-mode/python-mode/blob/develop/README.md)
- [Releases](https://github.com/python-mode/python-mode/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/python-mode-python-mode
