Shuhei Kagawa

Switching color schemes of Vim and Alacritty

Feb 14, 2020 - Vim

I like fountain pens and good notebooks. They spark joy when I write on paper. Computer terminals are like stationery. A good terminal setup makes it fun to work with computers. Here is how I improved colors on my terminal and made it easy to switch them depending on the time and the mood.

Ayu Light for Vim and Alacritty

Using official color schemes

I have been using Dracula color scheme on Vim and Alacritty for a while. I liked the colors, but I had a small problem with it on Vim. The pop-up of coc.nvim had the same color as the background color, and it was hard to distinguish a pop-up and the background.

dracula from flazz/vim-colorschemes

I was using Dracula from vim-colorschemes, which hadn't been updated for three years. I tried the official Dracula color scheme for Vim. It had a different background color for pop-ups! Yes, it's subtle, but now I can distinguish pop-ups from the background.

dracula from dracula/vim

vim-colorschemes is a great way to try out different color schemes. You can get a random color scheme by :colorscheme random. But once you pick a few favorite ones, it's worth checking if they have official color schemes that are likely to be more maintained.

The same goes for Alacritty. I was using the Dracula color scheme that I converted with my tool from iTerm2-Color-Schemes for Alacritty. Dracula has its official Alacritty theme, and it looks better!

termguicolors

I started trying other color schemes and found Vim's termguicolors option in ayu-vim's README. It enables true colors (24-bit colors) instead of 256 colors (8-bit).

if has('termguicolors')
  set termguicolors
endif

I turned it on, and the colors looked gorgeous! Before learning about termguicolors, I had tried light color schemes like Ayu Light and given up because of too low contrast (left in the following image). With termguicolors, light color schemes became finally usable!

ayu light in 256 colors and true colors

Switching color schemes

After trying dozens of color schemes, I picked the following:

  • Ayu Light: Good in the morning or at a place with natural light.
  • Pink Moon
  • Nord: Low-contrast theme. Good in the night.

I started switching color schemes depending on the time and the mood and bumped into a couple of issues. It was tedious to update the color schemes of Vim and Alacritty together. Also, I manage my .alacritty.yml and .vimrc in a git repository. It was annoying that the repository had unstaged changes every time I switched color schemes.

Solution

Alacritty

I decided to remove .alacritty.yml from the git repository and generate it out of a base template and color scheme files. Once I prepared a YAML file for each color scheme, it was quite easy with a one-liner.

cat alacritty/base.yml alacritty/${color}.yml > .alacritty.yml

Vim

I could have generated .vimrc, but it felt weird because VimScript is a programming language. Instead of generating the whole .vimrc, I decided to generate a color scheme file .vim/color.vim, which is in .gitignore

echo 'let ayucolor="light"\ncolorscheme ayu' > ~/.vim/color.vim

and load it from .vimrc.

let color_path = expand('~/.vim/color.vim')
if filereadable(color_path)
  exec 'source' color_path
else
  " Default color scheme
  colorscheme pink-moon
endif

Putting them together

Then, I created a shell script named colorscheme to switch color schemes of Vim and Alacritty together.

#!/bin/sh

color=$1
dotfiles=~/dotfiles
alacritty=${dotfiles}/alacritty

configure_alacritty() {
  cat ${alacritty}/base.yml ${alacritty}/${color}.yml > ${dotfiles}/.alacritty.yml
}

configure_vim() {
  echo $1 > ${dotfiles}/.vim/color.vim
}

case $color in
  dracula)
    configure_alacritty
    configure_vim 'colorscheme dracula'
    ;;
  nord)
    configure_alacritty
    configure_vim 'colorscheme nord'
    ;;
  pink-moon)
    configure_alacritty
    configure_vim 'colorscheme pink-moon'
    ;;
  ayu-light)
    configure_alacritty
    configure_vim 'let ayucolor="light"\ncolorscheme ayu'
    ;;
  *)
    echo "Supported colorschemes: dracula, nord, pink-moon, ayu-light"
    exit 1
    ;;
esac

Now I can switch color schemes with only one command! (I still need to restart/reload open Vim sessions, but I can live with it.)

colorscheme ayu-light
colorscheme nord

If you are curious about the full setup, check out my dotfiles repo.

Summary

  • Official color schemes may have more features than color scheme bundles
  • Enable termguicolors on Vim
  • Switch color schemes with a command!

Goodbye, Textile

Jan 27, 2020 - Blog, JavaScript

Textile is a markup language that is similar to Markdown. This blog had had posts written in Textile for more than a decade—I feel old now! I removed the Textile files last weekend. This post is a memoir on the markup language.

I started using Textile on a blog engine called Textpattern. I don't remember exactly when, but probably around 2004 or 2005. I was a university student. Movable Type was the most popular blog engine at the time, but it changed its license towards a more commercial one. Textpattern was a new open-source software. I fell in love with its minimalism. There were not many Textpattern users in Japan. Information in Japanese was very little if not none. I read documentation and forums in English and translated some into Japanese with a few fellows whom I had never met in person.

After a few years, Wordpress became a thing, or I realized it did. Even after I moved to Wordpress, I kept writing in Textile. I liked editing Textile more than editing rich text on WYSIWYG editor. I am not sure whether I had heard of Markdown at the time. But it was not as popular or dominant as it is now.

I started this blog with Textile on Wordpress in 2008 when I started my first job. And I migrated it to Octopress in 2012. I started writing in Markdown with Octopress because it was the lingua franca of GitHub where all the cool things were happening. I had a bit more than a hundred posts in Textile. I kept them in Textile because Octopress supported Textile as well. In 2014, I rebuilt this blog with a handmade static site generator using Gulp. I carried the old Textile files over. I even wrote gulp-textile plugin, which was just a thin wrapper of textile-js. It was my first npm package.

Since then, I implemented a few Markdown-only features like syntax highlighting and responsive table in this blog. The outputs of Markdown and Textile diverged. Last weekend, I wanted to implement responsive images. One more Markdown-only feature. Then I thought it was time to convert the Textile files to Markdown.

Converting Textile to Markdown

I didn't want to convert a hundred posts by hand. I had tried tomd a few years ago, but I was not satisfied with the result. The old Textile files had raw HTML tags and some classes for styling. Also, I was afraid of missing some details that I don't remember anymore. So I decided to write a conversion script.

I used textile-js to parse Textile. It turned out that textile-js output HTML string or JsonML. JsonML was new to me. It is basically HTML in JSON format. Each text node is represented as a string. Each element node is represented as an array whose first item is the tag name, an optional second item is an object of attributes and the rest are child nodes.

<a href="https://foo.com"><img src="foo.png" alt="Foo" /> Yay</a>
[
  "a",
  { "href": "https://foo.com" },
  ["img", { "src": "foo.png", "alt": "Foo" }],
  " Yay"
]

I wrote a switch statement to handle tags and added tag handlers one by one.

switch (tag) {
  case "img":
    return /* render <img> */;
  case "a":
    return /* render <a> */;
  default:
    throw new Error(`Unknown tag: ${tag}`);
}

I also added console.log for unknown attributes. In this way, I was able to make sure that all tags and attributes were handled. The script worked well to convert more than one hundred posts. The full script is on Gist.

Responsive Images with a Static Site Generator

Jan 26, 2020 - Blog, JavaScript

Responsive images

An img without width/height attributes causes a page jump when it's loaded. It happens because the browser doesn't know the dimensions of the image until its data is loaded—only the first part that contains dimensions is enough though. It's common to specify width and height of img tag to avoid the jump.

<img src="/images/foo.jpg" alt="Foo" width="800" height="600" />

But img tag with width and height doesn't always work well with Responsive Design because the dimensions are fixed. I wanted images to fit the screen width on mobile phones. So I left images without width/height and let them cause page jumps.

Recently, I came across a similar problem at work and learned a cool technique to create a placeholder of the image's aspect ratio with padding-top. If the image's aspect ratio is height/width = 75/100:

<div style="position: relative; padding-top: 75%;">
  <img style="position: absolute; top: 0; left: 0; max-width: 100%;" />
</div>

The div works as a placeholder with the image's aspect ratio that fits the width of its containing element. Because the img tag has position: absolute, it doesn't cause a page jump when it's loaded.

I decided to implement it on this blog. This blog is made with a custom static site generator. I'm not sure if it's useful for anyone else, but I write how I did it anyway…

Limiting overstretch

In addition to images that are wide enough to always fill the full width of the content area, I had images that are not wide enough to fill the full width of a laptop, but wide enough to fill the full width of a mobile phone. Not to stretch the image on laptops, I decided to go with another wrapper to limit the maximum width of the image. If the image's width is 500px:

<div style="max-width: 500px;">
  <div style="position: relative; padding-top: 75%;">
    <img style="position: absolute; top: 0; left: 0; max-width: 100%;" />
  </div>
</div>

Getting Image Dimensions

The placeholder technique requires image dimensions. I used image-size module to get image dimensions.

The following function gets dimensions of images in a directory and returns them as a Map.

const util = require("util");
const path = require("path");
const { promises: fs } = require("fs");
const sizeOf = util.promisify(require("image-size").imageSize);

async function readImageSizes(dir) {
  const files = (await fs.readdir(dir)).filter(f => !f.startsWith("."));
  const promises = files.map(async file => {
    const filePath = path.resolve(dir, file);
    const dimensions = await sizeOf(filePath);
    return [file, dimensions];
  });
  const entries = await Promise.all(promises);
  return new Map(entries);
}

Custom renderer of Marked

This blog's posts are written in Markdown, and its static site generator uses marked to convert Markdown into HTML. One of my favorite things about marked is that we can easily customize its behavior with a custom renderer.

I used span tags to wrap img tag because they are rendered in p tag, which can't contain block tags like div.

class CustomRenderer extends marked.Renderer {
  image(src, title, alt) {
    const dimensions = this.imageDimensions && this.imageDimensions.get(src);
    if (dimensions) {
      const { width, height } = dimensions;
      const aspectRatio = (height / width) * 100;
      return (
        `<span class="responsive-image-wrapper" style="max-width: ${width}px;">` +
        `<span class="responsive-image-inner" style="padding-top: ${aspectRatio}%;">` +
        `<img class="responsive-image" src="${src}" alt="${alt}">` +
        "</span>" +
        "</span>"
      );
    }
    return super.image(src, title, alt);
  }

  // To set images dimensions when images are changed
  setImageDimensions(imageDimensions) {
    this.imageDimensions = imageDimensions;
  }
}

And CSS:

.responsive-image-wrapper {
  display: block;
}
.responsive-image-inner {
  display: block;
  position: relative;
}
.responsive-image {
  position: absolute;
  top: 0;
  left: 0;
}

Result

Here are a few examples:

Yay, no more page jump! Well, web fonts still make the page slightly jump, but that's another story...

I skipped some details that are specific to my website. The full code is on GitHub.