Changelog
0.4.7
- Tabs can now be reordered by dragging them along the tab strip. Displaced tabs slide into place with a spring animation, and a released tab settles into its new slot.
- Font fallback discovery now works on Linux and Windows: glyphs missing from your primary font — Nerd Font icons, CJK, emoji, and box/symbol characters — are discovered from installed system fonts (fontconfig on Linux, font-kit on Windows) and registered on the fly, instead of rendering as tofu (missing-glyph boxes). Previously only macOS did this. (#1630 by @nikicat, #1642 by @pinpox; closes #1015).
- A tab's custom background color now follows the tab when tabs are reordered or closed, instead of staying pinned to a slot and painting under the wrong tab. The color picker also closes when its tab is closed.
- New tabs show
~as a placeholder title until the running program sets one. - The smooth (trail) cursor no longer animates between tabs and panels — switching tab or split places the cursor directly instead of sliding a trail across the screen.
0.4.6
- Box-drawing and related symbols render again. Box-drawing, block elements, braille, powerline separators, geometric shapes, and legacy computing symbols are now drawn as crisp, pixel-aligned atlas sprites that tile seamlessly at any font size and scale factor — they no longer fall back to the font's (often gappy) glyphs. These had been dropped during the v0.4.x grid rewrite.
- Kitty graphics: the
X=/Y=sub-cell pixel offset is now applied when placing images (#1645 by @floens) — the parser already understood it, but placements were pinned to the cell boundary. Offsets are clamped to the cell box per the kitty spec, and an offset that spills the image into an extra row/column is now accounted for in cursor movement and row occupation. - Updated
wgputo 29.0.3 and thelibrashaderfilter chain to 0.11 (the wgpu path used for RetroArch shaders on Windows/WASM and behind thewgpufeature flag elsewhere).
0.4.5
- Tab color picker now also opens on right-click (previously Control + left-click only).
- New reset swatch in the tab color picker: a slashed box at the end of the swatch row clears the tab's custom color back to the default.
- Fix clicks and cursor overrides being intercepted over the empty band at the top of the window when
navigation.hide-if-singlehides the tab strip on a single tab. New sharedNavigation::island_visible(num_tabs)predicate keeps the renderer, click router, and cursor handler aligned — the empty band now passes through to the grid for selection and double-click maximize.
0.4.4
- fix characters disappearing while typing on macOS — e.g. "agg" rendering as "ag", "355" as "35" (#1595). The text-run iterator now breaks the run around the cursor cell so speculative substitutions on later cells can't blank earlier ones.
- fix panic on window resize / maximize (#1593, #1596 by @kronberger-droid). Grid dimensions are now sourced from the same snapshot that sized the row buffers, not from a live value that could race ahead.
- macOS: text selection at the top of the window now works when
navigation.hide-if-singleis on with a single tab (#1512, #1516 by @0x0aa). Click routing and the cursor override now share a single gate that matches what the renderer paints. - reduce per-frame allocations in the rioterm renderer: per-cell styles are materialized once per snapshot, per-row dirty bits skip unchanged rows on partial damage, and visible-cell extras live in a per-frame map.
- text-run shape cache key is now position-independent, and runs no longer break on space — adjacent words shape in a single call and cache hits stay high as the cursor walks a row.
0.4.3
- fix wrong font size and window chrome alignment after connecting/disconnecting an external monitor (#1588 by @tauil).
- drop the
copaandrio-proc-macroscrates — the escape-sequence parser now lives inrio-backendwith hot-path optimizations (bulk ASCII dispatch, inline OSC buffer, flat codepoint-width table, fused validate+decode). - adopt
simdutffor SIMD UTF-8 transcoding, validation, and base64 decoding.
0.4.2
- macOS Metal: window transparency now actually renders. The
CAMetalLayeris flipped to non-opaque wheneverwindow.opacity < 1or a blur style is set, so the alpha in the drawable's pixels reaches the compositor. Default windows stay opaque so the macOS opaque-window fast path is preserved. - Per-cell background alpha: cells that don't paint an explicit background color now write
alpha = 0instead of stamping the default theme background at full alpha. The drawable's translucent clear shows through, which is what makes the window actually look transparent (not just the margins). Cells with an SGR-set background, selection / search highlights, and inverse-video cells stay fully opaque. - New
window.opacity-cellsconfig option. Off by default — cells with an SGR-set background stay fully opaque so syntax-highlighted regions and TUI panels (Neovim, tmux, lazygit) keep their contrast. Set totrueto apply the opacity multiplier to those cells too, so the entire terminal — including TUI surfaces — shares the configured translucency. window.blurnow accepts the macOS liquid-glass styles in addition to the existing bool. New string values:"macos-glass-regular"(regular opacity) and"macos-glass-clear"(highly transparent), both available on macOS 26 (Tahoe) and later. Older macOS / other platforms fall back to the standard system blur and emit a warning instead of failing. Existingblur = true/blur = falseconfigs are unchanged.
0.4.1
- small updates on goreleaser build config for perf optimizations.
0.4.0
Note: There's a decent chance v0.4.0 may not work for many users. I've done my best to test it on my very old Windows and Linux machines, but it's difficult to cover all cases. This version includes many rewrites, so I recommend checking that it works properly before updating.
- reduced app size (dmg for example went from 16mb to 7.11mb).
- macos core text for searching and shaping.
- yeslogic-fontconfig-sys and windows core apis for searching. swash keeps for shaping.
- removed the extra ir for rendering so no more textruns, less memory and faster operations
- vulkan native support.
- no more font extras, rio will always find the glyph if the font is installed.
- wgpu now is optional via feature flag, this feature flag is required if you use rio with retro arch shaders. This is a movement to make rio possible for official debian.
- rio now only renders on Screen::render to ensure GPU is not stuck or overloaded during high render peaks.
- renderer.backend is only three options:
wgpu,metalandvulkan.
tested with 7e222ef2f1bc0a9eb4ff7f111b8f4c94faa9be97
| Scenario | main | new-v4 | Delta |
|---|---|---|---|
| Idle frame | ~150 µs | ~100 µs | v4 ~33% faster |
| Typing, light redraw | ~300 µs | ~200 µs | v4 ~33% faster |
| Shell output / row damage | 1000–2000 µs | 100–300 µs | v4 5–10× faster |
| 25-row scroll frame | 5500 µs (renderer=3448 µs) | 1661 µs (emit=1424 µs) | v4 ~3× faster |
| Trail animation tail (CPU work) | ~300 µs | ~200 µs | comparable, v4 slightly faster |
| GPU exec time (both) | ~700–1000 µs | ~700–1500 µs | comparable |
| First-frame setup | 15 638 µs | 9 397 µs | v4 ~40% faster |
0.3.11
- Windows: native
MessageBoxWclose-confirmation dialog whenconfirm-before-quit = true(default), mirroring macOS'sapplicationShouldTerminateNSAlertinrio-window/src/platform_impl/macos/app_delegate.rs. Replaces rio's in-window GPU-rendered confirmation modal on Windows. NewEventLoopRunner.confirm_before_quit: Cell<bool>set via the same publicEventLoop::set_confirm_before_quitthat macOS already uses (cfg gate widened toany(target_os = "macos", target_os = "windows")). TheWM_CLOSEhandler inevent_loop.rschecks the flag andvsync_state.window_count() <= 1(only the last window prompts — matches macOS's app-wide quit semantics) and on confirm-No swallows the message so the window stays open. rioterm'sWindowEvent::CloseRequestedhandler now follows the same fast path on Windows as on macOS —routes.remove(&window_id)+ maybe-exit()— since the user already confirmed at the OS layer. - Windows render loop now matches the macOS / CVDisplayLink model exactly:
Window::request_redrawsets a per-windowArc<AtomicBool>dirty flag instead of immediately callingRedrawWindow(RDW_INTERNALPAINT). The DwmFlush worker thread (rio-window/src/platform_impl/windows/event_loop/vsync.rs) is the single source of frame timing — per composition cycle it iterates the window registry and, for each window wheredirty || should_present_after_input(1 s post-input window, identical to macOS), callsRedrawWindow(.., RDW_INVALIDATE). ExistingWM_PAINT→RedrawRequestedpath is unchanged. Plumbing: newvsync::VSyncSharedState(Arc<RwLock<HashMap<HWND, Arc<AtomicBool>>>>registry +Mutex<Instant>for last input) lives onActiveEventLoop, threaded intoWindowDataand theWindowstruct so input handlers,request_redraw, and the worker all share it.Window::Dropunregisters beforeDESTROY_MSG_ID. Replaces the earlier "always invalidate per vsync" zed mirror — that approach worked but didn't compose with the app's ownrequest_redraw, which made scroll feel locked to DWM tick. Now scroll-driven renders queue immediately into the next vsync (≤16 ms wait) and idle frames are skipped entirely. Net behaviour matches macOS one-to-one. - Windows:
window.blur = truenow actually blurs (was a no-op stub). Implementation callsDwmSetWindowAttributewithDWMWA_SYSTEMBACKDROP_TYPE = DWMSBT_TRANSIENTWINDOW(Acrylic) when blur is on,DWMSBT_NONEwhen off — uses the existingset_system_backdropplumbing inrio-window/src/platform_impl/windows/window.rs. Wired both at window creation (viaattributes.blurinon_create) and at runtime via the cross-platformWindow::set_blur. Requires Windows 11 22H2+; older builds silently no-op (matches the previous behaviour). - Windows: vsync-driven render loop with 1-second post-input sustain (matches macOS / Linux / zed behaviour). All inside
rio-window— frontends still just respond toRedrawRequested. New worker thread (platform_impl/windows/event_loop/vsync.rs) callsDwmFlush()per composition cycle; when DWM is disabled, the monitor is asleep, or the call returns under the 1 ms threshold (RDP / occluded), it falls back tothread::sleepat the queriedDwmGetCompositionTimingInfo.qpcRefreshPeriodinterval, with arateRefreshnumerator/denominator fallback for spuriously low values and a 16.6 ms (60 Hz) default if both queries fail. Same heuristic zed uses ingpui_windows/src/vsync.rs. Each tickPostMessageW's a registered custom message (Winit::VsyncTick) to the existingthread_msg_target; the handler inthread_event_target_callbackenumerates thread windows viaEnumThreadWindows, filters withIsWindowVisible, and postsRedrawWindow(hwnd, RDW_INTERNALPAINT)per visible window so the existingWM_PAINTpath emitsRedrawRequestedunchanged. The fan-out is gated on a newEventLoopRunner.should_present_after_input()(1 s window after the last input event) — same field/method names as the macOS / Wayland / X11 implementations.mark_input_receivedis hooked intopublic_window_callback_innerforWM_KEY{DOWN,UP},WM_SYSKEY{DOWN,UP},WM_MOUSE{MOVE,WHEEL,HWHEEL}, all four button down/up pairs,WM_TOUCH, andWM_POINTER{DOWN,UPDATE,UP}.EventLoop::dropnow joins the worker thread (via astub()-and-mem::replaceswap) before destroyingthread_msg_targetso the worker can neverPostMessageWto a freed HWND. New windows-sys featureWin32_System_PerformanceforQueryPerformanceFrequency.build.rsno longer panics when cross-compiling between host and target OS (#[cfg(target_os = ...)]branches now also checkCARGO_CFG_TARGET_OS), socargo check --target x86_64-pc-windows-gnuactually works from a macOS workstation. - Linux: vsync-driven render loop with 1-second post-input sustain (matches macOS / zed behaviour). Two changes inside
rio-window(no rioterm churn — frontends still just respond toRedrawRequested). Wayland (platform_impl/linux/wayland/event_loop/mod.rs): thewl_callback::Donehandler now unconditionally re-arms the nextwl_surface.frame()and drives an auto-loop, modelled on zed'sgpui_linux/src/linux/wayland/window.rs:572-587. Compositors stop delivering Done for occluded windows so the loop pauses naturally without an explicit visibility check. X11 (platform_impl/linux/x11/mod.rs): a newcalloop::timer::Timersource reschedules at the primary monitor's xrandr-derived refresh rate (monitor::mode_refresh_rate_millihertz, fallback 60 Hz); each tick setsEventLoopState.vsync_pendingand the main pump fans the flag out to every visible window via the existingredraw_senderchannel. Mirrors zed'sgpui_linux/src/linux/x11/client.rs:1934-1971. Both paths gate the syntheticRedrawRequestedon a newshould_present_after_input()(1 s window after the last input event) — same field/method names as the existing macOS implementation inplatform_impl/macos/window_delegate.rs:139,997.mark_input_receivedis hooked into the WaylandKeyboardHandlerandPointerHandlerand into the X11 KeyPress/KeyRelease + XInput2 ButtonPress/Release/Motion dispatch sites inevent_processor.rs. Net effect: ProMotion / 144 Hz / 240 Hz monitors now stay at peak refresh during typing/scroll on Wayland and X11; idle terminals don't burn extra CPU because the gate falls back to the existing dirty-driven path outside the 1 s window. - Full kitty graphics protocol Unicode-placeholder support (
kitten icat --unicode-placeholderand any tmux/yazi/etc. that uses U+10EEEE placement cells). Wire-protocol bugs fixed: (1) the APC parser storedU=1into a privatecmd.virtual_placementfield that was never propagated toPlacementRequest, soplace_graphicalways fell through to the direct-overlay branch; (2)place_virtual_graphicwas auto-writing U+10EEEE cells to the grid, racing kitty's own writes (per the spec the application emits the cells, the terminal only stores metadata); (3) icat usesa=T,U=1(combined transmit-and-display) which goes throughkitty_transmit_and_display, notplace_graphic— that handler now also routes virtual placements correctly and pushes pixel data topending_images(the regular overlay path did this implicitly viaplace_kitty_overlay); (4) the diacritics table had 299 entries instead of the canonical 297 (two stray entries\u{06EA}and\u{06ED}from earlier work), shifting every index past the divergence point soimage_id_highdecoded as the wrong byte (e.g.0x5Cstored,0x5Edecoded). Replaced with the 297 entries fromkitty/gen/rowcolumn-diacritics.txt. Shader bug fixed:image.metalinterpretedsource_rect.zwas size but the new code passes it as end-coords; switched tomix(.xy, .zw, corner)(only worked before because every overlay used the full-image default[0,0,1,1]). Renderer side, modeled cell-by-cell on ghostty'sgraphics_unicode.zig: newRow.kitty_virtual_placeholderper-row dirty flag set inCrosswords::inputand checked by the scan loop, so we only walk rows that contain a placeholder (page.zig:1953-1958); newIncompletePlacement+can_append/append/completeimplements kitty's diacritic continuation rules — a cell with missing row/col/high-byte diacritics inherits from the previous cell on the row and the col field can be omitted to mean "auto-increment from prev" (graphics_unicode.zig:407-535); the renderer now walks each visible row left-to-right, builds runs of consecutive cells that belong to the same(image_id, placement_id, image_row)with sequential image columns, and pushes ONEGraphicOverlayper run instead of one per placement (matches ghostty'sPlacementIterator); per-run aspect-preserving fit, centering, and source-rect clipping factored intokitty_virtual::compute_run_geometry— handles partial visibility (placement scrolled half off-screen renders only the visible image slice), runs that fall entirely in the centering padding (returnsNone), and runs that straddle the padding boundary (clips both the screen rect and the source rect so the rendered slice exactly covers the fitted-image area). Placeholder cells render withstyle.background_color = Noneso the per-cell bg quad doesn't cover the BelowText image.GraphicOverlaygained asource_rect: [f32; 4]field (default[0,0,1,1]viaFULL_SOURCE_RECT) wired through to the existingImageInstance.source_rect. Tests cover: the 297-entry diacritic table,IncompletePlacement::from_cellacross all input shapes (Indexed fg, Spec fg + 3rd diacritic, underline=placement_id, missing diacritics, Named fg → 0), allcan_appendcases (row inherit, col inherit, sequential col, col jump, row mismatch, image-id mismatch, image-id-high inherit), a 3-cell run with only the first cell carrying diacritics, parser-levelU=1propagation, the metadata-only contract ofplace_virtual_graphic, an end-to-end pass that feeds icat's exact wire format and asserts both the resulting grid cells AND the per-row dirty flag, andcompute_run_geometryacross exact-fit / image-taller-than-grid (horizontal centering + left-padding cull) / image-wider-than-grid (vertical centering + top-row cull) / partial-visibility-scrolled-off-top / origin-offset / zero-sized-image cases. - macOS Metal renderer: triple-buffered pipeline modeled on zed's
gpui_macos::InstanceBufferPool.CAMetalLayer.maximumDrawableCountis now 3 (was 2). The six per-frame buffers (text vertices, quad instances, kitty/sixel image instances, bg-image instance, bg-fill instance, Globals uniform) collapsed into a single pooledmetal::Bufferper frame: text/quad/image data bump-allocates from the pool buffer with 256-byte aligned offsets, andGlobals(transform +input_colorspace, ~80 B) goes throughset_vertex_bytes/set_fragment_bytesso no buffer is needed for it at all. The pool starts at 2 MiB, doubles on overflow up to a 256 MiB cap (matches zed). On overflow we end-encoding, drop the never-committed command buffer, grow the pool, and retry the frame from scratch — old smaller buffers in flight get rejected byreleaseaftergrowand dropped naturally, no "all 3 slots must grow together" coordination.command_buffer.add_completed_handlerreleases the buffer back to theArc<Mutex<InstanceBufferPool>>on the GPU completion thread. Net effect: CPU can stay up to 3 frames ahead of the GPU without racing the previously singleStorageModeSharedbuffers (the old code was relying on luck — every frame shared one allocation), and ProMotion 120 Hz is now reachable without dropped frames during heavy scenes.MetalRenderer::resizeis gone (no more uniform buffer to refresh on resize);Renderer::render_metalnow owns command-buffer / encoder / drawable / commit, soSugarloaf::render_metalis just a one-liner that handsbg_colorand theMetalContextover. - Fix Nerd Font glyphs from an
extrasfamily (e.g.extras = [{family="JetBrainsMono Nerd Font Mono"}]) overflowing into the next grid cell.FontLibraryData::loadwas loading every extras/symbol-map font withis_emoji = true, which madefont_cache::resolve_withclamp every glyph from that font towidth = 2.0; the terminal grid still budgeted one cell per PUA codepoint, so the layout advanced 2× per glyph and the next character painted on top of the right half. Extras and symbol-map fonts now load withis_emoji = false, andFontData::from_data/from_sliceauto-promote tois_emoji = truewhen the SFNT carries a color table (COLR,CBDT,CBLC, orsbix) — mirroring ghostty'sFT_HAS_COLOR()/ CoreText SBIX check, so a real emoji font inextrasstill gets the wide-cell / color-atlas treatment without needing a config flag. - Removed the dedicated
fonts.emojiconfig slot. Rio now relies entirely onfonts.extrasplus the color-table auto-detection above: dropextras = [{family="Apple Color Emoji"}](or any color family) and it's picked up as emoji; otherwise the bundled Twemoji continues to serve as the default color-emoji fallback. User[fonts.emoji]sections in existing configs are silently ignored instead of erroring. - PUA / Nerd Font glyphs with an adjacent blank cell now fill the full 2-cell constraint crisply instead of rendering half-sized or blurry.
pua_constraint_widthin the rioterm renderer still picks 1 or 2 cells based on neighbour content; the compositor's fit pass (sugarloaf/src/renderer/compositor.rs) dropped the.min(1.0)cap so the glyph can scale up to the 2-cell slot — and to avoid stretching a bitmap atlas entry (which is blurry),GlyphCacheSession::get_at_size(id, size)now rasterizes the glyph fresh atfont_size × cellswhencells > 1. Cache key(id, size)already disambiguates the two rasterizations, so 1-cell and 2-cell lookups for the same codepoint coexist. Fixes JetBrainsMono NF Mono (patched to ~1-cell advance) showing as half-sized inside a 2-cell slot next to Cascadia NF whose glyphs render ~2 cells wide natively. - Fix vertical positioning of constraint-fit PUA glyphs. The compositor was placing the scaled glyph at
baseline − entry.top × scale, which drifts upward as the scale grows because the top-bearing scales faster than the descent-bearing. Switched to cell-centered placement (topline + (line_height − sh) / 2.0), matching ghostty's.align_vertical = .center1choice forisSymbol(cp)— symbols aren't baseline-anchored the way text characters are. - macOS Metal renderer: wide-gamut pipeline with ghostty-compatible
nativealpha blending.CAMetalLayeruses plainBGRA8Unormtagged withkCGColorSpaceDisplayP3(earlier iterations usedBGRA8Unorm_sRGB, which makes Metal do linear-light blending — physically correct but visibly brighter on AA edges and translucent overlays than ghostty's and Terminal.app's default). The three pipeline color attachments moved back toBGRA8Unormto match. Fragment shaders (renderer.metal,image.metal) nowsrgb_to_linear → sRGB-to-P3 matrix → linear_to_srgbbefore returning, emitting gamma-encoded DisplayP3 bytes that the drawable stores verbatim and the compositor displays directly.MTLClearColorgoes through the same encode on the Rust side (sugarloaf::prepare_output_rgb_f64) so the first cleared pixel matches a shader-drawn quad of the same theme colour. Net effect: same saturation as before (P3 gamut preserved), same glyph weight as ghosttyalpha-blending = native/ Terminal.app (no more AA-edge brightening). Themetalcrate doesn't wrapCAMetalLayer.colorspaceyet, so the setter goes throughmsg_send!; theCGColorSpacehandle ismem::forget-ed to guarantee the pointer stays live for the layer's lifetime, and a post-set read-back logs a warning if the colorspace didn't take. - Breaking (macOS):
[window] colorspacedefault flipped fromdisplay-p3tosrgb. The semantics also changed to match ghostty'swindow-colorspace: the setting now describes how Rio interprets input color bytes (hex values in config, ANSI direct-color sequences), not which colorspace the Metal surface uses. The Metal surface is always DisplayP3 regardless of this setting. Withsrgb(the new default), Rio applies a Bradford-adapted sRGB D65 → DisplayP3 D65 matrix (in linear light, aftersrgb_to_linear) so#ff0000renders as sRGB-standard red — matching ghostty and every other app. Users who preferred the previous more-saturated P3-interpreted look can pin it withcolorspace = "display-p3". The matrix runs in the fragment shader (prepare_output_rgbinrenderer.metal/image.metal) driven by a newinput_colorspace: u8field on theGlobalsuniform, and at the Rust boundary for theMTLClearColor(sugarloaf::prepare_output_rgb_f64) so the first HW-cleared pixel lands in the same colorspace as shader-emitted pixels.MetalRenderer::newtakes the colorspace at construction and writes it into every uniform upload; the Metal encoder now binds the uniform buffer to fragment slot 1 (in addition to the existing vertex slot 1) sofs_main/image_fs_maincan read the flag. - New
OpenCommandPalettekey-binding action (defaultCmd+Shift+Pon macOS,Ctrl+Shift+Pelsewhere), user-remappable viaopencommandpalettein[bindings]. Replaces the hardcodedCmd/Ctrl+Shift+Pbranch that used to sit inscreen::process_key_eventahead of the binding system, so the palette shortcut now composes with mode-gating like every other action. - New
List Fontscommand palette entry: selecting it keeps the palette open and swaps the list for the host system's font families (viafont-kit'sSystemSource), fuzzy-filterable. Enter copies the selected family to the clipboard and closes the palette. Each font row shows a hand-drawn (rect-only) rounded copy icon on the right to advertise the action. - Command palette scrollbar now reuses the terminal scrollbar's look and behaviour through shared primitives in
renderer::scrollbar(opacity_from_last_scroll,compute_thumb,draw_thumb) — 6 px wide,[0.6, 0.6, 0.6, 0.5]gray, no rounded corners, 2 s visibility + 300 ms fade after the last scroll event. Only appears when the user has actually scrolled and resets on palette close / query change. Sugarloaf::font_family_names()+FontLibrary::family_names()for enumerating the host font catalog.- Rio
Attributesre-exported at the sugarloaf crate root so downstream callers don't have to reach intofont_introspector.
0.3.10
- Fix tab-title overflow spilling into neighbour tabs / off-screen when the title is longer than the tab width (issue #1508). The island was adding the full title as a text span, measuring it, and centering — with no truncation — so a wide
text_widthproduced a negative x offset and the text bled left of the tab's origin. AddedSugarloaf::char_advance(ch, attrs, font_size)(and extendedResolvedGlyphwithadvance_units+units_per_em, populated once at resolve time alongside the existing fallback walk) so the tab bar can walk titles char-by-char and truncate with a trailing…to fittab_width - 2·TAB_PADDING_X. WiredTAB_PADDING_Xup and dropped its#[allow(dead_code)].
0.3.9
- Fix progress bar visually freezing at the left edge when a TUI heartbeats
OSC 9;4faster than the indeterminate animation cycle (issue #1509). The island had oneprogress_last_updatefield doing double duty as the animation phase reference and the stale-bar dismissal timestamp; every report yanked the animation back to t=0. Now split intoprogress_started_at(only reset on actual state transitions) andprogress_last_seen(bumped every report, drives the 15 s timeout) — same separation ghostty's GTK apprt gets for free fromGtkProgressBar.pulse()plus a sideglib.timeoutAdd. - Fix emoji with VS16 variation selector (e.g.
🎟️,⚠️) visually overflowing into the next grid cell. Rio's font shaper correctly uses cmap format 14 to pick the wide emoji glyph for(base, U+FE0F)clusters, but the grid still budgeted only one cell for the base, so the glyph painted over its neighbour — misaligning vim/tmux vertical split lines on any row containing a text-presentation emoji. Rio now promotes the cell toWide+Spaceron VS16 (and narrows it back on VS15) for any sequence listed in Unicode'semoji-variation-sequences.txt, matching kitty and ghostty. New workspace craterio-grapheme-widthcarries the table (forked fromwezterm-char-props, MIT). - Add
scrollback-history-limitconfig option (default10000, set to0to disable scrollback). Fixes #993. - Harden Kitty graphics protocol: reject images exceeding 10000 px per axis or 400 MiB, add per-chunk base64 decoding so clients like
chafa --format=kitty(which pads every chunk) merge correctly, time out stale chunked uploads after 10 s, combinei=/I=/p=response keys, validatei=/I=mutual exclusion, and requirei=fora=qqueries. - Fix kitty image protocol whenever using transmission.
- Fix color picker not rendering correctly.
- Selection feel: bumped the click/drag side-of-cell threshold from 50% to 60% (matches ghostty), so selections no longer flip to the next cell as eagerly when the cursor crosses the midpoint.
- Default URL/path regex now matches local paths in addition to schemed URLs. Ported verbatim from ghostty's
src/config/url.zig: rooted (/abs), explicitly-relative (./x,../x), home (~/x), env-var-rooted ($VAR/x), hidden-dir (.config/x), and bare relative paths with a dotted filename (src/main.rs). Lookbehinds reject mid-word starts (the/barinsidefoo/bar) and trailing punctuation (see https://example.com.won't include the period). - Switch hint regex matching from the Rust
regexcrate toonig(Oniguruma) — same engine ghostty uses. Adds lookbehind/lookahead support so the default regex matches ghostty's behavior exactly, and user[hints.rules]regexes can now use lookarounds and backreferences. - When a hint match looks like a local path, rio now resolves it against the terminal's OSC 7 working directory, expands
~/and$VAR/, and only dispatches the resolved absolute path to the OS opener if the file exists; URL schemes (https://…,mailto:, …) are passed through unchanged. - Restore OSC 8 hyperlink underlines. The cell repack moved hyperlink data into a side table and the renderer's
square.hyperlink().is_some()check was disabled with a// temporarily disabledcomment that was never lifted; the renderer now reads the cell'sHYPERLINKflag directly so OSC 8 spans are underlined again. - Fix hover hint underline not appearing on the very first hover after startup.
update_highlighted_hintsnow marks the renderer'spending_updatewith full terminal damage when the hint changes, mirroring the pre-refactorset_hyperlink_rangebehaviour, so the partial-damage render path can no longer skip the re-shape. - Fix two
context::titletests panicking under sandboxed builds (e.g. Void's xbps-src) where$HOME=/tmpcollapsed the test path to~. The tests now use a path prefix that can't plausibly be$HOME.
0.3.8
- Fix click between panels not working if it's on alternative screen.
- Fix divider not appearing (regression of 0.3.7).
0.3.7
- Reduced GPU upload.
- ~17k quads before: ~8.5 MB
- ~17k quads after: ~1.55 MB
- 5.5x reduction.
0.3.6
- Performance improvements.
- Now a cell is 8 bytes.
// Bit layout for Square(u64)
//
// bits 0..20 (21): codepoint (Unicode scalar value, max 0x10_FFFF)
// OR low bits of bg color when content_tag != Codepoint
// bits 21..22 (2): wide (Wide enum)
// bits 23..29 (7): per-cell flag bits (CellFlags), incl WRAPLINE at bit 0
// bits 30..31 (2): content_tag (NEW)
// 0 = Codepoint (text cell, use style_id below)
// 1 = BgPalette (bg-only cell, palette index in 32..39)
// 2 = BgRgb (bg-only cell, RGB packed in 32..55)
// 3 = reserved
// bits 32..47 (16): style_id (when tag == Codepoint)
// bg palette idx in low 8 (when tag == BgPalette)
// bg RGB.r:g in low 16 (when tag == BgRgb)
// bits 48..63 (16): extras_id (when tag == Codepoint)
// bg RGB.b in low 8 (when tag == BgRgb)
- Add support for
window.columnsandwindow.rowsfor window sizing. Ignores invalid0values with fallback towindow.width/window.height, and guarantees a minimum startup size of300x200logical pixels. - Avoid send damage events to Application when terminal already has damage found and unprocessed.
- Fix macOS window close button (red semaphore) showing quit confirmation dialog. Clicking the close button now always closes the window. Quit confirmation is only triggered by Cmd+Q.
- Remove
window.macos-use-quit-dialogconfiguration option.
0.3.5
- Fix underline cursor rendering.
- Fix underline cursor disappearing on blank cells when
window.background-imageis set orwindow.opacity < 1.0. The empty-run dispatch in the renderer now also paints decoration-only spans. - Fix
navigation.unfocused-split-opacitynot dimming unfocused splits. The setting is now applied as a semi-transparent overlay drawn on top of inactive panes and works correctly on both opaque and transparent windows. Default raised from0.4to0.7and clamped to[0.15, 1.0]. - Add
navigation.unfocused-split-fill: RGB tint used for the unfocused-split overlay. Defaults to the terminal background color. - Re-implement
window.background-image. It had been a silent no-op since the renderer rewrite — setting it would clear the window to transparent and paint nothing on top. The image is now uploaded into a dedicated GPU texture sized exactly to the source dimensions (no glyph-atlas pollution, no atlas-size limit) and stretched to cover the full window. - Add
window.background-image.opacity:0.0–1.0multiplier applied to the image's alpha channel. Defaults to1.0. - Breaking: removed
window.background-image.width,height,x, andy. They had no effect under the old implementation and the new dedicated pipeline always fills the window. - Surface background-image load failures (missing path, decode error) through the in-window assistant warning popup instead of a silent log line.
- Fix FreeBSD build.
0.3.4
- Fix kitty image protocol overlay when has a new tab.
- Fix kitty image protocol placeholder content being rendered.
0.3.3
- CPU rendering support by
renderer.use-cpu. - Removed Font char cache friction.
- Fix kitty image protocol and sixel.
0.3.2
- Performance: Skip shaping for empty cells, perf gain can be over 50% depending of the visible rows.
- Private user area should adjust based on cells.
- Cells now by default are created with \0.
- Selection ignore \0 cells.
0.3.1
- Fix crash when filters option is on.
0.3.0
- Quit Confirmation Dialog: New in-window quit screen with Rio logo, triggered by Cmd+Q / confirm-before-quit
- Respond with
yto quit ornto cancel - Optional native macOS quit dialog via
window.macos-use-quit-dialogconfig
- Respond with
- Kitty Graphics Protocol: Display images directly in your terminal
- Direct placements (U=0)
- Virtual placements (U=1)
- Diacritic-based row/column encoding (283 combining characters)
- RGB color encoding for image/placement IDs (24+8 bit support)
- Virtual placement rendering (infrastructure complete, rendering pending)
- Sixel Graphics: Full support with proper scrolling and positioning
- Graphics Rendering Improvements:
- Fixed vertical positioning alignment
- Fixed scrolling (images persist when origin scrolls off-screen)
- Fixed duplicate rendering with per-frame deduplication
- LRU cache with automatic eviction
- Native Metal Support (macOS): Hardware-accelerated rendering with Metal
- New GPU-Rendered Navigation: Faster, smoother tab interface
- Command Palette: Quick access to terminal functions
- Toggle Appearance Theme: Switch between dark and light themes at runtime via key binding (
ToggleAppearanceTheme) or command palette (only available when adaptive theme is configured) - Custom Mouse Cursor: Configurable mouse cursor effects via
effects.custom-mouse-cursor - Trail Cursor: Smooth spring-animated cursor trail using neovide-style physics (enabled by default via
effects.trail-cursor) - Desktop Notifications: Support for OSC 9 (iTerm2) and OSC 777 (rxvt) terminal notifications using native platform APIs (macOS
UNUserNotificationCenter, Linux D-Bus, Windows Toast) - Force Theme: New
force-themeconfiguration property to override the system theme when using adaptive themes - Quake Window Mode: Drop-down terminal from top of screen
- macOS Traffic Light Positioning: Customize position of window control buttons
- Configure via
macos-traffic-light-position-xandmacos-traffic-light-position-y - Defaults to standard macOS positioning (11.4, 16.1)
- Not available in Tab navigation mode
- Configure via
- OSC 9;4 Progress Bar Support: Terminal progress indicator (ConEmu/Windows Terminal compatible)
- Scroll Bar: Overlay scroll bar that appears on scroll and fades out after 2s
- Works per-panel in split views
- Draggable thumb with click-on-track jump scrolling
- Configure via
enable-scroll-bar(enabled by default)
- Tab Title
RELATIVE_PATHvariable: New template variable that shows a home-relative shortened path (e.g.~/Documents/a/rioor…/a/psone/starpsx). Default tab title on macOS/Linux changed to{{ TITLE || RELATIVE_PATH }}. - Wgpu now is always f32.
- This fixes non arm chip macos use cases.
Breaking Changes
- Navigation modes simplified - if you use
TopTab,BottomTab, orBookmark, change to:[navigation]mode = "Tab" - Default
Decorationschanged toTransparenton macOS (wasEnabled) - Removed:
TopTab,BottomTab, andBookmarknavigation modes - Available modes:
Plain,Tab,NativeTab(macOS only) - Tab color configuration simplified: Removed
tabs-foreground,tabs-active-foreground, andtabs-active-highlight- Use
tabsfor inactive tab text and border color (default:#cccccc) - Use
tabs-activefor active tab text color (default:#ffffff)
- Use
- The old padding api became margin:
# It will apply margin rules to the main container# CSS-Likemargin = [10] # (10px to all)margin = [10, 5] # (top and bottom margin are 10px, right and left margin are 5px)margin = [10, 5, 15, 20] # (top margin is 10px, right margin is 5px, bottom margin is 15px, left margin is 20px)# It will apply margin rules to panels[panel]margin = [5] # (5px to all)row-gap = 0 # (0px)column-gap = 0 # (0px)
Technical Details
- Complete rendering architecture rewrite for GPU-based UI
- Parser now supports APC sequences for Kitty graphics protocol
- Removed legacy layer/quad rendering system
- Added Metal backend for macOS, split WebGPU backend for cross-platform
- New
kitty_virtualmodule for placeholder encoding - Graphics cleanup with LRU eviction strategy (evicts up to 5 oldest when atlas full)
- Added 5 unit tests for graphics rendering (positioning, LRU, deduplication)
0.2.36
- Fix DECSCUSR.
0.2.38
- Update wgpu to v0.28.
- Update Rust to v1.92.
0.2.37
- Support window bg color via OSC.
- Fix vi cursor not displayed when moving.
- Fix font loader for fallbacks and extra.
- Fix font size updating through config.