Log in
Seblog.nl

#vim

Chasing the casing in Vim

A lot of programming is really just taking data in one form and turning it into another. Imports and exports. Within different contexts, different conventions apply. Within Laravel, database columns are usually snake_case, yet within PHP most variables are camelCased. Within HTML and CSS, things are usually kebab-cased, until you find a React component, which are usually PascalCased.

Every once in a while I end up copying names from one source and having to turn them into another case. With the Vim language of editing, turning a_cased_string into aCasedString usually involves me typing f_ to jump my cursor to the next underscore, and then x~ to delete that underscore and turn the next character into it's uppercase variant. I then have to do that for however many times there are underscores in my target string. (Subsequent jumps to underscores can be made with ; though.)

The conversion back from aCasedString to a_cased_string is always a bit more bothersome, because you need to insert a lot of underscores. I usually do it with a second pass: use fC to jump to the first insert point, then use i_ and escape to insert the underscore, then jump with fS, and use the . to repeat my last insert. Then when I am done with the string, I use guiw to change the case of the 'inner word' to lowercase (gu).

The nice thing about that approach is that it feels efficient: I am using all kinds of obscure Vim keystrokes to get my work done and I feel like a wizard. I never touch my mouse! The bad thing about the approach is that it is still a lot of work, especially in longer strings or with many occurrences. Today I thought: there must be a better way for this. Maybe there is even a plugin?

And yes there is, and of course it's by Tim Pope. It's called Abolish and it's main purpose seems to be auto-replacement (which I don't want to use) but it also adds a very handy :Subvert command, and precisely the mappings I wanted to have.

If I now every have to change any token to camelCase, I can just jump my cursor to it and type crc. Do I need snake_case? Just crs and that's it, no matter how long it is. And of course it works with the . command as well. Why have I allowed myself to do all the nonsense I just described above? Just install the tpope-plugin and you're done.

Last year I shared a Vim keybinding that I use quite frequently: I mapped gy to "+y, meaning that with the gy I yank text into the system clipboard (without the awkwardness of typing double quote and plus).

I recently added another mapping to it: if I do gY, it will actually yank the full content of the open file into my system clipboard. This saves me the awkwardness of typing gggyG. See my mapping below though: by using the command style yank, I actually don't let the cursor jump, which is much nicer.

nmap gy "+y
vmap gy "+y
nmap gY :%y+<cr>

Toggling Github Copilot in Vim with unimpaired

I was trying to remove the Github Copilot configuration from my Vim setup, but then I noticed it was not there at all. I have been neglecting to commit changes to my dotfiles from my MacBook, as I did not have the courage to share stuff I was just trying out. On my new Linux-based Thinkpad I do in fact commit everything, as running open source stuff makes me want to work in public more too.

But since this config was only in the copy of my dotfiles that now lives on ~/dotfiles-mac, it will get lost now that I don't copy it. A good reason to blog about it.

First: unimpaired is a Vim plugin by tpope, and it is one that should just be part of Vim itself. It adds all kinds of mappings with the [ and ] brackets, and many of them I use daily (most notably [q, [e and [space). The o variant of this will change options, with the special yo binding to toggle the option. I use yoh all the time (toggles search highlighting) and also yol is very useful (shows invisible characters).

I wanted to be able to toggle Github Copilot for the current buffer in this same style. Luckily, the g was still available, and luckily, unimpaired provides an easy way to add new mappings. Unlucky as we are, copilot does not actually provide a toggle, but with Ctrl+R and = in insert mode, we can evaluate some internal options and print a string based on that. See the full command below.

" Going with that flow
Plug 'github/copilot.vim'

" Toggle github copilot
nmap <script> <Plug>(unimpaired-enable)g  :Copilot enable<CR>
nmap <script> <Plug>(unimpaired-disable)g :Copilot disable<CR>
nmap <script> <Plug>(unimpaired-toggle)g  :Copilot <C-R>=copilot#Enabled() ? "disable" : "enable"<CR><CR>

As for why I stop with Copilot? It was very useful in my time at Sneaker District, as I was the only developer working on the project and I wanted to be faster than I was on my own. Having a junior developer in my editor was a nice thing to have: it provided me with good suggestions that I usually had to edit a bit.

But I also noticed it slowed me down at times: I got into the habit of stopping to type to see what it would suggest. And sometimes I just stopped to think, and then it would give me suggestions for directions I did not want to go in. For now, I want to experience less distractions, and get into the habit of typing and thinking for myself again.

(There is also some money involved, and the question whether or not you want to send your code to Github for this purpose. It is nice to have those concerns gone, but it wasn't my primary one.)

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.