Ghostty - switching now
Ghostty caused quite a stir a while back in the bubble of Linux/Un*x console jockeys. Those of us who liked working in the shell (and still do) are always hunting for the ultimate terminal emulator.
Naturally that kept me "busy" too, and since I like messing around with Neovim (too much, probably), WezTerm was the natural candidate: configured entirely in Lua, it can even hand events to your config, so you can personalise the thing completely. On top of that it is really fast, supports Kitty's image protocol and even brings its own multiplexer, so your shells don't die when you close the window.
Kitty does a lot of that as well - I ran it as my primary terminal for a while. I already wrote something about Ghostty over here, back then I focused mainly on speed - and because speed isn't everything, I still stuck with WezTerm as my daily driver for quite a while.
So why Ghostty after all?
Good question. WezTerm is genuinely great. So is Kitty. Both have every right to exist. But here is what annoyed me: Kitty and Ghostty both use their own TERM value to know what is allowed in the shell.
That can cause trouble when you work on remote systems over ssh. Kitty's answer is to use the kitten ssh wrapper instead of plain ssh - then all terminal settings get copied over. Which in turn caused other problems.
For someone like me, who has to "fight" 😉 with dozens of different systems every day, that was a real issue. Which is how I ended up back on WezTerm.
But not everything is rosy there either. The thing can do a lot, and much of it is great. The Lua support is lovely. I built some genuinely fun stuff with it. Mostly eye candy: a background chooser, semi-transparency, a parallax scroll effect on the background image. That last one really is brilliant, you won't find it anywhere else. Proper eye-catcher.
Except - and this is the honest version of the story - I hardly ever used it. The first thing I did was always switch to transparent or focus mode. A nice feature with no practical use. And half the machinery around it was really just treating symptoms: with the WebGpu backend the fonts looked kind of frayed to me, and to keep the text readable I kept dialling the transparency up and down. Hence the keybind, hence the whole contraption. The actual fix turned out to be a one-liner: front_end = "OpenGL". Problem gone - and with it the reason for all the tinkering.
Add to that the Lua config getting more and more time-consuming to maintain, because you always want to extend and improve something. A thousand lines of Lua for a terminal. Ghostty is rather minimalist in comparison. And that is a good thing. You set it up once the way you need it - done.
One difference, and it is a bit annoying: when you share a window over Teams, WezTerm shares it including all tabs. Ghostty, oddly, only shares the currently open tab. Very strange behaviour, probably down to the macOS APIs (as usual). Worth knowing if you work that way.
The TERM story is settled
That was my main argument against Ghostty, and it is gone now. Ghostty ships with the fix, you just have to turn it on - the default is off. A look at the defaults:
The two no-ssh-* are the interesting ones. If you want it simple, put this in your config:
Then everything is on, and ghostty +show-config shows you what that actually resolved to:
ssh-terminfo installs Ghostty's terminfo on the target host automatically when connecting, using infocmp and tic. ssh-env catches the case where that fails and falls back to xterm-256color. The sudo wrapper makes sure sudo vim doesn't take the terminfo away from you again. What used to be my manual infocmp -x | ssh SERVER -- tic -x - now just happens.
And the best part: Ghostty keeps track of which hosts are already taken care of.
Cached hosts (35):
...
35 boxes, the oldest entry 310 days old. That is why I don't notice any of it anymore - the extra roundtrip only happens on the very first connect to a host. One pitfall remains, and it bites us admins specifically: if a box gets reinstalled but keeps its name, the cache still believes everything is in place. The symptoms are back, with no obvious reason. This helps:
By the way, those "weird timeouts" I used to get were exactly this. Arrow keys send sequences starting with ESC. If the terminfo doesn't know them, readline or vim can't decide whether you pressed ESC or started a sequence - so it waits. Feels like a hung terminal, but it is just a waiting loop.
Ghostty shaders
What finally pushed me to switch was the config, and then the surprisingly practical eye candy I could build with the shaders.
I had no idea what these shaders are good for or whether they do anything useful (well, "useful" is a matter of definition anyway). But what I ended up building with them genuinely surprised me.
Some of you might remember the smooth cursor effect in Word: while typing, the cursor didn't "jump", it animated its way to the target. That looked kind of cool, and it is exactly what I could rebuild with a shader. Really fun behaviour. And there are several cursor shaders to pick from, plus all sorts of nonsense from falling snow to CRT simulation.
A few things I stumbled over - all three share the trait that ghostty +validate-config waves them through without a word:
- There are no end-of-line comments. A
custom-shader = foo.glsl # nice notemakes the note part of the filename. The shader then simply doesn't load. Comments need their own line. - Relative paths resolve relative to the file the line sits in - not relative to the main config. If you pull your shader lines in from a subfolder via
config-file, you will be hunting for a while. Absolute paths are the calmer option. - Compile errors only show up in the log. A shader that doesn't build is silently ignored. You can check with
log show --last 5m --predicate 'process == "ghostty"' | grep -i shader.
The good news: a broken shader costs you the effect at most, not the session.
Ghostty does have a limit, though, and you notice it precisely when you come from tinkering with WezTerm: the config is a static key-value list. No logic, no state, no events. You can set a background image, but you can't tie it to transparency. And for transparency there is only toggle_background_opacity - on or off, no steps. Changing the value itself even requires a full restart on macOS. After my WebGpu experience I don't need the dial anymore, but it is worth knowing.
As for speed: in my vtebench runs Ghostty is clearly faster than WezTerm. Even with a shader running, which surprised me at first. It does make sense though - vtebench measures parser throughput, and the shader is a GPU pass tied to the refresh rate. The renderer draws its 60 to 120 frames per second no matter how many megabytes the parser chews through.
Fun to play
And sure, this can change again. At some point over the next few months Ghostty will start annoying me and I'll move somewhere else. But until then, Ghostty is my playground.
Ok, all of this is playing around, I'll admit that. Sure. But it is the nice kind. And a terminal you can play with is a good thing. In the end all that matters is: is the text readable, does my software work the way it should, does everything look right. And it does. Graphical tools run without a hitch (viu for example, or imgcat, or morpheus).
Ghostty is a proper all-rounder. Good to work with.