Changelog
All notable changes to the twrite editor engine will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.9.2] - 2026-09-13
Fixed
Ctrl+Up/Ctrl+Downcursor follow (twrite-gpui): scrolling the viewport with these keys now keeps the cursor visible. When the scroll pushes the cursor out of view the cursor moves to the first or last visible row while preserving the column (VimCtrl+E/Ctrl+Ystyle). If the cursor is already visible it is not moved.Ctrl+Shift+Up/Ctrl+Shift+Downextend the selection while scrolling. Fixes #40.
[0.9.1] - 2026-09-13
Fixed
- Crates.io packaging: point all three crates at the workspace
README.mdso the registry page renders documentation (previously shipped no readme), and expand thetwritefacade crate docs (layout overview, quick start, feature list). - Heading click mapping in Hidden conceal mode (
twrite-gpui):offset_for_positionnow shapes hit-test text with the sameLineMetricsfont size and code-block flag used by paint, so mouse clicks on concealed headings (#through######) land on the clicked glyph instead of shifting right.
[0.9.0] - 2026-09-13
Changed
- Structured
KeyCodeenum replaces stringly key names (breaking):- New crossterm-style
twrite_core::KeyCode(Char,Enter/Tab/Escape/Backspace/Delete/Insert, arrows,Home/End/PageUp/PageDown,F(u8),Unidentified). Space isChar(' '), not a named variant. KeyEvent { key: String }becomes{ code: KeyCode, … };translate_key_downis now the single GPUI-string boundary (verified against gpui 0.2.2's Linux keysym table).ContextMenuItem::with_hinttakesKeyHint; hints render as kbd chips (one per modifier + key) instead of free text.- Synthetic prompt-bar actions (
focus_search, …) leave the key channel for a dedicatedSearchActionchannel (EditorHook::on_search_action,Editor::press_search_action). EditorHook::before_insert(&str)becomesbefore_insert(char);Modifiersgainsctrl/alt/shift/metaconstructors.
- New crossterm-style
[0.8.0] - 2026-09-13
Changed
- Migrated from git-pinned GPUI to official
gpui 0.2.2from crates.io:- Workspace
gpui/gpui_platformgit pins (zed-industries/zed@d85eade) replaced by registrygpui = "0.2.2"; thegpui_platformcrate is gone (officialgpuifolds the platform backends in as cargo features). - App entry becomes
Application::new().run(|cx: &mut App| …)instead ofgpui_platform::application().run(…)(all 7 examples + docs updated). FocusHandle::focus(window, cx)becomesfocus(window);ShapedLine::paintdrops its explicitTextAlign/boundsargs (0.2.2 bakes inTextAlign::Left— same pixels for gutter numbers).Font::default()andKeyDownEvent::prefer_character_inputdo not exist in the 0.2.2 API: font probing goes throughSharedString::new, and Alt-modified keys always keep physical key names (AltGr/macOS Option accents fall back; revisit when upgrading past 0.2.2).- Downstream migration: delete any
gpui_platformdependency andgpuirevpins;gpui = "0.2"+twrite = "0.8"share a singlegpuivia semver unification.
- Workspace
Added
- Platform-backend passthrough features (
twrite):wayland,x11,font-kitmap togpui/.... Wayland-only example:twrite = { version = "0.8", default-features = false, features = ["wayland"] }alongside matchinggpuifeatures.
[0.7.0] - 2026-09-11
Added
- Expandable right-click context menu (
twrite-core,twrite-gpui):- Headless
context_menumodule intwrite-core:ContextMenuItem(id, label, hint, enabled, divider),ContextMenuCaps(host-supplied clipboard/undo/selection capabilities),ContextMenuContext(click row/col snapshot for hooks), andContextMenuState(open/items store mirroringPromptState). - Built-in edit rows with enablement matrix: Undo, Redo, Cut, Copy, Paste, Delete, Select All.
- New
EditorHookmethods:context_menu_items(hook rows append after built-ins; reusing a well-known id overrides that default in place) andon_context_menu_action(runs before built-in dispatch;Consumedhalts). - GPUI right-click handling with VS Code-style selection policy (click inside selection keeps it, else cursor moves), custom themed overlay (
EditorTheme::menu_*) with viewport clamping, keyboard navigation (Up/Down+Enter,Escapedismisses), andEditorConfig::{context_menu, show_default_menu_items}flags. - New
EditorBuffer::{can_undo, can_redo}helpers. - New
context_menuexample (hook-contributed UPPERCASE/separator actions) andrecipe-context-menu.mddocs page;hooksexample gains demo menu rows.
- Headless
[0.6.0] - 2026-09-11
Added
- Blinking cursor support (
twrite-gpui):- Configurable periodic cursor blinking via
EditorConfig::cursor_blink(defaults totrue). - Cursor blink resets to fully visible immediately on keyboard typing, mouse navigation/selection, and scrolling.
- New
Editor::reset_blink_cursorandEditor::set_cursor_blinkAPI methods for external control and configuration.
- Configurable periodic cursor blinking via
- Multi-click selection & drag-expansion snapping (
twrite-core,twrite-gpui):- Double-click highlights the clicked word under the cursor; triple-click highlights the entire line (including trailing line terminator).
- Multi-click drag selection extends with boundary snapping: dragging after double-click expands word-by-word; dragging after triple-click expands line-by-line.
- New boundary helpers in
twrite-core:find_word_range_at,find_line_range_at, and convenience methodsEditorBuffer::{word_range_at, line_range_at}. - New
twrite_gpui::SelectionGranularityenum (Character,Word,Line) tracking active drag granularity onEditor. - Hyperlink opening and task checkbox toggling restricted to single clicks (
click_count == 1) so double-clicking links selects text without re-opening browsers.
- Find & replace toolbar UI redesign (
twrite-gpui):- Replaced abbreviated chips (
Aa,W,All) with full-word checkboxes (Highlight All,Match Case,Whole Words) with keyboard mnemonic underlines. - Redesigned search bar with rounded framed input box,
Find in pageplaceholder, caret steppers (^,v), and close button (✕). - Added expandable replace row with
Replace withinput box and interactiveReplaceandReplace Allaction buttons.
- Replaced abbreviated chips (
- Modularized editor and markdown syntax engines (
twrite-core,twrite-gpui):- Modularized
crates/twrite-gpui/src/editor.rsinto focused submodules:mod,mouse,keyboard,clipboard,geometry, andrender. - Extracted inline markdown syntax parser into
crates/twrite-core/src/batteries/markdown/inline.rs.
- Modularized
Fixed
- Find & replace UX fixes (
twrite-core,twrite-gpui):- Fixed keyboard submit on replace: pressing
Enterwhile focused in the replace input replaces the current match and advances to the next match. - Fixed mouse click closing replace box: clicking into the replace box focuses it cleanly without toggling/closing, and toolbar background clicks no longer propagate to the editor canvas.
- Normal bar cursor in prompt inputs: replaced inverted block cursor with a standard vertical bar cursor in text inputs (
find,replace, and prompt line). - Extended text navigation & editing shortcuts in prompts: added
Ctrl+Left/Ctrl+Right(word movement),Ctrl+Backspace/Ctrl+Delete(word deletion),Ctrl+K(clear to end), andAlt/Metaequivalents.
- Fixed keyboard submit on replace: pressing
- Find & replace functionality (
twrite-core,twrite-gpui):- Fixed replacement text not updating while editing in the replace prompt.
- Fixed
replace_currentandreplace_allfailing to read live input from the active prompt. - Added wrap-around match replacement in
replace_currentwhen the cursor is past the last match. - Added
Tabshortcut and click-to-focus navigation between find and replace fields. - Exposed
replace_mode,is_replace_prompt,query, andreplacementonSearchSnapshot.
[0.5.0] - 2026-09-10
Added
- Getting-started pass (
examples/,README.md): numbered example order (simpletovim) with header pointers, a README run map, a battery-neutral hooks demo, and a newpromptexample (goto-line plus command palette onctx.prompt). - Headless find & replace engine (
twrite-core): newsearchmodule withSearchQuery(literal + regex, case and whole-word toggles),find_matches/find_next/find_prev(wrapping), version-cachedSearchStatefor interactive find, and single-undo batch replace (EditorBuffer::replace_many,replace_all_querywith$1/$namecapture expansion,replace_one_query, line-scopedcollect_replacements). Newregexworkspace dependency;EditorError::{EmptySearchPattern, InvalidRegex}. - Headless prompt primitive (
twrite-core): newpromptmodule withPromptState(UTF-8-safe single-line editing,Ctrl+W/U/A/Eshortcuts, submitted-input history with draft restore, item list + wrapping selection,Tabcompletion,fuzzy_score/fuzzy_filter) inBottomBarandTopPaletteplacements, plusHookEffect::{Save, Load, Quit, Message}for app-level requests frontends drain. - Stock
SearchHook(twrite-core):Ctrl+Ffind (selection seeds the query) with live refresh,Enter/F3next,Shift+F3previous,Ctrl+Hreplace field,Ctrl+Enterreplace-one,Alt+Asingle-undo replace-all,Escapeclose, andSEARCH n/m/REPLACEstatus text. Exposesnavigate_next_from/navigate_prev_fromfor vim*/#. - Vim search & ex-commands (
examples/vim.rs)://?live search withn/Nfollowing the direction,*/#word under cursor, and a:prompt supporting:w [path],:q[!],:wq,:e path,:<num>, and:s/:%swith[g][i]flags (regex,$1captures, single undo). - Prompt box renderer (
twrite-gpui): newPromptBardrawing the sharedPromptStateas a bottom line or floatingF1palette (input with block cursor, item rows, message line).Editorowns the state, swallows buffer keys while it is open, andflush_effects()executes file effects inline (take_effects()leavesQuit/Messagefor hosts). Themarkdownexample wiresCtrl+F/Ctrl+Hfor free. - Search toggles + highlight-all (
twrite-core,twrite-gpui):SearchHookgains Match Case / Whole Word / Highlight All flags (status shows[Aa] [w] [H]),Alt+C/Alt+W/Alt+Hshortcuts,Up/Downmatch stepping (Alt+Up/Alt+Downstill reach input history), and aSearchSnapshotbridge (EditorHook::search_snapshot, defaultNone) that composite hooks forward.Editorsyncssearch_matchesafter input andPromptBarrenders clickableAa/W/Allchips plus↑/↓steppers that dispatch through the same key path; the canvas paints a viewport-clippedtheme.search_matchwash under the text.
Changed
- BREAKING:
HookContextgainsprompt: &mut PromptStateandeffects: &mut Vec<HookEffect>; constructors take five arguments. - BREAKING:
Editorgainsprompt,pending_effects, andfile_pathfields (struct literals need updating);load_filerecords the path forSave { path: None }. - Multi-edit undo/redo now track length shifts, so growing batch replacements round-trip exactly in one undo step.
Fixed
- Capital and shifted-symbol input (
twrite-gpui):translate_key_downnow preferskey_char(the typed character) over the physical key name, so Shift+letter, shifted symbols, and non-US layouts type correctly everywhere. Command combos keep physical names; Option/Alt keeps toggle bindings unless the platform prefers character input. Also revives Shift-bound keys (G,N,*,:,$) in the vim example.
[0.4.0] - 2026-09-07
Added
- FPS counter & performance HUD (
twrite-gpui): newfpsmodule withFrameStats(rolling 120-frame window tracking FPS, average frame duration, and worst frame hitch) andfps_badge(color-coded GPUI status element: green ≥ 50fps, amber ≥ 25fps, red below).Editor::frame_statsrecords frame intervals on paint without forcing repaints; integrated into themarkdownandsimpleexamples. - Indexed fence query helpers (
twrite-core):fence_rows,is_fenced_row,table_block_at_with_fences, andtable_layouts_with_fencesallow callers to hoist theO(N)fence scan out of hot per-row loops and evaluate fenced status inO(log F)time via binary search.
Fixed
- Markdown selection recalculation & table query jank:
MarkdownHighlighter::highlight_line,should_wrap_line, andexpand_linenow serve point queries from a shared, version-cached table layout pass (cached_layouts), eliminating upward buffer walks and per-row allocations that previously caused multi-millisecond hitches when selection changes flipped line cache states in large tables.- Table fence detection shares one cached linear fence scan per document
version (
cached_fence_rows), removing quadratic0..rowfence rescanning.
[0.3.0] - 2026-09-04
Added
- Markdown GFM tables (battery-only, no core changes):
table_block_at,parse_delimiter_row,find_unescaped_pipes,split_table_cells,TableBlock/TableRowKind/TableAlignment, andTABLE_{HEADER,CELL,DELIMITER}_TAGcustom tags (markdown.table.*, styled with existingPunctuation/Bold/Dimmed(pipes are neverHidden, so concealment preserves column mapping).MarkdownConfig::{visual_tables, table_navigation}(both defaulttrue) andMarkdownHookTab/Shift+Tabcell navigation (appends a skeleton row past the last cell) plusEnterrow continuation / blank-row table exit. - Aligned table columns:
MarkdownConfig::table_alignment(defaulttrue) pads every column to the block's max display width (measured on unconcealed source text, so widths are cursor-stable; delimiter dashes are extended and:--/--:/:-:alignment is honored), and table rows opt out of soft-wrapping so the grid never breaks mid-row. - Generic display-expansion engine:
ConcealedLine::{expanded, DisplayPad},display_width(Unicode column widths), and defaultedSyntaxHighlighter::{expand_line, should_wrap_line}hooks wired throughLayoutCache::CachedInput::{concealed, allow_wrap}into canvas prepaint, hit-testing, and scroll estimation. Markdown tables are the first client; other highlighters are unaffected (defaults are no-ops). - New
unicode-widthworkspace dependency backingdisplay_width.
Changed
- Battery layout: the Markdown battery is now
batteries/markdown/(config,table,highlight,hook,linksmodules with colocated tests); public pathstwrite_core::markdown/twrite::markdownunchanged.
[0.2.0] - 2026-09-03
Added
- Syntax-agnostic core: new
HighlightTag::{Blockquote, HorizontalRule, TaskUnchecked, TaskChecked}structural tags;SyntaxHighlighter::extract_linksandEditorHook::on_clickextension points (both with default impls). - Battery registry:
twrite_core::batteriesmodule documenting the contract and checklist for adding feature-gated batteries; Markdown moved tobatteries/markdown.rswith the publictwrite_core::markdown/twrite::markdownpaths unchanged. - Viewport input cache: new
twrite_gpui::{LayoutCache, CachedInput}sharing highlight/conceal/link work across prepaint and hit-testing, with hit/miss stats. - Thousand-line perf proofs: headless
highlight_perf/layout_perfintegration tests (deterministic fixtures,#[ignore]d timing cases) asserting cache hit rates. MarkdownHook::with_configandinteractive_taskstoggle.- Font handling:
EditorConfig::{font_family, code_font_family}(Noneinherits the host GPUI text style);Editor::face_availabilitysurfacing bold/italic face probe results from prepaint;RunFontsparam bundle forbuild_line_text_runs(code spans use the code font). - Font auto-select:
EditorConfig::platform_monospace_candidatespluspick_familychoosing the first candidate with bold + italic faces at paint time; explicitfont_familyis trusted verbatim;Editor::selected_font_familyand family-aware status reporting; examples use the default auto-select path. - Tag extensibility:
HighlightTag::Heading(u8)levels and openHighlightTag::Custom(&'static str)extension point withSyntaxTheme::set_custom_tag_color(unregistered names fall back to the foreground); heading metrics use a single min-level scan.
Changed
- BREAKING:
Editor::offset_for_positionnow takes&mut self. - BREAKING:
VisibleLineLayoutgainstask_state;Editorgainslayout_cacheandhighlighter_rev(struct literals need updating). - BREAKING: new
HighlightTagvariants (exhaustive matches need new arms); theme maps them to comment/punctuation/string. - BREAKING:
HighlightTag::{Heading1..Heading6, Speaker, Dialogue, Choice}removed in favor ofHeading(u8)andCustom(&'static str);SyntaxTheme::{speaker, dialogue, choice}fields removed (register custom colors instead). LineMetrics::for_linederives quote/divider/task state solely from tags (no raw string checks); code-block detection via full-lineCodespans.- Hit-testing (
offset_for_position, link and checkbox lookup) uses binary search over visible lines. - Click handling dispatches task toggles to hooks with
after_edit/on_selection_changenotifications; cursor offset preserved.
[0.1.0] - 2026-09-03
Added
- Core Buffer Engine (
twrite-core):- Rope-backed text storage with byte and char indexing.
- Granular undo and redo transaction history.
- Multi-line cursor navigation, word boundaries, and line-end movements.
- Monotonic document version counter.
- Styling & Syntax Engine:
- Multi-span interval splitting algorithm (
split_line_intervals). - Semantic highlight tags (
HighlightTag) and explicit font styles (TextStyle). - Catppuccin Mocha syntax theme integration in GPUI canvas.
- Live shaping with bold weights, italic fonts, and underline decorations.
- Multi-span interval splitting algorithm (
- Versatile Hook System:
HookContextproviding mutable access to text buffer, selection, and cursor styles.EditorHooklifecycle (on_key,before_insert,after_edit,on_selection_change,status_text).- Built-in
AutoPairsHookwith auto-closing, selection wrapping, and smart backspacing. - Dynamic cursor shapes (
Bar,Block,Underline,Hidden).
- Examples:
simple: Minimal baseline editor.syntax: Markdown headings, inline code, and story script dialogue.hooks: Concurrent hook composition with auto-pairs and markdown shortcuts.vim: Full modal editing (Normal, Insert, Visual) built 100% via hooks.
- Package Architecture:
- Centralized workspace versioning with
version.workspace = true. - Single top-level facade import (
use twrite::Editor;). - Pinned GPUI dependency revision for reproducible downstream builds.
- Centralized workspace versioning with