GeistHaus
log in · sign up

Gabe Berke-Williams

Part of gabebw.com

stories primary
Converting to Neovim
My journey to a working LSP config.
Show full content

As a longtime Vim user, I’ve been eyeing Neovim for a while. At work, I use VS Code because it supports language servers, which can speak LSP (the Language Server Protocol). As much as I like Vim, working on a large codebase is infeasible without LSP support. I tried to use the LSP packages that have popped up for Vim, but I couldn’t get them working. Neovim has native support for LSPs, so I decided to 1) switch to Neovim and 2) set up LSP support.

Step 1: Razzle Dazzle

I have a large and complicated .vimrc file, but I wanted to try out Neovim’s features on Neovim’s terms. So I copied kickstart.nvim’s init.lua file into my Neovim (at ~/.config/nvim/init.lua) and launched Neovim.

Wow. Here’s my raw reaction: “holy shit”.

wow

Here’s a closer look at that screenshot:

a screenshot of the lazy.nvim install screen

This looks like a modern terminal application! It reminds me of charm or rich. There’s lots of color, a floating popup window, parallel processes. I was sold.

Step 2: copy over my existing Vim config

I deleted the kickstart.nvim code and ported my .vimrc to Neovim.

Neovim understands Vimscript, but its “native” scripting language is Lua. Since Vimscript is a terrible language, and I like learning new things, I pored over the Neovim Lua guide and the “Learn Lua in X minutes” cheatsheet.

There was a lot of fumbling with the various abstractions of Neovim/Lua/Vimscript but I eventually figured it out and copied over my existing config in Lua. I didn’t translate the portions that are pithier in Vimscript, like autocommands in groups. I used vim.cmd to run Vimscript directly.

-- `vim.cmd` accepts arbitrary Vimscript
vim.cmd [[
augroup vimrc
  autocmd!
  autocmd User Rails nnoremap <Leader>u :Eunittest<Space>
  autocmd BufReadCmd set nohlsearch
augroup END
]]
-- Compare to the following equivalent "pure Lua" approach.
augroup = vim.api.nvim_create_augroup("vimrc", { clear = true }),
vim.api.nvim_create_autocmd("User Rails", {
  group = augroup,
  callback = function(event)
    vim.keymap.set("n", "<Leader>u", ":Eunittest<Space>", { noremap = true })
  end
end

vim.api.nvim_create_autocmd("BufReadCmd", {
  group = augroup,
  callback = function(event)
    -- The Lua equivalent to `set nohlsearch` in Vimscript
    vim.o.hlsearch = false
  end
end

You can check out this PR to see where I converted from Vim to a Neovim config.

Step 3: oh yeah, LSPs

Once my vim config was ported over, I set up LSPs. This was confusing because “having an LSP” is actually a lot of different pieces, and people talk about them as if they’re all one thing. But I learned that I can have an LSP working and still not have autocompletion. Here are the individual pieces, and the PR in my dotfiles where I implemented them.

  1. Having an LSP installed on your computer (e.g. gem install ruby-lsp)
    1. People use mason.nvim for this, a full package manager. I decided to just install each LSP manually until I understood everything better. I still haven’t installed mason.
  2. Neovim configuration for that LSP so Neovim can talk to it
    1. Everyone uses nvim-lspconfig for this.
    2. I discovered that I had to explicitly set up each LSP that I wanted to use. I assumed Neovim would automatically detect what’s installed.
    3. I implemented that in this PR.
  3. Fun mappings for various LSP functions, like a fuzzy finder for all of the methods in your project
    1. Neovim’s Lua interface lets me call LSP functions directly (:lua vim.lsp.buf.references), but it’s not ergonomic. I set up Telescope because it’s the most popular fuzzy finder, and it has a lot of built in pieces for LSPs.
    2. There’s a fun easter egg in Telescope: try :Telescope planets.
    3. I implemented the nice Telescope mappings in this PR.
  4. Autocompletion on <Tab>
    1. I decided to use nvim-cmp.
    2. nvim-cmp can even autocomplete file paths in code as you type them, which is so cool.
    3. I implemented that in this PR.
Step 4: enjoy it

Now I have a working LSP with autocompletion! It’s so nice.

http://gabebw.com/blog/2025/04/05/converting-to-neovim
How to Make a Custom Ringtone for your iPhone
It's easy!
Show full content

Update: I made a website to make a ringtone out of any audio file. Try it!


First, get a file on your Mac with the sound you want. It can be a video or audio file, in pretty much any format. Just for kicks, let’s say it’s a file called “Carly Rae Jepsen - I Really Like You.mp4”.

You’ll also need to have ffmpeg installed.

Create the ringtone file

Run this command:

ffmpeg \
  -i "Carly Rae Jepsen - I Really Like You.mp4" \
  -vn \
  -ac 1 \
  -b:a 128k \
  -f mp4 \
  -c:a aac \
  -ss 41.5 \
  -t 29.99 \
  "ringtone.m4r"

Let’s break that down:

  • -i <filename>: the input filename
  • -vn: strip out all of the video (“video null”). That makes the resulting mp4 file audio-only.
  • -ac 1: 1 audio channel
  • -b:a 128k: set bitrate of the audio to 128k. I’m not sure why, honestly: I copied this from an online post. But if I had to guess, it’s to keep the filesize down by limiting the bitrate, while still keeping it high enough that there’s almost no loss in quality. I’m not a huge audiophile but I don’t think many people are worried about the audio quality of their ringtone. Some quick testing showed that a 64k bitrate leads to a 244Kb file; 128k leads to 484k; and 256k leads to 956Kb.
  • -f mp4: output format is MP4. Usually ffmpeg will figure it out based on the output file’s extension, but m4r is a weird one, so we specify it.
  • -ss 41.5: start the clip 41.5 seconds into the file
  • -t 29.99: end the clip after 29.99 seconds from when it started. Ringtones on iPhone have a maximum length of 30 seconds.
  • ringtone.m4r: the output file name. iPhone ringtones must end in .m4r.

I often have to run this a couple times to figure out exactly where to start (-ss) the clip. If you want to re-run it and automatically overwrite the file without prompting, add -y.

Now you have a ringtone.m4r file.

At this point you may want to rename it to something other than ringtone.m4r, because the file name will be the name of the ringtone in your phone.

Get the ringtone on your phone

You need to physically connect your iPhone to your computer. It’s odd, but there doesn’t seem to be any other way to do it.

After connecting your phone, open Music.app and find your phone in the sidebar on the left. Then drag the ringtone.m4r file onto the big white space in the center to add it.

Now it’s a ringtone, and you can go to any contact, edit it, and set the ringtone to your newly added ringtone.

http://gabebw.com/blog/2024/06/30/how-to-make-a-custom-ringtone-for-your-iphone
Hyperlinks in the Terminal
You don't always need to use bare URLs.
Show full content

Yesterday I learned that you can show a hyperlink in most terminals. That is, you can show some text that links to a URL, like this, instead of a bare URL, like https://gabebw.com/archive.

To check if your terminal and emulator work with this, you can check the list here. As of today, macOS’s Terminal.app does not support this.

Or you can run this command:

printf '\e]8;;http://example.com\e\\This is a link\e]8;;\e\n'

That will print the text “This is a link”, all of which links to example.com. Your terminal (or terminal emulator) will have different ways to test it, but try hovering over it to see if there’s an underline. If there is, then open it with whatever modifier your terminal uses (e.g. for Alacritty: Cmd-click; for Kitty: Ctrl-Shift-click).

This works by using something called OSC 8. (OSC stands for “operating system command”.) It’s hard to find more detail on it, not least because “operating system command” returns lots of unrelated results, but here’s Wikipedia.

Unfortunately, this does not work in tmux yet, so if you’re running tmux inside a terminal that does work, you’ll have to exit tmux for the link to work. (In a script I wrote that prompted this blog post, I added a check for the $TMUX env variable. If it’s in tmux, then it prints a bare URL instead.)

Sources

Thank you to this gist blog post.

http://gabebw.com/blog/2024/01/06/hyperlinks-in-the-terminal
Fun Facts and Tidbits from 2022
66 facts that delighted me this year.
Show full content
  1. In the 1912 Olympics, the then-President of the International Olympic Committee, Baron Pierre de Coubertin, won a gold medal…for literature!
  2. “Omicron” literally means “little O” in Greek (o mikron); compare to omega, or “great O” (ō mega).
  3. Coors encouraged the organization of its gay and lesbian employees into the Lesbian and Gay Employee Resource in 1993. Its acronym was LAGER.
  4. Rat tickling is an effective way to improve laboratory rat welfare. And you can get certified in it.
  5. The RMS Carpathia moved heaven and earth to rescue the survivors of the Titanic.
  6. Ornithologist Mario Cohn-Haft predicted the existence of a bird 25 years before confirming it.
  7. The “imperial phase” is the name for the period in which a musical artist is regarded to be at their commercial and creative peak simultaneously.
  8. St. Francis of Assisi is the reason for the popularity of the name Francis. His given name was Giovanni, and Francis was his nickname, which spread due to his fame.
  9. The stitching at the top of a big 40 lb bag of rice or oats is a chain stitch.
  10. Things that are older than you think:
    1. Sharks are older than trees (420 million vs 370 million years old).
    2. Crocodiles and their relatives are older than Saturn’s rings (200 million years old vs 100 million years old).
    3. The Appalachian Mountains are older than bones (!!!).
    4. Altoids were created in the 1780s.
  11. The Emmy Awards are named after “immy”, an informal term for the image orthicon tube that was common in early television cameras.
  12. Male dromedaries have an extremely weird organ in their throat. Turn on your sound for this video.
  13. The nonsense syllables in e.g. “Sweet Caroline” (“Sweet Caroline, bum bum bum”) are called non-lexical vocables.
  14. The Burpee exercise was named after Dr. Royal H. Burpee (!), a man with an exquisite sense of style.
  15. You can fit all the planets (Pluto included) between the Earth and the Moon.
  16. Some Kosovans are named “Tonibler” after Prime Minister Tony Blair, who was credited with ending the Kosovo War.
  17. Vladimir Putin’s name in French is Vladimir Poutine.
  18. Jan Evangelista Purkyně (1787-1869) was such a famous scientist that when people wrote to him, all that they needed to put as the address was “Purkyně, Europe”.
  19. Julie Kavner, the voice of Marge Simpson, is reclusive and part of her contract says that she will never have to promote The Simpsons on video.
  20. The “Bell” in Taco Bell’s name came from its founder, Glen Bell, who started selling tacos from the side window of his hamburger stand and then expanded.
  21. Papa John’s franchises in Russia are called PJ Western (!).
  22. The stamp on toilet paper that you see at hotels sometimes is called a pixie stamp.
  23. Under federal law, a ship that only visits US ports must be flagged (registered) in the US. Thus, almost all cruise ships at least stop in Mexico or Canada to avoid the expense and regulation of a US registration.
  24. Bangkok’s formal name is so long that it is an official world record: Krung Thep Maha Nakhon Amon Rattanakosin Mahinthara Ayuthaya Mahadilok Phop Noppharat Ratchathani Burirom Udomratchaniwet Mahasathan Amon Piman Awatan Sathit Sakkathattiya Witsanukam Prasit.
  25. The names “Arctic” and “Antarctic” derive from the Greek word for bear (“arktos”), referring to Ursa Major’s visibility (or lack thereof) from each region.
  26. A number of Latin American people misheard R2-D2’s name as “Arturito”.
  27. The Bizzaria is a “bizarre” cross between a citron and an orange, and the fruit looks like half of each fruit combined. Check out that picture!
  28. Seymour Cray, the father of supercomputing, loved to dig tunnels to help him think through a problem: “While I’m digging in the tunnel, the elves will often come to me with solutions to my problem.”
  29. The Versace logo is Medusa’s head.
  30. Just as unitarianism is belief in God with one person, and trinitarianism is belief in a Holy Trinity, so too is there belief in God with two persons, and it has the excellent name of binitarianism.
  31. In 1972, Dr. John Fryer risked his career to tell his colleagues that gay people were not mentally ill.
  32. Passion fruit flowers look completely wild.
  33. There are two types of fun. Type 1 Fun is enjoyable while it’s happening. Type 2 Fun is miserable while it’s happening, but enjoyable afterwards. (For example, some exercise.)
  34. Baseball’s first openly gay player, Glenn Burke, invented the high five in 1977.
  35. The actor who played Ser Bronn in Game of Thrones (Jerome Flynn) was a singer with the best-selling UK single of 1995.
  36. Apparently you could make a pretty good living as an actor in a gorilla suit in the 1940s. At least if you were Ray “Crash” Corrigan.
  37. The first and second derivatives of position are called velocity and acceleration. The third derivative is jerk. And the fourth/fifth/sixth are snap, crackle, and pop.
  38. The “pool smell” is chloramines, not chlorine: compounds formed in pool water after chlorine has done its job and had a fight with a germ or algae or whatever. Ideally your pool wouldn’t smell like much.
  39. Many wineries color their wines a deeper red using a compound called Mega Purple.
  40. The space-age, Jetsons-like architecture has a formal name: Googie architecture.
  41. This is not a fun fact, exactly, but I love that Raimbaut d'Aurenga, who died before the year 1200, gets compliments on his fire bars 850 years later on Wikipedia:

    He was a major troubadour, having contributed to the creation of trobar ric, or articulate style, in troubadour poetry. About forty of his works survive, displaying a gusto for rare rhymes and intricate poetic form.

  42. In the Dublin whiskey fire of 1875, 13 people died. But none of them died from smoke inhalation, burns, and so on. They died from alcohol poisoning from drinking the whiskey that flowed out of the factory!

  43. I had cause to learn a bit about violins this year, and I was impressed by how much effort goes into the parts of the violin that are not the actual body. For example, bows can cost tens of thousands of dollars by themselves. I also found this website about violin strings, which I can only describe as having “Ollivander’s wand shop vibes”.

  44. Just as Harlem in NYC is named after Haarlem in the Netherlands, so too is Brooklyn named after Breukelen.

  45. Magnús Scheving created the TV show Lazytown. But before that, he became the Icelandic champion in aerobics after making a bet with his friend that they could each master a topic they knew nothing about in three years. His friend became the Icelandic champion in snooker as a result of the bet. I can’t believe this worked for both of them.

  46. In Babe: Pig in the City, the landlady’s elderly uncle is named Fugly.

  47. There was a small but vocal movement to import hippopotami to America to raise them for food in the early 1900s.

  48. The state flag of Hawaii includes the Union Jack.

  49. The hamster’s Arabic name translates to “Mr. Saddlebags” in reference to its roomy cheek pouches.

  50. Rivers Cuomo has a GitHub account.

  51. Oprah’s birth name was Orpah.

  52. Hallmark Cards owns Crayola.

  53. Steve Aoki is the heir to the Benihana fortune. Also, actress Devon Aoki is his half-sister.

  54. I was extremely amused to discover that Pilates was founded by Joseph Pilates. It’s like if baseball was started by Jim Baseball!

  55. The sport of rugby is named after Rugby School, 300 years after its founding.

  56. Sagittarius B2 is a giant molecular cloud that (probably) smells like raspberry rum.

  57. Shrapnel is named after Henry Shrapnel.

  58. Aqua Net was a popular hair spray of the 1960s. It was created by Rayette, which used the profits to buy…Fabergé, the jewellery company, for no reason I’ve been able to find. Maybe Rayette just really liked fancy eggs.

  59. The Dutch baseball league is called Honkbal Hoofdklasse, which is an extremely funny name.

  60. There are 7 sizes of piano, not just “regular” and “grand”.

  61. The town of Kenner, Louisiana was called “Cannes-Brûlées” by the French settlers. I just love that they landed there and thought “It’s just like Cannes, except it’s a million degrees hotter”.

  62. Zellerbach Hall, a local performing arts space in the SF Bay Area, is named after the Crown Zellerbach corporation, which invented modern molded egg cartons (among other things).

  63. Ratherius, a bishop born in 890 CE, was so quarrelsome and difficult to get along with that he was sent to prison and exiled.

  64. Pit vipers are called that not because they like to hang out in pits in the ground but because they have an infrared organ that is exposed via a deep pit on their head.

http://gabebw.com/blog/2022/12/27/fun-facts-and-tidbits-from-2022
What I learned building Candle in Rust
Useful Rust patterns I learned from building Candle.
Show full content

Candle is a tool that takes in a body of HTML plus some CSS selectors, then runs the CSS against the HTML to find what you’re looking for. For example:

$ echo "<h1 id='my-id'><span>text</span></h1>" | candle 'h1 {text}, h1 attr{id}'
text
my-id

It’s the largest project I’ve built in Rust so far, and I learned some excellent parts of Rust that will continue to be useful for my new projects.

I like filter_map

It’s a small thing, but I like the filter_map method. Here’s an example of where it’s useful.

In Candle, each combination of a selector and an operation (e.g. a.highlighted attr{href}) is called a Finder. Each Finder is tested against each HTML element and its operation is run if it matches:

// Lightly edited to remove some code that's not important here
impl<'a> Finder<'a> {
    fn match_and_apply(&self, element: &ElementRef) -> Option<String> {
        if self.selector.matches(element) {
            match self.operation {
                FinderOperation::Text => Some(...),
                FinderOperation::Attr(attr) => {
                  // Some(value) or None, if the attr doesn't exist
                },
                FinderOperation::Html => Some(...)
            }
        } else {
            None
        }
    }
}

If the selector matches and the operation returned something, it returns Some(...). Otherwise, it return None. Returning an Option here makes it easy to filter out empty results using filter_map:

finders.iter().filter_map(|finder| finder.match_and_apply(&element)) {

filter_map runs the closure, and if the closure returns Some(x), it uses x (i.e. it auto-unwraps the value) and if it returns None, filter_map discards the value entirely. So it turns Vec<Option<String>> into Vec<String>. This pattern pops up pretty often and filter_map is a neat solution.

The Read trait and testing

The Read trait allows for reading bytes from a source. It requires one method, read. Other methods are defined in terms of Read. (This is similar to how Rust’s Iterable module only requires a next method but gives many other methods defined in terms of next.)

I had a method that read from io::stdin, called read_from_stdin that took zero arguments. In order to test it, I changed it to instead take in anything that implements Read:

-fn read_from_stdin() -> Option<String> {
+fn read_from<R: Read>(mut reader: R) -> Option<String> {

Now instead of calling read_from_stdin, I call read_from(io::stdin()). The real benefit comes from testing the method. I can now pass in a Cursor, which lets us use slices or vectors as if they’re files. Here’s the full test:

#[test]
fn test_less_than_1024_bytes_of_html(){
    let html = r#"
        <!DOCTYPE html>
        <meta charset="utf-8">
        <title>Hello, world!</title>
        <h1 class="foo">Hello, <i>world!</i></h1>
    "#;
    let result = read_from(Cursor::new(html));
    assert_eq!(result, Some(html.to_string()));
}

(Here’s the commit where I added Read and Cursor.)

Cursor is a very helpful struct and I’ll definitely keep it in mind when writing future tests.

Pipes and panic

In an early version of Candle, piping the output to anything that truncated the output (like head) would cause it to panic:

$ curl --silent daringfireball.net | candle 'html {html}' | head -1
<html class="daringfireball-net" lang="en">
thread 'main' panicked at 'failed printing to stdout: Broken pipe (os error 32)', src/libstd/io/stdio.rs:792:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

This is a common problem with any Rust program that writes to the terminal in Rust. There’s an open Rust issue about it. As I understand it, when enough output has been read, the kernel closes the pipe and sends a SIGPIPE signal to the application that’s generating output (i.e. candle) so it gracefully dies. However, Rust ignores SIGPIPE, and keeps trying to generate output, and then panics when it detects the now-broken pipe.

Specifically, the issue is this:

println!("{}", &string);

println! panics when it runs into any errors. The fix is to use writeln!, which does not panic but instead returns std::io::Result<()>. We can then catch broken pipes:

let mut stdout = io::stdout();

if let Err(e) = writeln!(stdout, "{}", &string) {
    if e.kind() != ErrorKind::BrokenPipe {
        eprintln!("{}", e);
        process::exit(1);
    }
}

This new code does 2 things:

  1. It catches the Err and ignores it if it’s a broken pipe, and
  2. If it’s a real error, it prints the error to STDERR and exits with an error code (but without printing a gross error message)

This is much better than println!‘s panicky behavior.

(I did this in two PRs: #13 and #14.)

http://gabebw.com/blog/2019/10/13/learning-rust-by-candlelight