Episode #1

Advanced Vim Workflows

Watch three typical workflows of someone with a decade of Vim experience. I'll edit 40 files at once with :argdo, turn bags of JavaScript functions into classes with a macro, then create a keyboard shortcut for playing the mp3 under cursor.

April 05, 2020

Show Notes

Software

  • Neovim - The vim fork I happen to use. I have no opinion on whether it's better or worse than regular Vim. This video used version 0.4.3
  • afplay - Play audio from the command line (or from Vim)
  • Hanabi - The board game I was replicating.

Vim Plugins

  • Surround.vim - Mapping for adding, editing, and deleting surrounding tags, parentheses, etc.
  • ALE linter - Linting in Vim.
  • Abolish.vim - Change strings to snake_case, MixedCase, camelCase etc.
  • Fugitive.vim - Incredible git integration with Vim.
  • Caw.vim - Syntax-aware commenting in Vim.

Dotfiles

Bonus Tips

  • Zantox had the excellent observation: If you start recording a macro with say qa, you can "pause" the recording by stopping the recording as normal (q), then when you're ready to "unpause" start recording again with qA. This would append what you record to register a instead of overwriting it. This could have been used in 2:30 to check what was needed at the bottom of the other file by "pausing" the recording, switching to that window and scrolling down, then switching back and "unpausing".
  • u/calvers70 adds: when you're essentially diffing files like you were near the start, you can use :set scrollbind in both windows to sync the scroll between the two. Would have allowed viewers to see that @endsection at the bottom of the other file.

Errata

  • 4.16: I said "command line mode". It's actually "command mode". Thanks to the great people at r/vim for catching that.

Screencast.txt

Transcribed by Rugo Obi

This is looking very promising. And that's how you edit something like 40 files in one go.

Welcome to Semicolon&Sons.

We show actual workflows in production codebases, using timeless technologies. Which brings us to Vim.

In some form or other, it's been around as long as Zelda, Prozac, and your host here.

Personally, I've used Vim as my only editor for about a decade. So I'd like to give you a glimpse into what my editing looks like.

One of the great things about Vim is that it takes the tedium out of a lot of editing tasks.

Workflow I

For example, one time I had to modify 40 or 50 view files, that were just more or less plain HTML to use a new type of template.

Doing that manually would have absolutely sucked, but with Vim it became not only rapid but also a lot of fun.

Let me show you exactly how that worked.

So I'm going to first show you the goal file.

This is a blade. php file, and it extends from some sort of layout that has a bunch of content. There's no head tag or html tag within this view file, rather that's included within the overall layouts file.

Now what I want to do is transform the forum files.

I’ll show you forum-0.blade.php as a typical example.

And it's just basically a normal HTML sort of file.

So let me start that transformation process right now.

I'm going to begin by recording a macro, with qf.

A macro is going to be held within the f register, you can see it’s recording in the bottom left hand corner.

I'll begin with 2dd to delete two lines, then dst to delete the surrounding HTML tag. dd again to delete line. dat to delete this head tag and dst to delete this body tag. There's no parallel body tag in the right hand pane.

Next we see that the @include("header") does not exist there so we'll delete that too. But we'll go and add the other two headers, the extends("layouts.app") and also, the @section("content").

That's looking pretty good.

Let's skip down to the bottom. And we have something we don't need there, but there's something we do need to include and that's an @endsection.

That's not viewable in the right at the moment but you can take my word that it's necessary in there.

I'm going to finish recording that macro by pressing q, and I'm going to jump up to the top of the screen again and take a look.

I'm going to save real quick, and now I'm going to check out from Git with this :Gread command, the original.

Yep, there you go, and then run that macro again with @f to see if it works.

And sure enough, it does.

So I'll undo that once more, and test it out on another file.

So here's a different HTML file. It looks quite similar. That's because I turned all the, most of the HTML, into lorem ipsum or rather the contents of the HTML, so as not to reveal any private information.

So, running that macro again with @f.

Yep. And it does its job perfectly.

So let me do a :Gread to go back, I'll explain why in a sec.

So I'm going to save the original form in both cases, and now I'm going to populate something called the args list with all the files I want to modify. Those are going to be the forum-whatever files.

You can see a bunch of buffers have opened up at the top. And for a full list of them I'm going to type :args, you can see them at the bottom of the screen. Something like 46 of them.

Now what I'm going to do is run the argdo command with the macro from normal mode.

Normal mode is when I was dashing around, deleting lines and tags, but I'm not in normal mode right now. I'm in command mode.

So, :ardo normal @fw (write to save it). So let's run that.

This is going to take a little bit of time as you can see, because there's so many files for it to work through. But so far so good, it's looking rather promising. Oh, there's a tiny bit more.

And let's save everything here with :wal and quit, and then use git diff from the command line to see what's happened. Let me get git status first in fact.

Well, that's certainly a lot of files edited. That sounds good.

git diff. That's the old, that's the new. That's the old. That's the new, and we're going to see some of the new ones.... that’s the old, that's the new.... This is looking very promising. And that's how you edit something like 40 files in one go.

Workflow II

Here's the situation.

I have five JavaScript files, each of which amounts to a bag of top level functions.

What I want to do is wrap them all into JavaScript classes, and turn their functions into static methods as a way to namespace in a growing codebase.

So I'm going to start by recording a macro into the @q register, and then doing a file-wide search and replace for functions that are top level.

I'm going to use the ^ (caret) symbol to ensure that. I want to avoid any inline callbacks and so on by doing this, and replace that with a static keyword.

:%s/^function/static/

The linter rightly complains, but that's fine, we'll fix that soon.

So next I'm going to select everything on the screen, like so (GGvg), and then use the Surround plugin to wrap it all in curly braces (S{).

Finally, I'm going to add the class keyword, and then use the expression register (Control-R =)to run a tiny bit of Vim scripts to get the current file name: =expand("%:t") That's in there, but the current file name is not usable yet as a JavaScript class or at least it's not standard.

So I'm going to use abolish.vim's crm shortcut— or rather, key binding — to transform this into a mixed case.

Now, I'll finish with the recording of the macro (q), :Gread the original file out from Git and rerun that macro to check if it works, and it does.

So let's attempt this with some of the other files.

Glorious. Excellent. Let’s scroll down to make sure everything's good. And it is.

And good, and good. And just like that, I've transformed all these files into classes, very very efficiently.

Workflow III

One of the best things about Vim is how it's so versatile that it can tailor itself to — rather you can tailor it to — whatever kind of code you're working with and whatever the demands are in that particular project.

For example, here I have a file to play some sounds within a game.

So for those who aren't familiar with Ruby, I'm going to demonstrate what the public API is here.

I'm going to use the g command to grab all the self. lines which are the public methods here, the static methods, and then copy that to the second line.

:g/self./t2

This is going to cause some linting errors, but I'm going to comment it (gcc), and then save it in order for the auto-formater to go to work.

And you can see that's nice. And now I'm going to do, kind of block selection ctrl-v.. Select down to that line and then select towards the . (f.), hit c for change (typing PlaySound as the replacement) and then, bingo. We have our documentation.

Now, the next thing we want to do is play the sound within the editor, so we know what particular sound we're working with without having to leave here.

So the way to do that on the command line normally, at least on my machine, is afplay, then the name of the particular file.

:! afplay jingle.mp3

Now, there's an issue with this, in that I'm unable to move my cursor while that's playing, ie, it's blocking.

So, I'm going to rerun the same thing, but in an asynchronous manner and edit the last command with ctrl-f. And then I'm going to wrap this in :call jobstart('afplay jingle.mp3"), and yeah. I hit ‘Enter’ to run it.

Now you can see I can move my cursor at the same time, that's an improvement.

Now, our end goal is to play whatever mp3 the cursor is currently on. So the next step here is just to demonstrate that we can get that particular word echoed on the command line.

The way to do that, will be with something like the following.

Here’s the syntax <cWORD>. And let's see what happens.

:echo expand("<cWORD>")

We're getting clue.mp3, that's correct. And what are we getting here failure.mp3. Okay, now all we have to do is combine the last two commands together.

So I'm going to go and edit a previous command. Delete the start bit here, yank this into a register. And then I'm going to essentially paste this here. Get rid of to the end of the line and then fix this syntax issue. I'm adding a space there because we're wanting to construct a string with the space between the program name, and the particular argument.

:call jobstart("afplay" . expand("<cWORD>"))

And we're getting that failure sound there. And what happens if we press a : up-arrow and run the same one?

We get a different sound. Excellent.

Now, let's put the cherry on top of that cake. Let's create a keyboard shortcut to do that just within the context of this program.

So I'm going to go up, and edit this previous command. And I'm going to add the normal mode mapping, with leader, which is call on my computer, and let's say p for play. And what that's going to do is call all this stuff, so I'm going to wrap that in a command mode piece of syntax and add a return at the end.

:nmap <leader>p :call jobstart("afplay" . expand("<cWORD>"))<CR>

So this should work with leader p, ,p. So, let me try it now.

Lovely.

And there you have it.

Thank you for watching.

Show notes about the plugins used are available on Semicolon&Sons.com.

Also for anyone more interested in the how than the what, hop on my mailing list for exclusive content.