<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Get Info: #tooling</title>
    <description>Posts tagged “tooling” — Blog of independent game and app developer Matt Sephton. Featuring vintage Macintosh, game development, digital artwork, Japanese esoterica, video game reviews, hacks and tips, and much more.</description>
    <link>https://blog.gingerbeardman.com/tag/tooling/</link>
    <atom:link href="https://blog.gingerbeardman.com/tag/tooling/index.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Fri, 17 Jul 2026 19:49:45 +0000</pubDate>
    <lastBuildDate>Fri, 17 Jul 2026 19:49:45 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>

    
      
        <item>
          <title>Fresh Files Extension for Nova editor</title>
          <description>&lt;p&gt;I spotted &lt;a href=&quot;https://github.com/FreHu/vscode-fresh-file-explorer&quot;&gt;Fresh File Explorer&lt;/a&gt;, a VS Code extension, &lt;a href=&quot;https://github.com/FreHu/vscode-fresh-file-explorer&quot;&gt;on Hacker News&lt;/a&gt; and loved the idea—a file sidebar that only shows recently modified files.&lt;/p&gt;

&lt;p&gt;The default file sidebar in an editor shows &lt;em&gt;everything&lt;/em&gt;, which in a large project is mostly noise. I thought it would be fun to have something like this for &lt;a href=&quot;https://nova.app&quot;&gt;Nova&lt;/a&gt;, so I reimplemented the concept from scratch using only the OG repo readme as a reference. No code is shared, it’s a completely new extension built against Nova’s API. That means I can’t do as much as the VS Code extension, such as colour coding the files like a heatmap, but it’s still quite useful as it is.&lt;/p&gt;

&lt;p&gt;The result is a sort of hybrid filesystem/git/bookmarks sidebar which I’m finding quite pleasant to use.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/fresh-files-extension-for-nova.png&quot; alt=&quot;PNG&quot; title=&quot;Fresh Files showing pinned files and recent changes&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;In Git repositories, Fresh Files uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git status&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git log&lt;/code&gt; to figure out what’s changed. In non-Git workspaces it falls back to filesystem modification times, so it works in any folder. It shares some functionality with the built-in Git Sidebar but I’ve been careful to not duplicate too much.&lt;/p&gt;

&lt;p&gt;There are two modes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Pending Changes&lt;/strong&gt; — shows uncommitted files (or files modified in the last day if there’s no Git)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Historical&lt;/strong&gt; — shows files modified within a configurable time window, from 1 hour to 360 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can toggle the time window from the sidebar header, the command palette, or project settings.&lt;/p&gt;

&lt;h2 id=&quot;features&quot;&gt;Features&lt;/h2&gt;

&lt;p&gt;The sidebar supports both a flat file list and a directory tree view, with sorting by recency or name. Files show relative timestamps like “2h ago” or “3d ago” so you can see at a glance what’s freshest.&lt;/p&gt;

&lt;p&gt;Other things worth mentioning:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Pinned files&lt;/strong&gt; — pin files to keep them visible regardless of time window&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;File history&lt;/strong&gt; — right-click any file to see its commit history with diffs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Diff Search (Pickaxe)&lt;/strong&gt; — find commits where a string was added or removed, file-scoped from the sidebar or repo-wide from the command palette&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Line History&lt;/strong&gt; — view git history for the current line or selection&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Exhume&lt;/strong&gt; — view deleted file contents with syntax highlighting&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Resurrect&lt;/strong&gt; — restore deleted files to their original location&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Show All Files&lt;/strong&gt; — temporarily show all tracked files, overriding the time window&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Search Fresh Files&lt;/strong&gt; — full-text search across fresh files from the command palette&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;New File&lt;/strong&gt; — create a new file from the sidebar context menu&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Move to Trash&lt;/strong&gt; — delete files from the sidebar context menu&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Quick Open&lt;/strong&gt; — fuzzy-open from just the fresh files via the command palette&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Deleted file indicators&lt;/strong&gt; — deleted files show up with a distinct icon&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Auto-refresh&lt;/strong&gt; — the sidebar updates after saves, git commits, checkouts, and merges&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;context-menu&quot;&gt;Context menu&lt;/h2&gt;

&lt;p&gt;Right-click gives you New File, Show in Finder, Copy Path, Copy Relative Path, Move to Trash, Pin/Unpin, Show File History, Diff Search, and Exhume/Resurrect for deleted files.&lt;/p&gt;

&lt;h2 id=&quot;get-it-now&quot;&gt;Get it now&lt;/h2&gt;

&lt;p&gt;You can install it at: &lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.FreshFiles/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.FreshFiles/&lt;/a&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Tue, 24 Feb 2026 13:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2026/02/24/fresh-files-extension-for-nova-editor/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2026/02/24/fresh-files-extension-for-nova-editor/</guid>
        </item>
      
    
      
        <item>
          <title>Remove Comments Extension for Nova editor</title>
          <description>&lt;p&gt;I’ve released a new extension for the Nova editor.&lt;/p&gt;

&lt;p&gt;It’s called &lt;em&gt;Remove Comments&lt;/em&gt; and it …removes comments from the current line, or selected lines, in your code. That’s it!&lt;/p&gt;

&lt;p&gt;Oh, I’ve tried to support as many comment/syntax formats as I can think of. Sadly it’s not possible to get the current comment formatting from the Nova API, so I had to roll my own logic and heuristics.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.remove-comments/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.remove-comments/&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;examples&quot;&gt;Examples&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;JavaScript (C-style comments):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// This comment will be removed&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// This trailing comment will be removed&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* This block comment will be removed */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After running Remove Comments:&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Python (hash-style comments):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# This comment will be removed
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# This trailing comment will be removed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After running Remove Comments:&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;HTML:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- This comment will be removed --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Content&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- This trailing comment will be removed --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After running Remove Comments:&lt;/p&gt;
&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Content&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Mon, 08 Dec 2025 15:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/12/08/remove-comments-extension-for-nova-editor/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/12/08/remove-comments-extension-for-nova-editor/</guid>
        </item>
      
    
      
        <item>
          <title>How to tame a user interface using a spreadsheet</title>
          <description>&lt;p&gt;Many years ago, while working at Apple and running a lab at WWDC, I met a guy who was using a piece of &lt;a href=&quot;https://en.wikipedia.org/wiki/IAd_Producer&quot;&gt;Apple software designed for creating interactive ads&lt;/a&gt; to design a Car Play user interface for a popular US car manufacturer. I was impressed by his ability to think outside the box and told him so. I mentioned to him how human interface designers at Apple &lt;a href=&quot;https://www.youtube.com/watch?v=DGn7BcFGigc&amp;amp;t=474s&quot;&gt;were using Keynote to rapidly prototype user interfaces and animations&lt;/a&gt;. The discussion then took a strange turn onto spreadsheets.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;spreadsheets&quot;&gt;Spreadsheets?&lt;/h2&gt;

&lt;p&gt;In my opinion, spreadsheets are one of the greatest user interface design tools ever created. I believe that the most effective tool for a job is one that gets out of the way as much as possible. Even better if it’s a tool that you already have access to, or at the very least, can use at no cost.&lt;/p&gt;

&lt;p&gt;Before you have time to load up Figma, Sketch, Photoshop, or any other design software, I’ll have already typed in my content placeholders in a spreadsheet and be most of my way towards a solution. I’ll be merging and moving cells at lightning speed—rationalising layouts, determining the optimal use of space, and creating order out of chaos.&lt;/p&gt;

&lt;p&gt;The purpose of using a tool that may seem ill-suited to the task at hand—designing user interfaces—is to liberate yourself from the clutter or baggage of content and style. This allows you to focus solely on layout, positioning, and hierarchy. You don’t have to worry about type sizes, or get bogged down with software choices. You can quickly adjust the size or structure of the screen by adding/removing rows/columns. With a spreadsheet, it’s just the keyboard between you and the perfect layout.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;lets-take-your-brain-to-another-dimension&quot;&gt;Let’s take your brain to another dimension&lt;/h2&gt;

&lt;p&gt;One frequent criticism of spreadsheets is that they are “only 2D” or that a grid isn’t versatile enough to solve your problem. But if you pay close attention, you’ll realise that spreadsheets can occupy as many dimensions as you require by using multiple sheets. Like the stack of ledger paper that was used before spreadsheets were invented. An episode of The Computer Chronicles called “The Spreadsheet Wars” aired in 1988 and has a great &lt;a href=&quot;https://youtu.be/eAiZBUYNUBA?si=TMiXXLj1gtFwZQkp&amp;amp;t=1154&quot;&gt;demo of spreadsheet software Lotus 1-2-3 doing 3D spreadsheet manipulation&lt;/a&gt; in ways that are still impressive several decades later.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://archive.org/details/GridSystemsInGraphicDesignJosefMullerBrockmann&quot;&gt;Grid systems are well known across many fields of design&lt;/a&gt; for being a huge assist when solving layouts and rationalising problems. The grid enforces structure invisibly during the design process. A spreadsheet is a highly configurable grid system that is ready to be put to work on your problem!&lt;/p&gt;

&lt;p&gt;You might also consider the use of plain paper, graph paper, and a bitmap editor. Which might be best suited for sketching out a quick icon idea? It’s easy to understand that graph paper is more suitable than plain paper for drawing images made up of pixels, and in many situations it may be quicker and more suitable than a bitmap editor. &lt;a href=&quot;https://www.moma.org/collection/works/188382?artist_id=38483&amp;amp;page=1&amp;amp;sov_referrer=artist&quot;&gt;Susan Kare famously used a book of grid/graph paper to draw many early Macintosh icons&lt;/a&gt; because icon creation software had yet to be invented.&lt;/p&gt;

&lt;h2 id=&quot;theres-an-app-for-that&quot;&gt;There’s an app for that&lt;/h2&gt;

&lt;p&gt;I use Apple Numbers to do most of my spreadsheet work, because it’s the one that I have easiest access to most of the time—it’s installed on both my Mac and iPhone. You might want to use Google Sheets, Microsoft Excel, or even a simpler table tool like &lt;a href=&quot;https://smoothcsv.com&quot;&gt;SmoothCSV&lt;/a&gt;. There is no wrong choice.&lt;/p&gt;

&lt;p&gt;Here are some examples of layouts I’ve solved using a spreadsheet.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;yoyozo&quot;&gt;YOYOZO&lt;/h2&gt;

&lt;p&gt;Perhaps my most famous example is the stats screen of &lt;a href=&quot;/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/&quot;&gt;my hit game &lt;em&gt;YOYOZO&lt;/em&gt;&lt;/a&gt;. With this one I already had a TTF font file that I’d created for the manual, so I went the extra mile and planned a pixel perfect layout in the right colours and everything. A fun fact you might not know: &lt;em&gt;YOYOZO&lt;/em&gt; was featured in Ars Technica’s “Best Games of 2023” list, alongside Mario and Zelda. This spreadsheet thing is great!&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/spreadsheet-ui-yoyozo.png&quot; alt=&quot;IMG&quot; title=&quot;Pixel perfect precision using the exact font&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;enhanced-sfxr&quot;&gt;Enhanced sfxr&lt;/h2&gt;

&lt;p&gt;This year I’ve made two enhanced versions of sfxr—&lt;a href=&quot;https://gingerbeardman.itch.io/enhanced-sfxr-for-love2d&quot;&gt;one for Love2D&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/search?q=from%3Agingerbeardman%20sfxr&amp;amp;src=typed_query&amp;amp;f=live&quot;&gt;one for macOS&lt;/a&gt;—and I can’t remember which one this was done for. I usually get rid of the header rows and columns but in this instance they acted as sidebar and title bar. Regardless, whilst it might not look like much this sort of pre-planning and organisation saved a ton of time when I was working on the app. It involved no code and was done in a minute or two!&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/spreadsheet-ui-sfxr.png&quot; alt=&quot;IMG&quot; title=&quot;Organising the large components that make up the window&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;spindle&quot;&gt;Spindle&lt;/h2&gt;

&lt;p&gt;Another of my “coming soon” apps is &lt;a href=&quot;https://twitter.com/search?q=from:gingerbeardman%20spindle&amp;amp;src=typed_query&amp;amp;f=live&quot;&gt;an optical media dumper whose USP is its ability to dump from multiple drives at the same time&lt;/a&gt;. I wanted a UI that would stay mostly the same across both the setup and dumping states, and also that was easily repeatable when additional drives duplicate the app UI. The benefit of working with a grid here is that it was 1:1 transferrable to SwiftUI grid system!&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/spreadsheet-ui-spindle.png&quot; alt=&quot;IMG&quot; title=&quot;Two states of the same interface showing what changes and what remains the same&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;driving-game&quot;&gt;Driving Game&lt;/h2&gt;

&lt;p&gt;This final one is an example of a racing game. Something that you might already be more familiar with and so be able to figure out how you can apply the technique to your own work. Something like Mario Kart, F-Zero, Colin McRae Rally, or WipEout. Let’s go!&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/spreadsheet-ui-racer.png&quot; alt=&quot;IMG&quot; title=&quot;A fairly standard driving/racing game head up display&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 11 Oct 2025 00:03:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/10/11/how-to-tame-a-user-interface-using-a-spreadsheet/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/10/11/how-to-tame-a-user-interface-using-a-spreadsheet/</guid>
        </item>
      
    
      
        <item>
          <title>Three.js Completions Extension for Nova editor</title>
          <description>&lt;p&gt;I’ve released an extension to add &lt;a href=&quot;https://threejs.org&quot;&gt;Three.js&lt;/a&gt; completions support to the Nova editor.&lt;/p&gt;

&lt;p&gt;The coolest thing about this extension is that most of the code is automatically generated straight from the Three.js TypeScript files. This means there’s the potential for less errors in the data, and I can easily update the extension whenever a new release of Three.js comes out just by running a single command. Why work &lt;em&gt;harder&lt;/em&gt; when you can work &lt;em&gt;cleverer&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.ThreeJS/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.ThreeJS/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might be quick to deduce that—yes—I’m making a 3D game. 😘&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 04 Oct 2025 15:00:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/10/04/three-js-completions-extension-for-nova-editor/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/10/04/three-js-completions-extension-for-nova-editor/</guid>
        </item>
      
    
      
        <item>
          <title>Updates to Extensions for Nova editor</title>
          <description>&lt;p&gt;In late 2024, I spent some time improving my tools by &lt;a href=&quot;/2024/10/17/extensions-for-nova-editor/&quot;&gt;building a set of extensions for Nova editor&lt;/a&gt; to streamline some time-consuming tasks I encounter during blogging and game development.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/nova-extensions.png&quot; alt=&quot;IMG&quot; title=&quot;My current list of extensions for Nova editor&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;This week, I released major updates to many of them with enhancements and new features:&lt;/p&gt;

&lt;h2 id=&quot;bookmarks&quot;&gt;Bookmarks&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.Bookmarks/&quot;&gt;Version 2.0.0&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Adds sorting, the ability to bookmark folders, marking of missing items, and other improvements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;markdown-file-linker&quot;&gt;Markdown File Linker&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.MarkdownFileLinker/&quot;&gt;Version 2.0.0&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Results in the choice palette are now sorted by default using dates found in filenames.&lt;/li&gt;
  &lt;li&gt;Along with other filtering improvements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;unwrap-paragraph&quot;&gt;Unwrap Paragraph&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.unwraptext/&quot;&gt;Version 2.0.0&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;You can now unwrap the text surrounding cursor, which removes some friction.&lt;/li&gt;
  &lt;li&gt;In selected text multiple paragraphs will be unwrapped individually.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;word-counter&quot;&gt;Word Counter&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.wordcounter/&quot;&gt;Version 2.0.0&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Watched words can be managed through the sidebar, added from selected text, or via typing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;yaml-tag-picker&quot;&gt;YAML Tag Picker&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.YAMLTagPicker/&quot;&gt;Version 2.0.0&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;You can quickly insert or update the creation/modified ISO date in your front matter.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Plus!&lt;/em&gt; I also released a brand new extension:&lt;/p&gt;

&lt;h2 id=&quot;csv-to-md-table&quot;&gt;CSV to MD table&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.csv2md/&quot;&gt;Version 1.0.0&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Convert tabular data between CSV/TSV and Markdown formats.&lt;/li&gt;
&lt;/ul&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 30 Aug 2025 15:03:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/08/30/updates-to-my-extensions-for-nova-editor/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/08/30/updates-to-my-extensions-for-nova-editor/</guid>
        </item>
      
    
      
        <item>
          <title>Automatic build number incrementing in Xcode</title>
          <description>&lt;p&gt;Note: these days I mostly let App Store Connect manage version numbers.&lt;/p&gt;

&lt;p&gt;There are many ways to do automatic build version number incrementing in Xcode. I don’t know of any really easy universal way of doing it, so this is simply my preferred way of doing it. I just added it to a new project so thought I’d document if only for my own future reference!&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-create-a-xcconfig&quot;&gt;1. Create a .xcconfig&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Add a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BuildNumber.xcconfig&lt;/code&gt; to your project&lt;/li&gt;
  &lt;li&gt;Add a &lt;em&gt;Pre-Action Script&lt;/em&gt; to your &lt;em&gt;Scheme&lt;/em&gt;:
    &lt;ul&gt;
      &lt;li&gt;In Xcode, edit your scheme (&lt;em&gt;Product &amp;gt; Scheme &amp;gt; Edit Scheme&lt;/em&gt;).&lt;/li&gt;
      &lt;li&gt;For both “Run” and “Archive” actions, add a &lt;em&gt;Pre-action&lt;/em&gt; with a script like the one below&lt;/li&gt;
      &lt;li&gt;Here I’m using a sort of reverse notation timestamp format, &lt;em&gt;you can use whatever works for you&lt;/em&gt;&lt;/li&gt;
      &lt;li&gt;Make sure to set “Provide build settings from” to your project&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;BUILD_NUMBER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;+%y%m%d.%H%M&apos;&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;PROJECT_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;basename&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PROJECT_FILE_PATH&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; .xcodeproj&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;CURRENT_PROJECT_VERSION = &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$BUILD_NUMBER&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PROJECT_DIR&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PROJECT_NAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/BuildNumber.xcconfig&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Updated build number to: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$BUILD_NUMBER&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/xcode-automatic-build-number-01.png&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-reference-the-xcconfig-in-your-project&quot;&gt;2. Reference the .xcconfig in your project&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;In your project set the base configuration file to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BuildNumber.xcconfig&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;In the main editor, you’ll see two sections: &lt;em&gt;PROJECT&lt;/em&gt; and &lt;em&gt;TARGETS&lt;/em&gt;&lt;/li&gt;
      &lt;li&gt;Click the name of your project and make sure &lt;em&gt;Info&lt;/em&gt; tab is selected&lt;/li&gt;
      &lt;li&gt;You can set the base configuration at the project level (as here) or for each target individually&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/xcode-automatic-build-number-02.png&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-set-the-version-to-use-the-config-variable&quot;&gt;3. Set the version to use the config variable&lt;/h2&gt;

&lt;p&gt;In the target build settings, set: Current Project Version to: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(CURRENT_PROJECT_VERSION)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;It will show the version number in place&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/xcode-automatic-build-number-03.png&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-confirm-by-displaying-the-value-in-your-app&quot;&gt;4. Confirm by displaying the value in your app&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CFBundleVersion&lt;/code&gt; for the build version number&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/xcode-automatic-build-number-04.png&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sat, 28 Jun 2025 16:01:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/06/28/automatic-build-number-incrementing-in-xcode/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/06/28/automatic-build-number-incrementing-in-xcode/</guid>
        </item>
      
    
      
        <item>
          <title>Monokai Pro Vibrant theme for Nova editor</title>
          <description>&lt;p&gt;I neglected to blog about this in October 2024, so here it is now.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/gingerbeardman/Monokai-Pro-Vibrant.novaextension/releases/&quot;&gt;Monokai Pro Vibrant&lt;/a&gt; is my fork of the &lt;a href=&quot;https://github.com/keisto/Monokai-Pro.novaextension&quot;&gt;unofficial Monokai Pro theme&lt;/a&gt; for &lt;a href=&quot;https://nova.app&quot;&gt;Nova&lt;/a&gt; editor, by Tony Keiser.&lt;/p&gt;

&lt;p&gt;My fork has a few quality of life changes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Theme Vibrancy is enabled, making focused and unfocused windows more distinguishable&lt;/li&gt;
  &lt;li&gt;Colour tweaks improve contrast when highlighting found instances of search terms&lt;/li&gt;
  &lt;li&gt;Comments are now &lt;em&gt;italicised&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use the Spectrum variation of the theme with the &lt;a href=&quot;https://github.com/IBM/plex/releases/tag/%40ibm%2Fplex-mono%401.1.0&quot;&gt;IBM Plex Mono&lt;/a&gt; font to great effect.&lt;/p&gt;

&lt;p&gt;Since late-2025 I’m now using font &lt;a href=&quot;https://mass-driver.com/trial-fonts&quot;&gt;MD IO Trial&lt;/a&gt; (free for personal use).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/nova-monokai-pro-vibrant.png&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;monokai-pro&quot;&gt;Monokai Pro&lt;/h2&gt;

&lt;p&gt;The original &lt;a href=&quot;https://monokai.pro&quot;&gt;Monokai Pro&lt;/a&gt; theme by Wimer Hazenberg is not free, so I would encourage you to buy a licence even if you’re using an unofficial port or modification of the theme. It’s only right to support software developers. You can get a licence to use Monokai Pro from the original creator: &lt;a href=&quot;https://monokai.pro&quot;&gt;monokai.pro&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;download&quot;&gt;Download&lt;/h2&gt;

&lt;p&gt;I filed a &lt;a href=&quot;https://github.com/keisto/Monokai-Pro.novaextension/pull/9&quot;&gt;PR for these changes&lt;/a&gt;, but it has not yet been accepted. I’m not comfortable distributing this on the official extension gallery at this time. So grab it from GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/gingerbeardman/Monokai-Pro-Vibrant.novaextension/releases/&quot;&gt;github.com/gingerbeardman/Monokai-Pro-Vibrant.novaextension/releases/&lt;/a&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 14 May 2025 19:50:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/05/14/monokai-pro-vibrant-theme-for-nova-editor/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/05/14/monokai-pro-vibrant-theme-for-nova-editor/</guid>
        </item>
      
    
      
        <item>
          <title>Amazfit activity tracker and watch face asset generation</title>
          <description>&lt;p&gt;I recently wanted to track my heart rate and sleeping patterns, but do not want an Apple Watch. So I ended up buying a cheap &lt;a href=&quot;https://uk.amazfit.com/products/amazfit-band-7&quot;&gt;Amazfit Band 7&lt;/a&gt; from &lt;a href=&quot;https://www.decathlon.co.uk/p/activity-tracker-band-7/_/R-p-349533?mc=8828106&quot;&gt;Decathlon&lt;/a&gt; sports store. The one I bought was reduced because of a damaged box—I love a good bargain!&lt;/p&gt;

&lt;p&gt;It’s a cool little thing and goes for an unbelievable 18 days on a single charge with default settings. Since I bought it I’ve had to charge it once! Even more amazing is that this little thing runs apps and watch faces powered by JavaScript, has an OLED screen, senses heart rate, step count, blood oxygen, sleep patterns, and lots more features I’ll never use. And it still gets multiple weeks on a single charge! The app it comes with is fairly low-nag if you don’t want to buy into the ecosystem.&lt;/p&gt;

&lt;p&gt;Anyway, I found some &lt;a href=&quot;https://amazfitwatchfaces.com/amazfit-band/view/58&quot;&gt;nice watch faces&lt;/a&gt; but if anything they were all a little bit busy for me. Ideally I’d make my own but it seemed like a lot of work—I would need to create images for every digit at every size as well as any other things I want on the face—so I put it on the rainy day list.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;ok-computer&quot;&gt;OK, Computer&lt;/h2&gt;

&lt;p&gt;Today it isn’t raining but I do have some time on my hands, which is the next best thing. I didn’t want to spend time in Figma exporting images over and over again. I get cold shivers thinking about slicing images in &lt;a href=&quot;https://en.wikipedia.org/wiki/Macromedia_Fireworks&quot;&gt;Fireworks&lt;/a&gt;. So I wondered if I could create a tool that would make light work of generating the images I need from a font of my choosing.&lt;/p&gt;

&lt;p&gt;It was pretty straightforward and I ended up with what you see below, where you can customise: font, size, color, alignment, filename prefix, and required characters/phrases/symbols. And then you can download all the images in one click! &lt;em&gt;NICE.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I do my best to adhere to the specs required by the most common watch face building tools, and in almost all cases I’ve been able to use the images exactly as they come out of the tool. There is some extraneous padding, which means you have to use quite large negative spacing (equivalent to kerning) but it all works well in practice.&lt;/p&gt;

&lt;p&gt;There’s nothing that ties the assets being generated to the Amazfit Band 7, so feel free to use the tool for other purposes if you want to just generate some digits, text, or symbols as images.&lt;/p&gt;

&lt;p&gt;The web app tool is at &lt;a href=&quot;https://www.gingerbeardman.com/amazfit/&quot;&gt;gingerbeardman.com/amazfit/&lt;/a&gt; if you want to try it out.&lt;/p&gt;

&lt;p class=&quot;screen&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/amazfit-web-app.png&quot; alt=&quot;IMG&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;heres-one-i-made-earlier&quot;&gt;Here’s one I made earlier&lt;/h2&gt;

&lt;p&gt;This is the first face I created using the tool: DIN. I’m generating images of numbers, and a few other things, in a few different sizes and two colours. The darker text might look too dark on your display, but on the OLED display of the device it’s plenty bright enough for secondary information. I used a &lt;a href=&quot;https://amazfitwatchfaces.com/forum/viewtopic.php?t=2743&quot;&gt;Windows tool&lt;/a&gt; to lay things out and generate the package, and &lt;a href=&quot;https://amazfitwatchfaces.com/forum/viewtopic.php?t=3873&quot;&gt;installed it locally&lt;/a&gt; in developer mode on my device.&lt;/p&gt;

&lt;p&gt;You can install &lt;a href=&quot;https://amazfitwatchfaces.com/amazfit-band/view/353&quot;&gt;the watch face&lt;/a&gt; using the &lt;a href=&quot;https://amazfitwatchfaces.com/awapp&quot;&gt;AmazFaces app&lt;/a&gt; on iOS or Android. It’s also available on the official watch face store in the &lt;a href=&quot;https://apps.apple.com/gb/app/zepp/id1127269366&quot;&gt;Zepp app&lt;/a&gt;.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/amazfit-band-7-din.png&quot; alt=&quot;IMG&quot; title=&quot;DIN looks super on OLED!&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The typeface is &lt;a href=&quot;https://github.com/amcchord/datto-d-din&quot;&gt;D-DIN Condensed Bold&lt;/a&gt;—a cool cut of &lt;a href=&quot;https://en.wikipedia.org/wiki/DIN_typeface&quot;&gt;DIN&lt;/a&gt; and it’s free and released under &lt;a href=&quot;https://en.wikipedia.org/wiki/SIL_Open_Font_License&quot;&gt;SIL Open Font License&lt;/a&gt;. Thanks very much to Datto and to the type designer, &lt;a href=&quot;https://luc.devroye.org/showcase-charlesnix/&quot;&gt;Charles Nix&lt;/a&gt; at &lt;a href=&quot;https://www.monotype.com/studio/charles-nix&quot;&gt;Monotype&lt;/a&gt;!&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Fri, 11 Apr 2025 22:05:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2025/04/11/amazfit-activity-tracker-and-watch-face-asset-generation/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2025/04/11/amazfit-activity-tracker-and-watch-face-asset-generation/</guid>
        </item>
      
    
      
        <item>
          <title>Word Counter extension for Nova editor</title>
          <description>&lt;p&gt;So the &lt;a href=&quot;/2024/10/24/macro-extension-for-nova-editor/&quot;&gt;Macro extension&lt;/a&gt; and &lt;a href=&quot;/2024/10/17/extensions-for-nova-editor/&quot;&gt;a bunch of smaller extensions&lt;/a&gt; were supposed to be it, but it’s so much fun to create these that whenever I get an idea for an extension I can’t help making it.&lt;/p&gt;

&lt;p&gt;This time I wanted to keep track of how many times I’m calling certain functions in my code. That’s quite a niche requirement so I made a more general purpose word counter with sidebar, thresholds and coloured blobs!&lt;/p&gt;

&lt;p&gt;You can configure a list of words to count, and the thesholds that will trigger the grey/green/yellow/red coloured blobs, on a per-project basis using the workspace &lt;em&gt;Project Settings…&lt;/em&gt; panel.&lt;/p&gt;

&lt;p&gt;And you can double-click on a word in the sidebar to search the current document for it. This feature uses AppleScript so you’ll need to give Nova permissions, though it should prompt you to do that if it doesn’t have them already.&lt;/p&gt;

&lt;p&gt;Note: the screenshot below is slightly old as I’ve since refined the images in the extension.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/nova-word-counter.png&quot; alt=&quot;PNG&quot; title=&quot;Counting some Playdate SDK specific function names&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;downloads&quot;&gt;Downloads&lt;/h2&gt;

&lt;p&gt;You can download it at: &lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.wordcounter/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.wordcounter/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the &lt;a href=&quot;https://github.com/gingerbeardman/Word-Counter&quot;&gt;source code is available on GitHub&lt;/a&gt; and PRs are more than welcome!&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Sun, 27 Oct 2024 17:12:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2024/10/27/word-counter-extension-for-nova-editor/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2024/10/27/word-counter-extension-for-nova-editor/</guid>
        </item>
      
    
      
        <item>
          <title>Macro extension for Nova editor</title>
          <description>&lt;p&gt;After creating &lt;a href=&quot;/2024/10/17/extensions-for-nova-editor/&quot;&gt;a bunch of smaller Nova Extensions quite quickly&lt;/a&gt; I wondered how far I could push things, just as a personal challenge. I had the idea of implementing a Macro text recording and playback system. There was something similar in TextMate editor, and whilst the Nova API doesn’t currently allow as sophisticated a system I thought it was something still worth exploring.&lt;/p&gt;

&lt;p&gt;Design of the system was incremental, with improvements and simplifications that came about through using it. It took a few aborted attempts to land on a good mechanism of recording and playing back “actions”, which currently encompass:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;insertions&lt;/li&gt;
  &lt;li&gt;deletions&lt;/li&gt;
  &lt;li&gt;cursor position changes&lt;/li&gt;
  &lt;li&gt;selection changes&lt;/li&gt;
  &lt;li&gt;selection replacements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I encode these changes with some simple JSON markup. Insertions are represented as text, and replacements as pairs of text, whilst deletions, cursor position changes, and selection changes are represented as numbers where positive means “to the right” and negative means “to the left”. It’s a simple system but should prove to be expandable if the Nova API is expanded to include extra capabilities, such as hooking into the &lt;em&gt;Find&lt;/em&gt;, &lt;em&gt;Files&lt;/em&gt; sidebar, or the &lt;em&gt;Command Palette&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Macro data can end up quite large, as we’re recording single character or position changes, so I added the ability to compress a macro by coalescing actions of the same type into one. A sort of naïve huffman encoding scheme. This makes a huge difference to both size and playback speed.&lt;/p&gt;

&lt;p&gt;You can download the extension at: &lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.Macro/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.Macro/&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;recording&quot;&gt;Recording&lt;/h2&gt;

&lt;p&gt;Recording is done on-demand, only when you want it. A notification will signal recording has begun. When you’ve finished you click the Stop button in the notification and the macro will be automatically saved to the sidebar with a sequential name. You can rename it afterwards if you’d like.&lt;/p&gt;

&lt;p&gt;You can also start the recording using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; sidebar icon, or using the command via the Editor menu or &lt;em&gt;Command Palette&lt;/em&gt;, but recording is always stopped through the notification. It took a while to arrive at this mechanism, and for a long time the extension was a lot more complicated and confusing to use.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/nova-macro-extension-record.png&quot; alt=&quot;IMG&quot; title=&quot;Macro recording notification&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;example-macro&quot;&gt;Example Macro&lt;/h2&gt;

&lt;p&gt;Let’s type:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Nova!&amp;lt;move left 1&amp;gt;&amp;lt;select -3&amp;gt;&amp;lt;replace &quot;ova&quot; → &quot;O&quot;&amp;gt;VA&amp;lt;move left 3&amp;gt;&amp;lt;delete 3&amp;gt;ova&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The end result of which is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Nova!&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we were to record this, the results would be…&lt;/p&gt;

&lt;p&gt;Raw macro (17 actions):&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Macro 1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;actions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;N&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;o&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;POS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;←&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;-2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;-3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;REP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;old&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ova&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;new&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;O&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;V&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;A&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;POS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;←&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;POS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;←&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;POS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;←&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;DEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;DEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;DEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;o&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;isExpanded&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Compressed macro (6 actions):&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Nova!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;POS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;←&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;-3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;REP&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;old&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ova&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;new&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;O&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;VA&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;POS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;direction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;←&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;DEL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ova&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Compression also helps with readability though I added a function to copy a human-readable version of the macro,which is how the &lt;a href=&quot;#example-macro&quot;&gt;text at the start of this example&lt;/a&gt; was generated.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/nova-macro-extension-anim.gif&quot; alt=&quot;IMG&quot; title=&quot;Playback of the above macro&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;sidebar&quot;&gt;Sidebar&lt;/h2&gt;

&lt;p&gt;Recorded macros live in a bespoke sidebar, and can be expanded or collapsed on demand. This allows us to see each action in the macro and exactly how the compressed version differs. The context menu provides functions to manipulate and manage the macros.&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/nova-macro-extension-sidebar.png&quot; alt=&quot;IMG&quot; title=&quot;Macro sidebar with raw and compressed versions, expanded to show all actions&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;context-menu&quot;&gt;Context Menu&lt;/h3&gt;

&lt;p&gt;After a macro is recorded you can manipulate it using the context menu of items in the Macro sidebar:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Replay&lt;/li&gt;
  &lt;li&gt;Compress&lt;/li&gt;
  &lt;li&gt;Duplicate&lt;/li&gt;
  &lt;li&gt;Rename&lt;/li&gt;
  &lt;li&gt;Copy (Compressed)&lt;/li&gt;
  &lt;li&gt;Copy (Raw)&lt;/li&gt;
  &lt;li&gt;Copy (Readable)&lt;/li&gt;
  &lt;li&gt;Delete&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;configuration&quot;&gt;Configuration&lt;/h2&gt;

&lt;p&gt;Recording selections adds extra weight and slowness to a macro, so whilst the extension defaults to recording text selection changes I added the option to switch it off. If you’re only interested in the smallest size macros you can enable automatic compression of every macro which happens as the macro is saved.&lt;/p&gt;

&lt;p&gt;Playback is very quick but in some situations, such as playing back for a screen recording, you might want the text to appear more slowly, so I added a playback speed option.&lt;/p&gt;

&lt;p&gt;Note: if you want to see the smoothest character-by-character playback, you should make sure compression and record selection changes are both switched off.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;improvements&quot;&gt;Improvements&lt;/h2&gt;

&lt;p&gt;There are still some improvements I’d like to make in the future: to be able to more easily see the cursor during playback, and to highlight the current action in the sidebar so you can keep track of progress. We’ll see if/when I get around to those.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/gingerbeardman/Macro&quot;&gt;source code is available on GitHub&lt;/a&gt; and PRs are more than welcome!&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Thu, 24 Oct 2024 14:02:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2024/10/24/macro-extension-for-nova-editor/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2024/10/24/macro-extension-for-nova-editor/</guid>
        </item>
      
    
      
        <item>
          <title>Extensions for Nova editor</title>
          <description>&lt;p&gt;I’m a big believer in solving problems yourself if it’s possible rather than waiting for app updates that might never arrive. Making extensions for the &lt;a href=&quot;https://nova.app&quot;&gt;Nova editor&lt;/a&gt; that I do most of my programming and blogging in is so much fun! So, here are some of my own creation:&lt;/p&gt;

&lt;p&gt;View all: &lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;yaml-tag-picker&quot;&gt;YAML Tag Picker&lt;/h2&gt;

&lt;p&gt;Allows you to easily select tags for the front matter in your blog posts. It scans your existing posts for tags and presents them in a Choice Palette, making it easy to maintain consistent tagging across your blog. Nice!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.YAMLTagPicker/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.YAMLTagPicker/&lt;/a&gt;&lt;/p&gt;

&lt;p class=&quot;tofigure&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/yaml-tag-picker.png&quot; alt=&quot;IMG&quot; title=&quot;Searching existing tags for the word “play”&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;markdown-file-linker&quot;&gt;Markdown File Linker&lt;/h2&gt;

&lt;p&gt;Allows you to insert links to local files as Markdown, perfect for linking between articles in your Jekyll blog! Also includes the ability to wrap the current text in a link, and if there’s a URL on the clipboard that’ll be used as the link destination.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.MarkdownFileLinker/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.MarkdownFileLinker/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You choose a local file using the file selector, such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Users/matt/Projects/blog/_posts/2023/2023-11-21-yoyozo-how-i-made-a-playdate-game-in-39kb.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it will be inserted as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve selected some text before invoking the extension, you’ll get:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[YOYOZO](/2023/11/21/yoyozo-how-i-made-a-playdate-game-in-39kb/)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an image you might end up with:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;![IMG](∕images/posts/yoyozo-teaser.gif)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;reindent-o-matic&quot;&gt;Reindent-o-matic&lt;/h2&gt;

&lt;p&gt;Allows you to apply &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.editorconfig&lt;/code&gt; indent rules to the current file, or all files matching specific extensions. Important: changes are applied but not saved, giving you the opportunity to review.&lt;/p&gt;

&lt;p&gt;Useful when reusing code from one project that used space indentation to a new one that uses tab indentation. Ideally Nova should do this automatically, and sometimes it does, but not every time for reasons I can’t figure out.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.Reindent-o-matic/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.Reindent-o-matic/&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;toggle-scroll-bars&quot;&gt;Toggle Scroll Bars&lt;/h2&gt;

&lt;p&gt;Allows you to see the scrollbar at all times. This is useful because the scrollbar also contains source control change markers. With scrollbars always visible you can more easily locate changes across the entire length of your document.&lt;/p&gt;

&lt;p&gt;This has already saved me so much time and effort and I’ve only been using the editor this way for a week. So I thought I’d create an extension to make it easier for other people to enjoy this usability improvement.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.scrollbars/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.scrollbars/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scroll Bars on/off&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/scrollbars-on-minimap-off.png&quot; alt=&quot;on-off&quot; /&gt; &lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/scrollbars-off-minimap-off.png&quot; alt=&quot;off-off&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br clear=&quot;both&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;unwrap-paragraph&quot;&gt;Unwrap Paragraph&lt;/h2&gt;

&lt;p&gt;OK, I got carried away. This one adds the ability to merge multiple lines of selected text into one.&lt;/p&gt;

&lt;p&gt;This is useful for combining multiple lines of data into one, or reflowing a paragraph of text that has had manual line breaks applied such as something pasted from an old email. It also colalesces areas of white space into a single space. So, it’s not just “Join Lines” but something more specific.&lt;/p&gt;

&lt;p&gt;In the &lt;em&gt;TextMate&lt;/em&gt; editor this command is called Unwrap Paragraph, so I’ve kept the same name.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.unwraptext/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.unwraptext/&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;example&quot;&gt;Example&lt;/h2&gt;

&lt;p&gt;Text, before:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	What 
exactly does  
this   extension	
do with the
text?  🤔
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Text, after:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;What exactly does this extension do with the text? 🤔
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Source code, before:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x050&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x104&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x202&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x050&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x451&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x104&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x505&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x088&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x272&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x104&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;mh&quot;&gt;0x050&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Source code, after:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x050&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x104&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x202&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x050&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x451&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x104&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x505&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x088&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x272&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x104&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x050&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;filter-through-command&quot;&gt;Filter Through Command&lt;/h2&gt;

&lt;p&gt;Run terminal commands on selected text. When you need to use a terminal command but you just want the results. The possibilities are endless as command line is your oyster! Comes with a library of useful commands including sort, base64 decode/encode, extract URLs, remove blank lines, count loc, remove HTML tags.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.FilterThroughCommand/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.FilterThroughCommand/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type your own command&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/nova-filter-through-custom-command.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br clear=&quot;both&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick from a library of commands&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/nova-filter-through-command.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br clear=&quot;both&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;bookmarks&quot;&gt;Bookmarks&lt;/h2&gt;

&lt;p&gt;Bookmark files in the Sidebar for easier management. Ideal if the Files Sidebar is too much, or you want a simpler view of the files you are working on.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.Bookmarks/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.Bookmarks/&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;tag-sync&quot;&gt;Tag Sync&lt;/h2&gt;

&lt;p&gt;Automatic synchronisation of closing tag when editing opening tag. Improve your coding efficiency by ensuring tag pairs stay synchronized while editing markup languages!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.tagsync/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.tagsync/&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;openscad&quot;&gt;OpenSCAD&lt;/h2&gt;

&lt;p&gt;Adds language support for &lt;a href=&quot;https://openscad.org&quot;&gt;OpenSCAD&lt;/a&gt;: &lt;em&gt;The Programmers Solid 3D CAD Modeller&lt;/em&gt;, including both syntaxes (with highlighting) and completions (with alternate forms).&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.openscad/&quot;&gt;extensions.panic.com/extensions/com.gingerbeardman/com.gingerbeardman.openscad/&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;macro&quot;&gt;Macro&lt;/h2&gt;

&lt;p&gt;Text editing recording and playback system. This one &lt;a href=&quot;/2024/10/24/macro-extension-for-nova-editor/&quot;&gt;has its own blog post&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;word-counter&quot;&gt;Word Counter&lt;/h2&gt;

&lt;p&gt;Count multiple words and have their tally displayed in the sidebar. This one also &lt;a href=&quot;/2024/10/27/word-counter-extension-for-nova-editor/&quot;&gt;has its own blog post&lt;/a&gt;.&lt;/p&gt;

</description>
          <author>by Matt Sephton</author>
          <pubDate>Thu, 17 Oct 2024 20:47:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2024/10/17/extensions-for-nova-editor/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2024/10/17/extensions-for-nova-editor/</guid>
        </item>
      
    
      
        <item>
          <title>OpenSCAD to Sprite Sheet workflow</title>
          <description>&lt;p&gt;I just released the “OpenSCAD to Spritesheet” workflow I created for &lt;a href=&quot;/tag/dailydriver/&quot;&gt;Daily Driver&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/gingerbeardman/openscad-spritesheet&quot;&gt;github.com/gingerbeardman/openscad-spritesheet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s a Frankenstein mish-mash of a Makefile and several shell scripts that evolved over many months/years. Initial rendering is done using OpenSCAD, and post-processing is done using ImageMagick. Model poses and rendering variations are controlled by variables in either the shell script or passed through to the model. The whole process is optimised to do as much in parallel as I could figure. More info at the link above! 🚗💨&lt;/p&gt;

&lt;h2 id=&quot;post-processing&quot;&gt;Post Processing&lt;/h2&gt;

&lt;p&gt;After exporting all frames there is some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image magick&lt;/code&gt; work to process the files as follows:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;stitch frames together into a single sprite sheet&lt;/li&gt;
  &lt;li&gt;split sprite sheet into RGBA channels&lt;/li&gt;
  &lt;li&gt;process channels to recolour and dither as required&lt;/li&gt;
  &lt;li&gt;recombine processed channels into new sprite sheet image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can read about that in a &lt;a href=&quot;/2021/06/05/channelling-rgb-into-1bit/&quot;&gt;previous blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;benchmarks&quot;&gt;Benchmarks&lt;/h2&gt;

&lt;p&gt;A full build of 36 cars is as follows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;3GHz 6-core Intel Mac mini 32GB
    &lt;ul&gt;
      &lt;li&gt;100% CPU for ~26 minutes&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;M1 Pro 10-core MacBook Pro 16GB
    &lt;ul&gt;
      &lt;li&gt;70% CPU for ~9 mins&lt;/li&gt;
      &lt;li&gt;about 3x speedup&lt;/li&gt;
      &lt;li&gt;approx 16 seconds per car&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s parallel 3D rendering, PNG writing &amp;amp; compositing &amp;amp; processing, and copying of ~140K files (which takes up ~0.5GB of disk space).&lt;/p&gt;

&lt;h2 id=&quot;example-model&quot;&gt;Example Model&lt;/h2&gt;

&lt;p&gt;Not to scale! Sizes of features are exagerated to allow for them to appear correct when rendered at a very small size.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/openscad-spritesheet-model-car.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;example-output&quot;&gt;Example Output&lt;/h2&gt;

&lt;p&gt;990 frames each for car and shadow, total of 1980 frames per sprite sheet. Each sprite sheet takes up about ~400KB of RAM on Playdate, and only one is loaded at a time.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/posts/openscad-spritesheet-car-table-38-38.png&quot;&gt;&lt;img src=&quot;https://cdn.gingerbeardman.com/images/posts/openscad-spritesheet-car-table-38-38.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</description>
          <author>by Matt Sephton</author>
          <pubDate>Wed, 04 Oct 2023 15:19:00 +0000</pubDate>
          <link>https://blog.gingerbeardman.com/2023/10/04/openscad-to-sprite-sheet/</link>
          <guid isPermaLink="true">https://blog.gingerbeardman.com/2023/10/04/openscad-to-sprite-sheet/</guid>
        </item>
      
    

  </channel>
</rss>
