From QLColorCode-Extra to QLCodePreview
16 September 2026
In 2012 I created QLColorCode-extra github.com/BrianGilbert/QLColorCode-extra, a fork of the Quick Look syntax-highlighting plugin QLColorCode github.com/anthonygelibert/QLColorCode with support for Drupal filetypes. Drupal was my day job by then, and being able to space-bar a .module or .info file in Finder and see it colourised was one of those small quality-of-life wins you don’t notice until it’s gone. In late 2020 with the upstream being maintained again I made an issue there about it The upstream issue, “Add Drupal CMS filetypes”, opened December 2020 and still open. Issue #74 so there would only be one project to maintain. The maintainer, Anthony Gelibert Gilbert and Gelibert: different spellings of, well, almost the same word., asked me to rebase and file a pull request. I did the rebase, but as the still-open issue attests, the pull request itself never happened.
That was the era of classic .qlgenerator plugins, and for years they just worked. But Apple has been deprecating that mechanism for a long time. Quick Look previews and thumbnails now belong in app extensions, using QLPreviewProvider and QLThumbnailProvider inside a sandboxed .appex bundled with a host app. As of macOS Sequoia (15), third-party qlgenerators were no longer usable in the normal way, and support has been fully removed now.
With that in mind, and with QLColorCode itself unmaintained since mid-2021 The last commit to QLColorCode’s default branch was July 2021; its final release, 4.1.0, shipped in December 2020., I created QLCodePreview to fill the gap: a Quick Look preview extension that renders syntax-highlighted previews for source code, config, markup, and more, directly in Finder and Quick Look. The highlighter is self-contained and dependency-free, running entirely inside the sandboxed extension. The code targets macOS 12.0 and later, and the release build is universal: one zip with both Apple Silicon and Intel binaries in it. I have no Intel Mac to test on, though, so Apple Silicon macOS Big Sur (version 11) is the earliest macOS version that supports Apple Silicon. It launched alongside the first M1 Macs (MacBook Air, MacBook Pro, and Mac mini) in November 2020. QLCodePreview builds for macOS 12, so it won’t run on Big Sur itself. is the only half of it I’ve actually run, and in practice that means any M-series Mac on Monterey or newer. An Intel Mac gets the same zip and I’d expect it to work; I’ve just never been able to check.
Why a self-contained highlighter
Two reasons, really. The first is the sandbox: a Quick Look extension runs inside a locked-down .appex, and the classic QLColorCode approach of shelling out to the external highlight tool doesn’t fly in there. The second is efficiency. I wanted space-bar to mean instant preview, and forking a process for every file gets in the way of that.
I did think about going further and building in Language Server Protocol support for smarter highlighting, but LSP’s being more suited to IDE requirements, that would have significantly bloated the app. Lightweight and fast was the whole point, so the highlighter is self-contained and dependency-free, running entirely inside the extension.
The efficiency pass
Before launch I ran an efficiency pass over the whole pipeline.
Keyword classification used to run through ICU regex alternation branches; it’s a set lookup now On a 2 MB objc file, the scan alone went from 440 ms to 52 ms (8.4x), end-to-end from 497 ms to 138 ms (3.6x). Python 4.5x, PHP 3.8x, JS 3.4x, CSS 2.6x.. Tokens used to fill a segments array on their way to the renderer, they stream straight in now. The escaper appended HTML one character at a time until I swapped it for bulk run-copies written directly into the render body. Line-number mode no longer splits tokens apart on newlines just to number the lines. And compiled regexes are shared across highlighter instances and drained per request, so a request never pays for the previous one’s setup.
The output and the binary got the same treatment. Token colours come out as CSS classes rather than inline styles, so a colour crosses the wire once, not once per token. The Info.plists ship as binary. The extension links Foundation and nothing heavier: it renders HTML replies and never touches an AppKit type, so AppKit no longer loads or registers its classes in the extension process. Builds default to Release and compile with -Oz clang’s optimise-for-size level. Benchmarked interleaved against -O2: 256.9 vs 257.2 ms average on a 2 MB preview, identical within noise..
This matters more than it would in most apps. The extension is a process macOS spins up the second you space-bar a file Also best-of-five on 2 MB files during the pass: htmlEscape 133.5 ms to 1.8 ms, line-number render 215 ms to 167 ms with peak memory 79.8 MB to 44.8 MB, and the bundle 564 KB to 414 KB.. Across the pass a 2 MB preview went from about half a second to about 130 ms.
Settings
The host app is mostly a settings window. Two tabs. Preview Settings has the things you’d expect: font and font size, light and dark themes, line numbers and gutter width, soft-wrap, tab width, and a max file size past which it won’t render. Custom File Types lets you map any file extension to one of the supported languages, install to php being the canonical example. So the Drupal filetypes that started this whole story in 2012 are now a settings tab instead of a fork.
Changes take effect immediately for new previews, no rebuild. Under the hood the preferences live in an App-Group defaults suite group.com.konstruuktlabs.QLCodePreview, the one defaults domain both the host app and the sandboxed extension can read. shared between the app and the extension, which is the only way settings can reach code running inside that sandbox.
The extensions macOS won’t give up
A few extensions I can’t have. Space-bar an .html, .htm, or .xhtml file and you see the rendered page, not source. macOS tags those public.html and public.xhtml, and the built-in handler Historically Web.qlgenerator, now the modern system preview provider. renders them in a WebKit view before any third-party extension gets a look in. And .ts as well: Apple’s CoreTypes maps it to public.mpeg-2-transport-stream, a video type, so Quick Look hands the file straight to the system video handler. You get a video player for your TypeScript.
None of it is fixable from inside the extension. A third-party Quick Look extension can’t outrank a system handler that owns a public UTI: UTExportedTypeDeclarations has no priority field, and no public API lets a third-party UTI claim an extension over a public one. macOS picks the first matching system UTI and doesn’t fall through to a secondary, so a TypeScript declaration on the file is never selected. That also means the Custom File Types tab can’t rescue these; the block happens in the OS before QLCodePreview is ever asked. .tsx works fine, and renaming a .ts file to .tsx is the practical fix, unless Apple decides to make things more overridable.
Where to get it
Everything lives at konstruukt.com/projects/qlcodepreview. konstruukt.com/projects/qlcodepreview The page tracks what it supports, the install steps, and where releases will land as they ship. If you’d rather build from source, that’s one script and nothing but Apple’s Command Line Tools: ./build.sh install builds the app and the extension, drops them in ~/Applications, and registers everything with LaunchServices. The one thing the script can’t do is the toggle macOS insists on, and that toggle only appears after the app has been run once (System Settings → General → Login Items & Extensions → Extensions → QLCodePreview), so launch QLCodePreview and flip that yourself. Then space-bar something.
Donationware
I’ve decided to make QLCodePreview donationware. The app is free, and it stays free. But free for you isn’t free to make: Apple Developer Program membership due every year, and developing and hosting everything around the app costs real money. If it earns its keep on your Mac, there’s a Buy me a coffee link in the app’s footer, or you can go straight to buymeacoffee.com/itsvrk.
