Latest Posts (20 found)
(think) 2 days ago

Building Emacs Major Modes with TreeSitter: Lessons Learned

Over the past year I’ve been spending a lot of time building TreeSitter-powered major modes for Emacs – clojure-ts-mode (as co-maintainer), neocaml (from scratch), and asciidoc-mode (also from scratch). Between the three projects I’ve accumulated enough battle scars to write about the experience. This post distills the key lessons for anyone thinking about writing a TreeSitter-based major mode, or curious about what it’s actually like. Before TreeSitter, Emacs font-locking was done with regular expressions and indentation was handled by ad-hoc engines (SMIE, custom indent functions, or pure regex heuristics). This works, but it has well-known problems: Regex-based font-locking is fragile. Regexes can’t parse nested structures, so they either under-match (missing valid code) or over-match (highlighting inside strings and comments). Every edge case is another regex, and the patterns become increasingly unreadable over time. Indentation engines are complex. SMIE (the generic indentation engine for non-TreeSitter modes) requires defining operator precedence grammars for the language, which is hard to get right. Custom indentation functions tend to grow into large, brittle state machines. Tuareg’s indentation code, for example, is thousands of lines long. TreeSitter changes the game because you get a full, incremental, error-tolerant syntax tree for free. Font-locking becomes “match this AST pattern, apply this face”: And indentation becomes “if the parent node is X, indent by Y”: The rules are declarative, composable, and much easier to reason about than regex chains. In practice, ’s entire font-lock and indentation logic fits in about 350 lines of Elisp. The equivalent in tuareg is spread across thousands of lines. That’s the real selling point: simpler, more maintainable code that handles more edge cases correctly . That said, TreeSitter in Emacs is not a silver bullet. Here’s what I ran into. TreeSitter grammars are written by different authors with different philosophies. The tree-sitter-ocaml grammar provides a rich, detailed AST with named fields. The tree-sitter-clojure grammar, by contrast, deliberately keeps things minimal – it only models syntax, not semantics, because Clojure’s macro system makes static semantic analysis unreliable. 1 This means font-locking forms in Clojure requires predicate matching on symbol text, while in OCaml you can directly match nodes with named fields. To illustrate: here’s how you’d fontify a function definition in OCaml, where the grammar gives you rich named fields: And here’s the equivalent in Clojure, where the grammar only gives you lists of symbols and you need predicate matching: You can’t learn “how to write TreeSitter queries” generically – you need to learn each grammar individually. The best tool for this is (to visualize the full parse tree) and (to see the node at point). Use them constantly. You’re dependent on someone else providing the grammar, and quality is all over the map. The OCaml grammar is mature and well-maintained – it’s hosted under the official tree-sitter GitHub org. The Clojure grammar is small and stable by design. But not every language is so lucky. asciidoc-mode uses a third-party AsciiDoc grammar that employs a dual-parser architecture – one parser for block-level structure (headings, lists, code blocks) and another for inline formatting (bold, italic, links). This is the same approach used by Emacs’s built-in , and it makes sense for markup languages where block and inline syntax are largely independent. The problem is that the two parsers run independently on the same text, and they can disagree . The inline parser misinterprets and list markers as emphasis delimiters, creating spurious bold spans that swallow subsequent inline content. The workaround is to use on all block-level font-lock rules so they win over the incorrect inline faces: This doesn’t fix inline elements consumed by the spurious emphasis – that requires an upstream grammar fix. When you hit grammar-level issues like this, you either fix them yourself (which means diving into the grammar’s JavaScript source and C toolchain) or you live with workarounds. Either way, it’s a reminder that your mode is only as good as the grammar underneath it. Getting the font-locking right in was probably the most challenging part of all three projects, precisely because of these grammar quirks. I also ran into a subtle behavior: the default font-lock mode ( ) skips an entire captured range if any position within it already has a face. So if you capture a parent node like and a child was already fontified, the whole thing gets skipped silently. The fix is to capture specific child nodes instead: These issues took a lot of trial and error to diagnose. The lesson: budget extra time for font-locking when working with less mature grammars . Grammars evolve, and breaking changes happen. switched from the stable grammar to the experimental branch because the stable version had metadata nodes as children of other nodes, which caused and to behave incorrectly. The experimental grammar makes metadata standalone nodes, fixing the navigation issues but requiring all queries to be updated. pins to v0.24.0 of the OCaml grammar. If you don’t pin versions, a grammar update can silently break your font-locking or indentation. The takeaway: always pin your grammar version , and include a mechanism to detect outdated grammars. tests a query that changed between versions to detect incompatible grammars at startup. Users shouldn’t have to manually clone repos and compile C code to use your mode. Both and include grammar recipes: On first use, the mode checks and offers to install missing grammars via . This works, but requires a C compiler and Git on the user’s machine, which is not ideal. 2 The TreeSitter support in Emacs has been improving steadily, but each version has its quirks: Emacs 29 introduced TreeSitter support but lacked several APIs. For instance, (used for structured navigation) doesn’t exist – you need a fallback: Emacs 30 added , sentence navigation, and better indentation support. But it also had a bug in offsets ( #77848 ) that broke embedded parsers, and another in that required to disable its TreeSitter-aware version. Emacs 31 has a bug in where an off-by-one error causes to leave ` *)` behind on multi-line OCaml comments. I had to skip the affected test with a version check: The lesson: test your mode against multiple Emacs versions , and be prepared to write version-specific workarounds. CI that runs against Emacs 29, 30, and snapshot is essential. Most TreeSitter grammars ship with query files for syntax highlighting ( ) and indentation ( ). Editors like Neovim and Helix use these directly. Emacs doesn’t – you have to manually translate the patterns into and calls in Elisp. This is tedious and error-prone. For example, here’s a rule from the OCaml grammar’s : And here’s the Elisp equivalent you’d write for Emacs: The query syntax is nearly identical, but you have to wrap everything in calls, map upstream capture names ( ) to Emacs face names ( ), assign features, and manage behavior. You end up maintaining a parallel set of queries that can drift from upstream. Emacs 31 will introduce which will make it possible to use files for font-locking, which should help significantly. But for now, you’re hand-coding everything. When a face isn’t being applied where you expect: TreeSitter modes define four levels of font-locking via , and the default level in Emacs is 3. It’s tempting to pile everything into levels 1–3 so users see maximum highlighting out of the box, but resist the urge. When every token on the screen has a different color, code starts looking like a Christmas tree and the important things – keywords, definitions, types – stop standing out. Less is more here. Here’s how distributes features across levels: And follows the same philosophy: The pattern is the same: essentials first, progressively more detail at higher levels. This way the default experience (level 3) is clean and readable, and users who want the full rainbow can bump to 4. Better yet, they can use to cherry-pick individual features regardless of level: This gives users fine-grained control without requiring mode authors to anticipate every preference. Indentation issues are harder to diagnose because they depend on tree structure, rule ordering, and anchor resolution: Remember that rule order matters for indentation too – the first matching rule wins. A typical set of rules reads top to bottom from most specific to most general: Watch out for the empty-line problem : when the cursor is on a blank line, TreeSitter has no node at point. The indentation engine falls back to the root node as the parent, which typically matches the top-level rule and gives column 0. In neocaml I solved this with a rule that looks at the previous line’s last token to decide indentation: This is the single most important piece of advice. Font-lock and indentation are easy to break accidentally, and manual testing doesn’t scale. Both projects use Buttercup (a BDD testing framework for Emacs) with custom test macros. Font-lock tests insert code into a buffer, run , and assert that specific character ranges have the expected face: Indentation tests insert code, run , and assert the result matches the expected indentation: Integration tests load real source files and verify that both font-locking and indentation survive on the full file. This catches interactions between rules that unit tests miss. has 200+ automated tests and has even more. Investing in test infrastructure early pays off enormously – I can refactor indentation rules with confidence because the suite catches regressions immediately. When I became the maintainer of clojure-mode many years ago, I really struggled with making changes. There were no font-lock or indentation tests, so every change was a leap of faith – you’d fix one thing and break three others without knowing until someone filed a bug report. I spent years working on a testing approach I was happy with, alongside many great contributors, and the return on investment was massive. The same approach – almost the same test macros – carried over directly to when we built the TreeSitter version. And later I reused the pattern again in and . One investment in testing infrastructure, four projects benefiting from it. I know that automated tests, for whatever reason, never gained much traction in the Emacs community. Many popular packages have no tests at all. I hope stories like this convince you that investing in tests is really important and pays off – not just for the project where you write them, but for every project you build after. This one is specific to but applies broadly: compiling TreeSitter queries at runtime is expensive. If you’re building queries dynamically (e.g. with called at mode init time), consider pre-compiling them as values. This made a noticeable difference in ’s startup time. The Emacs community has settled on a suffix convention for TreeSitter-based modes: , , , and so on. This makes sense when both a legacy mode and a TreeSitter mode coexist in Emacs core – users need to choose between them. But I think the convention is being applied too broadly, and I’m afraid the resulting name fragmentation will haunt the community for years. For new packages that don’t have a legacy counterpart, the suffix is unnecessary. I named my packages (not ) and (not ) because there was no prior or to disambiguate from. The infix is an implementation detail that shouldn’t leak into the user-facing name. Will we rename everything again when TreeSitter becomes the default and the non-TS variants are removed? Be bolder with naming. If you’re building something new, give it a name that makes sense on its own merits, not one that encodes the parsing technology in the package name. I think the full transition to TreeSitter in the Emacs community will take 3–5 years, optimistically. There are hundreds of major modes out there, many maintained by a single person in their spare time. Converting a mode from regex to TreeSitter isn’t just a mechanical translation – you need to understand the grammar, rewrite font-lock and indentation rules, handle version compatibility, and build a new test suite. That’s a lot of work. Interestingly, this might be one area where agentic coding tools can genuinely help. The structure of TreeSitter-based major modes is fairly uniform: grammar recipes, font-lock rules, indentation rules, navigation settings, imenu. If you give an AI agent a grammar and a reference to a high-quality mode like , it could probably scaffold a reasonable new mode fairly quickly. The hard parts – debugging grammar quirks, handling edge cases, getting indentation just right – would still need human attention, but the boilerplate could be automated. Still, knowing the Emacs community, I wouldn’t be surprised if a full migration never actually completes. Many old-school modes work perfectly fine, their maintainers have no interest in TreeSitter, and “if it ain’t broke, don’t fix it” is a powerful force. And that’s okay – diversity of approaches is part of what makes Emacs Emacs. TreeSitter is genuinely great for building Emacs major modes. The code is simpler, the results are more accurate, and incremental parsing means everything stays fast even on large files. I wouldn’t go back to regex-based font-locking willingly. But it’s not magical. Grammars are inconsistent across languages, the Emacs APIs are still maturing, you can’t reuse files (yet), and you’ll hit version-specific bugs that require tedious workarounds. The testing story is better than with regex modes – tree structures are more predictable than regex matches – but you still need a solid test suite to avoid regressions. If you’re thinking about writing a TreeSitter-based major mode, do it. The ecosystem needs more of them, and the experience of working with syntax trees instead of regexes is genuinely enjoyable. Just go in with realistic expectations, pin your grammar versions, test against multiple Emacs releases, and build your test suite early. Anyways, I wish there was an article like this one when I was starting out with and , so there you have it. I hope that the lessons I’ve learned along the way will help build better modes with TreeSitter down the road. That’s all I have for you today. Keep hacking! See the excellent scope discussion in the tree-sitter-clojure repo for the rationale.  ↩︎ There’s ongoing discussion in the Emacs community about distributing pre-compiled grammar binaries, but nothing concrete yet.  ↩︎ Regex-based font-locking is fragile. Regexes can’t parse nested structures, so they either under-match (missing valid code) or over-match (highlighting inside strings and comments). Every edge case is another regex, and the patterns become increasingly unreadable over time. Indentation engines are complex. SMIE (the generic indentation engine for non-TreeSitter modes) requires defining operator precedence grammars for the language, which is hard to get right. Custom indentation functions tend to grow into large, brittle state machines. Tuareg’s indentation code, for example, is thousands of lines long. Use to verify the node type at point matches your query. Set to to see which rules are firing. Check the font-lock feature level – your rule might be in level 4 while the user has the default level 3. The features are assigned to levels via . Remember that rule order matters . Without , an earlier rule that already fontified a region will prevent later rules from applying. This can be intentional (e.g. builtin types at level 3 take precedence over generic types) or a source of bugs. Set to – this logs which rule matched for each line, what anchor was computed, and the final column. Use to understand the parent chain. The key question is always: “what is the parent node, and which rule matches it?” Remember that rule order matters for indentation too – the first matching rule wins. A typical set of rules reads top to bottom from most specific to most general: Watch out for the empty-line problem : when the cursor is on a blank line, TreeSitter has no node at point. The indentation engine falls back to the root node as the parent, which typically matches the top-level rule and gives column 0. In neocaml I solved this with a rule that looks at the previous line’s last token to decide indentation: See the excellent scope discussion in the tree-sitter-clojure repo for the rationale.  ↩︎ There’s ongoing discussion in the Emacs community about distributing pre-compiled grammar binaries, but nothing concrete yet.  ↩︎

0 views
(think) 5 days ago

Learning Vim in 3 Steps

Every now and then someone asks me how to learn Vim. 1 My answer is always the same: it’s simpler than you think, but it takes longer than you’d like. Here’s my bulletproof 3-step plan. Start with – it ships with Vim and takes about 30 minutes. It’ll teach you enough to survive: moving around, editing text, saving, quitting. The essentials. Once you’re past that, I strongly recommend Practical Vim by Drew Neil. This book changed the way I think about Vim. I had known the basics of Vim for over 20 years, but the Vim editing model never really clicked for me until I read it. The key insight is that Vim has a grammar – operators (verbs) combine with motions (nouns) to form commands. (delete) + (word) = . (change) + (inside quotes) = . Once you internalize this composable language, you stop memorizing individual commands and start thinking in Vim . The book is structured as 121 self-contained tips rather than a linear tutorial, which makes it great for dipping in and out. You could also just read cover to cover – Vim’s built-in documentation is excellent. But let’s be honest, few people have that kind of patience. Other resources worth checking out: Resist the temptation to grab a massive Neovim distribution like LazyVim on day one. You’ll find it overwhelming if you don’t understand the basics and don’t know how the Vim/Neovim plugin ecosystem works. It’s like trying to drive a race car before you’ve learned how a clutch works. Instead, start with a minimal configuration and grow it gradually. I wrote about this in detail in Build your .vimrc from Scratch – the short version is that modern Vim and Neovim ship with excellent defaults and you can get surprisingly far with a handful of settings. I’m a tinkerer by nature. I like to understand how my tools operate at their fundamental level, and I always take that approach when learning something new. Building your config piece by piece means you understand every line in it, and when something breaks you know exactly where to look. I’m only half joking. Peter Norvig’s famous essay Teach Yourself Programming in Ten Years makes the case that mastering any complex skill requires sustained, deliberate practice over a long period – not a weekend crash course. The same applies to Vim. Grow your configuration one setting at a time. Learn Vimscript (or Lua if you’re on Neovim). Read other people’s configs. Maybe write a small plugin. Every month you’ll discover some built-in feature or clever trick that makes you wonder how you ever lived without it. One of the reasons I chose Emacs over Vim back in the day was that I really hated Vimscript – it was a terrible language to write anything in. These days the situation is much better: Vim9 Script is a significant improvement, and Neovim’s switch to Lua makes building configs and plugins genuinely enjoyable. Mastering an editor like Vim is a lifelong journey. Then again, the way things are going with LLM-assisted coding, maybe you should think long and hard about whether you want to commit your life to learning an editor when half the industry is “programming” without one. But that’s a rant for another day. If this bulletproof plan doesn’t work out for you, there’s always Emacs. Over 20 years in and I’m still learning new things – these days mostly how to make the best of evil-mode so I can have the best of both worlds. As I like to say: The road to Emacs mastery is paved with a lifetime of invocations. That’s all I have for you today. Keep hacking! Just kidding – everyone asks me about learning Emacs. But here we are.  ↩︎ Advent of Vim – a playlist of short video tutorials covering basic Vim topics. Great for visual learners who prefer bite-sized lessons. ThePrimeagen’s Vim Fundamentals – if you prefer video content and a more energetic teaching style. vim-be-good – a Neovim plugin that gamifies Vim practice. Good for building muscle memory. Just kidding – everyone asks me about learning Emacs. But here we are.  ↩︎

0 views
(think) 5 days ago

How to Vim: Auto-save on Activity

Coming from Emacs, one of the things I missed most in Vim was auto-saving. I’ve been using my own super-save Emacs package for ages – it saves your buffers automatically when you switch between them, when Emacs loses focus, and on a handful of other common actions. After years of using it I’ve completely forgotten that exists. Naturally, I wanted something similar in Vim. Vim’s autocommands make it straightforward to set up basic auto-saving. Here’s what I ended up with: This saves the current buffer when Vim loses focus (you switch to another window) and when you leave Insert mode. A few things to note: You can extend this with more events if you like: Adding catches edits made in Normal mode (like , , or paste commands), so you’re covered even when you never enter Insert mode. works reliably in GUI Vim and most modern terminal emulators, but it may not fire in all terminal setups (especially inside tmux without additional configuration). One more point in favor of using Ghostty and not bothering with terminal multiplexers. The same autocommands work in Neovim. You can put the equivalent in your : Neovim also has ( ) which automatically saves before certain commands like , , and . It’s not a full auto-save solution, but it’s worth knowing about. There are several plugins that take auto-saving further, notably vim-auto-save for Vim and auto-save.nvim for Neovim. Most of these plugins rely on – an event that fires after the cursor has been idle for milliseconds. The problem is that is a global setting that also controls how often swap files are written, and other plugins depend on it too. Setting it to a very low value (say, 200ms) for snappy auto-saves can cause side effects – swap file churn, plugin conflicts, and in Neovim specifically, can behave inconsistently when timers are running. For what it’s worth, I think idle-timer-based auto-saving is overkill in Vim’s context. The simple autocommand approach covers the important cases, and anything more aggressive starts fighting against Vim’s grain. I’ve never been fond of the idle-timer approach to begin with, and that’s part of the reason why I created for Emacs. I like the predictability of triggering save by doing some action. Simplicity is the ultimate sophistication. – Leonardo da Vinci Here’s the thing I’ve come to appreciate about Vim: saving manually isn’t nearly as painful as it is in Emacs. In Emacs, is a two-chord sequence that you type thousands of times a day – annoying enough that auto-save felt like a necessity. In Vim, you’re already in Normal mode most of the time, so a quick mapping like: gives you a fast, single-keystroke save (assuming your leader is Space, which it should be). It’s explicit, predictable, and takes almost no effort. As always, I’ve learned quite a bit about Vim by looking into this simple topic. That’s probably the main reason I still bother to write such tutorial articles – they make me reinforce the knowledge I’ve just obtained and make ponder more than usual about the trade-offs between different ways to approach certain problems. I still use the autocommand approach myself – old habits die hard – but I have to admit that gets the job done just fine. Sometimes the simplest solution really is the best one. That’s all I have for you today. Keep hacking! instead of – it only writes when the buffer has actually changed, avoiding unnecessary disk writes. – suppresses errors for unnamed buffers and read-only files that can’t be saved.

0 views
(think) 1 weeks ago

How to Vim: To the Terminal and Back

Sooner or later every Vim user needs to drop to a shell – to run tests, check git status, or just poke around. Vim gives you two very different ways to do this: the old-school suspend and the newer command. Let’s look at both. Pressing in Vim sends a signal that suspends the entire Vim process and drops you back to your shell. When you’re done, type to bring Vim back exactly where you left it. You can also use or from command mode if you prefer. Vim 8.1 (released in May 2018) introduced – a built-in terminal emulator that runs inside a Vim window. This was a pretty big deal at the time, as I’ll explain in a moment. The basics are simple: In Neovim the key mapping to exit terminal mode is the same ( ), but you can also set up a more ergonomic alternative like by adding to your config. One of the most useful aspects of is running a specific command: The expands to the current filename, which makes this a quick way to test whatever you’re working on without leaving Vim. The output stays in a buffer you can scroll through and even yank from – handy when you need to copy an error message. You might be wondering how compares to the classic command. The main difference is that blocks Vim until the command finishes and then shows the output in a temporary screen – you have to press Enter to get back. runs the command in a split window, so you can keep editing while it runs and the output stays around for you to review. For quick one-off commands like or , bang commands are fine. For anything with longer-running output – tests, build commands, interactive REPLs – is the better choice. The story of is intertwined with the story of Neovim. When Neovim was forked from Vim in early 2014, one of its key goals was to add features that Vim had resisted for years – async job control and a built-in terminal emulator among them. Neovim shipped its terminal emulator (via libvterm) in 2015, a full three years before Vim followed suit. It’s fair to say that Neovim’s existence put pressure on Vim to modernize. Bram Moolenaar himself acknowledged that “Neovim did create some pressure to add a way to handle asynchronous jobs.” Vim 8.0 (2016) added async job support, and Vim 8.1 (2018) brought the terminal emulator. Competition is a wonderful thing. Here’s the honest truth: I rarely use . Not in Vim, and not the equivalent in Emacs either ( , , etc.). I much prefer switching to a proper terminal emulator – these days that’s Ghostty for me – where I get my full shell experience with all the niceties of a dedicated terminal (proper scrollback, tabs, splits, ligatures, the works). I typically have Vim in one tab/split and a shell in another, and I switch between them with a keystroke. I get that I might be in the minority here. Many people love having everything inside their editor, and I understand the appeal – fewer context switches, everything in one place. If that’s your style, is a perfectly solid option. But if you’re already comfortable with a good terminal emulator, don’t feel pressured to move your shell workflow into Vim just because you can. That’s all I have for you today. Keep hacking! Dead simple – no configuration, works everywhere. You get your real shell with your full environment, aliases, and all. Zero overhead – Vim stays in memory, ready to resume instantly. You can’t see Vim and the shell at the same time. Easy to forget you have a suspended Vim session (check with ). Doesn’t work in GUIs like gVim or in terminals that don’t support job control. – opens a terminal in a horizontal split – opens it in a vertical split – switches from Terminal mode back to Normal mode (so you can scroll, yank text, etc.)

0 views
(think) 1 weeks ago

How to Vim: Build your .vimrc from Scratch

People often think that getting started with Vim means spending hours crafting an elaborate with dozens of plugins. In reality, modern Vim (9+) and Neovim ship with remarkably sane defaults, and you can get very far with a configuration that’s just a few lines long – or even no configuration at all. If you launch Vim 9 without a file, it automatically loads – a built-in configuration that provides a solid foundation. Here’s what you get for free: That’s actually a pretty reasonable editing experience out of the box! You can read the full details with . Neovim goes even further with its defaults – it enables (copies indentation from the previous line), (highlights all search matches), (makes Tab smarter at the start of a line), (reloads files changed outside the editor), always shows the statusline, and sets the command history to 10000 entries, among many other things. If you’re on Neovim, the out-of-the-box experience is excellent. See for the full list. Here’s something that trips up a lot of people: the moment you create a file – even an empty one – Vim stops loading entirely. That means you lose all those nice defaults. The fix is simple. Start your with: This loads the defaults first, and then your own settings override or extend them as needed. This gotcha only applies to Vim. Neovim’s defaults are always active regardless of whether you have an or . Here’s a minimal that builds on the defaults and adds a few things most people want: That’s five settings on top of the defaults. You might not even need all of them – already handles the fundamentals. For Neovim, you don’t need the line – all the equivalents are already active. You also get , , and for free, so the only settings left to add are the ones that are genuinely personal preference: One of the most underappreciated aspects of Vim is how much built-in support it ships for programming languages. When is active (which it is via or Neovim’s defaults), you automatically get: This means that when you open a Python file, Vim already knows to use 4-space indentation. Open a Ruby file and it switches to 2 spaces. Open a Makefile and it uses tabs. All without a single plugin or line of configuration. You can check what’s available with for syntax files or for filetype plugins. The list is impressively long. At some point you’ll probably want more than the bare minimum. Here are a few things worth considering as your next steps: And when you eventually want more plugins, you probably won’t need many. A fuzzy finder, maybe a Git integration, and perhaps a completion engine will cover most needs. But that’s a topic for another day. The key takeaway is this: don’t overthink your . Start with the defaults, add only what you actually need, and resist the urge to copy someone else’s 500-line configuration. A small, well-understood configuration beats a large, cargo-culted one every time. That’s part of the reason why when I started to re-learn Vim I’ve opted to slowly build a Vim 9 configuration from scratch, instead of jumping to something like Neovim + Kickstart.nvim or LazyVim right away. Less is more. Understanding the foundations of your editor matters. 1 Right now my is just 100 lines and I don’t foresee it becoming much bigger in the long run. If you want to see just how far you can go without plugins, I highly recommend the Thoughtbot talk How to Do 90% of What Plugins Do (With Just Vim) . It’s a great demonstration of Vim’s built-in capabilities for file finding, auto-completion, tag navigation, and more. That’s all I have for you today. Keep hacking! I guess this sounds strange coming from the author of Emacs Prelude, right?  ↩︎ – syntax highlighting – filetype detection, language-specific plugins, and automatic indentation – incremental search (results appear as you type) – keeps 5 lines of context around the cursor – shows instead of hiding truncated lines – mouse support in all modes remapped to (text formatting) instead of the mostly useless Ex mode And several other quality-of-life improvements Syntax highlighting for hundreds of languages – Vim ships with around 770+ syntax definitions Language-specific indentation rules for over 420 file types Filetype plugins that set sensible options per language (e.g., , , ) A colorscheme – Vim ships with several built-in options (try followed by Tab to see them). Recent Vim builds even bundle Catppuccin – a beautiful pastel theme that I’m quite fond of. Another favorite of mine is Tokyo Night , which you’ll need to install as a plugin. Neovim’s default colorscheme has also been quite good since 0.10. Persistent undo – lets you undo changes even after closing and reopening a file. A game changer. Clipboard integration – makes yank and paste use the system clipboard by default. vim-unimpaired – if you’re on classic Vim (not Neovim), I think Tim Pope’s vim-unimpaired is essential. It adds a consistent set of / mappings for navigating quickfix lists, buffers, adding blank lines, and much more. Neovim 0.11+ has adopted many of these as built-in defaults, but on Vim there’s no substitute. I guess this sounds strange coming from the author of Emacs Prelude, right?  ↩︎

0 views
(think) 1 weeks ago

Adding Empty Lines in Vim: Redux

A long time ago I wrote about adding empty lines in Emacs on my other blog, Emacs Redux. Now it’s time for the Vim version of this. Adding a blank line above or below the cursor is one of those tiny operations you do constantly, and Vim gives you several ways to do it – each with different trade-offs. Most Vim users reach for (open line below) or (open line above). These work, but they drop you into Insert mode. If you just want a blank line and want to stay in Normal mode, you need or . This gets the job done, but has a couple of annoyances: On the bright side, / accept a count – inserts 5 blank lines below. A lesser-known approach that avoids the issues above: The here is Vim’s black hole register, which is always empty. Since inserts register contents linewise, putting “nothing” results in a clean blank line. No trailing whitespace, no register pollution, and you stay in Normal mode. (using the expression register) works the same way, but is shorter and easier to remember. The downside? It’s verbose to type interactively, and the cursor still moves to the new line. This is the gold standard: They also accept a count – adds 3 blank lines below. In Neovim 0.11+ , these are built-in default mappings. In regular Vim, you need Tim Pope’s vim-unimpaired plugin. Note that the two implementations differ slightly – Neovim uses which preserves the cursor position exactly, while vim-unimpaired uses with mark jumps, which keeps you on the same line but resets the column to 0. If you’re on plain Vim without plugins, you can add equivalent mappings to your : The approach is the cleanest – no side effects on registers, marks, or undo granularity. In Insert mode your options are simpler: is handy when you want to add a line without breaking your Insert mode flow. My recommendation? Use and . Like most Vim users, I relied on and for years, but once I discovered vim-unimpaired (and later the equivalent built-in mappings in Neovim 0.11) I never looked back. More broadly, I’m a huge fan of the uniform / convention that vim-unimpaired pioneered – / for quickfix, / for buffers, / for blank lines, and so on. It’s a consistent, mnemonic system that’s easy to internalize, and I’m glad Neovim has been adopting it as built-in defaults. If you’re on Vim, do yourself a favor and install vim-unimpaired . That’s all I have for you today. Keep hacking! The cursor moves to the new blank line. Auto-indent may leave trailing whitespace on the “blank” line. It pollutes the repeat register (it records an insert operation). – adds a blank line below the current line – adds a blank line above the current line – adds a blank line below – adds a blank line above – creates a new line below (or splits the line if the cursor is in the middle of text). – executes as a one-shot Normal mode command, opening a line below. You end up in Insert mode on the new line. – same thing, but opens a line above.

0 views
(think) 1 weeks ago

Supercharging Claude Code with the Right (CLI) Tools

I’ve been using Claude Code quite a bit lately, and I got curious – what if I asked it directly which tools would make it more productive? Not the usual suspects like , or , but tools it wishes it had access to, tools that would genuinely extend its capabilities. So I did exactly that. I asked Claude Code: “What are the most valuable CLI tools I could install for you, outside of the ones you already have?” The answer was surprisingly thoughtful and insightful, so I figured I’d share it here along with my own commentary. Here are 10 tools, ranked by how useful they’d be for an AI coding assistant. Note: I write all my blog posts old-school, but this time around I took the liberty to just extend with my comments the output generated by Claude Code. Note also that the post includes some installation instructions that are macOS-specific. That’s what I got from Claude on my local machine (a Mac mini), and I felt it didn’t make much sense to tweak them given how many combinations of operating systems and package managers exist. This was Claude’s number one pick, and I can see why. ast-grep does structural code search and refactoring using AST patterns. Instead of fumbling with regex to find “all calls to function X with 3 arguments”, you write patterns that look like actual code: This is the kind of thing where regex is fragile and error-prone, but AST matching just works. Supports 20+ languages via tree-sitter . A structural diff tool that understands syntax. difftastic compares files by AST nodes rather than lines, so it won’t flag whitespace changes or reformatting as meaningful diffs. This makes reviewing AI-generated changes much clearer – and let’s be honest, reviewing changes is half the job when working with an AI assistant. AI assistants generate a lot of shell commands, and shell scripting is notoriously full of pitfalls (unquoted variables, vs. , POSIX compatibility…). ShellCheck catches these before they blow up. Given that shell bugs can be destructive (e.g., expanding to ), having a safety net here is valuable. A modern replacement with sane regex syntax – no more escaping nightmares. Uses standard PCRE-style regex and has a string-literal mode ( ) for replacing code strings full of metacharacters. Simple, but it eliminates a whole class of errors when generating substitution commands. Sloc Cloc and Code – a fast code counter that gives you an instant overview of a codebase: languages, lines of code, complexity estimates. Understanding the shape of a project before diving in is genuinely useful context for an AI assistant, and this is hard to replicate by manually scanning files. Note: I was under the impression that cloc is a better tool, but perhaps I was mistaken. 1 for YAML (and JSON, TOML, XML). Modern projects are drowning in YAML – GitHub Actions workflows, Kubernetes manifests, Docker Compose files. yq can programmatically query and update YAML while preserving comments and formatting, which is much more reliable than text-based editing that can break indentation. Structural search and replace that works across languages without needing a full parser. Complements ast-grep for simpler pattern matching – it understands delimiters (braces, parens, quotes) but doesn’t need tree-sitter grammar support. Great for quick refactoring across less common languages or config files. Note: I was happy to see that was written in OCaml, but when I installed it I got a warning that the project was deprecated and doesn’t support OCaml 5, so I’m not sure about its future. A command-line benchmarking tool that runs commands multiple times and gives you proper statistical analysis. When you ask an AI to optimize something, it’s nice to have real numbers. The flag produces results ready for a PR description. A file watcher that executes commands when files change. Useful for setting up persistent feedback loops – rerun tests on save, rebuild docs when markdown changes, restart a dev server after config edits. One command instead of cobbling together something with and shell scripts. A syntax-highlighting pager for and friends. Provides word-level diff highlighting, so when only a variable name changes in a long line, you see exactly that. Mostly benefits the human reviewing the AI’s work, but that’s arguably where it matters most. If you only install one tool from this list, make it . It’s the biggest capability gap – an AI assistant limited to regex-based search and replace is like a carpenter limited to a hand saw. Everything else is nice to have, but structural code understanding is a genuine superpower. You can install everything at once if you’re feeling adventurous: I’m not ashamed to admit that I had never heard of some of the tools (e.g. , and ), and I had only one of them installed ( ). 2 It’s never too late to learn something new! By the way, keep in mind that depending on the programming languages that you’re using there are other language specific tools that you can benefit from, so make sure to ask your favorite AI coding tool about those. That’s all I have for you today. Keep hacking! I asked Claude about this as well and it told me that it prefers because it’s written in Go (as opposed to Perl) and therefore it’s much faster than .  ↩ Of course, I didn’t really have it installed - I only thought I did, otherwise Claude wouldn’t have suggested it. (I switch between computers and my setup on all of them is not exactly the same)  ↩ I asked Claude about this as well and it told me that it prefers because it’s written in Go (as opposed to Perl) and therefore it’s much faster than .  ↩ Of course, I didn’t really have it installed - I only thought I did, otherwise Claude wouldn’t have suggested it. (I switch between computers and my setup on all of them is not exactly the same)  ↩

1 views
(think) 2 weeks ago

How to Vim: Many Ways to Paste

Most Vim users know and – paste after and before the cursor. Simple enough. But did you know that Vim actually has around a dozen paste commands, each with subtly different behavior? I certainly didn’t when I started using Vim, and I was surprised when I discovered the full picture. Let’s take a tour of all the ways to paste in Vim, starting with Normal mode and then moving to Insert mode. One important thing to understand first – it’s all about the register type . Vim registers don’t just store text, they also track how that text was yanked or deleted. There are three register types (see ): This is something that trips up many Vim newcomers – the same command can behave quite differently depending on the register type! With that in mind, here’s the complete family of paste commands in Normal mode: The “Direction” column above reflects both cases – for characterwise text it’s “after/before the cursor”, for linewise text it’s “below/above the current line”. How to pick the right paste command? Here are a few things to keep in mind: All Normal mode paste commands accept a count (e.g., pastes three times) and a register prefix (e.g., pastes from register ). In Insert mode things get interesting. All paste commands start with , but the follow-up keystrokes determine how the text gets inserted: Let me unpack this a bit: Note: Plain can be a minor security concern when pasting from the system clipboard ( or registers), since control characters in the clipboard will be interpreted. When in doubt, use instead. And that’s a wrap! Admittedly, even I didn’t know some of those ways to paste before doing the research for this article. I’ve been using Vim quite a bit in the past year and I’m still amazed how many ways to paste are there! If you want to learn more, check out , , , and in Vim’s built-in help. There’s always more to discover! That’s all I have for you today. Keep hacking! You can also use and with a register, e.g. to paste from register with adjusted indentation.  ↩ Characterwise (e.g., ): inserts text to the right of the cursor, to the left. Linewise (e.g., , ): inserts text on a new line below, on a new line above. The cursor position within the line doesn’t matter. Blockwise (e.g., selection): text is inserted as a rectangular block starting at the cursor column. The difference between / and / is all about where your cursor ends up. With the cursor lands on the last character of the pasted text, while with it moves just past the pasted text. This makes handy when you want to paste something and continue editing right after it. and are incredibly useful when pasting code – they automatically adjust the indentation of the pasted text to match the current line. No more pasting followed by to fix indentation! 1 and are the most niche – they only matter when pasting blockwise selections, where they avoid adding trailing whitespace to pad shorter lines. is the most common one – you press and then a register name (e.g., , , for the system clipboard). The text is inserted as if you typed it, which means and auto-indentation apply. This can be surprising if your pasted code gets reformatted unexpectedly. inserts the text literally – special characters like backspace won’t be interpreted. However, and auto-indent still apply. is the “raw paste” – no interpretation, no formatting, no auto-indent. What you see in the register is what you get. This is the one I’d recommend for pasting code in Insert mode. is like , but it adjusts the indentation to match the current context. Think of it as the Insert mode equivalent of . You can also use and with a register, e.g. to paste from register with adjusted indentation.  ↩

0 views
(think) 2 weeks ago

Neocaml 0.1: Ready for Action

neocaml 0.1 is finally out! Almost a year after I announced the project , I’m happy to report that it has matured to the point where I feel comfortable calling it ready for action. Even better - recently landed in MELPA , which means installing it is now as easy as: That’s quite the journey from “a fun experimental project” to a proper Emacs package! You might be wondering what’s wrong with the existing options. The short answer - nothing is wrong per se, but offers a different set of trade-offs: Of course, is the youngest of the bunch and it doesn’t yet match Tuareg’s feature completeness. But for many OCaml workflows it’s already more than sufficient, especially when combined with LSP support. I’ve started the project mostly because I thought that the existing Emacs tooling for OCaml was somewhat behind the times - e.g. both and have features that are no longer needed in the era of . Let me now walk you through the highlights of version 0.1. The current feature-set is relatively modest, but all the essential functionality one would expect from an Emacs major mode is there. leverages TreeSitter for syntax highlighting, which is both more accurate and more performant than the traditional regex-based approaches used by and . The font-locking supports 4 customizable intensity levels (controlled via , default 3), so you can pick the amount of color that suits your taste. Both (source) and (interface) files get their own major modes with dedicated highlighting rules. Indentation has always been tricky for OCaml modes, and I won’t pretend it’s perfect yet, but ’s TreeSitter-based indentation engine is already quite usable. It also supports cycle-indent functionality, so hitting repeatedly will cycle through plausible indentation levels - a nice quality-of-life feature when the indentation rules can’t fully determine the “right” indent. If you prefer, you can still delegate indentation to external tools like or even Tuareg’s indentation functions. Still, I think most people will be quite satisfied with the built-in indentation logic. provides proper structural navigation commands ( , , ) powered by TreeSitter, plus integration definitions in a buffer has never been easier. The older modes provide very similar functionality as well, of course, but the use of TreeSitter in makes such commands more reliable and robust. No OCaml mode would be complete without REPL (toplevel) integration. provides all the essentials: The default REPL is , but you can easily switch to via . I’m still on the fence on whether I want to invest time into making the REPL-integration more powerful or keep it as simple as possible. Right now it’s definitely not a big priority for me, but I want to match what the other older OCaml modes offered in that regard. works great with Eglot and , automatically setting the appropriate language IDs for both and files. Pair with ocaml-eglot and you get a pretty solid OCaml development experience. The creation of LSP really simplified the lives of a major mode authors like me, as now many of the features that were historically major mode specific are provided by LSP clients out-of-the-box. That’s also another reason why you probably want to leaner major mode like . But, wait, there’s more! There’s still plenty of work to do: If you’re following me, you probably know that I’m passionate about both Emacs and OCaml. I hope that will be my way to contribute to the awesome OCaml community. I’m not sure how quickly things will move, but I’m committed to making the best OCaml editing experience on Emacs. Time will tell how far I’ll get! If you’re an OCaml programmer using Emacs, I’d love for you to take for a spin. Install it from MELPA, kick the tires, and let me know what you think. Bug reports, feature requests, and pull requests are all most welcome on GitHub ! That’s all from me, folks! Keep hacking! is ancient and barely maintained. It lacks many features that modern Emacs users expect and it probably should have been deprecated a long time ago. is very powerful, but also very complex. It carries a lot of legacy code and its regex-based font-locking and custom indentation engine show their age. It’s a beast - in both the good and the bad sense of the word. aims to be a modern, lean alternative that fully embraces TreeSitter. The codebase is small, well-documented, and easy to hack on. If you’re running Emacs 29+ (and especially Emacs 30), TreeSitter is the future and is built entirely around it. - Start or switch to the OCaml REPL - Send the current definition - Send the selected region - Send the entire buffer - Send a phrase (code until ) to quickly switch between and files Prettify-symbols support for common OCaml operators Automatic installation of the required TreeSitter grammars via Compatibility with Merlin for those who prefer it over LSP Support for additional OCaml file types (e.g. ) Improvements to structured navigation using newer Emacs TreeSitter APIs Improvements to the test suite Addressing feedback from real-world OCaml users Actually writing some fun OCaml code with

0 views
(think) 1 months ago

A Fresh Look for the Blog: Switching to Jekyll’s Chirpy Theme

It’s been over 4 years since I last updated the look of my blog - in late 2021 I switched to the popular at the time “Minimal Mistakes” theme . It served we well, but like so many Jekyll themes it got abandoned in recent years, so I’ve been shopping recently for a good actively-maintained alternative. After some research I’ve decided on Chirpy as the best option for my use-case, but I’ve been putting off the actual switch for quite a while now. Today it hit me that this was the perfect task for an AI agent like Claude Code , so I’ve decided to just offload the migration to it. The whole process was really smooth - I started with the very simple prompt “Switch my blog’s theme to Chirpy” and pointed Claude to Chirpy’s docs. After less than 1 hour and fairly few follow-up requests to address some small issues (most related to deploying on GitHub Pages ), my updated blog was up and running. As a bonus Claude also: Good stuff! Obviously I could have the done the migration manually, as I’ve always done in the past, but these days I’m quite bored doing mechanical work that doesn’t require much thinking. This was the main reason why I procrastinated on this subject for so long and I think that such boring tasks for me are the most appealing use-cases for AI agents like Claude. If we spent less time on the things that we don’t enjoying doing, this obviously leaves us with more time to spend on the things that we’re actually passionate about. I’ll never use AI to write blog posts, but I’m perfectly fine with delegating the maintenance tasks around my various blogs to tools like Claude. There are many AI uses that I detest (news, blog posts, videos, etc), but I think in programming there are plenty of good use-cases for AI. Perhaps this modest article will inspire you to finish some programming-related task that you’ve been putting off for a very long time? That’s all I have for you today. I hope you’ll enjoy the new look of my blog. Keep hacking! Updated inconsistent tags in my articles (e.g. and ) Updated a reference to the blog’s theme in the colophon Provided a GitHub Action for the deployment of the blog that allows me to run Jekyll 4 directly

0 views
(think) 1 months ago

Tips on Using Mastodon

I’ve started using Mastodon over 3 years ago, in the aftermath of Twitter’s meltdown. At first I didn’t like it much, as there were a few things about it I found frustrating: Eventually I realized the simplest things you can do to have a nice Mastodon experience is to pick some reasonably popular instance (so you known it’s stable and won’t disappear after a while) and some decent third-party client. I never read the local timeline, so in the end I don’t think it matters much which instance you’re end up using. If you don’t want to waste time researching instances just go with the “default” mastodon.social instance. You can certainly do a lot worse, and of course, you can always move your account to another Mastodon instance down the road. When it comes to clients, I’ve played with quite a few and enjoyed the most the following: I’ve used Mona for about a year before switching to Ivory. Mona’s a good app overall, but I experienced some stability issues with it and I felt the UI was a bit cluttered. That’s why eventually I moved to Ivory. (plus I like their logo better) Both Mona and Ivory are proprietary apps and require a subscription to have access to all their features, but these are money well spent IMO. I really like desktop apps, so Mona and Ivory made my interactions with Mastodon way more pleasant (and frequent). Not that the webapps are bad, I just don’t like having a ton of pinned windows in my browser. One more thing - embrace hashtags in Mastodon! Twitter had made those popular in its early days, but eventually moved away from them. They are quite alive in Mastodon and I use them extensively to follow topics that are important to me. (e.g. posts about various programming languages) So, it’s a good to use them in your posts, so other people can discover your post via the hashtags. That’s all from me on the topic for now. I hope you’ve learned something useful. In the Fediverse we trust! You had to pick some instance to join (or host your own), so I spent quite a bit of time looking into various instances The default web UI was not my cup of tea - I found it to be complex and unwieldy. This was probably the main reason I didn’t enjoy using Mastodon at first. The interaction metrics (boosts, likes, etc) were often out-of-sync in different clients. That was improved in recent versions of Mastodon, but it was definitely surprising to me coming from Twitter. Mona (native app for all Apple devices) Ivory (same as Mona, but with a simpler user interface) Phanpy (simple and sleek web client)

0 views
(think) 1 months ago

How to Vim: Navigating Prose in Style

I don’t know about you, but I’m not using Vim solely for programming. I also write documentation in it, plus most of my blog posts (like this one). When dealing with prose (regular text), it’s good to know a couple of essential Vim motions: Vim’s check for beginning/end of sentence is not very precise, but it mostly gets the job done. And because paragraphs are just blocks of text surrounded by blanks lines that’s handy in programming contexts as well. The forward sentence motion positions the cursor on the first character in the next sentence, or on the line after the paragraph (if the sentence is the last in a paragraph). The backward sentence operates similarly - it goes to the first character in the previous (or current) sentence. The paragraph motions will take you to the empty lines before or after a paragraph. Due to the simple definition of a paragraph in Vim, those are quite reliable. I guess in the world of motions like the ones provided by and you might be wondering if learning the rudimentary motions is worth it all. In my experience it’s never a bad idea to be able to use someone else’s setup, and the built-in functionality is naturally the smallest common denominator. That’s all I have for you today. Keep hacking! and allow you to move backward/forward in sentences and allow you to move backward/forward in paragraphs

1 views
(think) 1 months ago

How to Vim: Alternative Approach to Find and Replace

The classic way to do “find and replace” in Vim is pretty well known: This will replace all instances of in the current buffer (that’s what the is about) with . The flag means you’ll get prompted for confirmation for every replacement. Not bad, right? Still, often you need to replace just a few instances of something, so the above might be a bit too much typing. Imagine you’re dealing with the following text: If you want to replace the instances with the fastest way to do this would be something like: Pretty sweet and quite interactive in my opinion. It also allows you easily skip matches you don’t want to replace. And there are a few other tricks you can keep in mind: So, there you have it - another way to do “find and replace” in Vim! Keep hacking! - this will take you to the beginning of - this will replace with - this will take you to the next match and repeat the last edit you did You can use to select the word under the cursor and start a search with it If you’re searching for something more complex (e.g. it has multiple words) you can use instead of . means the next search match.

0 views
(think) 1 months ago

How to Vim: Insert Thing at Point in Command Mode

Most Vim users probably know that they can use to insert the contents of registers, while typing some command. For instance - you can insert the clipboard contents with . Fewer people probably know that while typing a command you can use to insert the object under the cursor. There are several objects supported by this: When is set the cursor position at the end of the currently displayed match is used. With the part of the word that was already typed is not inserted again. I find those keybindings handy when doing commands like: For me is the most commonly used keybinding, but all of them have their uses from time to time. As usual you can find much more on the topic in Vim’s user manual. Try and see where this takes you. That’s all I have for you today. I hope some of you will find some of those commands useful. - the Filename under the cursor - the Filename under the cursor, expanded with as in - the Word under the cursor - the WORD under the cursor - the line under the cursor

0 views
(think) 1 months ago

How to Vim: Using Effectively the Command History

One of the frustrating aspects of Vim for me is that in insert mode you’re quite limited in what you can do. That’s fine most of the time, except when you’re in command-line mode (meaning you’re typing something like ). In command-line mode there’s no way to switch to normal mode, so if you make a typo or want to edit a command you’ve previous invoked that’s a bit painful. Recently I’ve discovered a way to mitigate the problem - namely the command history window. (see for details) You can trigger in a couple of ways: When the command history window opens you can edit its contents like any other buffer and normally you’d find some command that’s of interest to you, perhaps edit it, and afterwards press RET to run it. You can also close the window with either or . Note, that the command history window is special and while you’re in it you can’t really move around (e.g. switch to another window with ) For me the main use of the command history window is to reuse and tweak longer and commands, but I can imagine it having other uses as well. It’s certainly a good addition to any Vimmer’s tool belt. Going to back to the original problem I posted - how do you fix a typo while entering some command? Imagine you wrote the following: Just press and you can instantly fix the command in the command window. Now you can just do a (remember you’re in normal mode) and press RET to fire the fixed command. That’s all I have for you today. Keep hacking! P.S. If only Vim’s insert mode supported ’s keybindings or something similar… You can, however, get something similar using plugins like rsi.vim and readline.vim . Press and afterwards do a

0 views
(think) 2 months ago

How to Vim: Joining Lines

Joining adjacent lines is something that comes up often while editing text/code. That’s why it should come as no surprise that this is something well supported by Vim. You have two ways to join lines at your disposal: If you use the commands by themselves they are going to join the current line with the line after it, but like usual in Vim the real magic happens when you operate on some range of lines or text objects: In general I think that when it comes to joining multiple lines visual mode makes a lot of sense, as you’re pretty clear up front what you’re about to join. There a few more nuances to joining lines, especially if you’re using instead of , but I think that few people need to know of those. For the curious readers there’s always to learn more on the topic. That’s all I have for you today. Keep hacking! Using joins lines with a space between each line Using joins lines without a space between each line will join the next 5 lines will select the current paragraph in visual mode and join all the lines there

0 views
(think) 3 months ago

Burst-driven Development: My Approach to OSS Projects Maintenance

I’ve been working on OSS projects for almost 15 years now. Things are simple in the beginning - you’ve got a single project, no users to worry about and all the time and the focus in world. Things changed quite a bit for me over the years and today I’m the maintainer of a couple of dozen OSS projects in the realms of Emacs, Clojure and Ruby mostly. People often ask me how I manage to work on so many projects, besides having a day job, that obviously takes up most of my time. My recipe is quite simple and I refer to it as “burst-driven development”. Long ago I’ve realized that it’s totally unsustainable for me to work effectively in parallel on several quite different projects. That’s why I normally keep a closer eye on my bigger projects (e.g. RuboCop, CIDER, Projectile and nREPL), where I try to respond quickly to tickets and PRs, while I typically do (focused) development only on 1-2 projects at a time. There are often (long) periods when I barely check a project, only to suddenly decide to revisit it and hack vigorously on it for several days or weeks. I guess that’s not ideal for the end users, as some of them might feel that I “undermaintain” some (smaller) projects much of the time, but this approach has worked for me very well for quite a while. The time I’ve spent develop OSS projects has taught me that: To illustrate all of the above with some example, let me tell you a bit about copilot.el 0.3 . I became the primary maintainer of about 9 months ago. Initially there were many things about the project that were frustrating to me that I wanted to fix and improve. After a month of relatively focused work I had mostly achieved my initial goals and I’ve put the project on the backburner for a while, although I kept reviewing PRs and thinking about it in the background. Today I remembered I hadn’t done a release there in quite a while and 0.3 was born. Tomorrow I might remember about some features in Projectile that have been in the back of my mind for ages and finally implement them. Or not. I don’t have any planned order in which I revisit my projects - I just go wherever my inspiration (or current problems related the projects) take me. And that’s a wrap. Nothing novel here, but I hope some of you will find it useful to know how do I approach the topic of multi-project maintenance overall. The “job” of the maintainers is sometimes fun, sometimes tiresome and boring, and occasionally it’s quite frustrating. That’s why it’s essential to have a game plan for dealing with it that doesn’t take a heavy toll on you and make you eventually hate the projects that you lovingly developed in the past. Keep hacking! few problems require some immediate action you can’t always have good ideas for how to improve a project sometimes a project is simply mostly done and that’s OK less is more “hammock time” is important

1 views
(think) 3 months ago

Rust: Embrace Captured Identifier in Format Strings

While playing with Rust recently I’ve noticed that most Rust tutorials suggest writing code like this: Clippy (Rust’s mighty linter), however, doesn’t like it and suggests doing this instead: I hope you’ll agree that this looks cleaner. It certainly seems that way to me. At this point you might be wondering what’s the point of this article. Well, I thought I had come across some recent Rust feature and this was the reason why it wasn’t adopted much in the wild. Turns out, however, that this was introduced almost 4 years ago in Rust 1.58 . I think there are two reasons why the new style hasn’t taken off: If you ask something like ChatGPT how to print variables in Rust you’ll get mostly answers like: LLMs are great, but one certainly has to be aware of their limitations. You also have to be aware of the limitations of the updated syntax - most importantly it works only for variable names, as opposed to arbitrary Rust expressions: Perhaps future versions of Rust will address this limitation. Time will tell. So, that’s all from me. Let’s hope some LLMs will pick up on it and suggest to more people the “new” and improved syntax for captured identifiers in format strings. Keep hacking! There are many outdated tutorials out there LLMs were trained on large samples of code using the old style and they naturally favor it

0 views
(think) 3 months ago

Configure Quick Terminal Size in Ghostty

I’m a very heavy quick (dropdown) terminal user, so after adopting Ghostty one of the points of frustration for me was that I could not specify a default size for my quick terminal. Instead I had to adjust the size every time I started Ghostty. Fortunately, the problem recently got solved with the introduction of the config option in Ghostty 1.2. Its behavior is linked to that of ( by default). You can configure the default size for both the primary axis (e.g. height) and the secondary axis (e.g. width) in either a percentage or pixels. I like my dropdown terminal to be relatively big, so I just do the following: But, you can do a lot more, as indicated by the docs. If you specify two values, then the second value is the secondary axis. The examples below illustrate a few things you can do: As mentioned earlier, the primary axis is defined by the configuration. For the and values, the primary axis is the height. For the and values, the primary axis is the width. For , it depends on your monitor orientation: it is height for landscape and width for portrait. You’ve got many options, but I doubt many people will need those. That’s all I have for you today. Keep hacking!

0 views
(think) 5 months ago

Why I Chose Ruby over Python

This year I spent a bit of time playing with Python, after having mostly ignored it since 2005 when was learning it originally. I did like Python back then, but a few years afterwards I discovered Ruby and quickly focused my entire attention on it. There were many (mostly small) reasons why I leaned towards Ruby back then and playing with Python now made me remember a few of them. I thought it might be interesting to write a bit about those, so here we go. Disclaimer: This is not a rant and I know that: So, treat this as an amusing personal account and nothing more than that. Probably the thing about Python that bothered me the most what stuff that would normally be methods are global functions (that often call some object methods internally). I’m referring to the likes of: You can find the full list here . I’m guessing the reason for this (as usual) is historical, but I much prefer the way Ruby does things. E.g. instead of or vs . is a pretty weird beast as it can: Why would someone want a keyword for removing items from lists and dictionaries instead of some method is beyond me. Ruby’s arrays have methods like: In Ruby almost everything is an expression (meaning that evaluating it would result in a value). In Python a lot of things are consider “statements” - something executed for their side effects only. If you haven’t used languages like Ruby or Lisp this might sound a bit strange, but if we go back to the previous section about , we can observe that: That’s something that I really value and I consider it one of the bigger practical advantages of Ruby over Python. At first I thought the semantic indentation used by Python is super cool, as it reduces a bit the typing one needs to do: In hindsight, however, I quickly realized that it also: One more thing - 4 spaces by default seems a tad too much to me, although that’s obviously debatable. P.S. I feel obliged to admit I’m not a big fan of either and would have preferred instead, but it is how it is. I’m not a fan of Python’s type as for me it’s a pretty weird duck: I get how things ended up the way they are, but for me it’s not OK to be able to write code like . I’m also not a fan of treating empty collection literals as , although I definitely have less issues with this than with 0 and 1. To compare this with Ruby: This definitely resonates better with me. Side note: Lately I’ve been playing a lot with languages like OCaml, F# and Rust, that’s why to me it now feels extra strange to have a boolean type that works like an integer type. I really like the range literals in Ruby: Python has the function that kind of gets the job done, but for whatever reason it doesn’t have the option to mark something as inclusive range. Definitely not a big deal, but one of the many small touches of Ruby’s syntax that I came to appreciate over time. In Python one has to pass to instance methods explicitly, which always seemed to me like an excessive level of verbosity. Also, that’s quite uncommon in other object-oriented programming languages. Many special methods have names surrounded with , which I find both odd and not very easy to type. I get why this was chosen (to avoid naming conflicts), but I don’t like it regardless. I really like that in Ruby the return value of a method is the value of the last expression that got evaluated in the method. There’s a expression in Ruby, but it’s rarely needed in practice. In Python, by comparison, you always have to use , otherwise your method will return . Not a big deal in general, but as I spend a lot of time with Ruby and various functional programming languages, it’s definitely something that bothers me. Admittedly that’s a very small one, but I would have preferred if anonymous functions were created with a keyword like or instead of . In Ruby historically they were created with as well, but afterwards the shorthand was introduced as well. There’s nothing wrong with the Python syntax per se, but I think that in general for lambdas it’s better to have a more compact syntax. Ruby predicates typically have names ending in - e.g. , , . This makes them really easy to spot while reading some code. Python sticks to the more common convention of prefixing such methods with , , etc and that’s fine. One thing that bothers me a bit is that often there’s not spaces between the prefix and the rest of the name (e.g. ), which doesn’t read great in my opinion. More importantly, in Ruby and Python it’s common to have destructive and non-destructive versions of some methods. E.g. - vs in Ruby, and vs in Python. I don’t know about you, but to me it seems that: I’m guessing the reasons here are also historical. Multi-line text literals are common in many languages, but I’m not super fond of: Who thought that typing those would be easy? It’s not that HEREDOCS in Ruby are great either, but I guess they are at least more common in various programming languages. Ruby has and . That’s it. Everyone uses them. Life is simple. Things are a lot more complicated in the realm of Python where several different tools have been in fashion over the years: Now it seems that might replace them all. Until something replaces I guess… And that’s a wrap. I’m guessing at this point most Rubyists reading this would probably agree with my perspective (shocking, right?) and most Pythonistas won’t. And that’s fine. I’m not trying to convince anyone that Ruby’s a better language than Python, I’m just sharing the story of how I ended up in team Ruby almost 20 years ago. Back in the day I felt that Ruby’s syntax was more elegant and more consistent than Python’s, and today my sentiment is more or less the same. Don’t get me wrong, though - I like Python overall and enjoy using it occasionally. It just doesn’t make me as happy as Ruby does. I’ve long written about my frustrations with Ruby, so it feels pretty good to write for once about the aspects of Ruby that I really enjoy. Keep hacking! P.S. After writing this I realized I had already written a similar article 14 years ago, but I had totally forgotten about it! Oh, well… the things I prefer in Ruby over Python are super subjective for every thing that Ruby does “better” there’s something else that Python does better delete variables dictionary items (to remove some values) (to remove some index) There’s no obvious way to determine if actually removed something In Ruby, however, most deletions result in informative results makes things harder for tooling, as it can’t really rearrange indented code sections in editors you can’t have much in terms of auto-indenting as you type (as the editor can’t know when a block finishes without you outdenting explicitly) was added only in Python 2.3 it inherits from and are essentially 1 and 0 has nothing to do with numbers there the only things are considered logically false are and (inclusive range) (exclusive range) Ruby encourages you to favor the non-destructive versions of the methods, unlike Python Ruby’s more consistent than Python

0 views