Disclaimer: All I’ve done is write a few config files.
This is for you if:
- Your python IDE of choice is PyCharm
- You wish you had a command-line replacement for PyCharm on all the places you ssh into
- You wish you had access to at least some of the niceties of PyCharm when editing a one-off script, without having to create/import it into a new project.
- You are somewhat familiar with vim and can comfortably edit a single file on vim. You also know what a
PyCharm has worked wonderfully well for me, and the only time where I have to use something else is when I ssh into a server to put together a quick script. That something else tends to be vim, and this is an attempt to get vim as close to PyCharm as possible – especially the shortcuts so that I can work on vim the way I work on PyCharm (well, almost). The end result is still a far cry from PyCharm, but it makes navigating a codebase over ssh significantly less painful (at least for me)
But PyCharm can work over ssh
Yeah, but I don’t use PyCharm for one-off scripts. Besides, it pleases me to know that if I can ssh into the server (from an ipad, a phone, or a washing machine), I have an (approximate) IDE I can work on.
List of working approximations
- Sorta kinda uses the same colorscheme as PyCharm
- Toggles a project navigation sidebar (NERDTree) using
alt + 1. Approximates PyCharm’s
- Comment/uncomment multiple lines using
Ctrl+/, just like PyCharm
- Navigate to the definition of a method/variable using
Ctrl + Left clickor
Ctrl + b, just like PyCharm
- Jump to the previous location using
Alt + -. Approximates PyCharm’s
Ctrl + Alt + Left Arrow
- Fuzzy search for files using
Ctrl + o. Approximates PyCharm’s double shift press
- Search the entire code base using
Alt + f. Approximates PyCharm’s
Ctrl + Shift + f
- Edits made in the search results window are reflected on to the underlying file, just like PyCharm
- Syntax and linter errors show up as you type, just like PyCharm
- If you are editing files that are part of a git repository, there are indicators on the gutter to show added, modified and subtracted lines, just like PyCharm
F12brings up a terminal. Approximates PyCharm’s
Alt + F12
- Code folding using the minus (i.e
-) key. Approximates PyCharm’s
Ctrl + +
- Automatic file saves, just like PyCharm
- Rename methods/variables and automatically fix imports e.t.c across files, just like PyCharm
Why not just use python-mode?
I simply could not figure out the shortcuts that python-mode used. I thought it would be easier and more flexible if I install and configure the plugins myself.
- vim 8
- A Python virtualenv (or a conda environment). There’s some
pip installinvolved, though this is optional
Go here for the
Let’s start from a blank .vimrc
If you need a project-specific
.vimrc, see this. If not, everything goes into your
Let us begin by being able to see line numbers and some sort of syntax highlighting everywhere. Put these on your
" Some basic stuff set number set showmatch syntax on imap jj <Esc>
set showmatch is for highlighting matching parentheses – that’s useful. The last line maps double pressing the
j key in insert mode to
<Esc> – no more reaching for that far away Escape key using your pinky!
NERDTree for the sidebar
We’ll start our plugin-hoarding with NERDTree. With Vim 8, we can simply copy over a plugin directory to a certain place and Vim would just “pick it up” – there’s no need to use a plugin manager for achieving VimCharm. Create the necessary directory structure and clone NERDTree:
mkdir ~/.vim/pack/vimcharm/start/ -p git clone https://github.com/preservim/nerdtree.git ~/.vim/pack/vimcharm/start/nerdtree
Open some file (any file) on vim and type
:NERDTreeToggle – you should see the sidebar. Executing the same command again would close the NERDTree. PyCharm by default opens/closes the sidebar using
Ctrl + 1 . However, the terminal (and consequently Vim) cannot differentiate between 1 and
Ctrl + 1, so we’ll map this to
Alt + 1 instead. Before we do that, we need to determine what characters are sent by the terminal when we press the key combo. Simply run
cat without any arguments, and press
Alt + 1. You should see something like this:
You would also see that
Ctrl+1 produces the same character on
cat – as far as I know, there’s no way around this.
We need to map the character sequence for
Alt + 1 to
:NERDTreeToggle on our
set <A-1>=^[1 nmap <A-1> :NERDTreeToggle<CR>
Take care not to simply copy paste the character sequence on to your
.vimrc! That won’t work. You should open your
.vimrc on Vim, go to
insert mode, and press
Ctrl+v – this would put a caret under your cursor – now press
Alt + 1 and it should fill in the necessary characters there. Restart vim, and
Alt+1 should now open and close NERDTree.
Making it look like PyCharm
Gruvbox looks like the default PyCharm theme, kinda, so let’s get that:
git clone https://github.com/morhetz/gruvbox.git ~/.vim/pack/vimcharm/start/gruvbox
According to the installation page, we need to add this to our
autocmd vimenter * ++nested colorscheme gruvbox
Commenting and Uncommenting lines using Ctrl + /
We are going to use NERDCommenter for this. Clone it into the right directory, just as before:
git clone https://github.com/preservim/nerdcommenter ~/.vim/pack/vimcharm/start/nerdcommenter
Ctrl+/ cannot be directly mapped on your
.vimrc. So just like before, we insert the correct escaped character sequence into our
.vimrc by going into insert mode, pressing
Ctrl+v, and then pressing the desired keycombo (
Ctrl + / in this case).
" The part after "=" in the below line should be inserted using Ctrl+v while in insert mode and then pressing Ctrl+/ set <F13>=^_ noremap <F13> :call NERDComment(0,"toggle")<CR> " So that NERDCommenter can automatically decide how to comment a particular filetype filetype plugin on
We are telling Vim to map the character sequences that
Ctrl + / produces to the
F13 key, which probably does not exist on your keyboard, and then we map
F13 to the appropriate command to toggle comments. Restart Vim, open (or create) a python file and try
Ctrl + / while in normal mode – it should comment/uncomment the line.
Autocomplete on Vim does not feel as “fluid” as on PyCharm – for example, I haven’t managed to get it to work on imports – but it is still besser als nichts. Get jedi-vim:
git clone https://github.com/davidhalter/jedi-vim ~/.vim/pack/vimcharm/start/jedi-vim
Restart vim, and
Ctrl + space should already be giving you autocomplete suggestions. IMHO, jedi-vim displays too much information (what even is that bar thing that comes up on top?) – all I wanted was a simple autocomplete prompt. Put this on your
.vimrc to Make Autocomplete Great Again:
let jedi#show_call_signatures = 0 let jedi#documentation_command = "" autocmd FileType python setlocal completeopt-=preview
EDIT: I also installed supertab along with jedi:
git clone https://github.com/ervandew/supertab ~/.vim/pack/vimcharm/start/supertab
Go-to definition using Ctrl+click
This is something that jedi-vim already does – all we need are some
set mouse=a let g:jedi#goto_command = "<C-LeftMouse>" map <C-b> <C-LeftMouse>
The above lines enable mouse support on Vim, sets
Ctrl + left click as the combination for jedi’s goto command, and then recursively maps
Ctrl+b (which is also what PyCharm uses by default) to
Ctrl + left click as a keyboard-friendly alternative.
I also prefer the goto command to open a new tab when navigating to a different file. Here’s how to enable that, along with
Shift+k to move between tabs:
let g:jedi#use_tabs_not_buffers = 1 nnoremap J :tabp<CR> nnoremap K :tabn<CR>
Jump to previous location using Alt + minus
If you end up navigating multiple tabs away using
Ctrl+b, you can press
Ctrl+o repeatedly to jump back to your original position. Press
Ctrl+i to go in the other direction. These would come in handy if you have to quickly
gg to the beginning of the file to add an import – you can then press
Ctrl+o to go back to the line you were editing. I believe in PyCharm the default mappings for this are
Ctrl + Alt + Left arrow and
Ctrl + Alt + right arrow respectively. I remapped these to
Alt + - and
Alt + Shift + - (that would be in fact
Alt + _ ):
set <A-->=^[- noremap <A--> <C-O> set <A-_>=^[_ noremap <A-_> <C-I>
Remember that copy-pasting these won’t work and you will have to enter insert mode and press
Ctrl+v and then
Alt + -
Fuzzy file search
Fuzzy file search is what PyCharm does when you press the Shift key twice.
git clone https://github.com/kien/ctrlp.vim.git ~/.vim/pack/vimcharm/start/ctrlp
By default pressing
Ctrl+p should bring up the search prompt. We can’t map this to double shift (as in PyCharm) since vim can’t recognize
Shift key presses (unless it’s combined with a printable character). I decided to map this to
Ctrl + o instead (“o” for open), though this is not any better than the default setting. On your
let g:ctrlp_map='<C-O>' let g:ctrlp_cmd='CtrlPMixed'
The second line above specifies that the search should be over files, buffers, tags e.t.c – you may omit it if you do not want buffers to show up on your search.
ctrl+t on a search result will open it in a new tab.
Search everywhere and replace
One of the most useful features in PyCharm is the “search in project” dialog that
Ctrl + Shift + f brings up. For example, if I delete/rename a hard-coded string literal, this is the dialog that I would bring up to look for all occurrences of that string literal so that I can rename/delete all of them – right from the search window.
Instead of using the built-in vimgrep or making an external call to the ubiquitous grep, we are going to use ack because it excludes annoying things like gitignore and binaries from the search results by default.
We are going to map
Alt + F to a python-only smart-cased search with ack. Add these to your
set <A-F>=^[f nnoremap <A-F> :Ack! -S --python
Ctr+v and then press
Alt+F to get those escaped character sequence right. Also, there is an extra space after the
--python. Without it, the search term (eg: “foo”) that you type after pressing
Alt+F would end up being “–pythonfoo”.
Restart vim and press
Alt+f in a python file, enter your search term, and press enter. The results will be shown in a quick-fix window. Move your cursor to a search result and press enter to jump to that location. Press
t to open that location in a new tab. Either of these would shift the focus to the editor. Press
ctrl + w twice to shift focus back to the quick-fix window.
I usually use
/<pattern> to search within the file, but sometimes it’s useful to do a slightly fancier search. I’ve wired
Ctrl+f (the regular PyCharm find-in-this-file) to do a search within the open file:
nnoremap <C-F> :Ack! %<Left><Left>
By default, you cannot make any changes to the contents of the quick-fix window. In Pycharm, the search results are editable and the changes are reflected on the underlying file. We can pull this off using the quickfix-reflector:
git clone https://github.com/stefandtw/quickfix-reflector.vim.git ~/.vim/pack/vimcharm/start/quickfix-reflector.vim
That’s it! Now your edits on the search results should be reflected on the underlying files.
Spot syntax and linter errors as you type
The most annoying thing about writing Python on Vim, at least for me, was that the silly syntax errors I make won’t be discovered until I actually try to run my script – PyCharm usually catches these as you type. Let’s set this up on vim using ALE:
git clone https://github.com/dense-analysis/ale.git ~/.vim/pack/vimcharm/start/ale
You should also have a linter insalled. I use pylint, and a quick
pip install pylint does the trick. Restart vim and open a python file, and it should already be linting it as you type. Since ALE works asynchronously, there will be a slight delay (around a second) between you making a mistake and it being flagged on Vim – but in my opinion this is much better than a synchronous linting which freezes Vim, which is why I chose ALE instead of syntastic. However, the default ALE + Pylint combo is too whiny for my taste – I don’t want warnings about how I’m not writing a docstring for every single method; I have this on my
[MESSAGES CONTROL] disable=trailing-whitespace,missing-function-docstring,missing-module-docstring,no-else-return,miss ing-class-docstring,invalid-name
The above is far from how I would like linter to be configured, but it serves as an initial config. I also do not care for highlighting the offending word in a line – all I want is a non-invasive indication in the gutter. On your
let g:ale_set_highlights = 0
Show lines modified after the previous commit
Put vim-gutter at
~/.vim/pack/vimcharm/start/vim-gutter and restart vim. If you edit a file in a repo (or set up git on your current folder with
git init), the modified lines would be marked in the gutter. By default it takes 4 seconds for the appropriate mark to appear on your gutter – let us reduce it by putting this line in the
The end result is rather unflattering. ALE and git-gutter does not work well together – git-gutter’s modification marks are drawn over by the linter warnings, and in some-cases ALE ends up marking the wrong line with a warning. This thread suggests that there’s probably a way to get them to work the way I want, but I haven’t invested much time here.
Have a terminal handy
:term will open a terminal in a split window. Mapping this to
F12 is trivial, but we want to hide this terminal (instead of killing it) and bring it back again on pressing
F12. I could not get the “hide terminal on F12” part working, but I did figure out how to bring up a hidden terminal if it exists (or create a new terminal if it doesn’t) on pressing F12. Before we write a script for it, let’s go through the motions manually:
- Open Vim
:termto open a terminal in a split window
- Type something on the terminal
Ctrl+wand then type
:hideto hide our terminal window
- To show our hidden terminal, type
:sbuffer /bin/bash. This would open in a split window a buffer that has “/bin/bash” in its name. If you use something other than bash, you will have to change this string accordingly.
LoadTerminal() Vim script I wrote that would bring up an existing bash buffer if it exists, or create a new one if it doesn’t:
function! LoadTerminal() let bnr = bufnr('!/bin/bash') if bnr > 0 :sbuffer !/bin/bash else :term endif endfunction
Save it as
~/.vim/pack/vimcharm/start and add the following lines to your
source ~/.vim/pack/vimcharm/start/load_terminal.vim map <F12> :call LoadTerminal()<CR>
The annoying part here is that we can’t map key presses on the terminal window – so you’ll have to press
Ctrl + w and type
:hide to hide the terminal. Do let me know if you find a way to map this to a keystroke.
When you deal with large files, code folding (those tiny “-” signs that you click on PyCharm to collapse an entire class/method) is a godsend. Fortunately vim supports code folding right out of the box and all we need is this on our
set foldmethod=indent set foldlevelstart=99 nnoremap - za map _ zM map + zR
According to our mappings above, there are no folds (i.e every code block is “open”) when we open a file (this is what
Shift + - (i.e Shift and the minus key) will collapse all blocks, and
Shift + + will open all blocks. Use the minus key (i.e
-) to toggle collapsing a single fold. You might also want to check out this answer for a quick overview of what’s supported.
PyCharm saves the file as you type, sparing you from the hassle of having to press Ctrl+S across multiple tabs. We can get Vim to do this with vim-auto-save. Clone the repo to
~/.vim/pack/vimcharm/start/vim-autosave and add these to your
.vimrc to enable auto-save:
let g:auto_save = 1 let g:auto_save_events = ["InsertLeave", "TextChanged"]
A word of caution before we proceed – auto-saving can get quite annoying if enabled globally. I use project-specific vimrcs and use auto-save along with git – so if I accidentally auto-save something that I shouldn’t have, a
git diff is all I need to see what went wrong.
Refactor across files
Jedi-vim can do simple renaming, but I wanted to something more powerful. Enter ropevim. You need to
pip install rope, ropemode and ropevim. I have a miniconda environment set up, but you can install the packages to your global scope if you want to. We just need one file from the ropevim repo:
wget -O ~/.vim/pack/vimcharm/start/python_ropevim.vim https://raw.githubusercontent.com/python-rope/ropevim/master/ftplugin/python_ropevim.vim
Now let’s source it in our
Now add these to your
nnoremap <C-z> :RopeUndo<CR> set <A-z>=^[z map <A-z> :RopeRedo<CR> map <F6> :RopeRename<CR>
We have mapped
F6 to the rename operation, and
Alt + z to undo and redo respectively. Remember not to copy paste the mapping for
Alt + z, and press
Ctrl+v and then the desired keycombo to enter it in your
Restart Vim, open a python file, and try to rename a variable using
F6. You will get prompts to create a new ropevim project – press ‘y’ to create one locally, and then proceed to apply the rename. If you get an import error for ropevim when you start Vim, it’s probably because Vim uses the system python (which is probably a different version than the python in your virtualenv) and you pip-installed rope, ropemode and ropevim to a virtualenv. An alternative would be to do
conda install -c conda-forge vim on your anaconda/miniconda env so that the Vim in your env will use the local python (and hence your installed pip packages) instead of the system one.
If anything this exercise has made be better appreciate the work that the Jetbrains devs have put into their IDEs – all I wanted was a working subset of PyCharm’s basic features and what I got was a rather modest approximation. Do let me know (open an issue on Github?) if you managed to get any closer to PyCharm than this.