Log in
Seblog.nl

#vim

Expanding the search in Vim

I just discovered a new Vim trick I think I am going to use quite often, so I am sharing it here to look it up when I forget.

As you may know, you can issue the / command in Vim to start a search. Every character you type will be in the search, until you hit enter, which will jump you to the first occurance of your searched text.

Another thing you may know, is that you can use this search-motion as a motion to other commands as well. When you do v/here, you will visually select everything between the current cursor position and the first occurance of 'here' in the buffer. I use this to delete stuff that I can't target using the f and t motions.

Then there is the thing I once found out, but never could remember: once you press /, you are in a little submode I can find the name for. In this mode, you can use Ctrl+L and Ctrl+H to increase or decrease your search string by the characters of the first match. You can also use Ctrl+G to move that virtual cursor to the next match, or Ctrl+T to get to the previous. Once you hit enter you are on that last position, removing the need to do n.

But here it comes: when you are deleting with d/, you can also use Ctrl+G to get to that next match. If you searched for a small snippet that accidentally occurs before the place you wanted to go, you can press Gtrl+G to jump over it, and pressing enter will delete the full distance.

Using PHPStan to fill Vim's Quickfix list

I am using PHPStan and the ALE plugin to add some error checking to my Vim. It gives red arrows on lines that contain errors in my currently opened files. But sometimes, in a big refactor, I want to know all errors in my project.

Vim has a build-in feature for this: the quickfix list. It is designed to take the error output of a compiler and lets you jump to all those locations with the :cnext and :cprev commands. I personally use the essential Unimpaired plugin by the one and only tpope, which maps these to ]q and [q.

I use these a lot: the :grep command fills the quickfix list with all the occurrences of your search, like normal grep but with pagination (or, if you set your grepprg to something faster: like ripgrep with pagination).

To get PHPStan to fill this quickfix list, I looked for plugins, but they all seemed hairy. I was convinced this should be simple, and it was. The following leader mapping seems to just work:

nmap <leader>pa :cexpr system('vendor/bin/phpstan analyse --no-progress --error-format=raw')<cr>

Customising Git: some things I did

One thing that always puzzled me a bit about my own workflow, is that almost all of it is based in the terminal (I use Vim and Tmux), except for Git: where most people seem to use the Git CLI commands, I use a graphical program (Fork, which is quite good).

Another thing then: I never used Github professionally, apart from the time I was a self employed web developer, but back then I was the only developer on my projects. All my previous jobs had a self-hosted Gitlab running somewhere.

Long story short: I am trying to get better at Git in the terminal and using Github. And 'better at Git' to me both means 'being able to confidently rebase' as well as 'customise my workflow'.

Aliases in the Gitconfig file

Customising Git means setting configuration in the ~/.gitconfig file. This file contains settings for Git, like your name and email, but can also be used to add aliases. To create the first alias you can run git config --global alias.co checkout. After this, you can use git co as git checkout, which is shorter to type and yes I use this often now.

Another alias I have is this one:

publish = !git push --set-upstream origin $(git symbolic-ref --short HEAD)

If you try to push a branch that has no linked branch on Github (the upstream), Git will complain about it. It will be nice to you and state the command you should have ran, but I got tired of having to copy and paste that new command.

fatal: The current branch feature/new-shoes has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin feature/new-shoes

In Fork, this was just a checkbox away. With my new git publish command I get the convenience again: it will push the branch and set the upstream with the same name as I have locally, exactly as Git suggested I should have done, but in less typing.

Links to Github

So far we have seen two kinds of aliases: one that just aliases a simple Git command (b = branch) and one that actually ran a shell command, because we started it with a ! (note that you will have to start with ! git there). But there is another way: having a command that starts with git- in your path.

I have the following file as ~/bin/git-github, marked as executable (chmod +x ~/bin/git-github) and in my path (export PATH="$HOME/bin:$PATH" in my ~/.zshrc file):

#!/bin/zsh

local url
url=$(git remote get-url $(git remote))
url=$(echo $url | sed 's/.*github\.com[:\/]\(.*\).git$/https:\/\/github.com\/\1/')

if [ -n "$1" ]; then
  append="/$1/$(git symbolic-ref --short HEAD)"
fi

open -u "$url$append"

Yes, my ZSH is crude. Yes, I can better share this in Bash. Yes, it could've probably been on one line and be included as an alias in my Gitconfig. But it works for me.

The command figures out the Github URL of the project, based on the URL of the remote (it assumes you have only one). It then opens that URL with the macOS open command. If an argument is given, it appends that to the URL, with the name of the branch too.

In my Gitconfig I have two aliases that use this command:

pr = github pull/new
compare = github compare

With git github, my default browser will open a tab with the 'homepage' of the repository. With git compare, it will open a tab that contains a diff of the current branch and the default branch on Github (the remote versions of those). With git pr, the browser will open the correct page to open a for the current branch.

And you mentioned Vim and Tmux?

Yes, I actually use the obligatory Tim Pope plugin Fugitive. This means I can do a lot of things which you can also read about in the help file. (Which I read a lot these days.)

But this allows me to stage and commit and rebase and reword all my changes in Vim, and then when I am ready, run :G publish and :G pr and make a PR for it on Github. These two commands alone make me feel so much more productive: no longer do I have to search for another program to compose commits and then search for the browser to handle the cooperative side of it... I just handle everything in my editor.

For Tmux I have another nice addition: in my ~/.tmux.conf I have the following command:

bind-key y display-popup -E -h "90%" "git log --oneline --decorate --graph --all"

This will – when I press <prefix> + y – open a popup window, which closes when the command exits and has a certain height. It will show me an ASCII-art style graph of all the commits – one of the features I missed from Fork – as an overlay over my editor, a small keystroke away.

With this configuration – and a lot of reading the Fugitive help file – I feel much more at home with Git in the terminal.

I also watched this fantastic series on mastering Git and these unfortunately dated but still useful screencasts on Fugitive.

Quickly look up PHP docs from Vim

As I don’t use a full IDE like PHPStorm, I don’t get much help with the parameters to function calls from my editor. On one hand I find this a good thing: IDE users rely so much on autocomplete that they don’t remember names of things at all. On the other hand: who has the mental space to waist on such things?

My middle ground is that I look up a lot of things on PHP.net. They make that very simple: just add the name of the function you are looking for after the slash and they will give you the correct documentation. But: switching to a browser and typing out the address requires a lot of keystrokes. I found a solution.

nmap <Leader>pd :silent !open https://php.net/<c-r><c-w><cr>

This adds a leader key mapping to look up the word (so: function name) under the cursor. I prefix all my PHP related leader mappings with a p, but feel free to pick something else.

The :! runs a shell command, in this case open, which on Mac can be given a URL, which will then be opened in the default browser. I added silent to ignore the output of the command: I just want to open a URL.

As the URL, I let Vim type out the address to PHP.net, and since we are in command mode, one can do Ctrl+R, Ctrl+W, which will paste the word that is currently under the cursor (very nice to know in itself). We end the sequence with an enter (carriage return) to run it.

So the tip within the tip is Ctrl+R (register) Ctrl+W (word). In general Ctrl+R in insert mode gives you this interesting ‘paste from register’ mode that is good to know. See :help i_ctrl-r and :help c_ctrl-r for more.

Faster copy to clipboard in Vim

tl;dr: I just mapped Y to "+y and I am very pleased with it.

A coworker saw me copying some text out of Vim and humored me: everything seemed to happen like magic in my editor, but for a simple thing as copy and paste I needed a lot of keys.

It is true: in order to copy text within Vim, you can use the y command, combined with the motion of what you want to yank. So: yiw will yank inside a word, ggyG will go to the top of the file and then yank until the bottom (so, the whole file), yy will yank a full line. But these yanks are only pastable (with p) within Vim itself.

In order to get text out of Vim, you need to use a special register. Registers are a sort of named boxes, letters a to z, in which you can put snippets of text. To use it, you prefix your yank (or delete) with a quote: "ayi( will select register a and then yank the text within parentheses. To get to your system clipboard (the one all other programs use), you select the special register with "+.

Thus: my coworker saw me hunting for the "+y combination, probably. I almost always look at my keyboard when I do that, so awkward is the combination. But I just found a solution: Y.

nmap Y "+y
vmap Y "+y

By default, Y does a yy, which I never use, because it's inconsistent with other commands. D, for example is equivalent to d$, delete until the end of the line, same for C as c$. I guess it makes Y play along with V (line-wise visual select) and S (subsitute full line), but I always use yy anyway, so Y is free to use. When I now want to yank to my system clipboard, I just use Y instead of y and that's it.

You can also consider adding this as gy, which does not have a meaning, but g combines with various commands to activate variation of their meaning, so it's not a bad choice either. I mapped it to both and will see which one sticks.

Adding a prefix to TailwindCSS classes with VIM

Today we needed to add a TailwindCSS prefix to a small project we are building, to prevent clashes with other TailwindCSS classes on the same page. The setting in the tailwind.config.js was straightforward, we added this:

module.exports = {
  prefix: 'sf-',

... and now TailwindCSS will generate classes like .font-bold and .border-none as .sf-font-bold and .sf-border-none. Next up, we needed to replace all the classes in the project with their prefixed counterpart.

There were some plugins available to do this automagically in Webpack, but we decided we wanted to add them manually. A few of the classnames are dynamically set, and we doubted the plugins would find all those cases. Also: the plugins seemed to be old.

My co-worker started writing a regex for a search-and-replace, but soon stranded. You need to find the proper locations, and then within those locations, add the prefix. The prefix, however, is sometimes more of an infix, since there are other prefixes that are added before the prefix itself. See the class with md: for example:

<div className="bg-gray-100 px-4 md:px-0">
// Becomes:
<div className="sf-bg-gray-100 sf-px-4 md:sf-px-0">

While he worked on the regex and subsequently went to get coffee, I thought: I should be able to solve this in VIM in some way. I know I can make a visual selection and then type :! to get the selection as input into some shell command. If I pipe the lines to some script, I can then just split strings all I want.

One problem I ran into was that, if I make a visual selection within quotes (vi") and I then use the colon-exclamation to pipe it to some script (:'<,'>! php transform.php), I will receive the full line, as if I was using the line-wise V selection.

While searching the internet I thought: can I use a register here? And the internet had a solution for that. I ended up running the following command:

:vmap m "ay:call setreg('a', system('php transform.php', getreg('a')))<cr>gv"ap

This sets up a mapping for visual mode, and binds the m to do a series of things. It first selects the 'a' register and yanks ("ay). Since we are already in visual mode here, y is a direct yank of that selection. We are now in normal mode, so we continue to type out a command that calls a function to set register 'a' again with the output of a system call to php transform, taking in the current contents of register 'a'. We execute this command by pressing enter (<cr>). Register 'a' now contains the scripts output. We then re-select the last visual selection (gv in normal mode) and then paste from register 'a' ("ap). This effectively runs the script over the visually selected area.

I then just opened the alphabetically first file of the project and manually made visual selections that selected all the classnames. For this, I also temporary mapped m to vi", so I could just do mm once my cursor was in a className="..." (for which mouse mode is nice too: click + mm, very fast). Then, to get to each next file, just press ]f.

The project was small enough to do this last part so manually, but this at least saved me a lot of stops in between all the classnames, and also saved me the headache to scan for md: prefixes and pick the right spot. Even when the classnames were actually in a backtick-string it was no problem: as long as I defined a correct visual selection, m would replace things.

For completeness, here is transform.php:

<?php

$input = file_get_contents('php://stdin');

$classes = array_filter(explode(' ', $input));

$classes = array_map(function ($class) {
    $parts = explode(':', $class);
    $parts[count($parts)-1] = 'sf-'.$parts[count($parts)-1];
    return implode(':', $parts);
}, $classes);

echo implode(' ', $classes);

VIM productivity is a lie: all the time you save with your fancy commands and macros, you loose again on bragging about them to your coworkers.

Het jammere aan Vim is: het heeft een enorme leercurve, en de eerste keer dat je daadwerkelijk een paar seconden hebt bespaard omdat je je het perfecte commando herinnerde, ben er je minutenlang over aan het opscheppen tegenover andere Vim-gebruikers. #vim

So of course your editor does this too, but I just ran ag -l Hidden: | xargs vim and :argdo %s/^Hidden: true/Visibility: hidden/ and :argdo write (and the same for ag -l Private: | xargs vim and :argdo %s/^Private: true/Visibility: private/) on my content/ folder and it feels great.

Vim and Git

So I woke up this morning and thought: I should really dig more into using Git within Vim with tpope's vim-fugitive. So I did.

I was already using the :Gblame command it provides a lot. This command opens a vertical split, with on the right the current file and on the left for each line of that file the hash, author and date of the last time that line changed. This is great for quickly identifying the author and age of a piece of code, which helps me a great deal in understanding the purpose behind the code. It's sad that Git gives this feature a name with such a negative connotation. PHPStorm calls this 'Git annotate', which feels nicer. One can also do a git blame from the command line or on Github, but having this command right in your editor is very useful.

I noticed, however, that I can browse the commits even further, by pressing enter when the cursor was on such a line in the righthand buffer. I would instantly be lost in weird screens about commits. I also figured there would be ways to commit from Vim (what a Git plugin would it be else), but I hadn't figured out how it worked.

I think the best tour of the plugin, apart from it's help files, is this nice overview of five video's on Vimcasts.org, about the basic commands, the difference between committed files, the working copy and the index, resolving merge conflicts and other diffs, using Vim to browse Git and specificly browsing commit history. Recommended when diving into this, I learned a lot about the inner workings of Git too :)

Quick Vim thing that was useful to me: I was editing a .env file and added a comment for other readers, to point out that they should look at config/filesystems.php for more information. Then I thought, is that the right file? So I put my cursor on it and pressed gf, which opened the file, confirmed that it was the right file, and pressed Ctrl-O to get back.

So at one point I proudly admitted to a co-worker that I knew how to search and replace in Vim: I did /foo, then ciw, bar, and escape (changing the inner word to 'bar', or whatever the right replacement motion would be) and then press n and . for the number of times I needed to (first one goes to the next occurrence of 'foo', and the period key repeats the last edit, which is magic and very powerful in itself).

He replied with a less interesting, but in some cases more appropriate way of doing it: :%s/foo/bar/gc. This command takes the current file (%), and substitutes 'foo' for 'bar', with a flag for global (more than once) and choice. Vim will then stop at each 'foo' and gives you choices for 'y' or 'n' (and others), so you can interactively pick your replacements.

Today I leaned about the gn text object. Where iw stands for 'inner word', gn stands for 'go next'. So one can type /foo, then cgn, bar, and escape, which is almost the same. But then you can just keep hitting ., and it will perform the last action on the next occurrence of 'foo'. No need for n anymore!

I mean if you want to be presented with a choice for each replacement, go use :%s, but this gn thing is darn cool.

Maybe I should start a Vim-log of things I encounter and find useful. Most of them seem too advanced for the beginner and too trivial for the expert, but who's really an expert at Vim, aren't we all beginners for some part of it?


So, today I learned about the :cd command, which I instantly memorized (for it's just Unix). It changes the home directory of Vim, (by default this is the folder you start Vim in).

For example, if you're at ~/code and you want to check the contents of some file in my Seblog-project, I would do vim seblog/site/config/config.php. But as always, I need to check another file, so I use :e (short for :edit) to open that file, but because I was in ~/code when I opened Vim, I need to type: :e seblog/site/config/other-file.php. I find that annoying, so I would close Vim altogether, cd seblog and then vim ..

Now, I would just :cd seblog and then :e site/config/other-file.php. Or use :Ex (for :Explore) if I wanted to look around, but I knew that one already.