Vim for Python
Tags: vim, python
Published: 2020-01-08 13:05:00

I started working on a Python project about a year ago. It was very annoying at first, as I was trying to embrace the Python style and figure out my workflow and the tooling. It took me some time until I finally set the things appropriately.

In this article, I'd like to share how I made a productive Vim-based environment for working with Python.

![Here's How My Vim Looks Like](https://d3ep7iiirnq25a.cloudfront.net/assets/python-nvim-b380ce5d2ef80c6b7cbb6ef9966feec90f7632d334e87b6e3ffb0513ad17ab67.png)

**Disclaimer**: For some you, my setup may seem like a bit too much. You don't have to install every plugin listed in this article or copy every configuration line. Pick up what you like.

## Essentials

Let's start with a list of some general-purpose plugins which I find irreplaceable for any language.

- [vim-plug](https://github.com/junegunn/vim-plug) is a minimalistic plugin manager
- [scrooloose/nerdtree](https://github.com/scrooloose/nerdtree) to navigate the file tree
- [junegunn/fzf.vim](https://github.com/junegunn/fzf.vim) fuzzy search through the files (and much more, really)
- [tpope/vim-commentary](https://github.com/tpope/vim-commentary) ( or [scrooloose/nerdcommenter](https://github.com/scrooloose/nerdcommenter) ) — press `gcc` to comment out a line or `gc` to comment a selection in visual mode
- [liuchengxu/vista.vim](https://github.com/liuchengxu/vista.vim) which is a "tagbar" that learns from LSP servers

The other plugins I use include

- [jeetsukumaran/vim-pythonsense](https://github.com/jeetsukumaran/vim-pythonsense) provides some Python-specific text objects: classes, functions, etc
- [jiangmiao/auto-pairs](https://github.com/jiangmiao/auto-pairs) inserts closing quotes and parenthesis as you type

The colorscheme used on the screenshots is [joshdick/onedark.vim](https://github.com/joshdick/onedark.vim), which is inspired by the Atom theme.

## Syntax highlighting

Vim comes with syntax highlighting for many popular languages, including Python, though it is not always the best one.

There are several options to improve the default highlighting.

- [numirias/semshi](https://github.com/numirias/semshi), in my opinion, is the best. It works with Neovim only and requires the Python 3 support.
- [sheerun/vim-polyglot](https://github.com/sheerun/vim-polyglot) includes support for many languages including Python
- [python-mode/python-mode](https://github.com/python-mode/python-mode) is also a decent one although it comes with a lots of other stuff beside highlighting which I don't quite like

![Semshi (on the left) vs. the default one](https://d3ep7iiirnq25a.cloudfront.net/assets/python-hl-be538d29e5928a1cf9bf710cdf9a3df2ea195002a82315eca1d960af6f31e4c4.png)

My favorite color schemes (which I switch quite often) include [junegunn/seoul256.vim](https://github.com/junegunn/seoul256.vim) and [joshdick/onedark.vim](https://github.com/joshdick/onedark.vim) (the one on the screenshots).

## Indentation

You can set up indentation rules manually like this.

```viml
au BufNewFile,BufRead *.py
    \ set expandtab       |" replace tabs with spaces
    \ set autoindent      |" copy indent when starting a new line
    \ set tabstop=4
    \ set softtabstop=4
    \ set shiftwidth=4
```

A better alternative is [Vimjas/vim-python-pep8-indent](https://github.com/Vimjas/vim-python-pep8-indent) plugin. It does a much better job complying with the [PEP8](https://www.python.org/dev/peps/pep-0008/) style guide.

## Folding

Folding (`:help foldmethod`) is when you collapse junks of code so that they don't distract you from the part that you are looking at right now.

The best approximation is to use the folding method `indent` though it doesn't work perfectly all the time.

```viml
au BufNewFile,BufRead *.py \
  set foldmethod=indent
```

To toggle a fold you can press `za` (`:help fold-commands`), and I have it mapped to `Space` for convenience.

```viml
nnoremap <space> za
```

## Linting & Fixing

The fantastical [dense-analysis/ale](https://github.com/dense-analysis/ale) plugin can be used for linting (which essentially means checking for syntax errors) and auto-fixing extremely well. It's asynchronous, meaning that it won't block the UI while running an external linter, and it supports [a great range of languages](https://github.com/dense-analysis/ale/blob/master/supported-tools.md) and tools.

![Python + ALE = ❤️](https://d3ep7iiirnq25a.cloudfront.net/assets/python-ale-d8700af0b918c6101dd6f5855d56227adb17f992d2fa9b51484959871f469129.png)

ALE highlights problems with your code in the gutter. When you move the cursor to the problematic line, it shows the full error message at the bottom of the screen.

By default, ALE will use all possible linters it could find on your machine. Run `:ALEInfo` to see which linters are available and which are enabled.

It is better though to explicitly specify which ones you're going to use with a particular filetype:

```vim
let g:ale_linters = {
      \   'python': ['flake8', 'pylint'],
      \   'ruby': ['standardrb', 'rubocop'],
      \   'javascript': ['eslint'],
      \}
```

Some of the linters are also capable of fixing the problems in your code. ALE has a special command `:ALEFix` that fixes the whole file. So far, I'm only Google's [YAPF](https://github.com/google/yapf) as a fixer that formats the whole file when I press `F10` or save the current buffer.

```vim
let g:ale_fixers = {
      \    'python': ['standardrb'],
      \}
nmap <F10> :ALEFix<CR>
let g:ale_fix_on_save = 1
```

The last option is a huge time saver — it will automatically fix (and thus format) your file on save.

I also have a little piece of configuration that shows the total number of warnings and errors in the status line. Very convenient.

```vim
function! LinterStatus() abort
  let l:counts = ale#statusline#Count(bufnr(''))

  let l:all_errors = l:counts.error + l:counts.style_error
  let l:all_non_errors = l:counts.total - l:all_errors

  return l:counts.total == 0 ? '✨ all good ✨' : printf(
        \   '😞 %dW %dE',
        \   all_non_errors,
        \   all_errors
        \)
endfunction

set statusline=
set statusline+=%m
set statusline+=\ %f
set statusline+=%=
set statusline+=\ %{LinterStatus()}
```

And here are a couple of alternatives to ALE:

- [vim-syntastic/syntastic](https://github.com/vim-syntastic/syntastic) very popular one but synchronous which can cause significant lags in UI
- [neomake/neomake](https://github.com/neomake/neomake) asynchronous linting and make framework for Neovim/Vim (didn't try that one)

## Jedi

[Jedi](https://github.com/davidhalter/jedi) is a "language server", which simply means that it's a separate process that runs in background and analyses your code. Other clients (editors or IDEs) can connect to it and request some information, like completion options, or "go to definition" coordinates.

In order to use it, you need to install it with `pip install jedi`, and then also add a client.

One option is [davidhalter/jedi](https://github.com/davidhalter/jedi), which does a very decent job.

Here's what it can do:

- Press `ctrl + space` for the completion options
- `<leader>d` goes to definition
- `<leader>g` goes to assignment
- `K` shows the documentation
- and more

Recently I have switched to [neoclide/coc.nvim](https://github.com/neoclide/coc.nvim) and [coc-python](https://github.com/neoclide/coc-python). Coc is an "an intellisense engine" for Vim and Neovim and does a really good job when it comes to completion and communicating with language servers.

## Coc (Conquer of Completion)

To me, the selling point is the usage of the new `floating window` API of Neovim, which makes it very convenient.

`Coc-python` can use both Jedi and the Microsoft's Python Language Server. The first time you run it, it will ask you to install the components it needs (like a linter).

Coc provides (with the help of Jedi) some basic IDE capabilities like

- Going To definition

```viml
nmap <silent> gd <Plug>(coc-definition)
```

- Displaying documentation (in the floating window!)

```viml
nnoremap <silent> K :call <SID>show_documentation()<CR>
function! s:show_documentation()
  if (index(['vim','help'], &filetype) >= 0)
    execute 'h '.expand('<cword>')
  else
    call CocAction('doHover')
  endif
endfunction
```

- Smart rename (renames the exports across all files)

```
nmap <leader>rn <Plug>(coc-rename)
```

And of course the auto-complete menu (which you can see on the very first screenshot) appears as you type.

Basically, it's a very solid and comprehensive plugin which covers almost all of our needs.

[Read to the wiki](https://github.com/neoclide/coc.nvim/wiki) for more information about its capabilities.

The drawbacks of using Coc include:

- An expensive Node process that Coc needs to run in order to operate
- Own independent extension system and its own (JSON) configuration file
- The aspiration to do everything

Still, it does the job well, and I continue using it (until there's a better option).

## Where to go next?

Here are some other articles I learned from a lot:

- [VIM and Python – A Match Made in Heaven](https://realpython.com/vim-and-python-a-match-made-in-heaven/)
- [Vim for Python Development](https://stackabuse.com/vim-for-python-development/)
- [Editor Wars: VIM as a perfect Python IDE](https://dev.to/bezirganyan/editor-wars-vim-as-a-perfect-python-ide-19ne)

And some more articles in the similar vein from this website:

- [Vim for Ruby and Rails in 2019](https://www.vimfromscratch.com/articles/vim-for-ruby-and-rails-in-2019/)
- [Vim for JavaScript and React in 2019](https://www.vimfromscratch.com/articles/vim-for-javascript-and-react-in-2019/)

Also, don't forget to [Follow me on Twitter](https://twitter.com/janis_t) ;)

P.S.: I'm writing a book about the modern Vim setup for IDE-like experience. [You can check it out here](https://gumroad.com/l/vimfromscratchbook).