Using Vim as secondary editor

Dec 10, 2019

When working with source code, I often need some advanced editing capabilities, such as sorting a bunch of lines by alphabetical order or repeating a tedious edit multiple times throughout a file. Vim is a text-editor with powerful keyboard shortcuts that usually enable me to do exactly what I want in only a few keystrokes. I don't use Vim as my primary text editor, but when I need to go full power, I usually copy the text in Vim and do my edits there.

To make this process seamless, I created a simple keyboard shortcut (win+c) that takes the currently selected text and opens a new Vim window with it. When I close the window, the edited text is automatically put in my clipboard so that I can use ctrl+v to paste it where I need. Editing any text in Vim is now only two keyboard shortcuts away.

In this article, I'll first introduce how I built this keyboard shortcut, and then showcase some recent edits that Vim made possible and easy.

A Keyboard shortcut

To define the new keyboard shortcut, I created a bash script and mapped the keys win+c to trigger this script.

Since I use i3wm as my window manager, it was enough to add this line to my i3config file:

i3config
bindsym $mod+c exec --no-startup-id ~/scripts/edit-selection-in-vim.sh

This will trigger the bash script edit-selection-in-vim.sh, reproduced below:

scripts/edit-selection-in-vim.sh
#!/bin/bash

# Take the currently selected text and write it to a temporary file
xclip -o > /tmp/clipboard

# Edit this temporary file in a new Vim window
gvim -f /tmp/clipboard

# Put the content of the edited file into the clipboard
cat /tmp/clipboard | xclip -sel clip

# And remove the temporary file
rm -f /tmp/clipboard

Example of powerful edits

In this section, I'll quickly showcase two examples where Vim and this shortcut served me well. The first one is from yesterday and the second one from this afternoon.

How many lines per method?

I recently worked on refactoring a graphical application written in Python. While writing a report summarizing my work, I wanted to give an overview of a class by showing which methods it contained, and how many lines each method was.

To be clear, let's take a dummy code example. From a typical class definition in python, like this one:

example_class.py
class A:
    def method1(self, arg):
        print('Some code here')
        print('Some more code there')

    def method2(self, arg1, arg2):
        print('And even more code')
        print('Python is great')
        print('So is Vim')

I wanted to produce this output:

example_class.py
class A:
    def method1(self, arg): # 2 lines
    def method2(self, arg1, arg2): # 3 lines

It's an easy thing to do when there's only two methods but my class contained over 25 methods and was a few hundreds of lines long. Here's the real output that I put in my report:

class DeepflyGUI(QWidget):

    # methods to initiate and setup the GUI
    def __init__(self):  # 3 lines

    # methods called when the user sends input
    def onclick_camera_order(self):       # 9 lines
    def onclick_pose2d_estimation(self):  # 2 lines
    def onclick_first_image(self):        # 1 lines
    def onclick_last_image(self):         # 1 lines
    def onclick_prev_image(self):         # 1 lines
    def onclick_next_image(self):         # 1 lines
    def onclick_prev_error(self):         # 6 lines
    def onclick_next_error(self):         # 6 lines
    def onclick_calibrate(self):          # 3 lines
    def onclick_save_pose(self):          # 2 lines
    def onclick_goto_img(self):           # 8 lines
    def onclick_image_mode(self):         # 7 lines
    def onclick_pose_mode(self):          # 9 lines
    def onclick_correction_mode(self):    # 14 lines
    def onclick_heatmap_mode(self):       # 12 lines
    def keyPressEvent(self, key_ev):      # 12 lines 
    def eventFilter(self, iv, mouse_ev):  # 26 lines

    # methods to communicate messages to the user
    def prompt_for_directory(self):           # 6 lines
    def prompt_for_camera_ordering(self):     # 9 lines
    def prompt_for_calibration_range(self):   # 1 lines
    def display_error_message(self, message): # 2 lines

    # helper methods
    def setup_layout(self):  # 111 lines
    def uncheck_mode_buttons(self):  # 4 lines
    def enable_correction_controls(self, enabled):  # 3 lines
    def display_img(self, img_id):  # 3 lines
    def update_frame(self):  # 5 lines

In vim, this task requires only 50 keystrokes. Here they are (using for return and for escape):

qq/def ⏎jVnkk:!wc -lA lines⎋I # ⎋kJq25@q

This means that from any text editor, I can now perform these edits by selecting the text and typing (using for win key and ^ for ctrl key):

❖cqq/def ⏎jVnkk:!wc -lA lines⎋I # ⎋kJq25@q:wq^c

Yes, it's unreadable. Vim shortcuts are meant to be typed, not read. The point here is that I can batch edit several hundreds of lines and do exactly what I want in only ~50 keystrokes! And a Vim expert might use even less keystrokes.

For the Vim amateurs out there, here's a quick description of what this incantation does: record new macro; search for "def"; go down one line to put cursor on the first line of method's body; start a selection in visual-mode; go to next method then up two lines to put cursor on last line of method's body (which gives me a selection of the whole body); enter command mode and pipe the lines to wc -l, get the line-count back; write "lines" after the count and "#" before then join the two lines. Save the macro and repeat it 25 times.

And you, what's your editor and how would you have done it?

Sorting a bunch of lines

This afternoon, I was working on calepin, my static blog generator, and decided to sort the following lines by alphabetical order:

global_context = dict(
    author = config_data['author'],
    cache_dir = Path(config_data['cache']),
    config_path = Path(config_path),
    index_title = config_data['index_title'],
    input_dir = Path(config_data['input']),
    name = config_data['site_name'],
    output_dir = Path(config_data['output']),
    template_dir = _theme_dir / 'templates/',
    theme_dir = _theme_dir,
    theme_static_dir = _theme_static_dir,
    url = config_data['url'],
    written_files = [],
)

Here again, Vim made it dead easy. I selected the lines to sort, fired Vim with win+c and typed:

ggVG:!sort

And voilà!

If you want to know more about Vim, I recommend the video Mastering the Vim language by Chris Toomey: