What's New
Everything recently shipped, straight from the changelog. For curated announcements and deep-dives, see News.
[0.15.0] - 2026-08-30
Added
-
Typst documents fold and outline by section. A
.typfile folded at its brace pairs and nowhere else, and the Structure window listed a single entry —#align()— for a report with three sections, because Typst was being routed through the generic paths that know nothing about= Introduction. Headings now drive both, so the outline reads as the document’s own table of contents and a section collapses like one. Folding at#align/#tablebrackets still works alongside it.The hard part is telling a section marker from every other use of
=in a Typst file — an assignment in#let x = 1, an equality inside math, a rule of=characters — and from anything written inside a raw block, a line comment or one of Typst’s nesting block comments. Folding and the outline share one parser rather than each carrying a copy of that rule, so the two surfaces cannot disagree about where a section begins. -
#letand#showbindings join the Typst outline, and raw blocks fold. A template file can be almost entirely bindings, so outlining headings alone left exactly those files with an empty Structure window. Bindings now appear in document order, each nested under the section it is written in, with one above the first heading staying at the root.Three things are deliberately left out, because each would be noise rather than navigation:
#set(which configures the document rather than defining anything, and a run of#set page/#set textwould top most files), an indented#let(inside a function body, naming something that means nothing outside it), and destructuring or#show:forms, which name nothing you could jump to. Separately, a long embedded listing — the thing a reader most wants out of the way — can now be collapsed at its```fence. -
The Maven tasks tree lists every goal a plugin actually offers, read from the plugin’s own descriptor. Until now it could only show a goal written inside an explicit
<execution>, because the tree is built from a literal reading ofpom.xml. A plugin declared for direct invocation — javafx-maven-plugin, spring-boot-maven-plugin, exec-maven-plugin — carries only<configuration>, so it contributed no row at all:javafx:runcould be reached only by typing it into “Run custom…”, even though running it is the entire reason the plugin is in the pom.Each plugin gets a collapsed section listing its goals, with each goal’s own description as its tooltip. Reading the descriptor rather than keeping a table of well-known goals means it covers whatever is in your pom instead of whatever somebody remembered. It reads the local repository only, never the network, so opening a project never fetches anything and a plugin you have not downloaded simply contributes nothing; results are cached, since this re-runs on every save and tab switch.
-
The Help menu links to the documentation. It offered the palette, updates, the logs and About, but no way to reach the docs — the one entry that helps someone who does not yet know what the editor can do. The link is versioned, so a build lands on the documentation for what it actually is rather than on whatever is current. It is in Simple UI mode’s shorter Help menu too, where it belongs most, and is reachable from the palette as
Documentation.
Changed
-
Upgraded Eclipse LSP4J from 0.23.1 to 1.0.0, which implements LSP 3.18 and DAP 1.70. Language-server and debug-adapter behaviour is unchanged; this is the protocol library catching up. Two fields Editora reads were widened by 3.18: a diagnostic’s message may now be markup rather than plain text (the markup’s text is used, so a squiggle always has its explanation), and a document edit may now be a snippet edit carrying
${1:name}placeholders. A snippet edit is refused rather than applied — inserting it as plain text would write the placeholder markup into the file — which is the same all-or-nothing rule that already covers file creates and deletes. In practice no server sends one, since Editora does not advertise the capability. -
Each markup language’s actions sit under one submenu in the editor’s right-click menu. A Typst buffer spliced eight flat
Typst: …entries into it and a Markdown one seven, which pushed cut, copy, paste and the spelling suggestions far enough down to hunt for, and made file-type actions read as ordinary editing ones. They are grouped now, the way Maven’s and the language server’s actions already were; Markdown’s Table submenu stays nested inside. Run and Debug deliberately stay at the top level — they are the two entries reached without reading the menu. -
The branch dropdown is drawn like the VCS menu it duplicates. It offers the same commands — Commit, Push, Pull, Fetch, New Branch, Stash, Unstash — and drew them as bare text at a smaller size while the menu drew each with a glyph, so one set of actions read as two unrelated lists. Rows now take their icons from the same table the menu uses, at the same size and with the same layout, and the current branch’s check mark moves into the icon column instead of pushing its own name out of line.
New Branch also gets an icon of its own. It had been drawn identically to Switch Branch in the VCS menu, and had been spelling its icon as a fullwidth
+inside the label text in all six translations. -
Recent Files moved into the customizable part of the toolbar, between Save As and Undo. It is a file action — one of the ways to get a file on screen — and sitting beside the project switcher it read as a project control. It is now a real catalog item, so it can also be dragged or removed in Settings ▸ Toolbar. A toolbar you have already customized keeps the button: a saved layout is used verbatim, so a migration inserts Recent after Save As rather than letting it silently disappear from both halves.
-
The editor’s right-click icons match the rest of the application again. The menu draws from a hand-maintained twin of the main icon set, and the icon-family migration moved Undo, Redo, Cut, Copy, Paste, Bookmark, Personal Note, Find, Info and Debug to the outline family on one side only — so
Edit ▸ Undoand right-click ▸ Undo showed two different icons for the same command. The two sets are now checked against each other rather than by eye, so redrawing an icon in one place cannot silently leave the other behind.
Performance
-
Print pagination is roughly twice as fast. It was 94% layout: the search for how many blocks fit a page laid out a candidate group per step of a binary search, per piece emitted — on this repository’s CLAUDE.md, 16.1 of 17.1 seconds, laying out 1.31 million cumulative nodes.
A vertical group’s height is arithmetic rather than something to measure: it stacks its children at their preferred heights, so measuring each child once makes every prefix a sum. That is a property of that one container — wrapped text and side-by-side rows are still measured for real, because summing them would be meaningless — and the arithmetic is treated as a hint, with every emitted page still verified once against a real layout. A page that overflows is the exact bug the splitter exists to prevent, so it is not taken on trust.
CLAUDE.md 409 pages 17.1s → 9.2s (nodes laid out 1.31M → 643K) CHANGELOG 333 pages 3.4s → 2.4s README 46 pages 1.6s → 1.2s CONTRIBUTING 2 pages 247ms → 215ms
Fixed
-
Printing a long Markdown file produced a handful of pages, most nearly blank and a few shrunk to illegibility. A block taller than a page was given a page of its own and scaled uniformly to fit — right for an oversized image, catastrophic for text, because a Markdown list is one top-level block however long it is. Measured on this repository’s CLAUDE.md: of 19 top-level blocks six were over-tall, one of them a 296-page list rendered onto a single page at 0.3% scale. Fourteen pages for a document that is really 382.
An over-tall container is now regrouped into copies of itself holding as many children as fit — a list becomes several lists, a long paragraph several paragraphs — each carrying the original’s styling so it renders identically. The text stays vector rather than being sliced as an image, so it is crisp on paper; the cost is a seam that does not hang-indent. Uniform scaling survives only for a genuinely atomic over-tall block, where it is the right answer. A plain paragraph is a single run with no emphasis to split at, so long runs are cut at whitespace and no word is broken. Result on the same file: 382 pages, none scaled.
A separate, older overflow surfaced alongside it: the page container’s own padding and the gaps between blocks were not counted, so blocks summing to exactly the page height spilled over it — six of eight pages on a 200-item list, the worst by 31 pixels. Invisible on screen, because the preview clips.
-
The SVG preview showed a correctly-sized checkerboard and no artwork in packaged builds. jsvg 2.1.0 ships a broken module boundary: its JavaFX bridge reads a logging class the main module does not expose to it, so the first paint fails and what survives is the transparency pattern drawn just before it — a checkerboard the exact size the drawing should have been.
It reproduces only on the module path, which is every real installation, and not on the classpath — so the fat jar was fine and so was every test in this repository. Four other explanations were measured and ruled out first. There is no upstream fix available, 2.1.0 being the only release of that module, so the export is opened at launch on all three modular paths, with a test asserting all three carry it.
-
The main menu’s greyed-out items never un-greyed. The VCS menu came up almost entirely disabled inside a Git repository — Commit, Push, Pull, Fetch, Switch and New Branch, Git Log, file history and both stash entries — while the status bar an inch below showed the branch and its ahead/behind counts.
The gate was right; it was simply never re-run. Repository detection is asynchronous, so at the moment the window is built the answer is always “not in a repo”, and nothing re-evaluated it for the life of the window. That froze every context-dependent item, not only the Git ones: the preview, CSV,
.httpand Typst entries never followed a tab switch, and the debug step commands never followed a session suspending. The enabled state is now re-evaluated when a menu opens, and pushed from the three places the context actually moves — which is what keeps the macOS system menu bar honest, since AppKit owns the popup there and may not tell us it is opening. -
Switching a build tool back on in Settings left it dead. Re-enabling Maven — or npm, Cargo, Go, Gradle — gave no stripe button and no tasks tree, and the status row on the very same Settings page went on reading “No Maven project detected”, which is what made it look unfixable from the interface. Detection had been cleared while the tool was off and only the palette command re-ran it; the Settings checkbox did not, though the documented behaviour is that every settings apply re-detects. The status row is also re-read when detection finishes rather than immediately, since asking synchronously always answers “not detected”.
[0.14.0] - 2026-08-29
Added
-
The menu bar can share the window’s title bar. Off by default, under Settings ▸ Interface; it applies on restart, since a window’s style is fixed once it has been shown. With it on, the window is drawn without a system title bar and the menus, the window title and the system buttons share that one row — a full bar of vertical space back for the editor. Minimise, maximise, close, drag-to-move, double-click-to-maximise, edge resize and snapping all remain the platform’s own, so nothing about how the window behaves changes; only where the menus sit.
Linux and Windows. macOS is excluded and not for want of API — the menu belongs to the system bar at the top of the screen there, so drawing it inside the window would be a worse window rather than a taller one. The checkbox is disabled on macOS rather than hidden, so its note still explains why.
-
Every main-menu item that can carry an icon now does. Right-click menus have carried glyphs throughout, while the menu bar carried none; Save is now the same icon wherever you reach it from.
Partial on purpose: about 90 of the roughly 135 entries have one, and the rest are left blank rather than given an invented glyph — a picture that does not depict the thing beside it has to be read and then discounted, which is worse than an empty space. The File menu’s export block is the clearest case, where one tray-and-arrow would have meant “PDF”, “Word” and “HTML” equally, and so meant nothing. The icon column is reserved whether or not a row fills it, so the titles still line up. macOS is unaffected: the system menu bar owns rendering there and shows no item graphics, which is the platform convention.
-
Search Everywhere can take over the palette’s shortcut. Off by default, under Settings ▸ Interface ▸ Pickers (or the
View: Toggle Palette Opens Search Everywherecommand). With it on, the chord you have always pressed for the command palette opens Search Everywhere instead. Nothing is lost by switching: an empty query lists every command exactly as the palette does, and typing reaches project files and symbols as well. Which picker that chord opens is muscle memory, so this is something you turn on rather than something a release does to you, and Search Everywhere keeps its own shortcut either way. -
Search Everywhere has a keybinding. It shipped in 0.13.0 as a registered command with no chord in any of the bundled keymaps, so the only way to reach the picker that replaces the other pickers was to open the command palette and type its name. It is now
M-S-xin the Emacs keymap, sitting besideM-xwhere the mnemonic explains itself, andCtrl/Cmd+Shift+Ein the CUA, Sublime, VS Code and IntelliJ keymaps.
Changed
-
SVG files preview as vector art instead of a scaled bitmap. The
.svgpreview drew the file once into a fixed 2×-resolution image and scaled that, so zooming past 200% went soft — on the one preview whose entire content is vector. It now draws through JSVG’s JavaFX canvas and redraws at whatever size it is shown at, so it stays sharp at any zoom. Transparent SVGs get a checkerboard behind them, so a white icon on a transparent background is visible rather than looking like an empty pane.Nothing else changed renderer: Markdown badges and every export path (PDF, print, Word, ODF) still rasterize exactly as before.
-
The run-configuration group moved to the right end of the toolbar. The selector and its Run, Debug and Stop buttons now sit beside the project switcher rather than in the middle of the icons, the way an IDE pins its run widget. They are pinned there: the icon cluster is what collapses into the overflow chevron on a narrow window, and the control that starts a run is the wrong thing to lose to a window width. The trade is that the four are no longer draggable in Settings ▸ Toolbar; a bar you have already arranged keeps everything else exactly as you left it.
-
Search Everywhere now teaches the way the command palette does. Three things it was missing, all of which matter more now that it can stand in for the palette:
A command whose feature is switched off is listed, grayed, with an explanation naming the setting that would enable it, instead of being omitted. Omitting it is tidier in a mixed list, which is why it was built that way, but it also means you never learn the command exists — and a picker you cannot learn the product from is a worse palette than the one it replaces. Grayed rows sort after everything you can actually run, and the cursor steps over them.
The highlighted row’s description appears under the list, and
Ctrl+Hopens its documentation in your browser, both matching the palette exactly. The hint line along the bottom names them, along withC-n/C-p— it had been advertising three of the keys the picker accepts and staying quiet about the rest, which in a keyboard-first editor is where those keys go to be discovered or not at all.An empty query lists every command rather than showing a blank box, and a single-source query — an empty one, or a
>/#/@scoped one — is no longer capped. A>search used to return at most eight commands, which made the scoped mode strictly worse than the picker it stands in for. The cap exists so a large source cannot drown a small one; with one source in play there is nothing to drown. -
Simple UI mode keeps a menu bar — simplified, not hidden. It now shows File, Edit, Find, View and Help. The menus that go are exactly the ones Simple mode switches off — code intelligence, running and debugging, version control, tool windows — which would otherwise have stood there entirely grayed out, worse than not being offered. A menu is the browsable map of what the editor can do, which the mode aimed at someone new to it needs most. Toggling Simple UI mode is deliberately kept in the reduced View menu, so the mode is never a one-way door for anyone who entered it from there.
-
The window spends fewer horizontal bars on chrome. Three changes with one theme:
The breadcrumb names the active file but lived in the bottom bar stack, spanning the whole window between the tool stripe and the status bar, where it read as a second status bar describing the window. It now hangs under the editor area itself — as wide as the editor, directly under the text it names — and collapses your home directory to a single
~crumb that still navigates there. Spelled out, every file under$HOMEopened with two crumbs identical on every file you will ever have open, so the segments that actually distinguish this one began a third of the way along a bar that scrolls.In the status bar, indent, line endings and encoding are one segment (
Tab 4 · LF · UTF-8) — three facts about how the same file is written to disk that were three separately divided segments of an eleven-segment bar. They stay three click targets with three tooltips, because they run three different commands, and they now follow whether a file is open at all, so the Welcome tab no longer reports a tab size and a charset describing nothing. The echo line starts empty rather than on a standing “Ready”: a placeholder present on every frame that says nothing trains the eye to skip the one region real messages land in.The toolbar loses cut, copy and paste from the default layout — in a keyboard-driven editor they are the three actions nobody reaches for with the mouse — and About and Quit entirely. Both of those are in the Help and File menus and the palette, neither is a mid-edit action, and Quit sat in the top-right corner directly beneath the window’s own close button. Cut, copy and paste remain catalog items, one drag away in Settings ▸ Toolbar; an already-customized bar is untouched.
-
The default toolbar is one icon family, and New From Template has a glyph of its own. Open, Open Folder and Save As were still filled icons in a row of outlines — the Open folder in particular being the single solid shape there, which draws the eye for no reason. Open now takes the open-folder silhouette and Open Folder the closed one, deliberately the same closed folder as the Project rail, since those two mean the same thing on two surfaces. New From Template had been borrowing the generic document icon, which is the New File glyph minus its little plus — two adjacent buttons, both “new file”, telling themselves apart by one cross. It is now a page that already has content on it.
-
The minimap’s visible-range block is outlined rather than washed. It was a fill at about 14% alpha, which over a dense overview is invisible — the minimap then reads as noise with no “you are here” in it at all. The outline is derived from the same per-theme colour, so themes need no new entry, and it is stroked on half-pixel coordinates because a one-pixel stroke on an integer coordinate straddles two device pixels and renders as a two-pixel blur.
-
The floating Editor/Split/Preview toggle dims at rest and restores on hover, matching the HTML preview button already sitting in the same corner. Both float over the file’s first line or two, so they should recede until reached for.
-
The debug icon is a bug. It was a circle with eight rays — a sun — and that is how it read everywhere it appeared: the toolbar’s run cluster, the Run menu, the test panel, in every case beside a style class named for a bug. The editor’s own right-click Debug item already showed one, so the application disagreed with itself about what debugging looks like.
-
Status messages write your home directory as
~. The echo line printedOpened /home/you/src/…directly beneath a breadcrumb and a window title that both wrote the same path as~/src/…. Three implementations of that collapse already existed in the codebase and none of them reached the status bar; there is one now, and it is the one they all use. Messages that carried a whole path keep it — when several open files share a name, the path is the part that says which one. -
JSVG 1.7.2 → 2.1.0, which had been pinned since 2.0.0 could not be linked into the native build. That is fixed upstream, and the two classes 2.x relocated are a straight import change on our side — no new dependency and no behaviour change. SVG badges in the Markdown preview, and the SVG file preview, render as before; the loader still refuses to fetch anything the SVG points at, which is what stops a document reaching the network on your behalf.
The vector preview above does add one:
jsvg-javafx, the companion module holding the JavaFX canvas. Its module declaration requiresjavafx.swing— Editora itself never touches it — and left alone it resolves that to JavaFX 17.0.15, for which OpenJFX publishes no Linux ARM64 build at all. So the Linux ARM64 package could not be built, and the other four would have linked a JavaFX 17 module into an otherwise-26 runtime, which is the worse half of the same problem. It is pinned to the JavaFX version everything else uses. -
Dependency currency sweep. Jackson 2.22.1 → 2.22.2, Lucene 10.5.0 → 10.5.1, PDFBox 3.0.5 → 3.0.8, Apache POI 5.4.1 → 5.5.1, commonmark 0.28.0 → 0.30.0, plus the two build plugins that produce the native installers (moditect 1.2.2 → 1.3.0, jpackage-maven-plugin 1.6.6 → 1.8.0). Nothing user-visible, but two consequences are worth recording.
commonmark 0.30.0 adds limits on pathological input — a cap on table cells, on block nesting and on inline nesting — which is the right default for an editor that renders whatever file you open. The nesting limits degrade to plain text, but the table cap throws, and at a million cells that is reachable by a legitimate file: a 2.3 MB Markdown table trips it, well under the 5 MB point where the preview switches off anyway. The Markdown preview now reports that instead of going blank, which is what it would have done — the parse runs on a background pool whose exceptions nothing was reading.
POI 5.5.1 brings commons-collections4 4.5.0, which unlike 4.4 ships its own module descriptor, so the hand-written one for the native build was removed. Left in place it would have failed the packaged build only — the test suite stays green either way.
Performance
-
Toggling Simple UI mode blocked the interface for 230–550 ms; it is now 48–108 ms (median 233 → 55), measured over five open source files. Four causes, none of them work that needed doing:
Collapsing every fold cleared the fold shading on every region in the file, unconditionally — a styled-document rewrite rather than a property set, so thousands of paragraphs were rebuilt in a large source file, almost none of which had ever been shaded. The 80-column ruler was re-measured for every open buffer including background tabs, each measure forcing two layout passes. The gutter was rebuilt twice per buffer per settings apply. And applying the editor theme removed and re-added the same stylesheet every time, forcing a full restyle of the window. All four are guarded now, which helps every settings apply — a theme change, a checkbox, any toggle — not only this one.
Then the ruler again, from the other direction: it had been listening for viewport changes, which fire on every vertical scroll and every edit, neither of which can move a ruler whose position depends only on glyph width, gutter width and horizontal scroll. It now recomputes on a width change and when something that genuinely moves it says so — six or seven measures per toggle down to one or two. Leaving Simple mode went from a median of 57 ms to 18, and since every keystroke was paying a measure it could never need, this is a typing win as much as a chrome one.
-
Search Everywhere did a corpus-sized sort on every keystroke. Both of its big sources — project files and symbols — scored every candidate, sorted all the matches, and then threw away everything past the fortieth. A one- or two-character query matches most of a corpus, so that sort was over nearly everything, on the FX thread, between one keypress and the next. Each source now keeps a bounded heap of the best forty as it scans, so the cost no longer grows with how much of your project happens to match.
The scan itself was also building the highlight ranges for every candidate — the bold characters showing why a row matched — when only the visible rows need them, and the picker recomputes those at draw time anyway. They are no longer computed during the scan. The file source additionally rebuilt every path string relative to the project root on each keystroke; those are now kept from the walk that found them.
Measured on a generated corpus, for the worst case (a one-character query, which matches nearly everything): at roughly this project’s size 7.2 ms → 2.0 ms, at ten times its size 102 ms → 23 ms, and at the index’s cap 316 ms → 59 ms. A frame is 16.7 ms, so the first of those was about 40% of a frame per keystroke and is now closer to 12%.
Results are unchanged — same rows, same order. That is the whole basis for the change, so it is pinned by tests that compare against the previous algorithm directly rather than by asserting properties of the new one, over corpora built full of ties, which is the only place a bounded selection can differ.
Very large projects will still want this off the FX thread entirely; this makes the picker usable at a scale where it was not, rather than settling the question.
-
Typing in Search Everywhere rebuilt the whole keymap once per matching command. Each result carries the chord bound to it, and the lookup that produces it inverts the entire binding table into a fresh map. That call sat inside the match loop, so a one-character query against roughly six hundred commands built the same map hundreds of times before a single row was drawn, on the FX thread, on every keystroke. It is now built once per query, which is what the command palette had always done.
Fixed
-
Dragging the minimap moved the document in whole-line steps. The drag scrolled to a paragraph, and a paragraph is the smallest thing it could land on, so on a long file the document lurched a line at a time with stalls in between: measured on a 592-line file in a 900-pixel column, consecutive pixels of mouse travel produced scroll deltas of
16, 0, 16, 16, 0, …where the mapping wanted about two-thirds of a line per pixel. It scrolls in document pixels now — the same continuous mechanism the split Markdown preview already used — and the same travel gives10, 9, 10, 9, …. It got worse the longer the file, since each column pixel then covers more lines.Two adjacent faults the same gesture exposed. Pressing on the viewport box put its top under the cursor, so grabbing it anywhere but the very top threw the document most of a viewport (measured 679 pixels, about 42 lines) before it began tracking; a press on the box now takes hold of it and scrolls nothing, while a press elsewhere in the column is still a “go here”. The box’s hit area has a minimum height, because the box is the viewport’s share of the whole document — a pixel or two on a long file, which is to say unhittable on exactly the files where dragging it matters. And the mouse wheel over the minimap did nothing at all: the column is a sibling of the scroll area rather than a child, so the event reached nothing scrollable. It now scrolls the editor exactly as a wheel over the text does.
-
A narrow window hid the project switcher and Settings before any of the icons. A toolbar overflows from its end, and the fixed group — project switcher, Recent, the build badges, Settings — was the last thing on the bar, so those went into the overflow chevron while fourteen icons stayed. That is exactly backwards: every icon in the cluster is also in the menus and the palette, and the project switcher and Settings are not. Measured at about 1250 logical pixels, i.e. an ordinary 1280-wide laptop, where the whole group collapsed to a
». The group is pinned now, and a window short of space takes the icons. -
The Simple UI toolbar button looked identical whether or not the mode was on. It now reads as engaged, the same way the find, palette and split toggles do. Simple mode is a state you are in, and its own button is the most likely way back out of it. A
--simplelaunch shows it engaged too. -
The first Search Everywhere of a session opened with no row selected. Every later open highlighted the first row, but had scrolled the group header out of sight doing it, so neither was quite right. The first was different only because re-opening the picker clears a query box that still holds the previous query, and that change starts a second pass which lands after the list has been laid out and quietly fixes the selection — so an open was correct only if you had typed something the time before. Both now open with the first row selected and the header still visible, so the picker no longer comes up without the label saying what it is listing.
-
The Git Log tool window was missing from the VCS menu. The menu offered Show File History but never the whole-repository log, though it exists and has a keybinding. It sits with the compare and history group now.
-
Zen and Expert mode left half the toolbar on screen. The bar is two containers on one row — the icon cluster, which overflows into its own chevron, and a pinned tail holding the run controls, the project switcher, Open Folder, Recent, the build badges and Settings. Only the first was ever hidden, so a focus mode whose whole point is that the chrome goes away stripped the icons and left the tail sitting there, over a bar-height strip of reserved empty space. The row that holds both is now what gets hidden, and the tests assert a tail button is invisible through its ancestors rather than naming a container, so the check survives the next change to how that row is built.
-
Opening the quick-fix or completion list leaked a timer, permanently. Both anchored themselves to the caret by asking for the bounds of an empty range, and for an empty range RichTextFX allocates a throwaway caret to measure with — one whose 500 ms blink timer nothing ever stops. Every open registered another running timer with JavaFX’s frame loop, for the life of the process: measured at exactly one per open, so a session that leans on completion accumulated them all day and slowly paid for it on every frame. This is the same fault the 80-column ruler had, and the fix is the same one — measure a real character rather than an empty span, which allocates nothing. The regression guard now covers the popups as well as typing.
-
The toolbar’s right-click menu rendered a third larger than every other menu. A menu popup inherits the font of the node it is shown from, and the toolbar deliberately runs at a larger size so the labels beside its glyphs are readable. That size followed the popup out: the Customize Toolbar menu came up at 18px against 14px for every other menu in the app. It is pinned back to the standard size.
-
A second right-click on the toolbar opened a second menu beside the first. Each right-click built a new menu, so when the platform did not dismiss the old one it simply stayed on screen — two identical Customize Toolbar menus, side by side. There is now one menu, which moves rather than multiplies, and it closes on a click anywhere else in the window without relying on the platform to do it.
-
Two config tests could not fail. Each wrote a schema version as a literal to stand for “newer than this build understands”, so both quietly stopped testing anything the moment the schema was bumped past it — which this release does. They now derive the version from the current one.
[0.13.1] - 2026-08-27
Fixed
-
Editora 0.13.0 crashed on startup on any CPU without AVX-512 — most consumer Intel from 12th generation onward, and every AMD before Zen 4. The JVM aborted with
SIGILLa fraction of a second in, on whichever thread got there first, so the crash landed somewhere different each time and sometimes not at all: measured on a 13th-generation Core i7, four launches in six.The cause is the AOT cache that makes cold start ~40% faster. It archives generated machine code as well as class metadata, and the call adapters among that code are compiled for the CPU that built the release. GitHub’s Linux runners are Xeons with AVX-512, so the shipped adapters spilled the first sixteen vector registers with ordinary AVX instructions and then continued into AVX-512 encodings for registers that only exist on such a chip. The JVM maps an archive like that without complaint — its CPU-feature validation does not cover cached adapters — and the first thread to execute one hits an instruction its processor refuses.
Adapter caching is now off, in the packaged launcher and in the training step both, so a cache built from here on contains no such code and a user still holding a poisoned one no longer executes it. The startup win is kept in full: on the affected machine, first paint measured 1024 ms with the adapters off against 1704 ms with the whole cache disabled — they were contributing nothing.
Worth stating plainly, because it shapes how much to trust a green build: this was a lottery on which runner built the release, not a bug in Editora’s own code. CI passed because the archived instructions are perfectly legal on the machine that generated them, and no test could have run the result on different silicon. A guard now fails the build if either flag is dropped, since the symptom would once again appear only on other people’s hardware.
[0.13.0] - 2026-08-26
A code-navigation release. Navigation had four gaps that mattered: nothing was ranked, symbols existed only with a language server installed, five separate finders each asked you to decide what kind of thing you wanted before you could type its name, and every jump cost you your place. This closes all four.
Added
-
Search Everywhere — one picker over commands, files and symbols, so you can type the name of the thing instead of first choosing which finder it lives in. A leading
>,#or@restricts to commands, files or symbols for when you do already know; those are VS Code’s sigils rather than invented ones, since the muscle memory already exists.Results stay grouped by source rather than interleaved on raw score. The sources differ in size by orders of magnitude — tens of thousands of symbols, thousands of files, a few hundred commands — so a flat merge hands the whole list to whichever is biggest and the other two effectively disappear. Each source gets a guaranteed share, the groups compete on their best result rather than their bulk, and the overall cap trims a group’s tail instead of dropping a source outright.
A command the palette would gray out is omitted here rather than shown inert: in the palette a disabled row is the point — you learn the command exists and what would enable it — but in a short mixed list it is just noise.
-
Go to Symbol in Project, with no language server. Editora now carries its own symbol scanner, so the question works on a first run and in every language that ships a grammar but has no server — 16 language ids’ worth of declaration rules. A running language server remains the better answer and still takes precedence; this is what there is when there isn’t one.
The index is built lazily, on first use, not when a project opens: walking every file the moment a project opens spends real work for someone who may never ask it anything, and asking for a symbol is the justification. The first query pays for the walk — under a second on a 650-file project — and after that it is incremental, rescanning exactly the file you saved from the text already in memory. There is no second filesystem watcher; the Project tree already runs one, and files changed outside the editor are handled by
index.rebuild.The scanner under-reports on purpose. A missing declaration costs you one fallback to search; an invented one sends you somewhere that does not exist and teaches you not to trust the feature.
It is deliberately not built on the TextMate grammars, whose
entity.name.*scopes are the obvious source: tm4e grammars are not thread-safe and the registry is shared with the editor’s background highlighters, so an indexer walking a project would race them. -
Every picker now ranks what you typed, and shows why a row matched. The pickers only ever asked whether a candidate matched, never how well, so a scattered accidental hit ranked alongside a clean prefix one — and recent files, open buffers, bookmarks, snippets, projects, themes and LSP references were not ranked at all, showing matches in source order.
The new matcher scores contiguity, word and camelCase boundaries, exact case and compactness, matches whitespace-separated terms in any order (
toggle gitfinds Git: Toggle Blame), and knows a path’s basename from its directories. The alignment is computed by dynamic programming rather than greedily, which is the difference betweenmcmeaning the two humps ofMainControllerand meaning whicheverchappens to come first.Rows embolden the characters actually responsible for the match. The command palette previously ranked with one algorithm and emboldened with another, so the highlight could point at characters the ranking had never considered.
The Project tree’s filename filter now scores the whole relative path, so a query can name a directory as well as a file. Grouped and structural lists — build actions, branches, the Structure tree — take the better matcher but keep their own order, because a tree that reorders under a filter is worse, not better.
-
Sticky scroll pins the enclosing scope headers above the viewport, so deep in a long method you can still see what it belongs to. A header already on screen is not pinned — that would cover the real line with a copy of itself — and when the chain is deeper than the cap the outermost entries are kept: the innermost scope is the one you can infer from the code in front of you, while the file-level type has been off screen longest. Clicking a pinned row jumps to it. Settings → Editor → Display, or
view.toggleStickyScroll. -
Peek Definition shows a definition over the editor and leaves your place, scroll position and tab count where they were. Go-to-definition answers “what is this” by moving you, and most of the time the question is only a signature and a few lines of body — an answer that does not justify the move. Enter still commits to the real jump, so nothing is taken away. A library target with no file to read degrades to the full go-to-definition rather than reporting a failure.
-
Preview tabs. Browsing now costs one tab, not one per glance. Arrowing through a picker or following a definition to see what something is reuses a single slot, whose title is italic — the same “not durable yet” convention the unsaved marker uses. Editing the file, or opening it explicitly, promotes it to a permanent tab. An already-open file is never demoted into the slot: it earned its place by an earlier deliberate open, and quietly making it disposable would lose it on the next glance at something else.
-
Recent Locations lists the session’s trail newest-first with the line you were on, which is the thing you actually lost when a jump took you elsewhere.
Foo.java:214says nothing about why you were there; the line usually says it at a glance. It reads the same trail that Back walks rather than keeping a second log, so the picker and Back can never disagree about where you have been. -
Picker preview. The highlighted row is shown in the editor as the selection moves, and dismissing the picker puts everything back. The undo is half the feature — without it, arrowing through a list and pressing Escape leaves you wherever the cursor stopped, which is worse than not previewing at all. Preview navigates without taking focus and without recording history, so browsing a picker cannot itself become something to navigate back through.
-
Go to Related File pairs a file with its counterpart — a test and the class it tests, a header and its implementation, a component and its stylesheet. These are conventions rather than facts, so Editora proposes candidate names in preference order and offers only the ones that exist; a convention being wrong therefore costs nothing, which is what lets the list be generous. Coming from a test, the subject leads. Several matches offer a picker, since
Foo.cssandFoo.scsscan both exist and only you know which was meant. -
Go to Definition in a Split puts the definition in a new editor group and leaves the origin where it was — the pair you usually want on screen together is the call and the thing called. A definition in the same file jumps without splitting, since there is only one tab and the split would move it, leaving the group it came from empty.
-
Bookmark mnemonics. A bookmark can carry a single character, and that character is a chord that jumps to it from anywhere in the project. A mnemonic is unique within a project —
3means one place, not one place per file — because the chord is a name for a location, and a name resolving to several locations is a menu, not a shortcut. Assigning one that is already taken moves it. The panel shows the mnemonic first on the row: it is the only part of a bookmark that is otherwise invisible, and a shortcut you cannot see is one you will not remember assigning. -
The Windows installer now registers Editora under Explorer’s “Open with” for the text and source types it edits. Windows hands a file manager’s chosen file to an application as a command-line argument, and nothing maps an extension to Editora unless the installer says so — so until now the MSI installed an editor that no file manager could hand a file to. The Linux
.debalready did the equivalent (and more: it can also make Editora the default text editor).It does not take over your existing defaults: on Windows 8+ the shell’s per-user choice wins over anything an installer writes, so a type you already open with something else keeps opening with it and Editora is simply offered alongside. An extension nothing else claims does fall to Editora, which is the wanted outcome for the likes of
.tfvars.The list is every extension Editora actually resolves to a language, minus
.html/.htm/.xhtmland.svg— left to the browser and the image viewer, mirroring the choice already made for the Linux package so the two installers cannot drift apart in what they claim.The
.rpmremains without an association: it uses jpackage’s own desktop entry and ignores the maintainer-script overrides the.debrelies on. RPM users can still open files from within Editora.
Performance
-
Peak memory is down from ~908 MB to ~653 MB on a four-file session, measured on the installed package against a copy of a real configuration.
The initial heap was never pinned, and the JVM’s default for it is 1/64 of physical RAM clamped up to the maximum heap — so on a large-RAM machine the whole 2 GB heap was committed before the application started, and memory sat there for the best part of a minute until the periodic collector gave it back. The live heap is 63–75 MB when idle, so starting at 64 MB costs nothing to grow from. Startup is unaffected, and stop-the-world pauses get better rather than worse — 19.6 → 4.2 ms median, 31.7 → 9.0 ms worst — because a small young generation copies far less per collection. This only ever affected large-RAM machines, which is why a 16 GB laptop never saw it.
-
The undo history and the three decoded-image caches are now bounded by size, not by count. Keeping 50 whole-document snapshots is not a memory bound when the entries differ in size by three orders of magnitude, and a preview image is user content of any size. Both now carry a byte budget behind the count, whichever binds first winning — so ordinary files still get all 50 undo steps and a large one keeps fewer, deeper-in-time ones. On a 472 KB file, 50 edits cost 50 MB before and 34 MB after. Closing a tab releases its checkpoints immediately rather than at the next collection.
-
Startup reaches its first frame ~46 ms sooner. When the main class extends
Application, the JavaFX toolkit starts beforemainruns, leaving nothing to overlap it with. Editora now enters through a plain launcher instead, and spends the window while the toolkit starts loading the configuration on a background thread. The update checker’s HTTP client is also built lazily rather than during window construction — it serves a check that is throttled to once a day and runs off-thread, so none of it belonged on the startup path.
Fixed
-
The Linux package now describes itself properly in GNOME Software (and other software centres), instead of showing “editora / Editora”, “Unknown License”, “No details for this release” and a generic gear icon.
None of that came from the desktop entry, which was already correct — a software centre describes an installed application from AppStream metadata, and the
.debshipped none, so it fell back to the dpkg control fields that jpackage fills with defaults. The package now carries an AppStream metainfo file (name, summary, description, MIT licence, homepage, categories, release notes) plus a themed icon set, installed by the maintainer script and removed on uninstall. The control fields it does read are filled in too: a real description, a homepage, and a maintainer address instead ofEditora <Unknown>.The release entry is filled in from the version being packaged rather than hand-written, because a block that has to be edited on every release is one that eventually is not — and that failure looks exactly like the “No details for this release” being fixed here.
The application menu icon is untouched: the desktop entry keeps the absolute path it already resolves correctly, and the themed icons are additive, for the software centre only.
One thing this cannot fix: “Potentially Unsafe — Provided by a third party”. That label is about where a package came from, not what it says about itself — a software centre applies it to anything not installed from a configured repository, and no file inside a
.debcan assert otherwise.
Internal
docs/navigation-roadmap.mdrecords where navigation stood before this release, the four gaps that mattered, and the order they were closed in. It is kept as a roadmap rather than folded into the architecture notes because most of its value is the reasoning that did not survive contact — the decisions reversed along the way (the symbol index ended up on by default but lazy, rather than opt-in), and the one item dropped outright (a symbol breadcrumb, which sticky scroll subsumed).
[0.12.3] - 2026-08-17
Fixed
-
A file opened from the file manager now gets a window of its own instead of landing as a tab in whatever window you were working in.
Before the single-instance handoff, such a click started its own process and therefore its own window. The handoff was meant to stop duplicating the process — a second JVM, a second set of language servers — not to change what the click does; but it took over the window in front of you, and with the “Editora Expert Mode” entry restyled that window’s chrome as well. The requested focus mode now applies to the new window, where it is unambiguous, rather than being imposed on one already in use.
Unless the file is already open, in which case the window holding it is brought forward instead. Two independent buffers over one file loses edits — save one and the other is silently stale behind an external-change prompt — and re-opening a file you already have open is an ordinary thing to do.
These windows are deliberately left out of the saved layout, so a file opened from the file manager does not come back as an empty window on the next launch.
[0.12.2] - 2026-08-17
Fixed
-
A file opened from the file manager now brings the running Editora to the front, instead of leaving it behind the file manager with a GNOME notification you have to click.
This was the cost of the single-instance handoff added in 0.12.1, and the cause is the compositor rather than Editora. When a file manager launches a new process it hands that process an activation token, so the window it maps counts as user-initiated and gets focused. Forwarding breaks the chain: the token goes to the forwarder, which delivers the file and exits without ever mapping a window, while the process that owns the window is an older one you have not touched recently — so its raise is an unsolicited focus request from a background application, which GNOME refuses, flagging the window as demanding attention instead.
The window is now briefly pinned above the others to bring it forward — which the compositor honours, because that is a window state rather than a focus request — and released straight after, so nothing stays on top. The raise also happens before the file is opened rather than after, so it is immediate instead of waiting on the file to load, and still happens if opening fails.
[0.12.1] - 2026-08-17
Added
-
Opening a file from the file manager now reuses the running Editora instead of starting a second one. On Linux and Windows a file manager delivers the path as a command-line argument, which by definition means a new process — so clicking a file while Editora was already open paid a full cold start and left a second editor resident (measured: two JVMs at 670 MB and 1707 MB, for what looked like one application). macOS never had this problem, because Finder delivers an Apple Event that reaches the running app; this is the cross-platform equivalent, and it hands the launch to the very same code path that event does.
A second launch now delivers its files over a loopback socket and exits — measured at ~735 ms (JVM startup, which a new process cannot avoid) against ~1.5 s and a permanently resident second editor. The files open in the focused window, and a focus mode the launcher asked for is applied, so the “Editora Expert Mode” entry still means Expert Mode.
Deliberately narrow: only a launch that is purely “open these files” is handed over.
--project,--new-file,--config-dirand--devshape how a process starts and have no honest reading inside a running window, so those still get their own editor — as doeseditorawith no files at all.--new-instanceforces a separate editor in any case. An instance is scoped to its config directory, so a--devlaunch can never hand off to your real editor.The endpoint is loopback-only,
0600, and carries a random token; it is removed on exit, and an endpoint left behind by a crash is detected and reclaimed on the next launch. If anything about the handoff fails, the launch simply starts its own editor — the behaviour before this existed.
Changed
-
Startup is ~225 ms faster to the first frame that shows your file (measured on a packaged Linux build opening a file with
--expert: median 1702 ms → 1477 ms, ~13%). Four pieces of work were running on the JavaFX thread before the editor had painted, none of which the first frame needs:-
The minimap was the largest single piece of app code on the path to first paint (~135–230 ms). Its render iterates every paragraph and then calls
snapshot(), which forces a synchronous full-scene layout, and finishes by forcing aVirtualFlowlayout on top of that — and at startup the triggers arrive in a burst (theme colors, tab size, content settling), so the whole sequence ran three times over before the text appeared. Renders now coalesce to one per pulse, and the first is held until the editor has painted. The minimap is a secondary navigation aid; nothing about it needs to precede the text you are waiting for. Typing is unaffected — the 200 ms edit debounce already paced those renders. -
The debug adapter locations for Python and JavaScript were resolved on the JavaFX thread at startup, walking the VS Code / mason / plugin directories (~30–50 ms) for values not read until a debug session actually launches — and they were resolved again, off-thread, moments later, by the existing availability probes. Only the off-thread resolution remains. The Java bundle is still located synchronously: it has to reach the LSP layer before any jdtls session starts.
-
The rest of the session restore now yields a couple of frames before continuing. The file you asked for is already front-loaded, but the remaining tabs were filled in the very pulses that lay out and render it, so with a session of any size they pushed back the frame you were waiting for.
-
Fold recompute no longer forces a layout pass to discover it has nothing to do. Locating the viewport (
firstVisibleParToAllParIndex) forces aVirtualFlowlayout, and the recompute did it on every run — including the ones whose fold structure was identical, where the loop it feeds does nothing at all. That is the common case twice over: at startup the recompute triggered by setting a buffer’s language runs against a still-empty document (~10 ms of pure forced layout), and while typing this runs on every 250 ms settle, where most edits leave the fold structure untouched. It also stops perturbingestimatedScrollY, which that query does as a side effect, on runs that change nothing.
If a startup regression is ever suspected, measure it rather than reason about it:
scripts/measure-startup.shreports the phase breakdown, andEDITORA_PERF_SAMPLE=1now adds a per-phase sample of what is holding the JavaFX thread. Both of the costs above read as “rendering is slow” from the call graph, and neither was. -
[0.12.0] - 2026-08-16
Added
-
Tool window docking. A tool window can now be maximized over its split, re-docked to another stripe by dragging its button, shared with a second window on the same side, and floated into its own window. Maximize and float have a header button each and a palette command; a side holds at most two windows, split vertically on the left and right and horizontally at the bottom, where two consoles side by side are the only way both stay readable. Floating windows remember their bounds, and reuse them only when they still overlap a screen — a window saved on a since-detached monitor is re-centred rather than opened somewhere unreachable.
Maximize is expressed as divider positions rather than by reparenting, so focus, stripe state and remembered sizes all survive it. The two ways that could have shipped broken: a
SplitPanehonours its items’ minimum widths, so without zeroing them for the duration a “maximize” stops part-way and reads as a bug; and closing or persisting while maximized would record a 0-or-1 divider as that window’s size, reopening it next session covering the editor, permanently. -
A pom.xml summary preview. A
pom.xmlopens with its own rendering instead of the generic XML tree: coordinates and parent, then modules, properties, dependencies, managed dependencies, plugins, managed plugins, and each profile’s own set — with the artifact name and its version in aligned columns. The XML tree is one toggle away (pom.toggleView, or the preview’s right-click menu), and Settings → Editor → File previews turns the whole thing off.The point is the two indirections that otherwise leave you scrolling the file to answer “which version is this?”: a
${property}version is shown resolved, with the reference kept beside it, and a blank version is filled from the file’s own<dependencyManagement>and tagged managed. What the file cannot answer, it says — a version inherited from a parent pom reads inherited rather than a number Editora would be inventing. No parent pom is read, so this is a reading of the file, not an effective pom. -
Maven: update the versions in an existing project.
maven.updateVersionschecks the nearestpom.xml’s dependencies and plugins against Maven Central and shows what would change before writing anything — a row per artifact, current → latest, behind a dialog you can decline. The update is applied through the open buffer as a single edit, so one Ctrl-Z takes the whole thing back.Maven Central’s
<release>marker is deliberately not trusted (it means “newest non-snapshot published”, which for maven-surefire-plugin was a milestone for years), and a version is never walked backwards, which an unguarded set-to-latest would do to a pom pinned on purpose. -
A Maven submenu on the right-click menu — on a
pom.xmlin the editor, and on a folder or apom.xmlrow in the Project tree. One builder serves every surface, so they cannot drift into offering different actions, and it offers nothing at all when Maven is off or there is no pom. -
New Maven Project: advanced options. A collapsed section carrying the project
<url>, the Java release (a combo of the JDK majors actually installed, still editable), and an update-to-latest-versions checkbox. None of the three can be an archetype property —archetype:generateignores them and quickstart bakes its own into the pom template — so they are applied as a post-generation, format- preserving pom edit. Empty means “keep what the archetype wrote”. -
An LSP submenu in the editor’s right-click menu. A fully-featured server contributes up to eight actions, which pushed cut/copy/paste and the spelling suggestions far enough down the menu to hunt for; and a bare “Go to Definition” between “Paste” and a spelling suggestion named nothing about where it came from. Which actions a server contributes is unchanged — the submenu is only their container.
Changed
-
File Templates is no longer labelled beta in Settings.
-
The in-app jdtls install is pinned to a milestone build rather than the rolling
-latesttarball. Two users installing a week apart were getting different servers, and an untested overnight build shipped straight to them — which matters more for jdtls than for most servers, because Editora’s handling of it is measured against specific builds. A test fails if the pin drifts between the two files that carry it.
Fixed
-
Recent files that no longer exist are no longer offered. A recent list outlives the files in it, and an entry that cannot open is worse than no entry. Remote (SFTP) entries are deliberately never checked — that is a network round trip per entry, on the UI thread, every time the menu is built, and it answers “gone” for a host that is merely asleep. Entries are filtered rather than pruned, so a file on an unplugged drive comes back by itself.
-
Find in Files showed two different icons for one feature — the toolbar button and the tool window disagreed, the window carrying the plain magnifier that belongs to the in-file find bar.
-
The tool stripe’s drop-zone highlight stuck after a drag. Reordering a stripe button by dropping it on a neighbour left the dashed border painted for the rest of the session.
-
Creating a Maven project beside an existing one failed outright (“Unable to add module to the current project as it is not of packaging type ‘pom’”).
archetype:generateregisters the new project as a<module>of whatever project it finds and refuses when that one is an ordinary jar project. Generation now happens in a scratch directory and the finished project is moved into place; an aggregator (packaging=pom) is still left attached, where the module is what you want. -
“Failed to open” for a file that had just opened. Launching Editora with a position —
Editora notes.txt:42, which is what a terminal’s clickable-link setting sends — opened the file at the line and reported a failure. macOS delivers a launcher argument through theopenFilesApple Event as well as on argv, and the two were parsed differently: argv understood the:line[:column]suffix, the event took the whole string as a filename and looked for a file that had a colon and a number in its name. Both paths now go through the same parse, and OS-delivered files honour a line number as command-line ones always have. Existence is asked first, because a colon is a legal character in a macOS filename: a file really callednotes:1opens as itself rather than as line 1 ofnotes. -
Every debug launch logged a parse failure for a reply that was correct. jdtls answers the “where is java?” request with a bare path, which is not JSON; it was parsed as JSON anyway, logged a stack trace, and then fell through to the same value. Nothing was broken except the log — which is the problem, since a routine failure firing on every launch trains you to ignore the one that matters.
Internal
-
Routine dependency bumps, taken as one batch: JavaFX 26.0.1 → 26.0.2, Jackson 2.19.0 → 2.22.1, Gson 2.11.0 → 2.14.0, Lucene 10.2.2 → 10.5.0, java-diff-utils 4.12 → 4.17, joni 2.2.6 → 2.2.7, jcodings 1.0.58 → 1.0.64, JUnit 5.11.4 → 5.14.4. All patch or minor.
Verified with a packaged build as well as the suite, because a dependency change here can break the installer while every test passes: java-diff-utils is an automatic module carrying a moditect descriptor, and JavaFX is linked into the runtime image. The app image builds and its AOT training run — which launches the real GUI — completes, which is what says the jimage is whole.
Deliberately not taken: PDFBox, POI and MINA SSHD, whose jlink descriptors are hand-written and want a build and a launch of their own; commonmark, where a 0.x minor can move the API; and the early-access, alpha, milestone and major offers (JavaFX 28-ea, slf4j 2.1.0-alpha1, SSHD 3.0.0-M5, JUnit 6, LSP4J 1.0.0, JSVG 2.1.0).
-
The toolbar’s run-configuration selector rebuilds its rows in one assignment rather than replacing them and then appending its trailing “Edit Configurations…” row. A ComboBox whose items shrink while its popup is being clicked makes JavaFX throw out of its own event handling — reproduced against JavaFX 26 — and the two steps left the list one row short in between. Not presented as the fix for the crash that prompted the look: nothing in that report names a control, and this was simply the one place in the app with the shape.
-
RunConfigToolbarFxTestno longer races the program it launches. It asserted the Stop button was enabled straight after startingjava -version, and “is it running” is a live reading of the OS — so if the UI thread was descheduled for longer than that program takes, the child was already gone. It now runs a sleeper it stops itself, which also covers what the Stop button is for. -
An uncaught exception on the UI thread now records what had keyboard focus beside it. A crash inside a JavaFX control’s own event handling produces a stack with no application frames at all, so it names the control’s class and nothing about which of the app’s many lists or trees it was — which is the difference between a report that can be acted on and one that cannot.
-
The build’s AOT cache is now trained under the same garbage collector the packaged app runs. The switch to G1 in 0.11.0 updated the launcher but not the trainer, which kept training under SerialGC. No user-visible effect: a GC mismatch turns out not to invalidate the cache, the archived heap data follows the runtime collector rather than the training one, and 8 interleaved startup runs of each build found no difference beyond the noise. A test now fails if the trainer’s options and the launcher’s drift apart again, since the next flag to diverge may not be as harmless.
-
scripts/measure-startup.shruns on macOS. It usedtimeoutanddate +%s%3N, both GNU-only and neither present on a stock macOS, so the startup harnessCLAUDE.mdpresents as the permanent way to measure cold start could not run on the primary development machine at all. Both are now capability- detected. Three further defects surfaced while fixing it: agrepthat matched nothing killed the script underset -ebefore its own diagnostic could print, so a failed measurement exited 1 explaining nothing; the log that diagnostic pointed at was deleted by the exit trap before anyone could read it; and the usage text was a hardcoded line range that had already drifted. Measured impact of the missing timestamp: the fallback origin overstated time-to-first-paint by roughly 30% on macOS, all of it in the pre-mainphase.
[0.11.0] - 2026-08-11
Added
-
New Maven Project. Editora could only ever open an existing folder as a project. There is now an IDEA-style wizard: pick an archetype, fill in the coordinates, and the generated project is registered and opened in its own window. Reachable from the File menu, the Project tree’s New ▸ submenu, and the palette as Maven: New Project….
- Archetypes come from a curated list that ships with Editora, with Load full catalog… to pull Maven Central’s if you need something else. Where both list the same archetype the curated pin wins, since the published catalog is often years behind.
- The package name is derived the way IntelliJ does it (
my-app→my_app,2048→_2048,new→new_), so a project name that isn’t a legal Java identifier still produces one. - Generation shells out to
mvn archetype:generaterather than writing a pom by hand, so the feature needs Maven on your PATH — which is checked before the wizard opens, not after five fields have been typed. - The new project arrives ready to run: a run configuration is seeded for its main class, with
mvn -q compileas its before-launch step, and that class is opened so Run works on the first press.
-
Git: Initialize Repository… — you could clone a repository but not start one, so a folder opened as a project had to be taken to a terminal for
git init. The command prompts for a folder (defaulting to the project root), runsgit init, and brings the Git UI up the usual way: status bar, Commit window, gutter change bars. Creating a repository inside an existing one is refused, naming the enclosing root, since it’s nearly always a mistake and awkward to undo. -
Copy the Markdown preview as rich text. Copying from the preview now puts an HTML flavour on the clipboard beside the plain text, so one Copy pastes formatted into Word, Teams, Outlook or Gmail while a plain-text target still gets the markup-stripped version. Both go on together and the receiving app picks, so there’s nothing to configure. Equations paste as images; headings, bold, lists, tables and links survive even in an app that throws the styling away.
-
Each tool window remembers its own size. Sizes were three numbers — one per side — shared by every window docked there, so sizing the Project tree and then opening the Structure outline left both at whatever the last one had been dragged to. On a first open, a panel whose content is already showing a horizontal scrollbar is widened to fit, up to 45% of the split.
-
Create a file of any supported type from the Project tree. Right-clicking a folder now opens a single New ▸ submenu: a generic
File…andFolder…, Text and Markdown, then one submenu per family — Java (Class, Interface, Record, Enum, Annotation,package-info.java), Web, Scripts, Languages, Data & Config, Docs & Diagrams, Build & Ops — about fifty types in all, each carrying the icon the file will show in the tree. You are asked for a name, prefilled with a sensible one, and the file opens with the caret already in the body rather than at the end.- A new Java file gets its package declaration from where it is created: “New ▸ Class” in
src/main/java/demowritespackage demo;. A qualified name (text.Slug) creates the sub-package folder and extends the package to match, andpackage-info/module-infoare accepted despite not being identifiers. - The typed name always wins over the type’s extension —
notes.jsonunder Text File gives you JSON, notnotes.json.txt, and a dotfile stays a dotfile. A name that tries to climb out of the folder (../x) creates nothing at all, and an existing file is never overwritten. - Also on the command palette as File: New File of Type… (and in the File menu), creating in the active file’s folder.
- The existing New Folder…, New From Template… and New Maven Project… items moved inside that same New ▸ submenu, shortened to Folder… / From Template… / Maven Project….
- A new Java file gets its package declaration from where it is created: “New ▸ Class” in
-
Filter and keyboard-navigate the Commit window. It now opens with a filter box focused, like Bookmarks and Personal Notes: type to narrow the changed files, Down or Enter to step into and open one. Typing a group’s name (
untracked) lists that whole group. Inside the tree,C-n/C-pmove andC-f/C-bexpand or collapse a group — chords the panel used to swallow, since it takes its own keys. Filtering is only a view: hiding a staged file behind a filter does not disable Commit, and staging still acts on the files you selected, not on everything that matches. -
C-n/C-pnow move the results from the filter box in every filterable tool window — Project, Structure, Bookmarks, Personal Notes, the Commit window and the build task trees — so narrowing and then picking never needs a hand off the keyboard. Previously only the GitHub window’s list did this. -
Stage or unstage several files at once. The Commit window’s file list is now multi-select: extend the selection with Shift+Up/Down from the keyboard or Shift/Ctrl-click with the mouse, then right-click and Stage, Unstage or Discard the whole run in one go. A mixed selection offers both Stage and Unstage, each acting only on the files it applies to, and Discard confirms once for the lot. Also on the palette as Git: Stage Selected Files and Git: Unstage Selected Files, and the menu key opens the same menu for the focused row, so the flow never needs the mouse. Each action is one
gitinvocation for the whole selection, so one refresh — not one per file. -
Filter and keyboard-navigate the build tool windows. The Maven, Gradle, npm, Cargo and Go task trees now open with a filter box focused, the way Bookmarks and Personal Notes do: type to narrow the goals, scripts or targets, Down or Enter to step into and run the result. Matching a section title (
lifecycle) keeps that whole section. Inside the tree,C-n/C-pmove andC-f/C-bexpand or collapse a section — chords the panel used to swallow, since it takes its own keys. -
The breadcrumb no longer opens a menu taller than your screen — clicking a folder with hundreds of entries used to produce a flat list you could only inch through with tiny scroll arrows. Past thirty entries it now splits into ranges (
android … cargo), each opening a submenu. -
Filter the message log — the session’s status messages fill up fast, and finding the one you want meant scrolling. The popup now opens with a filter field focused; type to narrow, Down or Enter to step into the results. It is also 40% wider and 20% taller.
-
Copy details from About — the About panel was rebuilt as a native-style panel (icon, version, links, and a monospace block listing Java, JavaFX, your OS and your config path) with a button that copies that block for a bug report.
-
The gutter ▶ uses your run configuration inside a Maven or Gradle project. It used to launch an ad-hoc “run this main class” with no before-launch build, no program or VM arguments, no working directory and no environment — which in a build-tool project is the wrong launch, and in a freshly generated one simply failed, because nothing had compiled yet. The configuration naming the clicked class wins, otherwise the one selected in the toolbar. Outside a build tool the old behaviour is unchanged.
-
Opening a project no longer leaves Run complaining about a missing file. A saved Java configuration resolves its classpath through an open Java file, so a session that had only
pom.xmlopen reported “open a Java file from the project” with the configuration sitting right there in the toolbar. The class that will run is now opened when the project restores — in the background, so it never steals the tab you left on.
Changed
-
A run configuration no longer picks run or debug for you. Each entry carried a “Kind” of run or debug that decided only what the Run button did with it — so a debug-kind entry turned Run into a second Debug button and left no way to plain-run it at all. The field is gone: one configuration, two buttons. Existing entries keep working; a saved kind is ignored. In exchange every configuration now gets a Debug: <name> command alongside its Run: <name> one, so a keybinding can reach either verb — which is the one thing Kind was genuinely good for. There is also a new Debug: Run Configuration… picker beside the existing Run one.
-
Inlay hints now appear where they belong. They used to be parked at the end of the line, so a call with several arguments produced a trailing run of grey text whose only clue to which hint went with which argument was left-to-right order. Each hint now renders inline at the position it describes, gently pushing the code after it aside, the way IntelliJ and VS Code do it. They remain annotations, not text: selecting or copying a line still gives you exactly what is in the file.
-
Inlay hints no longer label every argument of every call.
System.out.println("Hello")used to earn a greyx:, because the JDK declaresprintln(String x)— a hint that tells you nothing, and the common case rather than an edge one. Names that explain nothing (x,arg0) and names that merely repeat their argument (foo(name: name)) are now always hidden, and a picker in Settings → Code Completion chooses whether the rest appear on literal arguments only (the new default, matching VS Code) or on all of them. Type hints are never filtered. -
Git, GitHub and Web are no longer marked Beta.
-
The Output console appears when you are in a Git repository. Git and GitHub write every command they run into a transcript there, and in a repository with no build file its stripe button never appeared — so those transcripts had no way in.
-
Chrome sizing and alignment throughout. The status bar reads 20% larger and the toolbar’s labels 30%. The tool stripe’s icons line up with the first line of code rather than with the tab strip above it, and Quit sits directly above them. Every toolbar control — the project and run-configuration pickers included — is now the same height as the icons beside it, with their own text at a size that fits. Tabs are square, and the hairlines stacked between the toolbar, tab strip, diff viewer and View-Mode banner are gone.
-
The Settings window opens larger (clamped to your screen) so the template, snippet, external-tool and toolbar lists show their contents without scrolling, and each page now opens at its top instead of wherever the previous page was scrolled to.
-
Project, Bookmarks, Structure, Personal Notes and Commit share one font size — the five list tool windows disagreed, so a glance from one to the next changed size. A tool window’s title also matches the stripe button that opens it, icon included.
-
Toolbar regrouped. Undo/Redo is its own group rather than sharing one with the clipboard; Close no longer sits beside Save, where a mis-click discarded the tab instead of saving it; Clear Recent moved inside the Recent dropdown, where it can’t be hit by accident; Recent sits with the project controls and Settings beside About. New File and the page glyph were drawn taller than every other icon and now match. The command palette’s lightning bolt became a
>_prompt, Simple mode’s empty square became a window frame, and Settings’ sun became a cog. -
A side tool window’s header now starts level with the stripe button that opened it. The stripes were inset to line their first glyph up with the first line of code, but the panels beside them were not, so a panel’s header sat a tab strip’s height above its own button. The band across the top of the window is continuous now too — the panel’s top inset was painting white into an otherwise unbroken strip.
-
The overlay popups use the interface font throughout. The command palette’s chord chip, Find in Files’ line numbers and the message log’s timestamps were each the one monospace element in an otherwise Inter card, so they read as a different kind of text rather than part of the same surface. The trade is that timestamps and line numbers no longer sit in a perfectly aligned column, which JavaFX can’t give us without tabular figures.
-
Git status letters (M/A/D/R/U) are bold in the Project tree and the Commit window.
-
Go to Line opens where every other popup does — it was the one overlay centred in the middle of the window.
-
The Maven icon is legible at icon size. It was the official Apache Maven mark, a two-feather illustration whose hairline quills collapsed into a smudge at 19px; it is now a single bold feather.
Performance
-
Typing no longer gets slower the longer you leave Editora open. Every keystroke permanently leaked two JavaFX animation timers through the 80-column ruler, which measured the caret with a zero-width query — and that form makes the editor allocate a throwaway caret whose blink timer nothing ever stops. The timers accumulated as pulse receivers and were never released, so the cost grew with the session and did not recover:
keystrokes pulse receivers median keystroke 0 7 — 500 1,019 6.4 ms 1,000 2,019 11.8 ms 2,000 4,019 28.4 ms Measuring an actual character instead leaves the count flat and the keystroke at about 2 ms however long you type. A benchmark now counts pulse receivers per keystroke so this can’t come back.
-
The packaged app uses a third less memory, with far shorter GC pauses. It shipped with the serial collector, chosen on the reasoning that a mostly-idle editor would get sub-millisecond pauses and a smaller footprint from it. Measured on a real session, none of that held — it sized a ~548 MB young generation it never handed back, against a live set of about 72 MB, and had that much to copy on every collection. Switching to G1 with periodic uncommit: RSS 1130 → 738 MB, longest GC pause 138 → 34 ms (median 91 → 8 ms), startup unchanged. A 138 ms stop-the-world is eight dropped frames on the typing path.
Fixed
-
Sliding along the menu bar switches menus again. On Linux, moving the mouse from one menu to the next often did nothing. A menu popup is a real window, and JavaFX sizes that window to include the drop shadow — so the wide shadow the interface pass gave menus left 26px of invisible but mouse-catching window above every open menu, covering the bar and its neighbours’ titles. The menu’s shadow now falls entirely below it, and the popup starts exactly at the bar’s bottom edge.
-
Running a Java project works from a cold start. Generating a project and pressing Run walked into a chain of separate failures, each of which blamed something that was working correctly:
- “Could not resolve the classpath — make sure the Java project has finished importing”, on projects that
had imported perfectly. Editora never told jdtls which JDKs were installed, so a project asking for a
Java 17 execution environment on a machine with 21 and 25 got an empty classpath and no error. The
installed JDKs are now declared when the server starts, read from each one’s
releasefile rather than by running it. - The same message on a saved configuration, because it hand-built the request from the configuration’s own (usually blank) project name instead of asking jdtls to identify the class, the way Debug and the gutter marker already did.
- “No main class was found in this project” when debugging a saved configuration — jdtls answered correctly and the reply was discarded. Any absolute path in it broke the parse, so this failed on essentially every real project. Classpaths containing a space, comma or bracket were being silently mangled by the same bug.
- “Could not find or load main class” once the classpath did resolve:
archetype:generatewrites sources only and Editora runs jdtls without autobuild, so nothing had been compiled. - “No language server for file”: a background Java file’s server start is deferred until its tab is shown, so the file the launch routed through had never been opened on jdtls even though its tab was.
- “Could not resolve the classpath — make sure the Java project has finished importing”, on projects that
had imported perfectly. Editora never told jdtls which JDKs were installed, so a project asking for a
Java 17 execution environment on a machine with 21 and 25 got an empty classpath and no error. The
installed JDKs are now declared when the server starts, read from each one’s
-
The toolbar’s Stop button is enabled while a program is running. Nothing refreshed it when a run started or finished, so it stayed disabled — and greyed out — no matter what was running.
-
A run configuration added on the Settings page shows up in the toolbar. It was written to disk and then never appeared in the selector, or as a palette command, until the next launch, with nothing logged.
-
Tab on an already-indented line now moves the caret to where you type. Pressing Enter inside a block leaves the new line already carrying its indent, so Tab has no indentation left to add — and it did nothing at all. Going back to the start of that line (
C-a, Home, a click) and pressing Tab left the caret sitting at column 0, which reads as Tab being broken. It now puts the caret at the end of the indent, the way Emacs’ own TAB does. Repeated Tab still refuses to pile on more indentation, and an over-indented line is left alone rather than “corrected”. The same applies with a language server handling the re-indent; on-type formatting is untouched, since that fires while you are typing. -
Java completion no longer leaves placeholder text in your file. A language server can send a completion as a snippet — text with placeholders — and Editora was flattening every one of them into literal characters. jdtls does this everywhere, so:
- Typing
import java.utiand acceptingjava.utilproducedimport java.util.*;with the caret past the semicolon: an on-demand import nobody asked for, and no way to carry on toArrayList. The server had actually sentjava.util.${0:*};— the*is a placeholder meant to be selected. It now is, so typingArrayListreplaces it and you getimport java.util.ArrayList;. - Accepting a method left its arguments as text to delete:
list.addinsertedlist.add(e)with a literale. Now the argument is selected and Tab steps through the rest, soaddAll(int index, Collection c)fills in like any snippet. - The same applies to every other server that sends snippet completions.
- A server that marks an item as a snippet but sends an unescaped literal
$(phpactor’s$user, shell variables) is still inserted verbatim — only a real placeholder starts a snippet. - Bundled snippets ending in
${0:something}also select that text now, instead of leaving the caret in front of it. - No more doubled semicolon. Completing an import a second time —
import java.ut|;, caret before a semicolon that is already there — producedimport java.util.*;;. The server’s replacement range covers that semicolon (which is why its insert ends with one), and Editora was replacing only up to the caret. It now honours the end of the range the server sends, so the old semicolon is replaced rather than kept. The end only ever extends past the caret and never beyond the current line: anything you typed while the popup was open is still absorbed, and a stale range can’t eat the lines below.
- Typing
-
Accepting a completion that adds an import no longer moves your cursor. Typing
List<Itemand hitting Tab insertedimport demo.Inventory.Item;correctly, but then left the cursor at the end of that new import line — so the next thing you typed went into the import statement. The cursor now stays on the identifier you were typing, following its own text down as the import is added above it. -
A long status-bar message no longer squashes the segments beside it. The echo line and the state segments share one row, and when they did not all fit every one of them was shrunk — so a wordy message reduced
LSP: jdtls,Editable, the caret position and the rest to bare ellipses. The message now truncates on its own and the segments keep their full width; the full text is still one click away in the message log. -
Debugging now runs a configuration’s before-launch step. Only the Run path did. A configuration whose before-launch was
mvn -q compiletherefore compiled when you pressed Run and silently debugged the previous class files when you pressed Debug — every breakpoint landing on a stale line number, in the one mode where that is hardest to spot. A failing step now aborts the debug launch, exactly as it aborts a run.
[0.10.0] - 2026-08-03
Added
-
Per-project settings you can commit — a project can carry
.editora/settings.tomlsaying which language server to run and whether to run it, overriding your global preferences for anyone who opens that project. The case it’s for: one repository needs a JDK 17 server and another a JDK 25 one, and you no longer have to remember to flip a global preference between them. Project: Edit Project Settings… creates the file with a commented example. Only toolchain settings can be overridden — appearance, keymap and fonts stay personal, because checking out a repository shouldn’t rearrange somebody’s editor. -
New Project From Template — scaffold a whole project and open it in its own window, rather than pointing Editora at a folder you made yourself. Pick a multi-file template, fill in its variables, choose where it goes, and the new folder is registered as a project and opened. Ships with a Python Project template (package layout, a test,
pyproject.toml, README and.gitignore); your own multi-file templates appear in the same picker. -
Long-running work now says it’s running — a find-in-files sweep over a big tree used to announce itself once and then go quiet, which looks identical to a hang. The status bar now shows what’s in progress with a spinner, and a count when several things run at once; it disappears entirely when nothing is happening.
-
Failures no longer slip past you — the status bar shows one message at a time and the next one replaces it, so an error that appeared while you were typing used to vanish without trace. Errors are now shown in red, recorded as errors in the message log, and leave a small count beside the status line that stays until you open the log. Warnings are marked too, without the count.
-
Rename now shows you what it will change — a rename that reaches beyond the current file lists every affected file first, with its change count and where it moves to, and you can untick any file before applying. A rename confined to the file you’re looking at applies straight away as before: it’s on screen and one undo away, so there’d be nothing to confirm.
-
Run configurations gain a before-launch step, and can be shared with your team — a configuration can name a command to run first (a build, a codegen step); a non-zero exit aborts the launch, so a stale binary is never run by accident. And Export Configurations to Project writes them to
.editora/run-configurations.jsoninside the project, where they can be committed; Import merges them back by name, so importing twice doesn’t duplicate and a colleague’s edit updates rather than doubles. -
A run-configuration selector in the toolbar — pick a saved configuration and hit Run or Debug, with Stop beside them; the choice is remembered across restarts. Each configuration also becomes a real command, so it appears in the command palette by name and can be given its own keyboard shortcut, the same way saved macros and external tools already work.
-
A menu bar — File / Edit / Find / View / Navigate / Code / Run / VCS / Tools / Window / Help, so the things Editora can do are browsable rather than only findable by name in the command palette. Each entry shows its current keybinding and updates when you switch keymaps; a command whose feature is switched off appears greyed rather than vanishing, so the menu stays a stable map. On macOS it sits in the system menu bar. Hide it from Settings → Interface or the palette (View: Toggle Menu Bar); it is hidden automatically in Zen, Expert and Simple modes. The palette is still the complete index — the menu is a curated subset of the ~470 commands.
-
Two files on screen at once — the editor area can now be split into independent editor groups, each with its own tabs and its own selection, so you can put a header beside its implementation or a test beside what it tests. Split Editor Group Right and Split Editor Group Down move the current file into a new group, Move File to Next Editor Group shifts it along, Focus Next Editor Group moves the keyboard between them, and Merge Editor Groups puts everything back. Closing the last file in a group collapses it, so you never end up staring at an empty pane. All five are in the command palette and bindable. Splits nest, so a side-by-side pair can hold a stacked pair and you can build an L-shaped layout — while splitting the same direction twice widens the existing row instead, giving you three columns rather than a lopsided chain. Drag a tab onto another group to move it there, or onto a group’s edge to split that group and drop the file on that side — a translucent highlight shows where it will land. Your split layout is saved with the session, so the arrangement you left comes back on the next launch (a file that has since disappeared no longer leaves a blank pane behind). This is distinct from the existing Split Editor commands, which show two views of the same file — those still work exactly as before, and the two can be combined.
-
The Git and GitHub command lines you run are now visible — the shared output console gained a Git and a GitHub tab holding a transcript of the
git/ghcommands Editora ran on your behalf: the command, its output, and its exit code and duration. Git logs the commands you asked for (commit, push, pull, checkout, stash, clone…) and deliberately not thestatus/diffreads it re-runs on every tab switch, which would bury them. Neither steals focus: the transcript is waiting when you open the window. -
Bracket-pair colorization — each
(),[]and{}is tinted by how deeply it is nested, so “how far in am I?” is readable without counting. It is a different question from the existing matching-bracket highlight, which answers “where does this one close?”, and the two combine on the same character. Brackets inside strings and comments are skipped — a stray{in a string would otherwise shift the colour of every bracket below it and read as the feature being broken. On by default, as in VS Code; Settings → Editor turns it off. -
Folding gained the four things it was missing — fold a selection into a range of your own choosing (which survives your edits and comes back on the next launch), fold all block comments, fold all
#regionmarkers (//#region,#region,#pragma region,<!-- #region -->and friends), and fold everything except the block you are in. All four are palette commands and bindable, and they merge with the folds Editora detects — or the ones your language server reports — rather than replacing them. -
Pasted Java code imports itself. Paste a snippet into a Java file and the Java language server is asked which imports it needs; they are added for you. This is the one paste behaviour nothing else in the stack can approximate, because it needs the type resolver. An answer that lost a race with your typing is dropped rather than applied to text it wasn’t computed for.
-
Smart semicolon placement for Java — typing
;part-way through an expression puts it at the end of the statement:compute(1, 2|)becomescompute(1, 2);|. The semicolon is never held back waiting for the server, so typing never stalls; it lands where you typed it and moves afterwards if the server disagrees, as a single undo step. Type straight on through and the correction is skipped rather than applied late. -
The GitHub tool window is filterable and keyboard-driven — Pull Requests, Issues and Runs share one filter box that matches on everything a row shows (number, title, author, branch, state, labels), with a leading
#optional so42and#42both find PR 42. The window opens with focus in the filter and the first row selected;C-n/C-pmove the selection without leaving the box, Down enters the list, Enter opens. The filter clears when you switch segment and survives a refresh.
Changed
-
Editora has its own look. The palette comes from the app’s own icon — a teal accent, an ink-navy ground and a periwinkle reserved for one job: the keyboard. Editora Light and Editora Dark ship as a matched pair and are the theme a fresh install starts in. Nothing changes for an existing install — your saved theme is kept, and the new pair is simply two more entries in Settings → Appearance alongside Primer, Nord, Dracula and the community themes.
-
One vocabulary for state, everywhere. Amber now means “not saved yet” in every place it can appear — the tab, the Switcher, the file tree, pickers — and it follows the theme instead of being one fixed ochre that suited a light background and little else. Red is broken, green is verified, olive and violet are git’s untracked and renamed, and periwinkle is only ever a keybinding.
-
The interface is set in Inter. Editora already bundled it for the Markdown preview and PDF export; now the whole interface uses it, on every platform, instead of whatever default font each system supplied.
-
Settings has been rebuilt. Every page reads the same way: one background, with each group of settings on a card, and every setting stated as a name, a plain-English line saying what it does, and its control on the right — instead of a stack of bare checkboxes whose labels had to carry the whole explanation. A tool’s detection result is a green or red pill on its own row, and a setting whose feature has a keybinding shows that chord beside its switch, so Settings teaches the keyboard instead of hiding it.
-
Less line, more space. Editora used to draw a hairline at nearly every seam, and in several places two at once — a panel’s frame plus the frame the tree inside it drew for itself. Those are gone; surfaces are told apart by their shade and their spacing. Menus, tab strips and toolbars are tighter, tabs are shorter, and the rounded corners are consistent from the command palette down to a spinner in Settings.
-
Menu keybindings line up. Each menu’s shortcuts share one column instead of trailing after their labels at whatever width each one happened to be, so a menu can be read down its right edge. (On macOS, where the menu bar belongs to the system, they stay beside the label — the native menu draws text only.)
-
Language, Tab Size and Line Endings open a picker, not a dialog. Clicking those status-bar segments used to open a separate window with a dropdown; they now use the same in-scene picker as everything else, which you can type into. The language list is around a hundred grammars, and it was not searchable before.
-
Projects are on by default. The feature shipped opt-in, behind a checkbox most people never found — and much of what Editora has grown since is anchored to a project: the file tree, per-project bookmarks and notes, find-in-files scope, run configurations, per-project settings. Existing installs are switched on when their settings are upgraded, including installs that had turned it off, since a saved setting cannot say which of the two it was. Settings → Workspace turns it back off.
-
The run-configuration toolbar group only appears where it applies — the selector and its Run / Debug / Stop buttons used to sit on every toolbar, so a project of Markdown notes carried a dropdown that could never fill. They now show when you are somewhere you could actually launch: with a project open, a Maven, Gradle, npm, Cargo or Go build file inside it or a makefile at its root — a sibling checkout’s build file from a directory above no longer counts. With no project open, any detected build does. Anything you have already saved keeps the group visible regardless: a run configuration only needs a
mainmethod, so a plain Java folder can hold one, and hiding the only UI that reaches it would be worse than the empty dropdown. The palette commands are unchanged and still work anywhere. -
Quick fixes now appear at the caret — the code-action list used to open as a card centred near the top of the window, the same surface used for picking a project. It now drops just below the cursor, where you are actually looking, with the server’s preferred fix preselected so Enter usually does the right thing. Arrow keys or
C-n/C-pmove, Enter applies, Escape orC-gdismisses, and the caret stays visible on the spot the fix will land. -
Run configurations are no longer Java-only, and no longer care which tab is in front — a saved configuration can now launch a Python script, a shell script or a make target as well as a Java main class, chosen with a Type field in Settings → Run Configurations. Script configurations need no project and no language server at all. Separately, running a saved configuration used to refuse unless a Java file happened to be the active tab — so saving one for your app and then opening a README broke it. Any open Java file in the project now serves, and it only complains when there genuinely isn’t one. Debugging remains Java-only, and now says so instead of reporting a confusing Java error.
-
The “Build Output” tool window is now just “Output” — it holds build tools, Git, GitHub and CI logs, so the old name undersold it. Your stripe placement and any keybinding are unaffected (only the label changed).
-
Running an incomplete run configuration takes you to it. It used to say which configuration was missing its main class (or its script) and leave you to go and find it. Pressing Run is a request to run it, so it now opens that configuration’s form with the field you need to fill in — the same thing the toolbar dropdown’s new Edit Configurations… does, from the other direction.
-
Edit a run configuration from the toolbar dropdown. The dropdown now ends with Edit Configurations…, which opens Settings on the Run Configurations page with the configuration you had selected already picked out — so changing the thing you just chose no longer means navigating there and finding it again. It is offered even with nothing saved, which is one way to create your first. Also available from the palette as Run: Edit Run Configurations….
-
Adding a run configuration starts from the file you are looking at. Settings → Run Configurations → Add used to create a wholly blank entry called “New Configuration”. It now prefills the main class from the active Java file (or the one your Gradle build declares), names the configuration after that class, and puts the cursor in the field that still needs you — the main class when there was nothing to suggest, the name otherwise. Adding twice from the same file gets you “App” and “App (2)” rather than two entries with the same name, which previously collided into a single palette command.
Performance
- Closing a large tab no longer stalls highlighting everywhere else. Files of the same language share one grammar, and only one of them can be tokenized at a time, so a background pass over a very large document held that lock for its whole duration and every other file of that language waited behind it. Two paths let such a pass run when nothing needed it any more: a pass that had already been superseded ran to completion instead of stopping, and a closed buffer could still dispatch a fresh one. Both are now refused.
Fixed
-
A run configuration with no main class now says so, instead of reporting a Java language server stack trace. Adding a configuration in Settings creates an empty one, and running it before filling in the main class sent an empty search to the language server, which answered with an internal
SearchPattern … is nullerror. It now tells you which configuration is missing its main class — the same thing the script configurations have always done about a missing script. -
A stray command-line flag no longer opens its value as a file. Any two-token option Editora doesn’t define — a JVM flag leaking into the program arguments, say — had its value treated as a path, so launching could greet you with
Failed to open: <cwd>/javafx.graphics/com.sun.glass.ui=com.editora. A token sitting directly after an unrecognized option is now only opened if it actually exists on disk;editora typo.txtstill reports the typo as before. -
Doctor no longer crushes the tool name. A row is laid out as one line, so a long resolved path shrank everything beside it and the name identifying the row was reduced to ”…” or “Docker…”. The name now keeps its width and only the paths shrink — ellipsized from the left, since the binary at the end is the informative part, with the full text on hover. The column also grows with the window and follows the text zoom.
-
The Split Vertical and Split Horizontal buttons are disabled where they would do nothing. The Welcome and Doctor pages and the image, hex, PDF and diff viewers aren’t text buffers and can’t be split, but the buttons stayed lit and silently did nothing when pressed.
[0.9.10] - 2026-07-26
Added
-
Three Java commands (palette) — Organize Imports sorts and prunes a file’s imports directly, without going through the code-action menu; Copy Fully Qualified Name puts the full name of the symbol at the caret on the clipboard; Reload Project Configuration makes the language server re-read
pom.xml/build.gradlewhen a dependency change hasn’t been picked up. -
Project-wide problems — the Problems window gained an Open files / Whole project selector, and a new Build Project command (palette) recompiles the Java project and fills it. Until now problems only ever came from files you had open, so a compile error in a file you hadn’t touched was invisible. The default is unchanged — open files only — so nothing gets noisier unless you ask for it.
-
Stack-trace frames into libraries — clicking a Java stack-trace line in the Run, Test or Build console now asks the language server to resolve it, so a frame inside a dependency or the JDK opens its source instead of reporting “not found”. Frames in your own code behave as before, and the previous filename-matching still handles anything the server can’t place (and every non-Java trace).
-
Fewer stray test gutters — the ▶ beside a JUnit class is now confirmed against the project’s real test source folders, so a class in
src/main/javathat happens to carry a@Test-shaped annotation no longer gets one. If the server can’t answer, the gutter stays as it was. -
Re-indent as you type — with a language server running, typing
;,}or Enter re-indents the current line to the server’s own convention. Indentation only: it never reformats the line under you. The local auto-indent still acts first, so nothing feels slower and the server just corrects the cases it gets wrong. Off by default — enable it in Settings → Code Completion, or via theview.toggleOnTypeFormattingpalette command. -
Java code generation — with the Java language server running, the code-action menu (
Ctrl-., or the editor right-click menu) now offers Generate toString(), Generate hashCode() and equals(), Generate Constructors and Override/Implement Methods. Each opens a checkbox list so you choose which fields or methods to include — Space toggles, Enter generates. These were previously absent entirely: the server withholds them unless the editor says it can drive the picker. -
Go to Implementation, Type Definition and Declaration — three navigation commands beside Go to Definition. Go to Implementation jumps from an interface or abstract member to the concrete overrides (a lone one jumps straight there, several fill the References tool window, and an implementation inside a dependency opens as read-only source); Go to Type Definition goes from a variable to the declaration of its type; Go to Declaration rounds out the set. All three are palette-discoverable, and the first two also appear in the editor’s right-click menu — but only when the language server actually supports them, so the menu never offers a dead entry. Works for every language whose server provides them, not just Java.
-
Language-server folding and selection — where a language server is running, code folding now comes from the server’s own understanding of the file instead of brace/indent scanning: an import block folds as one region, javadoc and block comments fold, and multi-line expressions no longer nest wrongly. Semantic expand/shrink selection uses the same source, so it respects strings and comments rather than guessing at brackets. Both fall back to the built-in behaviour whenever LSP is off, the file is remote, or the server offers neither — nothing gets less foldable than it was.
-
Deeper code folding — fold by nesting level (Fold Level 1–7, like VS Code’s
Ctrl+K Ctrl+1..7), fold or unfold the region at the caret and everything nested inside it (Fold/Unfold Recursively), and jump between folds (Go to Parent / Next / Previous Fold — the target is revealed if it’s hidden). Twelve new palette commands; bind them to keys from Settings → Keymap. -
Subword navigation — move and delete by camelCase / snake_case parts: stepping through
getUserNamelands onget→User→Name, and acronyms split correctly (HTMLParser→HTMLParser). Four palette commands (Go: Forward/Backward Subword, Edit: Delete Subword Forward/Backward); palette-discoverable everywhere, and bind them to a key from Settings → Keymap if you want VS Code’sCtrl+Alt+←/→. -
Convert indentation — two commands (palette: “Convert Indentation to Spaces” / “…to Tabs”) rewrite the whole file’s leading indentation between tabs and spaces. Only leading whitespace is touched — alignment, in-line content and string contents are left alone — and each direction reverses the other.
-
Select all occurrences — place a cursor at every occurrence of the selection (or the word under the caret) at once, turning them all into editable cursors (
Ctrl+Shift+Lin the VS Code and Sublime keymaps; palette-discoverable everywhere). From the Find bar, Alt+Enter does the same for every match of the current query and closes the bar. Matching is literal and case-sensitive. -
Copy with syntax highlighting — copying now also puts a colored HTML flavor on the clipboard, so pasting code into Slack, an email or a document keeps its highlighting instead of arriving as a grey block. On by default (Settings → Editor, and the
view.toggleCopyWithHighlightingpalette command); the colors are the light GitHub-style palette, since pasted code usually lands on a light background. A forced Copy With Syntax Highlighting command ignores both the setting and the size cap. Plain text is always on the clipboard too, so pasting into a plain-text field is unchanged. -
Go to matching bracket — jump the caret to the bracket paired with the one beside it, and press again to jump back (
Ctrl+Shift+\in the VS Code keymap, palette-discoverable everywhere as “Go: Matching Bracket”). A companion Select to Bracket command selects everything between a bracket pair, both brackets included. Matching is text-based, so a bracket inside a string or comment can pair with the wrong one — the same best-effort as the existing bracket highlight. -
Environment variables in run configurations — a saved run/debug configuration can now set environment variables (
KEY=value, quote values containing spaces). They apply to both Run and Debug, and are edited in Settings → Run Configurations. -
Gradle
applicationmain class — saving a run configuration in a Gradle project now pre-fills the main class the build declares (mainClass = '…',mainClass.set("…"), or the legacymainClassName), so the config matches whatgradle runwould launch instead of whichever file is open. -
Debug via build tool — debug a Gradle or Spring Boot app by launching it under a suspended JVM (Gradle
run/bootRun --debug-jvm, Mavenspring-boot:runwith a JDWP agent) and attaching the debugger when it’s ready. Complements Debug Main Class (which uses the language server) for projects where the build tool is the natural way to run the app. Command: Debug: Debug via Build Tool. -
Settings → Run Configurations — a master-detail editor for the saved run/debug configurations (name, kind, main class, module, program & VM arguments, working directory), so you can create, rename and edit them in place instead of only from the palette.
-
Run & debug Maven/Gradle project main classes — beyond single-file scripts, you can now run or debug a real project’s
mainclass. Run Main Class… / Debug Main Class… (command palette) pick any main class in the active file’s Maven or Gradle project (via the Java language server), run it in the Run console or debug it with breakpoints. A green ▶ also appears in the gutter next to everypublic static void main, and the editor right-click menu offers Run ’….main()’ / Debug ’….main()’. Debugging (and the fastest, project-wide Run) uses the Java language server; when it isn’t set up, plain Run falls back to the build tool — Maven resolves the classpath (mvn compile dependency:build-classpath) and runs the file’smain, and Gradle delegates to itsruntask (orbootRunfor a Spring Boot project). Save run configurations (main class + program / VM args + working directory) per project and re-run or debug them from the palette (Run Configuration… / Save Run Configuration… / Delete Run Configuration…); VM args apply to both Run and Debug. -
Right-click Select All / Copy in the Build Output and Test Runner consoles — copies the selection, or all the output when nothing is selected. Both read-only consoles share one helper.
-
Tooltips on the Test Runner’s per-row status icons — hovering a test’s pass/fail/skip/running glyph now says what it means. (The toolbar buttons already had tooltips.)
-
Doctor — external-tool health screen —
View: Doctorin the palette (also on the Welcome page) opens a full-tab report, likeflutter doctor, of every external CLI Editora’s features rely on: Git and the GitHub CLI (including its sign-in state), ripgrep, the preview/diagram tools (mmdc, maid, Graphviz, PlantUML, Typst), every enabled language server, the debug adapters (java-debug, debugpy, js-debug), the Run interpreters (JDK 25 check, python3, bash, make), the build tools, the selected AI agent CLI, elevated save, browsers, and the in-app installer’s prerequisites. Each row shows the resolved command, its version where available, and — when something’s off — a plain-language tip plus an Install… button (the in-app installer) or a Settings… link to the right page. Features that are switched off show as gray informational rows and are never probed; all probes run off the UI thread and only when the screen is opened or refreshed. -
Navigate inside library source (LSP, Java) — Go to Definition now also works from within an opened JDK/dependency source tab: put the caret on a symbol inside
String.class’s source andM-.chains onward — into another library class (opened the same way) or back into your own code. (#684) -
Call & type hierarchy (LSP) —
LSP: Call Hierarchyshows who calls the method under the caret (or what it calls — flip with the Callers/Callees toggle) in a new Hierarchy tool window, each level fetched from the language server as you expand it;LSP: Type Hierarchydoes the same for a type’s supertypes and subtypes. Enter or double-click jumps to any entry. (#682) -
Inlay hints (LSP) — the language server’s parameter-name and inferred-type hints, drawn as grey italic annotations after each line (aggregated per line). Off by default — toggle in Settings → Code Completion or with
Toggle Inlay Hintsin the palette; costs nothing while off. (#681) -
Real language-server progress — servers that report their long-running work through the standard LSP progress channel (jdtls importing/indexing a project, gopls loading packages, rust-analyzer’s first check) now drive the status-bar loading bar and show what they’re doing (title + percentage) in the echo area, instead of Editora guessing with a bare spinner that only covered startup. (#683)
-
Language servers now track external file changes — a
git checkout/branch switch, a CLI build, or an external editor touching project files is forwarded to the running language servers (workspace/didChangeWatchedFiles), so their project models no longer go quietly stale until a restart. Events come from the Project tree’s filesystem watcher and the external-change reload paths, coalesced so a branch switch lands as one batch. (#677) -
Rename symbol (LSP) —
F2(VS Code/Sublime/IntelliJ keymaps), the editor right-click menu, orLSP: Rename Symbol…in the palette renames the symbol under the caret across the whole workspace via the language server. The prompt comes pre-filled with the current name (the server validates the spot first, so you’re told up front when something can’t be renamed). Renaming a public Java class also moves its.javafile — the tab, session state, and language server all follow to the new path. Edits land as one undo step per touched file; files without an open tab open in background tabs. (#676) -
Occurrence highlighting (LSP) — rest the caret on a symbol and every occurrence in the file gets a subtle wash, with writes (assignments) shaded warmer than reads — the IntelliJ/VS Code idle affordance, powered by the language server so it’s semantic (a local
xdoesn’t light up an unrelated fieldx). Clears the moment the caret moves; zero cost while the caret is typing or LSP is off. (#675) -
Signature help (LSP) — typing
(or,in a call pops the server’s overload list at the caret: the active signature with the current parameter highlighted, an “n/m” overload counter, and the signature’s documentation. It follows your typing (the highlighted parameter advances as arguments are entered) and closes when the caret leaves the call, on Escape, or on scroll. Also on demand viaLSP: Signature Helpin the palette. (#674) -
Code actions / quick fixes (LSP) — the language server’s fixes and refactorings, at last reachable:
LSP: Code Actions(palette, the editor right-click menu, andCtrl-./Cmd-.in the VS Code, Sublime and IntelliJ keymaps) asks the server what it can do at the caret or selection — quick fixes for the diagnostics there, organize imports, generate methods, extract/inline refactorings — and shows a picker (the server’s preferred fix first). Applying works for every action shape: an inline edit, a deferred edit (resolved on demand), or a server-side command whose edits arrive viaworkspace/applyEdit— now implemented, so server-initiated edits land in the editor as normal undoable edits (one undo step per file; a multi-file fix opens untouched files in background tabs). (#670) -
Filter box in the Git Log and TODO tool windows — type to narrow the commit list (by subject, author, hash or date) or the TODO list (by keyword, tag, text or file) as you go. Both fields have the same clear (
✕) button the Project/Bookmarks filters use. -
Clear (
✕) button on the Find bar and Find in Files fields — empties a search/replace (or glob) field in one click, shown only while it has text. Matches the existing filter fields elsewhere in the app. -
n/pnavigation in the TODO window — the plain letters now move to the next/previous row just likeC-n/C-p(except while the filter field has focus, where they type normally). -
Label an existing revision in Local File History — right-click a revision and choose Set Label… to name it, so you can find it later by that name (the history filter already searches labels). Clearing the text removes the label. This is separate from Put Label, which records a new labeled snapshot.
-
Pop the AI Agent chat out into its own window — a button in the chat toolbar detaches the panel into a separate resizable window (the live conversation is kept); closing that window docks it back.
-
Abbreviations — a simple text-replacement dictionary: define short abbreviations that expand to longer text. Expand the word before the caret on demand with
C-x a e, or turn on Abbrev Mode to expand automatically as you type a space or punctuation. Define a new one withC-x a g, and manage the whole dictionary (add / edit / delete) in Settings → Editor → Abbreviations. The typed case is carried onto the expansion (btw→by the way,Btw→By the way,BTW→BY THE WAY), and a lowercase abbrev keeps any capitals inside its expansion. -
Auto-fill mode — break plain-text and Markdown lines at the fill column automatically as you type (Emacs
auto-fill-mode), the break-as-you-type companion toM-q(fill paragraph). Off by default; toggle it in Settings → Editor or with the palette. It’s prose-only, so it never wraps code, and each wrapped line keeps the paragraph’s indent (and a comment/quote marker in a comment). The fill column is the existingC-x fsetting (default 70). -
The
C-uprefix (universal) argument.C-ubefore a command gives it a numeric argument:C-u 5 C-nmoves down five lines,C-u 3 C-kkills three,C-u 40 -inserts forty dashes. A bareC-uis 4,C-u C-uis 16 (each repeat multiplies by four), andC-ufollowed by digits is that number;C-gcancels a half-typed argument. Most commands are simply run that many times;C-u C-SPCis special-cased to pop-mark (as in Emacs). Only the Emacs keymap bindsC-u, so the other keymaps are unaffected. -
Expand / shrink selection — grow the selection outward through syntactic levels (word → brackets or string → line → enclosing definition → paragraph → whole document) and shrink back through the exact same steps.
Shift+Alt+Right/Leftin the VS Code, CUA and Sublime keymaps;Ctrl+W/Ctrl+Shift+Win the IntelliJ keymap (Option+Up/Downon macOS); palette-discoverable everywhere. Bracket and string matching is text-based (it doesn’t yet consult a language server), so it’s the always-available baseline rather than fully syntax-aware. -
Occur (
M-s o) — list every line in the current buffer matching a regular expression in a keyboard picker; pick one to jump to it. The buffer-scoped counterpart to Find in Files. -
Tabify / Untabify — convert the selection (or whole buffer) between tab and space indentation using the tab width. Untabify expands tabs to spaces; tabify turns runs of spaces back into tabs where they reach a tab stop.
-
Align regexp — pad the selection’s lines with spaces so a regular expression lines up in a column (e.g. align a block of
=assignments or:key/values). It only adds spaces, so re-running it is a no-op. -
Mark ring —
C-SPCnow also records the spot on a per-buffer ring, andC-x C-SPC(pop-mark) jumps the caret back to the most recent mark, cycling through older ones — and back to where you started — on repeat. Marks follow their text as you edit, so returning to one lands on the right place even after you’ve typed above it. The ring is per buffer and lives for the session; it’s separate from the automatic jump-history (the palette’s Navigate Back / Forward), which tracks large motions on its own. -
Interactive query-replace (
M-%, andC-M-%for regexp) — the Emacs replace-with-confirmation loop: it stops on each match, highlights it, and waits for a keystroke —y/Space to replace,n/Backspace to skip,!to replace all the rest at once,.to replace this one and stop,q/Enter/Esc to quit. Regexp mode expands$1group references, honouring lookbehind/lookahead. In the Emacs keymap only,M-%now runs query-replace instead of opening the find bar’s replace mode; the find bar’s replace is still on the find bar (C-s) and in the palette. The other keymaps are unchanged. -
Narrowing (
C-x n …) — restrict the buffer to a region and work on it alone:C-x n nnarrows to the selection,C-x n dto the enclosing function,C-x n fto the foldable block around the caret, andC-x n wwidens again. This is real narrowing, not a display filter — the rest of the file is genuinely out of reach, so search, replace, macros and Select All all act on the region only. An amber Narrowed badge in the status bar shows the state and widens on click, because a narrowed buffer otherwise just looks like a truncated one. Saving always writes the whole file. Language-server support is suspended while narrowed (every position would be offset), and bookmarks, notes and breakpoints stop persisting until you widen; undo history is dropped at the narrowing boundary, though edits made while narrowed undo normally. -
Snippet regex transforms (
${1/regex/format/flags}). A tab stop can now derive one occurrence’s text from another’s — the VS CodeFormatStringin full: group references, the seven case modifiers (/upcase/downcase/capitalize/camelcase/pascalcase/snakecase/kebabcase) and the four conditional forms (${1:+if}${1:-else}${1:else}${1:?if:else}). The transform updates live as you type into the field. -
Emacs rectangle commands (
C-x r …) — operate on the columns between point and mark instead of the linear span: kill (C-x r k), copy (C-x r M-w), yank (C-x r y), delete (C-x r d), clear to spaces (C-x r c), open/shift-right (C-x r o), replace every line’s segment with a string (C-x r t) and number the lines down the left edge (C-x r N). A zero-width rectangle makesC-x r tthe prefix-a-block-of-lines idiom. Each command is one undo step, and the rectangle is remembered separately from the kill ring, as in Emacs. Columns are character columns, so a rectangle over tab-indented text does not follow what is on screen — that, and rectangle registers, are still to come. -
The Emacs kill ring. The kill commands —
C-k,M-d,M-DEL,C-M-k,C-S-DEL,M-z— used to simply delete, soC-kfollowed byC-ypasted whatever unrelated thing happened to be on the system clipboard. They now put their text on a 120-entry ring thatC-yyanks from,M-y(yank-pop) steps back through, and a new palette command Edit: Yank from Kill Ring… lets you pick from directly. Consecutive kills accumulate into one entry the way Emacs does, soC-k C-k C-kthenC-yrestores all three lines — and backward kills prepend, soM-DEL M-DELthenC-ygives the words back in reading order. Kills are still written to the system clipboard, so pasting into another application is unchanged; conversely, text you copied in another application wins over the ring, soC-ynever surprises you with a stale kill. The ring is per window and lives only for the session. -
Preserve case when replacing (the
ABtoggle in the find bar). Replacingfoowithbarnow rewritesFOOasBARandFooasBarinstead of flattening everything to lowercase, so a rename across mixed-case usages takes one pass rather than three.snake_caseandkebab-caseare cased per segment. -
Find in selection (the
Seltoggle). Opening the find bar with a multi-line selection now scopes the search and Replace All to it automatically; single-line selections keep seeding the query as before, so the two never fight over the same gesture. The scope follows its content as the buffer is edited.
Changed
- Language-server test coverage — the two classes that build every request Editora sends to a language server were reachable only through a forked subprocess, so nothing checked what actually goes on the wire. They now have in-process test seams and 121 new tests covering the wire format, the capabilities we declare, session lifetime (idle eviction, crash recovery, per-project workspaces), every request/response round-trip, the per-buffer gating, diagnostics routing, and the rename/quick-fix path that writes and moves files — including regressions for the four defects found in the recent audit. No behaviour change; this is why the next one gets caught by the suite instead of by you.
Performance
-
Incremental document sync (LSP). For servers that accept incremental changes (jdtls, pyright, tsserver, gopls, rust-analyzer — most of them), each typing pause now sends only the changed splice of the document instead of the whole text — a fraction of the transport, and the server can process the change incrementally. Computed as a shadow-vs-current diff (immune to event-ordering bugs by construction, pinned by a 2,000-step randomized convergence test), with a periodic full-text resync as a divergence safety net; full-only servers are unchanged. (#678)
-
Semantic highlighting transfers only what changed. For servers without viewport (range) requests — jdtls among them — every typing pause re-transferred the whole document’s semantic tokens. When the server supports token deltas, Editora now sends the previous result id and splices the returned changes onto its cached copy, falling back to a full request on any mismatch. Large Java files pay for their edits, not their size. (#679)
-
Cheaper diagnostics bursts. Language servers publish project-wide diagnostics in bursts (jdtls especially, on workspace open); matching each publish to its open tab re-resolved every tab’s canonical path with filesystem syscalls, on the UI thread. Successful resolutions are now cached (bounded LRU, dropped whenever the filesystem may have shifted — a rename/delete, an external change, window focus regain), so a burst costs map lookups instead of stat chains. The not-exists fallback is deliberately never cached, preserving the symlinked-path identity fix. (#680)
-
Find bar’s “Replace All” can no longer freeze the editor. A valid-but-pathological regular expression (catastrophic backtracking, e.g.
(a+)+b) ran unbounded on the UI thread during Replace All and could hang the whole editor with no way out — the incremental search was already time-budgeted, but the replace walk was not. Replace All now shares the same wall-clock budget: it abandons a runaway pattern, leaves the buffer untouched, and reports that the pattern is too slow. (#637) -
Auto Rename Tag no longer copies the whole document on every keystroke. In an HTML/XML buffer, live tag renaming built the entire file into a string per keystroke before deciding whether the edit even touched a tag name. It now runs a cheap local check on a small window around the edit first, so only an actual tag-name edit pays the full-document cost — a large HTML/XML file stays responsive while typing. (#661)
-
TODO / keyword highlighting now scans off the UI thread. The multi-pattern scan of the whole document ran on the UI thread on each typing pause; it now runs on a background thread (discarding superseded results) and applies the highlights back on the UI thread, so a large file no longer stutters while typing. (#662)
-
Abbrev expansion no longer copies the whole document on each space/punctuation keystroke. With Abbrev Mode on, expanding scanned the entire buffer for the word before the caret; it now reads only a bounded window before the caret, keeping large buffers responsive. (#663)
Fixed
-
Multi-caret movement chords — with multiple cursors, the Emacs movement chords (
C-f,C-b,C-n,C-p,C-a,C-e,M-f,M-b) now move every caret, not just the primary one — matching how the arrow keys already behaved. (Document/paragraph/sentence/page motions stay primary-only.) -
The Maven-aware
pom.xmllanguage server now actually starts — it was enabled by default, offered by the in-app installer, shown as configured in Settings and reported found by Doctor, yet everypom.xmlsilently fell back to the plain XML server. Its configured command was the one that never reached the LSP layer, so the server looked uninstalled no matter what you did. -
Inlay hints appear — they never did. The request asked for a range ending one line past the end of the file, and the language server answers that with an empty result rather than an error, so the editor could not tell it apart from “this file has no hints”. Requests are now clamped to the document. (The same off-by-one applied to semantic-token range requests.)
-
Signature help now tells the server which character triggered it — typing
(or,was reported as a plain manual invocation, and a refresh while the popup was open was not marked as a re-trigger. Servers that use either to decide what to show (and to keep the highlighted overload stable while you type arguments) now get the real picture. -
Java debugging no longer stays off when your language server already includes it — some jdtls distributions (Homebrew’s, for one) ship and load the java-debug plugin themselves. Editora only looked for its own copy of that plugin on disk, so it reported debugging as unavailable — which also pushed Run Main Class onto the slower build-tool fallback. It now recognises a language server that provides the debug commands, so Java Run/Debug work with no second copy to install.
-
Multi-module Maven Run without the language server — running a project main class via the Maven classpath fallback now resolves sibling-module dependencies in a reactor build (it runs from the reactor root with
-pl <module> -aminstead of only the module), so a submodule that depends on an uninstalled sibling runs correctly. -
Format Document no longer mangles the file. A multi-edit format was applied top-to-bottom using offsets computed against the original text, so the first edit that changed a line’s length shifted every later edit onto the wrong place — which is exactly what re-indenting does. Edits now apply bottom-to-top, as the LSP spec prescribes. The same path backs quick fixes and multi-file rename, so those were affected too. (#667)
-
Signature help works with Java. jdtls advertises the capability but ships the feature disabled; Editora now enables it (
java.signatureHelp.enabled), so typing(or,in a Java call shows the overloads. Typing(with bracket auto-close on also triggers it correctly now — the trigger check only accepted a single character, and auto-close inserts()as one edit. (#674) -
Renaming a Java class moves its file again. jdtls only emits the file-rename operation when the client declares create, rename and delete resource operations; Editora declared only rename, so the symbol was renamed but
OldName.javastayed behind. (#676) -
Go to Definition now reaches JDK and dependency classes. A Java definition inside a library — jdtls reports it under a
jdt://URI, not a file — was silently dropped, soM-.onString,List, or any dependency symbol claimed “no definition”. The class’s source is now fetched from the server (java/classFileContents) and opened in a read-only, Java-highlighted tab at the right line; a repeated jump to the same class re-selects its tab. (#665) -
A crashed language server is restarted automatically. A server that died mid-session (crash, OOM-kill, failed startup handshake) previously stayed dead until an app restart or a manual Restart Language Servers — diagnostics froze, edits stopped syncing, and nothing said why. The crash is now announced in the status bar, its stale diagnostics are cleared, and the server is restarted for the affected files — with a cap, so a server that keeps crashing isn’t re-launched forever. A server dying during startup also stops the loading bar immediately instead of spinning for a minute. (#666)
-
Format Document formats what’s on screen, not a stale copy. The formatting request now syncs the document first (like go-to-definition already did), so formatting right after typing can’t produce edits against a ~300 ms-old revision; and a stale whole-document edit whose position lies beyond the file is now skipped rather than clamped onto the last line. (#667)
-
Two windows on the same Java project no longer share one jdtls workspace. Each window’s jdtls now claims its own
-dataworkspace dir (the second claimant gets a suffixed dir), so concurrent windows can’t contend for the same Eclipse workspace lock/index. (#668) -
Language servers shut down when their last file closes. Closing the last open file of a project root now stops that root’s language server after a short idle grace (3 min), instead of keeping it — a whole jdtls JVM per browsed project — alive until the window closes. Reopening a file within the grace period keeps the server warm. (#669)
-
Snippet transforms are no longer discarded. A
${1/…/…/}occurrence was rendered as a plain mirror, so the bundled PowerShellforeach-itemexpanded toforeach (users in users)— the loop variable colliding with the collection — andsplatproduced$Get-ItemParams, which PowerShell parses as a subtraction. They now derive correctly (foreach ($usersItem in $users),$Get_ItemParams). -
A snippet’s value is no longer stolen by a leading mirror. When a plain
$1or a transform occurrence appeared before the${1:default}that gives the stop its value, the default was dropped and every occurrence rendered empty —$1 and ${1:default}expanded toand. The value-defining placeholder now wins wherever it sits in the body. -
Regex capture groups now work in the find bar’s replace.
$1was being inserted literally — searching(\w+)_(\w+)and replacing with$2-$1put the text$2-$1into the buffer. The same query in Find in Files substituted correctly, so the two halves of search disagreed and the documentation described the working half. Replacing a single match honours groups too, and a replacement naming a group the pattern doesn’t have now reports the error and leaves the buffer untouched instead of half-rewriting it. Literal (non-regex) mode still inserts$verbatim. -
Replace All no longer rewrites the whole document. It replaces only the span from the first to the last match, so the untouched remainder stays out of the undo entry and the caret is left where it was.
[0.9.9] - 2026-07-21
Added
- Workspace trust for build wrappers. A repository that ships its own
./mvnwor./gradlewgets that script run with your privileges the moment you trigger a Maven or Gradle build — so Editora now asks before the first build in a folder it hasn’t been told to trust. Trust is remembered per folder and inherited by subfolders, so a multi-module repo asks once; it’s revocable from Settings → Workspace → Trusted Folders or the palette. Declining means no build rather than a quieter one — a hostilepom.xmlorbuild.gradleexecutes code through the PATHmvn/gradletoo. Every other build tool launches your own toolchain and is never gated. - Create a pull request from the GitHub tool window — a
+button beside Refresh, opening the same form as the palette command, with the panel refreshing afterwards so the new PR appears in the list you created it from. - The Create Pull Request form gained reviewers, assignees and labels, and can push a never-pushed
branch first.
gh pr createon a branch with no upstream used to fail with an opaque error, because it could not prompt for where to push; the form now offers a pre-checked “Push branch to origin first” when that applies, and reports a push failure as a push failure. - The Git Log file list gained a context menu — Show Diff / Open File / Show File History / Copy Path (Open File is disabled for a deleted file), plus Enter to open a diff.
- Startup instrumentation (
EDITORA_PERF=1,scripts/measure-startup.sh) measuring launch through to the first frame that actually carries the file’s content. Inert by default. view.toggleStructuredPreview— the structured-data preview had a Settings checkbox but no palette command, unlike every other setting.
Changed
-
Bookmarks tool window: file-name rows are now bold, matching the file headers in Find in Files and the TODO window.
-
About dialog shows the git branch for snapshot builds — a build made from a worktree/feature branch now reads e.g.
0.9.9-SNAPSHOT (feat/foo), so a test build off a branch can be told apart from one offmaster. Release builds are unchanged. -
The HTTP Client is now the
.httpfile’s preview, not a separate tool window. Every other rich file type in Editora presents itself through the shared Editor/Split/Preview view, and.httpnow does too — running a request from the gutter opens the response beside the source, and the per-file view mode is remembered. -
Console panels scroll with your configured keybindings. Build Output, Run, External Tools and Debug marked themselves as owning their keys but had nothing to hand the chords to, so
C-n/C-p,C-v/M-vandM-</M->were silently swallowed and only the raw arrows worked. Auto-follow is also conditional now: scrolling back through a running build stays put instead of being yanked to the tail by the next output line. -
The Git Log commit list takes focus when the window opens. It tried to, but the log is fetched asynchronously, so the list was still empty;
n/palso move the selection in both lists now. -
The GitHub panel shows a spinner while a segment loads. Switching between Pull Requests / Issues / Runs left the previous segment’s rows on screen for the seconds each
ghcall takes, which read as a frozen window. A stale response from a fast double-switch is now dropped rather than repainting the wrong segment. -
Test Results tracks the running test. The tree scrolls to the newest result while a run is live, governed by a Follow toggle that auto-disables the moment you select a row. Clicking a test now shows its real output — per-test
system-out/system-errwere being dropped outright, so a passing test rendered as just “Passed” — and rows gained a context menu: Go to Test/Class, Rerun, and Debug This Test (with the test JVM suspended) when Java debugging is configured. -
Adding a bookmark moved off the gutter. A click anywhere in the gutter used to toggle one, and the breakpoint strip and Run glyph are narrow targets inside that same gutter — so a slightly-off click added a stray bookmark instead of setting a breakpoint or running the file. Use
C-c mor the editor right-click menu’s Add/Remove Bookmark, which acts on the right-clicked line. -
The command palette now grays out far more of what it can’t run. Commands whose feature is switched off were already shown grayed and inert, but only 17 features were wired up — so on a fresh install just 19 of ~550 commands were ever affected, and the feature looked broken. Two fixes:
- Missing features added to the gate: debugging, AI, the AI agent, TODO highlighting, spell check, the CSV grid, the structured-data preview, Markdown lint, and EditorConfig. Debugging in particular is off by default, so its whole command family used to look fully available on a fresh install.
- Commands are now also grayed when they have nothing to act on, not just when their feature is off:
Markdown commands outside a Markdown (or Typst) file, CSV commands outside a CSV, HTTP commands outside a
.httpfile, preview/export commands on a file with no preview, Git commands outside a repository (except Clone), editing and navigation commands with no file open, and debugger step/inspect commands with no suspended session.
The master toggle that switches a feature back on is never grayed, and commands that legitimately work with no file open (New, Open, Clone, the palette itself) stay actionable.
-
A grayed palette row now explains itself on hover, and says what to do about it — “Unavailable — this feature is turned off. Run “Toggle Debugging Support” to enable it”, or “Unavailable — the active file is not inside a Git repository”. Where Simple UI mode is what’s forcing a feature off, the tooltip points at Simple mode rather than the feature’s own toggle, which wouldn’t have helped.
Fixed
-
macOS on Apple silicon starts ~28% faster. Every tagged release from v0.9.1 through v0.9.8 shipped macOS arm64 without its ahead-of-time class cache — costing 300–480 ms of cold start — because the build’s cache-training run crashed on the CI runner’s virtual GPU once JavaFX 26 made Metal the default pipeline. Training now pins a pipeline any runner can render (the shipped app still uses Metal), and a missing cache fails loudly instead of leaving one line in a 10,000-line build log. Nothing about this was visible on real Apple silicon, which is why device testing never caught it.
-
Opening a file from the file manager is much faster and no longer jumps around. The requested file is shown first instead of after the whole previous session has restored, language servers start per tab as you visit it rather than all at once during launch, and
--no-sessionskips the saved tabs entirely (which the Linux “Editora Expert Mode” launcher now uses). Measured on an 8-file session opening one file: 5 → 1 child processes, 19 s → 8 s of CPU, 917 → 696 MB. -
A restored file no longer flashes to the top of the document. Two separate causes: applying syntax highlighting collapsed the viewport to line 1 a few frames after the file had already painted at its saved caret, and the virtual flow separately re-anchored itself to the top during a plain layout pass. Both are now held in the same frame they happen — correcting a frame later only turns the jump into a bounce.
-
A restored file no longer renders at its tail first, with the line numbers running from the end of the file back to the top.
-
A progressively-rendered preview no longer drags the editor around in Split view. Opening a typst, Mermaid or image-heavy file made the editor drift ~100 lines down and back over about two seconds, because the preview pane re-anchoring as its content grew looked identical to the user scrolling it.
-
Case-insensitive search now matches case pairs that change length — searching
STRASSEfindsStraße, andFIfindsfi. Applies to literal search in open buffers; regex search and the ripgrep-accelerated half of Find in Files keep their own engines’ simpler folding. -
No more “Already open” on every launch when a file passed on the command line is also part of the restored session — Editora was reporting a tab it had just created itself.
-
Command titles in the palette now use their family prefix consistently. 75% of titles already read
Family: Action(Edit: Cut,Git: Push), but theview.*family was a coin flip — 46 prefixed, 48 bare — so related commands scattered when scanning the list. All 48 are now prefixed, in all six languages, and a handful of minority spellings within one language were normalised too (Portuguese hadExibir:,Ver:andVista:all in use). Also prefixed the stragglers in theedit/editor/app/diagram/preview/file/install/typstfamilies, rewording where the prefix would have repeated a word (“Run File” becameFile: Run, notFile: Run File).Also brought into line:
tool.*now readsTool Window: <name>throughout, reusing each window’s already-translated name (Italian, Spanish, French and Portuguese had each been using two competing prefixes) — this also fixestool.go, whose title was the bare word “Go” and collided with the 33-commandGo:navigation family in palette search;markdown.*formatting commands are nowMarkdown: Boldrather than a bare “Bold”, with the table commands grouped under the existingTable:; andlsp.*is uniformlyLSP:. A new test (MessagesTest.aCommandFamilyIsEitherFullyPrefixedOrFullyBare) pins the convention across all six catalogs, so a family can no longer drift into a half-prefixed state. -
Fixed one command (
view.toggleSearchGitignore) that used the verb “Toggle” as a pseudo-prefix instead of its family’s, in every language, and eight non-breaking spaces before colons in the French catalog where the other 741 use a regular space.
[0.9.8] - 2026-07-20
Added
- GitHub integration, via your own
ghCLI. Editora never handles a token — it shells out to the authenticatedghyou already have, so GitHub Enterprise works with no extra setup. It stays invisible until a repo actually has something to look at:- Review a pull request in the editor. A “Files changed” tab lists every file with its status and
per-file
+/−counts; clicking one opens a read-only diff. The PR description renders as Markdown in a card above the list (long ones collapse behind a Show more toggle), andn/pstep through changes in any read-only diff. - Submit a review — approve, request changes, or comment — without leaving the editor.
- Check out a PR, create a PR, and open the current file on GitHub at the caret line.
- A PR / issue / Actions-runs tool window (
M-g p), plus a status-bar CI checks indicator for the current branch. - A failed CI run’s log opens in the Build Output console, and its stack frames are clickable — runner
paths like
/home/runner/work/repo/repo/src/…are mapped back onto your local checkout, so a red build takes you straight to the line.
- Review a pull request in the editor. A “Files changed” tab lists every file with its status and
per-file
- Test runner polish. An unfiltered
testrun now pre-seeds the whole expected test list greyed-out and flips each entry green or red as results land, instead of classes popping in already finished. JUnit test classes and methods get a run icon in the gutter (with a tooltip naming what it runs), and a test file’s right-click menu offers Run Tests for the whole class. - Debug tool window: gdb-style single-key controls (
n/s/f/…) with an active-focus highlight showing when the panel has the keyboard. - Linux
.deb: an “Editora Expert Mode” launcher. A second menu entry starts Editora with--expert --single-window, and is registered as the system-wide default handler for text and source files. Your own per-user file-handler choices still win. - Development builds now say so. Between releases the project version carries a
-SNAPSHOTsuffix, and asnapshotbadge appears in the toolbar (beside thedev modeone). The suffix also shows in--version, the About dialog, and the Welcome footer — so a build made from the current source is never mistaken for an installed release. A release build shows no badge.
Changed
- One pair of preview commands for every file type. The per-type toggles (Markdown preview, CSV grid, …)
are replaced by shared
view.togglePreviewandview.toggleSplitPreviewcommands that act on whatever the active file previews as. Each preview type still has its own Settings checkbox. - The GitHub tool window is repo-scoped rather than buffer-scoped, so it no longer disappears when you switch to the Welcome tab, and it only appears when the repo has an open PR, issue, or workflow run.
- The release flow reopens
masterat the next patch-SNAPSHOTautomatically once a release publishes, so the only manual version edit is setting the release version before tagging. Pre-releases (-rcN) don’t advance it.
Fixed
- An auto-import no longer lands on the wrong line. Accepting a completion whose text spans lines (a snippet, or an insert carrying newlines) shifted everything below the caret before the server’s extra edits were applied, so the import silently went to the wrong place. Those edits are now translated across the accept. Also fixes a same-line edit being off after a plain single-line accept. (#410)
- Launching with
--zen,--expert, or--simpleno longer flashes full chrome first. The window used to appear with the normal toolbar and tab bar and visibly strip itself once the session finished restoring — up to a second of wrong UI with a few tabs open. The mode is now applied before the window is shown. - Stepping in the debugger keeps focus in the Debug panel. Each step revealed the stopped line and yanked focus to the editor, so the next key press was lost. A fresh breakpoint hit while editing still takes you to the code. (#592)
- The line-number gutter no longer shifts text when a file reaches 10 lines. It now reserves two digits minimum, so the 9→10 transition stops nudging every line’s indentation rightward. (#590)
- Copy no longer leaves the selection highlighted. (#581)
- Test Results: running tests now brings up the Test Results window rather than Build Output; the tree updates live instead of only at the end; a stale report from a previous run is never merged; and the detail pane shows a header for every selection (and the run summary when nothing is selected) instead of going blank for a passing test.
- The GitHub panel’s PR/issue toggle now shows a clearly visible selected state.
[0.9.7] - 2026-07-18
Performance
- Opening the first file of a language no longer freezes the editor for a beat. Starting a language server
(forking the jdtls/pyright/tsserver process, scanning
PATH, writing the process ledger) ran on the UI thread. It now happens on a background thread — the editor stays responsive while the server comes up, and the loading indicator already covers the wait. (#407) - Language-server updates aren’t sent twice per keystroke. After a trigger character (e.g.
.), the editor materialized and pushed the whole document to the server both on the completion trigger and again on the debounced edit pulse — the same version twice. It now skips a resend when the document hasn’t changed since the last one. (#553) - Editing above a bookmark, note, or breakpoint no longer stutters. When an edit shifted a marker’s line
(e.g. holding Enter above a bookmark), the editor did a blocking
bookmarks.json/notes.jsonwrite plus a full tool-window tree rebuild on the UI thread — once per newline. That persist is now coalesced to a single write after editing settles; explicit actions (toggle/add/reorder) still save immediately. (#551) - The Structure outline no longer rebuilds while it’s closed. It re-split the whole document and rebuilt its tree on every edit (debounced) even when the tool window was hidden. It now defers the rebuild while closed and does it once when the window is reopened. (#549)
- The File Information panel no longer slows down typing. It re-counted words (a whole-document scan) on every caret move and re-materialized the whole document on every keystroke — even when the panel was closed. It now rides the debounced change stream, counts words only on an actual edit, reads only the character under the caret (not the whole file), and does nothing at all while its tool window is hidden. (#547)
- Zooming text is much smoother. A text zoom (
Ctrl+wheel,Ctrl+=/-/0, the status-bar−/+) reran the entire settings-apply cascade per notch — re-swapping the editor-theme stylesheet (a full-scene CSS reapply) and re-running ~20 featureapplySupport()calls (Git, LSP, previews, …), none of which change on a font zoom. Zoom now re-applies only the fonts and per-buffer view, so a Ctrl+wheel gesture no longer churns the scene stylesheet or re-detects every language server. (#545) - Moving the caret or typing in a large file is much lighter. The status bar recomputed the file size — which scans and copies the whole document — on every caret move and keystroke (and re-materialized the whole document per keystroke besides). The size now updates only when the file actually changes, debounced, so arrow-keying and typing no longer allocate the entire document on the UI thread. (#542)
Changed
- Bookmarks and Personal Notes now group by project, so they don’t appear/disappear when you switch projects. Each panel always shows the General (no-project) group and the current project, and a new “Show all projects” toggle reveals every other project’s bookmarks/notes too — grouped by project, and you can jump to any of them. File rows now use the real file-type icons and project headers are colored. (#565)
- The Git Log tool window is colored and uses real file icons. Each commit’s short hash reads in the accent color, and the changed-files list colors each row by its status (added/modified/deleted/renamed) with the actual file-type icon — matching the Commit tool window and Project tree — instead of flat text and a generic sheet icon. (#562)
- The Run, External Tools, and Debug consoles now color error output. A program’s stderr (e.g. a Python traceback) is tinted so it stands out from normal output, instead of appearing in the same plain color. (#558, #560)
- The command palette now shows commands that can’t run in the current context, grayed out, instead of hiding them. A command for a disabled feature (e.g. Git commands while Git is off) is listed with its keybinding but dimmed and non-actionable, so you can see it exists. (#532)
- The Maven / Gradle / npm / Cargo / Go task panels now open on the right by default instead of the bottom (a better fit for a vertical task list). A panel you’ve already moved keeps its side. (#531)
- The gutter fold arrow is 35% larger — easier to see and click. (#530)
Fixed
- Reloading or uninstalling plugins no longer leaks jar file handles. Each plugin’s class loader was never closed, so every Settings → Plugins reload (and every install/uninstall) left the old loader — and its open jar handles — behind; on Windows that also kept the jar locked, blocking uninstall/update while the app ran. Loaders are now closed once no window is using them (on reload, and at shutdown), while a plugin still running in another window keeps its loader until that window closes. (#442)
- Two windows connected to different SFTP servers no longer strand each other’s remote files. With more than one window open, the last one to connect took over remote-path resolution, so the first window’s remote recent files and session paths stopped resolving — and closing the second window broke them for the first even while it was still connected. Each window’s SFTP engine is now registered independently, so every open connection keeps resolving its own paths. (#436)
- Undo inside a snippet no longer half-reverts a repeated field. When a snippet field appears more than once
(e.g.
$1 = $1;), typing into it mirrored the text to every occurrence — but each mirror was a separate change, so one Ctrl-Z reverted only the mirror and left the field half-reverted (x = ;), cancelling the snippet. The field edit and its mirrors are now applied as a single change, so one Ctrl-Z reverts them together and the document stays consistent. (#415) - A keyboard config synced between macOS and Windows/Linux no longer double-binds your custom shortcuts. Custom keybinding overrides were kept in a single map, but a chord is modifier-specific (Cmd on macOS, Ctrl elsewhere) — so a shortcut rebound on a Mac and then opened on Windows stayed bound to both your new chord and the original default. Overrides are now stored per platform, so each OS uses (and only sees) its own. Existing configs migrate in place to the platform they’re opened on. (#439)
- Taking your time over an SFTP host-key fingerprint no longer times the connection out. When connecting to a new server, the trust-on-first-use dialog asks you to compare the host-key fingerprint — but the connection’s handshake timeout kept counting while the dialog was open, so a careful comparison could time out from under you. The time the dialog is on screen is now excluded from the handshake deadline; a genuinely unreachable host still fails at the normal timeout. (#486)
- The completion popup now offers snippets whose trigger isn’t a plain word. Typing
#inin C/C++ (or?xml,[PSCustomObject], …) never surfaced the matching#include/#ifndef/… snippet in the autocomplete popup — the popup only ever looked up the identifier run after the last symbol (in), which no snippet is registered under, so those had to be expanded with Tab. The popup now matches snippets on the whole token before the caret while words and language-server items keep the identifier prefix, and accepting one replaces the full token (never##include). (#446) - Local File History keeps garbage-collecting after a failed snapshot. If writing a history snapshot to disk failed, the internal completion callback was dropped, which left history GC disabled for the rest of the session (old revision blobs then accumulated on disk). Failures are now handled and logged, and GC keeps running. A few related background tasks (the log tail, the project file-watcher, a starting language server) were also hardened so an unexpected error can’t silently stop them. (#556)
- Text zoom now works inside Diff views.
Ctrl+=/-/0, the status-bar−/+, andCtrl+mouse-wheel resize the font when comparing two files or a file against Git, the same as in a normal editor. (#533) - Text zoom now scales the Welcome page too. (#540)
- The Commit panel now appears when a file changes outside Editora. If another program (a terminal
gitcommand, another editor, a build) modified files under the repo while Editora was focused, the Commit stripe, Git status, and gutter change bars didn’t update until you clicked away and back. They now refresh as soon as the project tree sees the change. (#529)
[0.9.6] - 2026-07-17
Security
-
Your AI API key is no longer sent in cleartext to a remote server. If you pointed an AI provider at a plain-
http://endpoint on another machine, Editora attached your key (anAuthorization: Bearertoken, or Anthropic’sx-api-key) with no check on the address, so the credential crossed the network unencrypted. Editora now refuses — before connecting — to attach a key to a non-loopbackhttp://host, with a clear message to use anhttpsendpoint or a loopback address. Plain-http loopback (LM Studio, Ollama, vLLM on127.0.0.1) is untouched, since that is the intended local-inference path and never leaves the machine, and a keyless local server is unaffected. (From the deferred backlog.) -
Switching AI provider no longer leaks the previous provider’s API key to the new endpoint. The API key was a single field shared by both AI providers, so configuring Anthropic and then switching to the OpenAI-compatible provider (a local model, or a hosted host like OpenRouter/Together/Groq) sent your Anthropic key — over plain http in that provider’s default configuration — to a server that had no business seeing it, unprompted on every inline-completion pause. The reverse leaked too. Keys are now stored per-provider: the Settings key field shows and edits only the currently-selected provider’s key, and a key configured for one provider is never sent to another’s endpoint. An existing key is migrated onto the provider that was active when it was saved. (From the deferred backlog.)
-
An SFTP password no longer lingers in memory for the whole session. After authenticating, Editora kept the password on the SSH session’s identity list until you disconnected — potentially hours — so a memory dump of a long-running session could recover it. It is now dropped as soon as the handshake completes, since a password is only needed to authenticate. (A password only needs one string copy at the SSH library’s boundary, which its API doesn’t let us zero; this shrinks how long that copy is reachable from “until disconnect” to “during the handshake”.)
-
Opening a terminal in an untrusted repo can no longer run its commands (Windows). “Open Terminal Here” built a
cmd.execommand line with the folder path spliced in (cmd /k cd /d <dir>), so a repository shipping a directory named with a shell metacharacter —&,|,^, all legal in Windows folder names — executed arbitrary commands the moment you opened a terminal there. The folder is now handed to the child process as its working directory, where no shell parses it, instead of being placed in any command line. (macOS and Linux were never affected — they exec the terminal launcher directly, with no shell. “Reveal in Explorer” was never affected either:explorer.exeis launched directly and runs no subcommand.) -
SFTP connections now verify the server’s host key. Editora accepted any key, so anyone positioned between you and your server — hostile Wi-Fi, a compromised router, DNS spoofing — could impersonate it silently, and with password auth Editora simply handed them the password, along with the contents of every file you opened or saved. Editora now checks the key against
~/.ssh/known_hosts(the same filesshuses, so a host you have already accepted at the terminal connects without a prompt). A host you have never connected to asks you first, showing its fingerprint to compare — and a host whose key has changed is refused outright, without offering a button that waves it through. -
The exported configuration archive is no longer readable by other users. Settings → Advanced → “Export Configuration” writes a zip of the config directory into your home folder — including
settings.toml, and so the AI provider’s API key, plus your private notes. The archive was created world-readable, which undid the lock-down of the files it copies. The session log (editora-session.log), which records your file paths and error text, was the same. Both are now owner-only, from one shared helper. (From a per-feature audit of the Appearance / Interface / Tool Windows / Advanced pages.) -
Your Anthropic API key is no longer sent to a local or third-party AI endpoint.
ANTHROPIC_API_KEYis exported on most developer machines, and Editora fell back to it for any provider — so selecting the OpenAI-compatible provider (the local LM Studio / Ollama path, which needs no key at all) attached that key as a bearer token to whatever endpoint was configured. The Settings key field was empty, so nothing on screen suggested a credential was in play, and inline completion sent it on every idle pause while typing. The environment fallback is now only used for the provider the variable belongs to. (From a per-feature audit of the AI pages.) -
Config files are no longer readable by other users on the machine. They were written with the default umask — mode 0644 in a world-traversable config directory — and
settings.tomlholds the AI provider’s API key, a billable credential, whilenotes.jsonholds the user’s private notes. Every config file is now owner-only, applied when the file is created so it is never briefly readable, and rewriting a file left world-readable by an earlier version tightens it. (From a per-feature audit of the AI pages.) -
The MCP server’s access token is no longer readable by other users on the machine. It was written with default permissions (world-readable) into a world-traversable config directory, and that token is the only thing protecting the MCP tools — which can run any editor command and read or write any file. It is now owner-only, and the token is compared in constant time. (From a per-feature audit of Typst + MCP.)
-
The HTML live-preview server no longer serves files outside the previewed file’s folder via a symbolic link. Its path-traversal guard was purely lexical, so a symlink inside the folder that pointed elsewhere (common in real project trees) could be fetched by the previewed page — an arbitrary-file read. The guard now resolves symlinks and rejects anything whose real target leaves the folder. (From a per-feature audit of the Web feature.)
-
Declining a plugin’s capability prompt now disables it. When updating an already-installed plugin, the new code is written to disk before the “here’s what it can do” prompt; declining used to leave the plugin enabled, so the rejected update ran on the next launch. Declining now disables the plugin. (From a per-feature audit of the Plugins feature.)
Fixed
-
Undoing a Format Document (or an auto-import) is now a single step. A multi-line reformat applied each of the language server’s edits separately, so it took one Ctrl-Z per edit to undo. The whole set is now one undo step. (#415)
-
Previewing a Typst document no longer churns the project tree. Each render writes and deletes a throwaway
.typfile beside your document; on some systems the project tree’s file watcher reacted to that by rebuilding itself on every render. It now ignores Editora’s own temporary render files. (#465) -
Error squiggles no longer briefly underline the wrong line while you type. After an edit, the diagnostic underlines, scrollbar ticks, and minimap marks stayed on their old line numbers until the language server re-reported (a fraction of a second later), so inserting a line above an error moved the squiggle onto unrelated text. They now clear on the edit and reappear, correctly placed, on the next update. (#417)
-
A PlantUML file with several
@startumldiagrams no longer hides the extras silently. Only the first diagram was shown, with no sign the others existed; the preview now shows the first plus a “N more diagram(s) not shown” note. (#459) -
Find References is faster on symbols with many references. Building the results list re-scanned the whole file once per reference; on a heavily-used symbol in a large open file that caused a visible stall. Each file is now scanned once. (#471)
-
Closing the window that started the MCP server no longer stops MCP for the other windows. Ownership now moves to a surviving window (keeping the same port and token, so a connected agent stays connected); the server is stopped only when the last window closes. (#463)
-
A stale MCP endpoint file left by a crash is now cleaned up at launch. If Editora exited hard while the MCP server was running,
mcp-endpoint.jsonlingered — pointing an MCP client at a dead port with a live-looking token. Editora now stamps the file with its process identity and removes a stale one on the next launch. (#464) -
Editing a Mermaid diagram no longer piles up background renders. Each pause while typing in a
.mmdfile queued another headless-Chromium render (~4 s each) even though only the latest result is shown, so a burst of edits could back up many seconds of wasted work. Superseded renders are now skipped, so only the diagram you’re actually editing is rendered. (#458) -
The Typst preview no longer retains gigabytes of page images. Its render cache was bounded by document count, but each entry is a whole multi-page document — so a 40-page document could retain ~570 MB, and editing it (each keystroke a new render) accumulated several times that. The cache is now bounded by total pages, keeping memory in check. (#461)
-
A Typst compile error now shows your file name, not an internal temp filename. Previously every typo in the Typst preview reported an error against
.editora-typst-<uuid>.typ(the throwaway file Editora renders through). The buffer’s name is now shown instead; line and column were already correct. (#462) -
A failed settings/session save is now surfaced instead of vanishing silently. If Editora couldn’t write
settings.tomlor the session state (full disk, read-only config directory, permissions), a setting change or the session on quit was lost with no indication. Editora now shows a status message on the failure. (#418) -
The JavaScript debug adapter you install through Editora is now the one that’s used. After installing vscode-js-debug via the in-app installer (or
install-js-debug.sh), Editora could silently keep using an older VS Code extension copy instead. Editora’s own installed adapter now takes precedence. (#474) -
Breakpoints in closed files are now hit. Setting a breakpoint, closing the tab, then starting a debug session left the breakpoint inert — the program ran straight past it. Editora now sends the breakpoints of files with no open tab to the debugger too (re-anchored against the file’s current text), matching VS Code and IntelliJ. (#473)
-
Accepting a completion that begins with its trigger character no longer duplicates it. For a language server that advertises a trigger character which is part of its own insert text (phpactor’s
$, shell variables), typing$and accepting$userinserted$$user. The accept now honors the server’stextEdit.rangewhen present, and otherwise consumes the overlapping trigger character, so it yields$user. Ordinary identifier completions are unaffected. (#445) -
Format Document now works for JSON, CSS, and HTML. The language servers for these files implement formatting but only advertise it when asked with a specific option, which Editora wasn’t sending — so Format Document reported “not supported” even though the server would have formatted the file. Editora now sends that option, and formatting works on
.json,.css, and.html. (From the deferred backlog.) -
A deleted project no longer reappears if a session write was in flight at exactly the wrong moment. Deleting a project deletes its session file and cancels any queued write of it — but if the background writer had already picked up that write, the cancel couldn’t stop it, and the file was re-created (and the “deleted” project came back the next time its folder was opened). The cancel now reliably suppresses an already-in-flight write. (From the deferred backlog.)
-
“Add to Dictionary” now clears the misspelled squiggle in every open window, not just the current one. The personal dictionary is shared across windows, but adding a word only refreshed the spell check in the window you added it from — other windows kept underlining the word until you touched a setting there. The change now fans out to every window immediately. (From the deferred backlog.)
-
A whole-line personal note re-attaches to the right line after an external edit. A note added to a line (rather than a selection) stored no surrounding context, so after the file changed outside the editor it latched onto whichever identical line was nearest — exactly wrong for the lines these notes usually mark (
},});, a repeatedimport). Line notes now capture the surrounding lines as context and the relocator scores partial context matches, so the note follows its real line. (From the deferred backlog.) -
A personal note whose text is extremely common no longer jumps to the wrong place after an edit. In a very large file where a note’s text occurs thousands of times, an edit could relocate the note far up-file to the wrong occurrence instead of admitting it couldn’t be placed. Such a note is now marked orphaned (which is honest and recoverable) rather than silently moved somewhere wrong. (From the deferred backlog.)
-
A personal note on a long selection now highlights the whole selection, not just the first 200 characters. Notes cap the amount of selected text they store, and the highlight was drawn over that capped text — so a note taken on a 500-character selection was highlighted over only its first 200. Notes now record the full selection length and highlight the complete span. (From the deferred backlog.)
-
CSV files using classic-Mac (CR-only) line endings are parsed into rows correctly. A file whose lines end with a lone carriage return had its records fused together (
a,b⏎c,dbecame one rowa, bc, d, with thebandcglued into a cell that exists nowhere in the file). The CSV parser now treats a lone CR — as well as LF and CRLF — as a record separator, matching RFC 4180 and other tools. (From the deferred backlog.) -
The crontab preview now shows all the requested next-run times for infrequent schedules. A leap-day job (
0 0 29 2 *) or a yearly one showed only its first upcoming run — the forward scan shared one ~4-year budget across every result, so it ran out before finding a schedule’s second occurrence. The scan budget is now per result, so all three next-run times appear (an impossible schedule like Feb 31 still stops rather than looping). (From the deferred backlog.) -
The SSH config preview now reflects overrides from earlier
Hostblocks. SSH uses the first value it finds for each option, so an earlierHost *(or matching wildcard) block can fixUser/Port/etc. before a later host-specific block does. The preview summarized each block from its own lines only, so it could state the exact opposite of whatsshwill do. For a block that names a single concrete host, the summary now resolves each value across the whole file (matchingssh -G); a wildcard block still shows its own declared values. (From the deferred backlog.) -
A folder named like a build file no longer triggers a spurious “can’t read the build file” error. The build-tool detector walked up looking for markers like
pom.xml/package.json/go.modand accepted a directory of that name as a match, then failed trying to parse it — so a project that merely had a folder calledpom.xmlsomewhere up the tree showed a build stripe and a parse error for a build file it doesn’t have. Build markers are now matched only as files. (From the deferred backlog.) -
Disabling a language server now clears its errors from the Problems window. Turning a server off in Settings (e.g. unchecking Java) stopped the server but left its already-reported errors listed forever — no server remained to retract them, and closing the tab missed them too. Disabling a server now clears its files’ diagnostics (Problems entries and the editor squiggles) as it shuts down. (From the deferred backlog.)
-
The Problems window now sorts the active file to the top even when it’s reached through a symlink. The diagnostics were stored under the path the language server reported (often a symlink path like
/tmp/…), while the active-file marker used the resolved real path (/private/tmp/…), so on macOS — and for any symlinked project directory — the two never matched and the file you were editing never floated to the top. Diagnostics are now keyed by the resolved real path, so the sort (and the tab-close clear) line up. (From the deferred backlog.) -
An HTTP request whose body is missing its blank line now warns instead of silently sending nothing. In a
.httpfile the body must be separated from the headers by a blank line; if you forgot it, a JSON body like{"a": 1}was parsed as a header (it has a colon) and the request went out with no body — a failure that looked like the server’s fault. Editora now recognizes that a line in the header block isn’t a real header and shows a clear warning atop the response (“a request body must be separated from the headers by a blank line”) rather than guessing where the body starts. (From the deferred backlog.) -
A hung AI endpoint no longer wedges every later AI request. If an endpoint returned response headers and then stopped writing (a stalled proxy, a crashed local server that keeps the socket open), the streaming read blocked forever — and because AI requests run one at a time, every subsequent commit-message, explain, or inline completion queued behind it permanently until you restarted. The stream now gives up if no data arrives for the response-timeout window and frees the worker; a healthy slow generation keeps streaming, since any activity resets the clock. (From the deferred backlog.)
-
The diff viewer no longer garbles non-UTF-8 files. When comparing a Latin-1 or UTF-16 file against its committed version (Compare with HEAD / a branch / a commit, or the Commit window’s staged/unstaged rows), the git side was force-decoded as UTF-8 — so every accented character came out as mojibake, and because the working side read correctly, the whole file showed as one giant spurious change. Both sides now decode a git blob the same way the editor reads the file (a byte-order mark wins, else the file’s
.editorconfigcharset, else UTF-8), so the text is right and only real edits show. (From the deferred backlog.) -
Rebinding a prefix chord no longer silently breaks every shortcut under it. Recording a lone
C-xin Settings → Keymaps drew no warning yet dead-endedC-x C-s(save),C-x C-f(find file),C-x g(commit),C-x C-c(quit) and the rest — the editor only checked for an exact-chord clash, andC-xalone matched nothing. The conflict check now also finds prefix collisions in both directions (a new chord that would shadow existing multi-key ones, and a shorter existing chord that would make the new one unreachable) and lists the affected shortcuts in the confirm dialog. (From the deferred backlog.) -
The HTTP client no longer silently drops a request header — a puzzling
401becomes diagnosable. A header value the JDK client rejects (a token pasted from a terminal with a trailing newline, or a value with an embedded control character) was skipped without a word, so the request went out missing that header.Authorizationwas already trimmed, but any custom auth header (X-Api-Key,X-Auth-Token) was not, so a pasted key with a stray newline dropped it and the request went out unauthenticated. Every header’s value is now trimmed of surrounding whitespace (spec-safe — servers strip it anyway), and any header still un-sendable (a restricted name, or an embedded control character — a header-injection attempt) is shown as a warning atop the response report instead of vanishing. (From the deferred backlog.) -
Git now works for a project opened through a symlink.
git rev-parse --show-toplevelreports the repo’s real path, while an open file keeps its as-opened (symlinked) path — so for a repo reached through a symlink (a macOS/tmpproject, or a symlinked work directory) the two never matched, and Compare-with-HEAD, stage, unstage, revert, and file history all reported “not in repo” or did nothing. Both paths are now symlink-resolved before comparing. (From the deferred backlog.) -
A user theme that defines its colours by reference is no longer read as light. A theme file can set up a palette and point the standard tokens at it (
-color-bg-default: -color-dark-1;) — ordinary CSS authoring. Editora only looked for a literal hex value, found none, and fell back to defaults: a dark theme got dark chrome around a glaring white editor with the light syntax palette. It now follows the reference. Relatedly, a token is no longer confused with a longer one that merely starts with it (-color-bg-default-hoverwas being read as the background). -
A stray or half-written
.cssfile is no longer offered as a theme. Anything ending in.cssin the themes folder appeared in the picker, so an empty or unfinished file became a selectable theme that stripped the app’s styling when chosen. A theme file now has to actually declare something. -
“Reset to Defaults” now actually resets your settings. It restored 23 of 181 preferences — a hand-written list of setters that stopped being updated years of features ago — so it silently left roughly 87% of your settings exactly as they were, including every AI, language-server, debugger, Mermaid, TODO, preview and build-tool option, plus Simple mode, the keymap and the UI language. It even left the AI API key in place. It is now driven off the same properties the settings file is written from, so a new setting is covered the day it’s added; text zoom and your keybindings are still deliberately preserved. (From a per-feature audit of the Appearance / Interface / Tool Windows / Advanced pages.)
-
The crontab preview described the day-of-month/day-of-week rule backwards.
0 0 13 * 5was explained as “on day 13 of the month and on Friday” — i.e. Friday the 13th. It actually runs on every Friday and every 13th, which is cron’s most famous gotcha and the reason the preview exists. The next-run times shown in the same row already said so, so the preview contradicted itself on screen. A day field covering every value made it worse:0 0 1-31 * 5claimed “on Friday” while running daily. The English is now derived from the same rule the matcher uses, and verified against croniter. (From a per-feature audit of the previews.) -
A weekend crontab was reported as a syntax error.
0 0 * * 6-7(Saturday–Sunday) and0 0 * * 5-7were rejected with “range start after end”, because 7 was folded to Sunday’s 0 before the range was read, turning 6–7 into 6–0. Real cron expands the range first and folds afterwards; Editora now does too. (From a per-feature audit of the previews.) -
Turning a preview off could strand a file in a preview it couldn’t leave — or evict one that still worked. Each preview asked “is this file my format?” rather than “does this file still have a preview?”. So switching off structured data stranded an XML file, whose tree rides that same setting, in a preview with nothing left to render and no toggle to escape with; and it also bounced a GitHub Actions workflow — which is YAML, but has its own preview — out of a view mode that is remembered per file. (From a per-feature audit of the previews.)
-
A breakpoint (or a bookmark) on one of two adjacent identical lines could vanish for good. Both are kept in a line-keyed map and are re-anchored to their saved line text when a file is opened, so when an edit made outside the editor shifted two same-text lines — two
});in a row, routine in real code — the upper one re-anchored down onto the line the lower one still matched exactly, and the second silently overwrote the first. Both lines still existed; one marker just disappeared. The self-heal then wrote the collapsed set back to disk, so it never came back — and for a bookmark, the note went with it. Each marker now resolves against the lines still free, so two never collapse onto one. (From a per-feature audit of Debugging.) -
Debugging a Java program with a quoted argument passed the wrong arguments. Program arguments are shared with Run, which passes
"hello world" secondas two arguments; the Java debug launch re-joined them into one space-separated string, so the program received three. Java only — Python and JavaScript were always correct, and the fix is what those already did. (From a per-feature audit of Debugging.) -
Toggling exception breakpoints during a debug session did nothing, while reporting that it worked. The filters were only read once, when the session started, so a later change updated a field nothing would read again: the program ran straight through an uncaught exception without suspending, even though the status bar said exception breakpoints were on. Confirmed against a real debugpy. (From a per-feature audit of Debugging.)
-
The debugger’s execution-line highlight could land on a file that has nothing to do with the stack frame. When a frame’s source isn’t on disk — a frame inside a dependency, or a path baked in by a build on another machine — opening it fails quietly, and the “you are here” line was then painted onto whatever file happened to be in front, scrolling it to an arbitrary line. (From a per-feature audit of Debugging.)
Added
-
Logpoints and disabled breakpoints can now actually be created. Both were fully built — they persisted, re-anchored, reached the adapter, and had their own gutter glyphs — but nothing in the app could set them: Edit Breakpoint only ever asked for a condition, despite its own description promising “condition, log message”. It is now a form with all three (condition, log message, enabled). A log message turns a breakpoint into a logpoint: the debugger logs it and never suspends. (From a per-feature audit of Debugging.)
-
Turning a language server off in Settings now actually stops it, and changing its command now takes effect. Neither did: the code that shuts down one server’s sessions could never match them, so a disabled server kept running, a re-pointed command kept using the old binary (while its Settings row turned green as if it had applied) and leaked the old process, and toggling Debug never reloaded the Java server with its debug plugin — the thing that makes Java debugging work at all. (From a per-feature audit of LSP.)
-
A language server that crashes no longer takes every language feature down silently. Its session stayed cached and looked healthy, so completion, hover, go-to-definition and diagnostics all quietly did nothing while the status bar still named the server, and nothing ever restarted it. A dead server is now detected and dropped, so the next file you open starts a fresh one. (From a per-feature audit of LSP.)
-
A language server that never finishes starting no longer leaks memory or spins forever. The handshake had no timeout, so the loading indicator never stopped, and every keystroke queued another full copy of the document — about a gigabyte after a few minutes of typing in a large file, against a 2 GB heap. (From a per-feature audit of LSP.)
-
Installing Typst now actually fixes the preview. A failed render was cached against the document’s text forever, so after installing the tool — which re-renders every open preview — the old “typst not found” was served straight back. Two documents with identical text in different folders also showed each other’s render, and editing a file they
#imported never refreshed the preview. (From a per-feature audit of Typst + MCP.) -
Typst works when its path contains a space —
/Users/John Smith/…was split into two arguments and every render failed, which also broke the installer for anyone whose home directory has a space. And the preview is no longer reported as ready for a command that can’t actually render. (From a per-feature audit of Typst + MCP.) -
Exporting a Typst document now reports the file it actually wrote. Typst renames
report.pngtoreport-1.png— even for a single page — so the status bar named a file that didn’t exist; an export that produced nothing also reported success. (From a per-feature audit of Typst + MCP.) -
Pressing Enter after a Typst list item numbered beyond ~19 digits no longer throws. (From a per-feature audit of Typst + MCP.)
-
A named PlantUML diagram now renders — and exports. PlantUML names its output after the diagram (
@startuml myclassdiagramwritesmyclassdiagram.png), but Editora looked for a fixed file name, so any named diagram showed a bare “render failed” in the preview — indistinguishable from a broken one — and exporting it reported “Exported to …” while writing no file at all. Naming a diagram is idiomatic PlantUML and required for multi-diagram files. (From a per-feature audit of Mermaid + Diagrams.) -
Installing Mermaid no longer leaves your diagrams showing “render failed”. A failed render was cached against the diagram’s text forever, so after installing
mmdc— which re-renders every open preview — the old error was served straight back. The diagram stayed broken until you edited it or restarted. The same applied to changing a tool’s path in Settings, and to DOT/PlantUML. (From a per-feature audit of Mermaid + Diagrams.) -
Mermaid’s linter is no longer reported as installed when it isn’t. The default maid command runs through
npx, which always launches — so on any machine with Node, maid showed as found, and every pause in typing fired a multi-secondnpxthat could never produce a diagnostic. (From a per-feature audit of Mermaid + Diagrams.) -
A diagram tool whose path contains a space now works —
C:\Program Files\Graphviz\bin\dot.exe, or anything underApplication Support, was split into two arguments and failed with “No such file or directory”, even though Settings offers a Browse… button. (From a per-feature audit of Mermaid + Diagrams.) -
Toggling a setting no longer stalls diagram linting and export for several seconds. Every settings or theme change re-probed for the tools — including the ~6.5-second
npxprobe — on the same single thread that serves live linting. Detection is now only re-run when a path actually changes, and a failed render no longer spawns the linter when it isn’t installed. Mermaid warnings also appear now, instead of being dropped. (From a per-feature audit of Mermaid + Diagrams.) -
An external tool’s output no longer lands in the wrong file. The result was applied to whatever tab was active when the tool finished, not the one it ran on — so switching tabs during a format run replaced the entire contents of an unrelated file with the formatted output of the first, and reported success. The target file is now captured when the tool starts, and its output is dropped if the file changed meanwhile. (From a per-feature audit of External Tools.)
-
A command that waits for input no longer freezes external tools for the rest of the session. A tool like
grep/jq/sortconfigured without a stdin source waited forever for input that never came, and its timeout could never fire — blocking every later tool run. Timeouts are now genuinely enforced (they were unreachable for any command that outlived them), and a runaway tool’s output can no longer exhaust memory. (From a per-feature audit of External Tools.) -
File paths and selections with spaces or apostrophes now reach the tool intact.
$FilePath$for~/My Docs/a.txtarrived as two separate arguments, and no amount of quoting fixed it — an apostrophe in the path (~/Bob's Files/) split it anyway and deleted the apostrophe. An empty$SelectedText$used to vanish entirely, shifting every argument after it. (From a per-feature audit of External Tools.) -
“Run last tool again” no longer runs a tool you deleted, disabled, or edited — it re-ran a stale copy, including the old command after you’d fixed it. (From a per-feature audit of External Tools.)
-
Editing external tools in one window no longer deletes tools added in another. (From a per-feature audit of External Tools.)
-
Two tools whose names differ only in punctuation no longer collide, silently making one unreachable and stealing its shortcut; and a tool that legitimately produces no output is no longer reported as having failed. (From a per-feature audit of External Tools.)
-
Opening a file that happens to contain the same text as another no longer steals that file’s personal notes. Notes were re-homed onto a file whose content hash matched — which is not identity: a
cp config.yaml config.backup.yaml, a duplicated LICENSE, or the boilerplateindex.tsin each package of a monorepo is enough. Merely opening the second file moved the first one’s notes onto it and deleted them from the original, permanently. A content match is now only trusted when the original file is gone from disk — i.e. an actual rename, which is the case this exists for. (From a per-feature audit of Workspace.) -
Local History no longer deletes revisions you named. “Put Label” marks a deliberate restore point, but retention treated it as cache: past the 30-day age limit, or once 50 automatic revisions had accumulated (a few hours of editing with autosave on), your labelled revision was silently dropped and its content deleted. Labels — and the copy captured just before a file is deleted — are now exempt from the age limit, the per-file cap and the project size budget. (From a per-feature audit of Workspace.)
-
Local History no longer loses a revision, or deletes the content of one it just recorded. Saving two files at once (or labelling during a slow save) could overwrite one revision with a list that never contained it, and the blob cleanup could run between a revision’s content being written and it reaching the index — deleting the content of a revision that was about to be listed, which then displayed as an empty file. (From a per-feature audit of Workspace.)
-
A note you marked resolved no longer comes back as open work. If the note’s text was temporarily absent — a branch switch, a colleague’s edit — its resolved mark was overwritten with “orphaned” and saved, and when the text returned it came back as active. (From a per-feature audit of Workspace.)
-
Release-candidate builds are now told when the final version ships.
1.0.0was considered older than1.0.0-rc1, so anyone running an RC never saw the update notice for the release it was a candidate for. (From a per-feature audit of Workspace.) -
Gradle’s “Load all tasks…” no longer misses tasks that have no description, and no longer throws the list away the next time you save. Any task registered without a description — common in real build scripts, and the norm for Gradle’s own “Other tasks” — was silently dropped from the enumeration, while the status line still reported the (short) count as a success. And because the task list was rebuilt from scratch on every save, tab switch and window focus, the ~90-second enumeration you just waited for vanished on the next Ctrl+S. (From a per-feature audit of Build Tools.)
-
Loading Gradle’s task list no longer leaks it into another project. Switching projects while the enumeration was running merged the first project’s tasks into the second one’s tree, where running one fails with “task not found”. (From a per-feature audit of Build Tools.)
-
A build wrapper that isn’t executable no longer breaks every build.
mvnw/gradleware preferred over everything, including your configured command — but a wrapper checked out without its execute bit (a Windows clone, an unzip that dropped modes) was preferred anyway, and every run died with “Permission denied” instead of falling back tomvn/gradle. On Windows the wrapper is also found now: it was being launched by bare name, which is neither on PATH nor resolved against the project directory. (From a per-feature audit of Build Tools.) -
A Bun project is no longer run with npm. Only the legacy binary lockfile (
bun.lockb) was recognized, so any project using Bun 1.2’s defaultbun.lockfell through to npm. (From a per-feature audit of Build Tools.) -
A build file that fails to parse now says so. The tasks window showed “No build tool detected” — the opposite of the truth, since the tool’s stripe is only there because the marker was found — and nothing reported the parse error. (From a per-feature audit of Build Tools.)
-
“Run custom…” no longer throws if the build file is deleted or renamed while its prompt is open. (From a per-feature audit of Build Tools.)
-
The TODO tool window’s edit actions no longer corrupt the line they rewrite. Marking a TODO done (or setting its priority, or editing its text) re-emitted the whole comment, which could damage it two ways: a block comment’s terminator was treated as part of the TODO’s text, so editing the text dropped the closing
*/and silently commented out everything below it; and a Markdown link right after the keyword was read as a[tag], so re-emitting it inserted a space and broke the link — in aTODO.md, exactly where such lines live. HTML-->comments were affected too. (From a per-feature audit of TODO highlighting.) -
Reopening a completed FIXME no longer turns it into a TODO. Marking an item done overwrites its keyword, so nothing remembers whether it started as a FIXME, HACK or XXX — Reopen simply assumed TODO and silently downgraded it. Reopen now asks which keyword to restore. (From a per-feature audit of TODO highlighting.)
-
A TODO edit on a read-only file now says why nothing happened, instead of appearing to do nothing at all. Log files and anything not writable on disk open in View mode — which is exactly the generated and vendored code a TODO scan turns up. (From a per-feature audit of TODO highlighting.)
-
Quick-adding a TODO pattern now matches case like the built-in ones do, so a new
REVIEWpattern no longer also flags the word “review” in ordinary prose. (From a per-feature audit of TODO highlighting.) -
Jumping to the next/previous TODO no longer scans a file whose highlighting is deliberately off (a large file), and a single stale mark can no longer blank every TODO highlight for a frame after a big delete. (From a per-feature audit of TODO highlighting.)
-
Keyboard macros now record Backspace, Delete and the arrow keys. They were invisible to the recorder — the editor handles them itself and no keymap binds them — so they were silently dropped: recording “type x, Backspace, type y” replayed as
xy, and the classic “go to line start, insert something, move down” macro replayed without ever moving. Home/End, Page Up/Down and the selection-extending variants (Shift-Down, Ctrl-Left) are captured too, with their modifiers. (From a per-feature audit of Macros.) -
Recording a macro no longer captures what you type into the command palette or the find bar. The capture hook saw every keystroke in the window regardless of what was focused, so recording a command via the palette also recorded the letters used to search for it — and replaying then typed them into your document. Only keys aimed at the editor are recorded now. (From a per-feature audit of Macros.)
-
A macro can invoke another saved macro again. Doing so while recording was silently dropped, because the filter meant to exclude the record/replay commands also excluded every saved macro’s own command. (From a per-feature audit of Macros.)
-
Renaming a macro no longer loses its keyboard shortcut when the new name is a close variant of the old one (
build→Build). (From a per-feature audit of Macros.) -
Two macros can no longer end up sharing one command, which made the older one unreachable from the palette and stole its shortcut. Names that differ only in punctuation (
my macro/my-macro), or that are all punctuation, are now refused with an explanation instead of silently shadowing. (From a per-feature audit of Macros.) -
“Replay N times” no longer freezes the window when given an absurd count, and a macro reached from inside another replay no longer reports success while doing nothing. (From a per-feature audit of Macros.)
-
Pressing Enter at the very start of a Markdown file that begins with a blank line no longer does nothing. The table-block finder threw an out-of-bounds exception there, and it runs on every Enter (and every Tab) in a Markdown buffer — so the keystroke was swallowed and an error went to the debug log. No table needed; any document starting with a blank line was enough. (From a per-feature audit of Markdown.)
-
A table cell containing a pipe is no longer destroyed by the next Tab. A
\|in a cell was treated as a column separator, so the cell was split in two and the table grew a phantom column — which bit hardest right after pasting CSV as a table, since that’s exactly what produces\|cells. Reflow, Tab navigation, the row/column operations and the CSV round-trip all keep escaped pipes intact now, and such a cell is padded to its real width so the columns still line up. (From a per-feature audit of Markdown.) -
Exporting Markdown to ODF (
.odt) no longer produces a file LibreOffice and Word refuse to open. A control character in the source — a form feed used as a page marker, or a stray escape/bell out of pasted terminal output — was written straight into the document’s XML, which forbids those characters outright. The export reported success and the file was unopenable. (From a per-feature audit of Markdown.) -
Making bold text italic no longer removes the bold. Selecting
**bold**and pressing italic (the format bar,markdown.italic, orC-c C-s i) matched the inner asterisk of the bold markers and stripped them, leaving*bold*instead of***bold***. (From a per-feature audit of Markdown.) -
The Markdown linter no longer flags — and its auto-fix no longer deletes — hard line breaks. Two trailing spaces are Markdown’s
<br>, allowed by markdownlint’s own defaults; “Fix all” stripped them, silently reflowing addresses, poems and signature blocks into a single paragraph. Three or more trailing spaces are still flagged. (From a per-feature audit of Markdown.) -
A
---under a list item is a horizontal rule again, not a heading. It was read as a setext heading titled with the list’s last item, which then appeared in the Structure outline and in generated tables of contents. (From a per-feature audit of Markdown.) -
Deleting a table’s last data row no longer leaves the cursor on the
---divider, where typing broke the table. (From a per-feature audit of Markdown.) -
Snippets whose prefix isn’t a plain word now expand with Tab. Expansion scanned back over letters, digits and
_only, so at#incit stopped on the#and looked upinc— which nothing is registered under. That left 42 bundled snippets unreachable from the keyboard: the c/cpp#include/#ifndef/#definefamily, the emmet!HTML skeleton,?xml, yaml---, ruby->, typst=, and PowerShell’s[PSCustomObject]. Tab now tries the whole token before falling back to the word before the caret. (From a per-feature audit of Completion.) -
Accepting a completion no longer immediately offers another one on the word you just completed. The guard against re-triggering was cleared on the next frame — ~264 ms before the trigger it was meant to suppress — so it never suppressed anything: accepting
applegrew a freshsghost, and accepting a language-server item re-opened the popup. Suppression is now tied to the accept’s own edit, so it lasts until you type something. (From a per-feature audit of Completion.) -
Inline completion no longer mangles the capitalization of what you typed. Ghost text matched case-insensitively but appended the dictionary’s own spelling to what you had typed, so an acronym at the end of a line (
APP) was offered — and on Tab accepted — asAPPle. A word is now only suggested when it would complete to a correctly-cased word. (From a per-feature audit of Completion.) -
Completion ranking no longer depends on the order snippets happen to be declared in. The comparator contradicted itself (two entries could each sort before the other), so identical candidates ranked differently depending on input order, and a snippet file with enough same-prefix entries could throw mid-keystroke. (From a per-feature audit of Completion.)
-
The completion popup no longer mis-highlights — or crashes on — a label containing a dotted capital İ. The matched characters were located in a lowercased copy of the label, and lowercasing is not always length-preserving, so the emphasis shifted onto the wrong characters and could run past the label’s end. (From a per-feature audit of Completion.)
-
A completion’s auto-import is dropped when the file changed while it was being resolved. Resolving an import is a round-trip to the language server, and its result used to be applied blind — so undoing the completion (or editing above it) meanwhile inserted the import in the wrong place, or for a symbol that was no longer there. Language-server completion results are likewise dropped now when an edit lands the caret back on the offset the request was made at. (From a per-feature audit of Completion.)
-
Your personal spell dictionary can no longer be silently lost.
dictionary.txtwas rewritten by truncating it first (a crash or full disk mid-write left it empty), and a single malformed byte anywhere in it made Editora discard every word without a word — after which removing a word rewrote the file from that empty set, making the loss permanent. It’s now written atomically and read leniently (a stray byte or a byte-order mark no longer costs you the file). (From a per-feature audit of Spell Check.) -
Adding or removing a word in Settings → Spell Check now takes effect immediately instead of only after toggling something else or restarting.
-
“Add to Dictionary” now clears the squiggle in every open tab, not just the one you right-clicked in.
-
Restored tabs get their squiggles without needing a scroll or keystroke. When several files asked for the same dictionary at once (session restore, a language switch), only the first was told when it finished loading — the rest silently showed no spell check at all.
-
Spell check no longer squiggles inside Markdown fenced code blocks. The “skip ``` code fences” rule was never actually switched on (it was decided before the file’s language was known), so opening a README put red squiggles on
sudo,cd,xzfand friends inside its code blocks. (From a per-feature audit of Spell Check.) -
Spell check no longer skips whole phrases joined by typographic punctuation. A sentence using an em dash (
results—surprising—showed), a smart apostrophe (world’s), or a non-breaking space had every word in that run silently left unchecked — and since macOS/iOS auto-substitute--→—and'→’, that’s ordinary typed prose. Those characters are now understood as part of the word, anddon’tis matched against the dictionary’sdon'trather than being reported as a misspelling. Non-breaking spaces (pervasive in pasted text, and required by French typography) now separate words properly. -
Editora no longer builds the spell-check dictionary at startup when spell check is turned off — it cost ~200 ms of CPU and ~1 MB of memory per language on every launch regardless of the setting.
-
A running plugin-defined shell command no longer delays quitting Editora — it ran on a non-daemon thread that could hold the JVM open until the command (or its 2-minute timeout) finished; it’s now a daemon thread.
-
HTML preview assets whose filename contains
+,%, or a space now load. The server decoded the URL path twice (and form-decoded+to a space), so<img src="my+file.png">404’d; it now decodes once. -
The live-preview server uses a bounded thread pool, so a flood of long-poll connections from another local process can’t grow threads without limit.
-
A
.http>>response-save operator can no longer write outside the request file’s folder (a../path is now rejected). -
macOS Option-key text input works again. Typing an accented or special character with Option (é, ç, ∞, dead keys) was being swallowed by the editor even when Option wasn’t a bound shortcut; unbound Option characters now reach the document, while bound Meta chords still don’t type a stray glyph. (From a per-feature audit of the keymap subsystem.)
-
Two default Emacs shortcuts that never worked now do. Find References (
M-?) and AceJump to line (M-g L) were written with key tokens the dispatcher can’t produce, so they silently did nothing; they’re nowM-S-/andM-g Shift-L. A test guards the whole class of unreachable binding. -
Numeric-keypad digits with a modifier now match the
M-1…M-9shortcuts instead of producing an unmatchable token. -
A snippet or template containing a plain dollar amount no longer fails silently. A body with text like
$12345678901(a$followed by a very long number) threw internally, so the snippet didn’t expand / the template didn’t create — the number is now treated as ordinary text. A body ending in a stray${is likewise handled instead of erroring. (From a per-feature audit of Snippets & Templates.) -
Multi-line snippets with a choice field now show their dropdown when expanded on an indented line. The
${1|a,b,c|}choice list was being dropped during re-indentation, so the field became a plain placeholder; the dropdown is preserved now. -
Running an External Tool on a remote (SFTP) file no longer hangs the app. The tool tried to launch a local process in the remote file’s directory, which can’t work; the “Running…” status hung forever with no output. External Tools are now disabled for remote buffers with a clear message. (From a per-feature audit of the Remote/SFTP feature.)
-
A remote file that was open when you quit no longer reopens the wrong local file on restart. The session stored the bare path (dropping the
sftp://host), so on the next launch a same-named local file could be opened in its place; remote entries are now stored as their full URI and skipped on restart until you reconnect (matching Recent Files). -
Failed SSH logins and reconnects no longer leak connections. A wrong password left the SSH session open, and reconnecting the same host stranded the previous filesystem; both are now closed.
-
Remote hosts given as bracketed IPv6 literals (
sftp://[::1]:2222/…) now parse and round-trip correctly. -
Replace All in Find-in-Files now changes exactly what the search preview showed. The results list matches line by line, but Replace All applied a regex over the whole file — so
;$previewed a hit on every line but only replaced the last one, and a cross-line pattern likefoo\nbarpreviewed nothing yet rewrote across the newline. Replace is now line-oriented like the preview (line endings preserved). (From a per-feature audit of Search.) -
Case-insensitive regex search now matches accented and other non-ASCII letters. A regex search for
café(case-insensitive) missedCAFÉ— the regex engine was folding only ASCII, unlike the plain-text search and ripgrep. All three now agree. -
Whole-word search for a term that starts or ends with punctuation now behaves like a word-boundary match. e.g.
+foonow matches insidea+foo, matching the regex/ripgrep whole-word rule; ordinary words are unaffected. -
Files with accented or non-ASCII names now work with Git. A file like
café.txtwas left with Git’s raw quoted-and-escaped name ("caf\303\251.txt") throughout the integration, so it got no color in the Project tree and staging / unstaging / discarding / diffing it failed with “pathspec did not match”. Editora now decodes Git’s quoting, so non-ASCII filenames behave like any other. (From a per-feature audit of the Git integration.) -
Diffing a renamed file from the Git Log no longer shows the whole file as newly added. For a commit that renamed a file, the “before” side was fetched under the new name (which didn’t exist yet at the parent), so the diff looked like a 100%-added file; it now uses the original name and shows the real change.
-
Pushing while on a detached HEAD no longer builds an invalid command. It used to try
push --set-upstream origin (detached), which Git rejects with a confusing refname error; it now falls back to a plainpush(Git then gives its own clear “detached HEAD” message). -
Staging/unstaging/discarding the current file works for files in subfolders on Windows. Those three actions built the path with backslashes, which Git treats as escapes; they now use forward slashes like the Project-tree actions already did.
-
Backspace on an empty auto-inserted bracket pair no longer eats the line’s indentation. With the caret between a just-typed
()/[]/{}sitting in a line’s leading whitespace, a single Backspace removed the pair and the indentation (and the newline above), jumping up to the previous line — because two Backspace handlers on the editor both ran even though the first had already handled the key (a JavaFX quirk:consume()stops the event reaching other nodes, not other handlers on the same node). Now the second handler yields, so Backspace removes only the pair. (First finding from a per-feature audit pass over the editor’s core editing mechanics.) -
Pressing Enter on a Markdown ordered-list item with an absurdly long number no longer throws. A list marker whose number overflowed a 64-bit integer (20+ digits) raised an uncaught error on Enter; it now falls back to a plain newline.
Added
-
Editora now tells you when a new version is available. On startup (at most once a day) it checks GitHub for a newer release; when one exists, a subtle “Update: X.Y.Z” indicator appears in the status bar — click it to open the release page (which dismisses that version’s notice). The About dialog shows an “Update available” link too, and Check for Updates in the command palette checks on demand. Automatic checks are on by default and can be turned off in Settings → Workspace → Updates (or View: Toggle Automatic Update Checks); the check contacts GitHub’s API over HTTPS and sends no data.
-
Copy or Cut with nothing selected now acts on the whole current line (VS Code’s
editor.emptySelectionClipboard). Put the caret anywhere on a line, press Copy and the entire line (with its newline) lands on the clipboard; press Cut and the line is removed in one undoable step. It’s on by default and can be turned off in Settings → Editor (“Copy/cut the whole line when nothing is selected”) or via the command palette (View: Toggle Copy Line When No Selection).
Changed
- The five build tools now share one tabbed “Build Output” window instead of a separate output window each. Maven, npm, Cargo, Go, and Gradle previously registered five distinct output tool windows (five stripe buttons, five rows in Settings → Tool Windows); they now stream into a single window with one tab per tool (created on first run, titled with the tool name, and auto-selected while it runs). Two concurrent builds stream into their own tabs instead of interleaving. The per-tool tasks windows are unchanged.
[0.9.5] - 2026-07-15
Fixed
-
Joining two lines no longer silently loses a breakpoint or bookmark on the lower line. Pressing Backspace at the start of a line (or Delete at the end of the line above) merges the two lines — the code survives, but a breakpoint or bookmark on the merged-up line was being dropped, so you’d set a breakpoint, tidy up the line above, and then debug without it. The marker now follows its code to the joined line.
-
French and Italian error/status messages no longer show a literal
{0}instead of the filename or error. In those languages an apostrophe (l',d',dell') sits right before the value placeholder, and an unescaped apostrophe is a MessageFormat escape that swallowed it — so “save failed: /path” came out as “save failed: {0}”, the actual path gone. 60 messages across French and Italian are fixed (save/open failures, theme changes, plugin/agent/build/export errors), and a test now guards every locale so it can’t come back.
Security
-
Previewing an untrusted Markdown file can no longer probe your internal network or leak credentials. An image reference in the source (
) was fetched with no restriction the moment the preview rendered, so a malicious document could reach cloud-metadata endpoints, internal hosts, or a Windows file share (an SMB credential leak) with no consent. The preview now refuses to fetch loopback, link-local, private, and UNC targets, re-checking each redirect hop; public images and local files still load. -
A malicious file template can no longer write outside the target folder. The single-file “New from Template” path didn’t contain its file-name pattern, so a
../…or absolute name could create a file anywhere writable. It now applies the same containment the multi-file path already used. (Templates aren’t loaded from the opened workspace, so this is defense-in-depth.) -
Format Document no longer corrupts the file if you edit while it’s working. A language server computes formatting against the file as it was when asked; if you typed during the round-trip, those edits landed at the wrong offsets, silently mangling lines. Editora now detects that the file changed and skips the stale result, telling you to run it again.
-
Semantic highlighting no longer briefly mis-colors code while you type. A slow server’s colors, computed against an older version of the file, could paint onto shifted text (a variable colored as a function, the whole overlay off by a line) until the next response caught up. Stale responses are now dropped.
-
A pathological regex in Find no longer freezes the editor. Typing a valid-but-catastrophic pattern (the classic ReDoS case) with regex search on could send the find bar backtracking for many seconds — on the UI thread, so the whole editor locked up with no way out. Regex search is now time-bounded and abandons a runaway match instead.
-
A hostile or fat-fingered
.editorconfigno longer throws when you open a file. An enormous numeric glob range like[{1..99999999999999999999}]crashed the read of any file it governed. It now degrades to matching any number. -
The systemd unit preview no longer throws on an out-of-range time value. A directive like
RestartSec=99999999999999999999(or one that overflows when converted to seconds) now shows the raw value instead of failing to render. -
Closing an image, hex, or PDF tab now actually releases it. Those tabs were only cleaned up when closed by clicking the ✕ — closing them with Ctrl-W, “Close All”/“Close Others”, or by closing the window leaked a live thread and (for a PDF) kept the file open for as long as Editora was running. Open and close a few dozen PDFs and it added up.
-
Closing a window now shuts down everything it started. The diff, external-tool and HTTP-client workers were left running, and an SFTP connection stayed open on the remote host with its SSH client pinned in memory for the rest of the session.
-
Stopping (or quitting during) a run or a build now kills the whole process tree.
npm run dev,mvnand./gradlewall launch a child process; only the wrapper was being killed, so a dev server kept holding its port and a build JVM kept running. Those processes are now tracked, so they’re also cleaned up if Editora exits unexpectedly. -
Auto-save can no longer undo a manual save. Hitting Ctrl/Cmd-S just as a queued auto-save fired let the auto-save land afterwards and put the older text back on disk — while the editor showed the file as saved. Writes to a file are now serialized, and a stale auto-save is dropped.
-
Auto-save no longer overwrites a file that changed on disk. A dirty buffer in a background tab was never checked for outside modification (that check only looks at the tab you’re on), so a timer could silently overwrite a file that a
git checkoutor a generator had just rewritten. Auto-save now skips such a file and leaves the usual “changed on disk” prompt to handle it. -
Saving a file is now crash-safe. Files were written by truncating them first, so an interruption (a crash, a full disk) could leave the document truncated or half-written. The new content is written to a temporary file and moved into place — and the file keeps its permissions (an executable script stays executable) and stays a symlink if it was one.
-
A character your file’s encoding can’t represent is no longer silently turned into
?. With an.editorconfigsettingcharset = latin1, an em dash, a curly quote,€, or an emoji was written to disk as a question mark — and the editor kept showing the real character until you reopened the file. Editora now saves as UTF-8 instead and tells you why. -
Trimming trailing whitespace no longer flips a whole file to Windows line endings. A mostly-LF file containing a single stray CRLF was rewritten entirely as CRLF the first time it was saved with
trim_trailing_whitespaceon. The file’s dominant line ending is now what’s preserved. -
Save-As now applies the destination’s
.editorconfig, instead of writing with the previous location’s charset, line endings, and trim rules (and continuing to do so on every later save). -
A crash or a full disk while saving can no longer wipe out your bookmarks, notes, breakpoints, or the project list. Those files were written by truncating them first and then streaming the new contents in, so an interruption left a half-written file — which Editora then treated as empty on the next launch and promptly overwrote. Every config store is now written to a temporary file and moved into place, so the real file is only ever replaced by a complete one. A config file that still can’t be read is kept as
<name>.corrupt.bakinstead of being silently replaced. -
Running an older Editora twice no longer destroys a newer config. A settings/session file from a newer build is moved aside so the older build can’t clobber it — but if a backup with that name already existed, the move was skipped and the newer file was overwritten anyway, leaving only a stale backup. Backups are now numbered.
-
Deleting a project no longer risks it coming back. A save queued moments earlier could re-create the deleted project’s session file, and because a project’s identity is derived from its folder, re-adding that folder later picked the old session right back up.
-
Pressing Ctrl/Cmd-S on a very large file no longer destroys it. A file of 50 MB or more is deliberately loaded only in part (the first 50 MB — or, for a log, the last 50 MB) and opened read-only. But read-only only blocks typing: Save needs neither an edit nor unsaved changes, and it wrote the buffer straight back over the file — truncating it on disk to whichever slice happened to be loaded, irreversibly. Every write path now refuses a partially-loaded file and says why.
-
Quitting no longer discards the unsaved work and the session of every window except the one you quit from. Quitting from inside the app never ran the other windows’ close handling, so their dirty buffers were dropped with no save prompt and their sessions (open tabs, carets, window bounds) reverted to whatever they were at launch. Quit now prompts and persists each window in turn, and any window’s prompt can cancel the quit. Each window’s services and subprocesses are shut down properly too.
-
--zenand--expertare now genuinely session-only, like--simple. Launching once with either flag wrote the mode into the saved session, so every later launch came up in it — with no hint as to why. The flags now apply the mode for the current run only and leave the saved session untouched (including the docked tool windows the mode closes). Turning the mode on from inside the app still sticks, as before. -
Dragging a folder together with something inside it no longer pops an error dialog. Selecting a folder and a file within it and dropping both on a target moved the folder first (contents and all), then failed trying to move the file from a path that no longer existed. Nested entries are now dropped from the move — the folder carries them along, as it always did.
-
A build-tool toggle that vanished from the build file no longer silently poisons every run. Ticking a Maven profile (or Cargo’s
--release) and then deleting it from thepom.xml— or pointing the window at a different project — left the id active with no checkbox to untick, so-P<profile>kept getting appended to every build, invisibly. Stale toggles are now dropped when the build file is re-parsed. -
The build-tool tasks tree no longer loses your selection and scroll position every time a file is saved, the tab changes, or the window regains focus (each of those re-parses the build file, which was rebuilding the whole tree from scratch).
-
The build-tool tasks tree’s Stop button no longer looks live before anything has run.
-
In the Commit tool window, a file’s status letter and its color can no longer disagree — a file staged as modified but deleted in the working tree showed “M” in the deleted (grey) color. Both now come from the same classification.
-
Closing a project window can no longer throw on the filesystem-watcher thread (a race between
dispose()and the watch loop).
Changed
- The Project tree builds a row’s right-click menu on demand rather than on every cell render, so scrolling a large tree (or any Git-status refresh) no longer allocates a ~20-node menu per visible row.
- The Project tool window now shows a single-letter Git status on each changed file (M / A / D / R / U), matching the Commit tool window. The colors it already had stay (modified = blue, added = green, deleted = gray, renamed = violet, untracked = olive), and the letter is prefixed like the Commit window’s rows. The Commit window’s file rows are now colored with the same palette too, so the two read identically (an unsaved-in-editor file still shows its amber ”•” instead of a Git letter).
Added
- The Project tool window now works like a mini file manager. Multi-select files/folders with Ctrl/Cmd- and Shift-click, then drag them onto a folder to move them (drop onto any folder or the project root; a name conflict in the target is skipped rather than overwritten, and a folder can’t be moved into its own subtree). Open editor tabs follow a moved file — or the files under a moved folder — to their new path. Delete now acts on the whole multi-file selection at once (one confirm), alongside the existing per-file Rename/Delete.
[0.9.4] - 2026-07-13
Added
-
Expert mode — a lighter focus mode than Zen that strips only the window chrome and keeps the whole editor view. A new per-window distraction-free overlay that hides the surrounding chrome like Zen — toolbar, tab bar, breadcrumb, tool stripes, and the whitespace guides — but keeps the full editor view: line numbers, the status bar, the minimap, the column ruler, and the current-line highlight. So you get a focused coding surface that still shows where you are. Toggle it from the palette (View: Toggle Expert Mode),
C-c C-e, Settings → Interface → Modes, the--expertlaunch flag, or the floating “E” button (top-right) to exit — mirroring Zen’s “Z”. Expert and Zen are mutually exclusive; like Zen it’s per-window state and never mutates your saved preferences, so leaving it restores them exactly. -
File Templates can write into a chosen folder. The template creation wizard gained an optional Folder field (with a Browse button): leave it blank to create an unsaved buffer (as before), or pick a folder to write the file(s) into. Invoking “New From Template…” from a project folder’s right-click menu pre-fills that folder.
-
Project tool window: create folders and act on the root. Folder right-click menus gained a New Folder… item, and the project root now has its own right-click menu (New Folder / New From Template / Reveal / Open Terminal / Local History / Git stage·revert) — with Rename omitted on the root so it can’t move the whole project folder.
Changed
-
Build tools (Maven / npm / Cargo / Go / Gradle) now have IntelliJ-style tasks tool windows instead of main-toolbar icons. Each detected build tool gets its own tool-window stripe (shown when the tool’s marker file is found) whose panel is a browsable tree of the tool’s goals / scripts / targets with a mini toolbar (Run / Reload / Stop / Run custom…); double-click or Enter runs a task, and the streaming output goes to a separate per-tool console window (auto-opens on a run). The per-tool icons were removed from the main toolbar; the searchable actions popup is still available from the command palette (
<tool>.showActions/ Maven: Show Actions, …). Everything else — marker detection, wrapper/executable selection, profiles and--release-style toggles, Gradle’s on-demand “Load all tasks…” — is unchanged.tool.<tool>now opens the tasks window; the output console istool.<tool>Output. -
The editor install banner for a missing language server now says “X language server isn’t installed” instead of “X language support isn’t installed” — clearer that it offers the optional LSP (code intelligence), not the language’s render/run tool, which may already work (e.g. the Typst preview renders via the typst CLI even when the tinymist language server isn’t installed).
Fixed
-
The Cargo, Go, and Gradle build-tool tool-window icons showed blank. Their SVG logos (vendored from Simple Icons) used SVGO’s compact packed elliptical-arc flags (e.g.
a1 1 0 000-.5), which JavaFX’sSVGPathparser can’t read — it fails with “invalid boolean flag” and renders an empty shape. Replaced the three with clean Material Design glyphs (a box for Cargo, a running figure for Go, stacked layers for Gradle) that parse and render correctly. A newIconsFxTestguards that every build-tool glyph produces a non-empty shape, so a future un-parseable path is caught. (Maven and npm were unaffected — their paths have no packed arc flags.) -
Installing the Typst language server (tinymist) from the banner/Settings now actually activates it. The extracted
tinymistbinary’s path was never written toSettings.typstLspCommand, so detection kept failing on the PATH default and the install banner reappeared right after the install “succeeded”. Added the missing persist case (InstallCoordinator.applyServerCommand) + a regression test covering every binary-archive server (clangd/kotlin/lua/xml/terraform/typst).
[0.9.3] - 2026-07-10
Added
-
GitHub Actions workflow preview. A workflow YAML (detected by content — a top-level
on:+jobs:— so it works regardless of path and keeps YAML highlighting) gets the same 3-mode view, rendering a plain-English digest instead of the generic YAML tree: the triggers (“push to main, develop”, “pull request to main”,workflow_dispatch→ “manual dispatch”, and aschedule:cron decoded via the cron engine → “on a schedule (…Monday through Friday)”), then each job with its runner,needs/if, and the ordered step list (anactions/checkout@v4step shown as “checkout”, arun:as its command). Pure, unit-testedghactions/core (GithubActionssniff +Workflowparser, reusing the existing YAML + cron support). On by default (Settings.githubActionsPreview); Settings → Editor → GitHub Actions, paletteview.toggleGithubActionsPreview(turning it off falls back to the YAML tree). No new dependency. -
In-app typst-CLI installer. Settings → Languages & Tools → Typst gains an Install… button (and the
install.typstClicommand) that downloads thetypstrenderer binary for your OS/arch from typst’s GitHub releases, extracts it (adds.tar.xzsupport — typst ships xz tarballs), and pointsSettings.typstPathat it, so the document preview lights up without a restart. Distinct from the tinymist LSP-server installer. The button shows Installed whentypstis already detected. -
Typst editing conveniences. For
.typfiles: snippets (a bundledtypst.json— figure, table, grid, equation, import, outline, bibliography, …); Insert Table (a grid size-picker that drops a#table(…)skeleton with the caret in the first cell — on the format bar, right-click menu, andtypst.insertTable); Insert Table of Contents (#outline(),typst.outline); Insert Image (typst.insertImage/ right-click — pick or paste/drop/drag an image, copied into the doc’sassets/and inserted as#image("assets/…")— the Markdown image-paste paths now handle Typst too); and Export the preview to PNG / SVG from the preview right-click menu (nativetypst -f png/-f svg, alongside the existing PDF), plustypst.exportPng/typst.exportSvgcommands. Typst-specific bits live in the pure, unit-testedcom.editora.typst.TypstMarkup. i18n ×6. -
Typst language server (tinymist).
.typfiles now get full LSP code intelligence via tinymist — completion, hover docs, go-to-definition, find-references, a precise document-symbol outline, diagnostics (squiggles + the Problems window), and Format Document — the 22nd server in Editora’s LSP layer (oneServerDef, root markerstypst.toml). It complements the existing Typst rendered preview. Off until the global LSP feature is on; auto-detected onPATH(tinymist lsp), with a per-server enable + command field and a one-click Install… button (downloads the tinymist binary from GitHub releases) under Settings → LSP.Settings.typstLspEnabled/typstLspCommand(schema 72→73). -
Three more config-file previews — systemd units, SSH config, and Dockerfiles — each gets the same 3-mode Editor/Split/Preview view that decodes the file into plain English:
- systemd (
.service/.timer/.socket/…): each directive glossed (“ExecStart→ runs: …”, “Restart=on-failure”, “WantedBy=multi-user.target→ enabled for: …”), and a.timer’sOnCalendar=is decoded into English + the next trigger times (e.g.Mon..Fri *-*-* 02:30:00→ “At 02:30, Monday through Friday” + upcoming runs), including thedaily/weekly/hourly/… shorthands andOn*Sec=monotonic timers via time-span decoding (“15min” → “15 minutes after boot”). - SSH config (
~/.ssh/config,ssh_config): one connection summary perHostblock — “Connects to example.com on port 2222 as deploy, key ~/.ssh/id_ed25519, via jump host bastion” — plus per-option glosses (agent forwarding, keepalive, proxy jump, …). - Dockerfile: a per-build-stage digest — base image, exposed ports, workdir, user, env count, entrypoint/command, health check, and a build-step count — so you can see what an image does at a glance.
All three are pure, unit-tested (
systemd/sshconfig/dockerfilepackages); on by default (Settings.systemdPreview/sshConfigPreview/dockerfilePreview); Settings → Editor + paletteview.toggleSystemdPreview/toggleSshConfigPreview/toggleDockerfilePreview. No new dependency. - systemd (
-
fstab mount preview. An
/etc/fstabfile (already syntax-highlighted) gains the same 3-mode Editor/Split/Preview view. The preview decodes each mount line into plain English — the device spec (UUID=…→ “the filesystem with UUID …”,LABEL=,//nas/share→ SMB/CIFS,host:/export→ NFS), the mount point + filesystem, the comma-separated options (noatime→ “access times not updated”,nofail→ “boot continues even if the device is missing”,uid=1000, …), and the fsck/dump columns (“fsck-checked first (root) · not backed up”). A malformed line (too few columns, non-numeric dump/pass) turns red. Pure, unit-testedfstab/core (Fstabparser +FstabDescribe); no new dependency. On by default (Settings.fstabPreview); Settings → Editor → fstab, paletteview.toggleFstabPreview. -
Typst document preview. Standalone
.typfiles get the same 3-mode preview (Editor / Split / Preview) as Markdown, rendered off-thread via the externaltypstCLI as a multi-page stack — one image per page. Editing updates in place with no flicker (the last good render stays visible while the new one runs; a compile error keeps it visible under a small banner). The document is compiled with--rootset to the file’s folder, so relative#image/#importreferences resolve. Export to PDF (native single file) / PNG / SVG (typst.export), and print paginates the pages. Syntax highlighting via a bundledsource.typstgrammar;//+/* */comments and brace folding. On by default — self-gating on detection, inert untiltypstis found (install via your package manager, e.g.brew install typstorcargo install typst-cli). Toggle (view.toggleTypstSupport) + command path under Settings → Languages & Tools → Typst.- Editing ergonomics on par with Markdown: pressing Enter in a Typst list continues the marker
(
-/+/N.) on the next line and ends the list on an empty item (Backspace clears an empty marker); selecting text pops the same floating format bar (Bold*, Emphasis_, Raw`, Link, Bullet- a heading dropdown), with matching right-click Format items and palette commands (
typst.bold,typst.emph,typst.raw,typst.link,typst.bulletList,typst.headingPromote/headingDemote). Headings use=/==, links insert#link("url")[…]— the purecom.editora.typst.TypstMarkupcore (unit-tested) supplies the Typst-specific pieces; the generic inline-wrap and bullet toggle are shared with Markdown.
- a heading dropdown), with matching right-click Format items and palette commands (
- Multi-file projects resolve. The preview compiles with
--rootset to the nearesttypst.tomlancestor (or the active project root when the file is inside it), so a document deep in a project can#import/#imagefiles above its own folder; relative refs still resolve because the throwaway input is written in the file’s own directory. The preview also caps at 40 stacked pages (a ”… more pages” note; export/print use every page), and remote/SFTP.typfiles fall back to an isolated root so a self-contained one still renders.
- Editing ergonomics on par with Markdown: pressing Enter in a Typst list continues the marker
(
-
Crontab schedule preview. A
crontab/*.cron/cron.d/*file (already syntax-highlighted) gains the same 3-mode Editor/Split/Preview view as Markdown. The preview decodes each job’s terse* * * * *schedule into plain English (30 2 * * 1-5→ “At 02:30, Monday through Friday”), lists the next few fire times computed from now, handles the@reboot/@daily/… macros, and red-flags a malformed line with the exact field error (e.g. minute 99 out of range). Because every field is parsed to describe it, the whole file’s validity is visible at a glance. Pure, unit-testedcron/core (CronExpressionwith the Vixie day-of-month/day-of-week OR-rule +Crontabfile parser); no new dependency. On by default (Settings.crontabPreview); Settings → Editor → Crontab, paletteview.toggleCrontabPreview. -
Cargo, Go, and Gradle build-tool support — completing the build-tool set (Maven + npm shipped earlier). Each detected tool gets its own toolbar icon, actions popup, and streaming console:
- Cargo (
Cargo.toml) — the standard subcommands plus[[bin]]/[[example]]targets and a--releasetoggle. - Go (
go.mod/go.work) — the standardgosubcommands over the whole module. - Gradle (
build.gradle[.kts]) — the common tasks plus an on-demand Load all tasks… that enumeratesgradle tasks; prefers the project’s./gradlewwrapper. Each parses its marker file with existing dependencies (no new libraries); toggle under Settings → Languages & Tools → Build Tools.
- Cargo (
-
npm build-tool support, plus a generalized build-tool framework. The Maven integration is now one tool-agnostic
com.editora.buildframework, so every build tool gets the same first-class treatment: its own toolbar icon (shown only when its marker file is detected), an IntelliJ-style actions popup, and a streaming per-tool console. npm is the first new tool — apackage.jsontoolbar icon whose popup lists everyscriptsentry (run portably as<pm> run <name>) plus common tasks (install, andcifor npm), using the package manager detected from thepackageManagerfield or the lockfile (npm/yarn/pnpm/bun). Discovery parsespackage.jsondirectly (no new dependency). Maven is unchanged in behavior. Toggle each tool under Settings → Languages & Tools → Build Tools (both on by default, inert until their marker file is found). Cargo, Go, and Gradle will follow. -
Every preview can now export to PDF from its right-click menu. Previously only Markdown, CSV, and the Mermaid diagram had a working preview → PDF; the newer previews either had no context menu or silently exported garbage (their content was run through the Markdown renderer). Now:
- SVG rasterizes to a PDF page; DOT/PlantUML render via their CLI to PDF (both were misrouted to Markdown before);
- JSON/YAML/TOML and XML tree previews gained a right-click Export to PDF that snapshots the whole tree (rendered in bounded row-chunks so a large document can’t build a giant image) and lays it across pages;
- Markwhen timelines gained Export to PDF (a snapshot of the timeline/calendar), no longer disabled.
Every preview also gained a matching Print (right-click) — the snapshot previews (SVG, Markwhen, the trees) and DOT/PlantUML paginate their rendered image across pages, mirroring the PDF path (previously only Markdown/CSV/Mermaid could print; the rest silently printed the source through the Markdown renderer). And a snapshot export/print is now forced to a light theme (Primer Light) regardless of the app theme, so a dark-theme user still gets an ink-friendly page. New
pdf/ImagePdfWriter(scale-to-width + slice-tall-across-pages, shared withprint/PrintService.prepareImages) +PdfExportService.exportImages, withEditorBuffer.snapshotPreviewChunks(lightUa)doing the off-screen render/snapshot. No new dependency (PDFBox was already bundled). Note: a tree/timeline/SVG/diagram PDF or print is a light raster snapshot; Markdown/CSV stay native-vector. -
Portable Linux install tarball (
Editora-<version>-linux-<arch>.tar.gz, x64 + arm64). A new release artifact for systems without.deb/.rpm: it bundles the same self-contained, AOT-trained app image the installers are built from (a jlink’d Java runtime + the nativebin/Editoralauncher — no system Java needed) plus a POSIXinstall.shthat installs to/opt/editorawhen run as root or~/.local/editoraas a regular user, wiring up aneditoracommand onPATHand an application-menu entry (StartupWMClass=com.editora.Appso the running window inherits the icon). Supports--system/--user/--prefix DIR/--uninstall; the image also runs in place (./Editora/bin/Editora) without installing. Built by the newscripts/build-tarball.shfrom the existingtarget/aot-image/Editora— the extract-and-install counterpart to the.AppImage, reusing the jpackageAPP_IMAGEoutput (jlink under the hood), so nopom.xmlchange and no second build. -
XML tree preview.
.xmlfiles (xsd/xsl/fxml/pom/wsdl/rss/…) join the structured-data preview with a collapsible DOM tree in the 3-mode view — each element shows its tag + inline attributes, children in document order, and text-only elements inlined astag = "value". A faithful DOM model (elements/attributes/text/comments), not a lossy XML→JSON shoehorn. Parsed off-thread with the JDK’s own DOM parser (no new dependency), hardened against XXE exactly like the Maven pom parser (DOCTYPE disallowed, external entities/DTD off). New purestructured/XmlNode+structured/XmlParser(unit-tested, incl. an XXE-blocked case) +editor/XmlTree; reuses the existing structured-preview hosting and the sameSettings.structuredPreviewtoggle (relabelled JSON/YAML/TOML → JSON/YAML/TOML/ XML)..svgstill renders as an image (excluded from the tree);.xhtmlstill uses the browser preview. Deferred: specialized dialect renderers (RSS/Atom feed view, like OpenAPI for JSON), attribute/text search. -
PDF viewer.
.pdffiles now open in a read-only page viewer (rasterized via Apache PDFBox — already a dependency, for PDF export) instead of the hex viewer. A top bar navigates pages (◀ Page N of M ▶) and zooms (out / in / fit-to-window / actual-size;Ctrl+wheel too). Pages are rasterized one at a time, off the FX thread on a single daemon thread (PDFBox’sPDDocumentisn’t thread-safe, so all document access is serialized there), so only the current page’s image is held — bounding the Prism texture pool. Newui/PdfViewerPane implements TabContent(mirrorsImageViewerPane); routed inopenPathbefore the hex fallback and round-tripped through session restore +tabPathlike the image/hex viewers. Works for local and remote (SFTP) PDFs; refused above 128 MB. No new dependency. Deferred: continuous page scroll, a page-jump field, text selection/search, password- protected PDFs. -
SVG image preview.
.svgfiles stay editable XML (with XML highlighting + LSP) but now gain a rendered-image preview in the same 3-mode view (Editor / Editor + Preview / Preview) as Markdown — edit the source, see the image re-render live. Rasterized off-thread via JSVG (already bundled for Markdown badge images, so no new dependency) and cached by source hash; the preview zoom resizes the image. Neweditor/SvgImages(async render cache, mirroringMermaidImages/DiagramImagesbut in-process — no external tool);EditorBuffer.isSvg()/hasSvgPreview()+ ascheduleRenderPreview()branch. On by default,Settings.svgPreview(schema 64→65) — Settings → Editor → SVG + paletteview.toggleSvgPreview. Deferred: export-as-PNG from the preview, a checkerboard backdrop for transparent SVGs. -
Structured-data preview for JSON / YAML / TOML, with OpenAPI/Swagger docs.
.json,.yaml/.yml, and.tomlfiles now get the same 3-mode preview (Editor / Editor + Preview / Preview) as Markdown: a collapsible, type-colored data tree rendered off-thread. A JSON/YAML document detected as an OpenAPI 3 / Swagger 2 spec instead renders as browsable API docs (title, servers, endpoints with colored method badges + params + responses, schemas), with a tree ⇄ API-docs toggle (structured.toggleView). New pure packagecom.editora.structured(StructuredNode/StructuredParser/OpenApiModel/OpenApiParser— all Jackson stays here, unit-tested)editor/StructuredTree+editor/OpenApiDoc(self-scrolling nodes hosted the CSV-grid way, so theTreeViewkeeps virtualization). Render-branch model (no coordinator, like Markwhen); a 50k-node cap guards pathological files. On by default,Settings.structuredPreview(schema 63→64) — Settings → Editor → Structured data + paletteview.toggleStructuredPreview. Adds a dependency: jackson-dataformat-yaml (+ snakeyaml 2.4; both real JPMS modules, no moditect). Deferred: GeoJSON map, jump-from-tree-to-source-line, per-file view persistence, expand/collapse-all, editing.
-
Diagram-as-code preview for Graphviz DOT and PlantUML. Standalone
.dot/.gvand.puml/.plantuml/.pu/.iumlfiles now get the same IntelliJ-style 3-mode preview (Editor / Editor + Preview / Preview) as Markdown and Mermaid: the whole buffer renders to an image via an external CLI (dot/plantuml), off the FX thread, cached by a hash of the source so an unchanged re-render is a cheap re-fit; the preview zoom resizes the image and a diagram exports to SVG / PNG / PDF (diagram.export). Both tools rasterize to PNG natively (no headless browser, unlike Mermaid’s mmdc). New generic, Mermaid-independent seam: pure packagecom.editora.diagram(DiagramKind/DiagramRenderer/DiagramService, the CLI facade + per-kind argv builders, unit-tested)editor/DiagramImages(the async render cache, mirroringMermaidImages) +ui/DiagramCoordinator(theCoordinatorHostfeature-coordinator pattern). Authored compactgrammars/dot.tmLanguage.json+grammars/plantuml.tmLanguage.jsonfor highlighting, plusLanguageRegistry/GrammarRegistry/Commenter/FoldRegionsentries. On by default — self-gating on detection, so it stays inert untildot/plantumlare found (install via your package manager, e.g.brew install graphviz plantuml); toggle + tool paths under Settings → Languages & Tools → Diagrams.Settings.diagramSupport/dotPath/plantumlPath(schema 62→63, additive). Commandsdiagram.export,view.toggleDiagramSupport,diagram.setDotCommand,diagram.setPlantumlCommand. Mermaid’s shipped path is untouched (the two share only theeditor/DiagramImagesrender-cache convention). Deferred: PlantUML@start/@endblock folding, a themed (dark) render, live linting, and the browser-raster diagram tools (D2, WaveDrom).
Changed
- CSV/TSV grid preview is now an in-editor Editor/Split/Preview view, mirroring the Markdown preview.
The spreadsheet grid for
.csv/.tsvfiles moved out of the bottom tool window into the same IntelliJ-style floating Editor / Split / Preview toggle that Markdown uses (top-right of the editor): Split shows the source and the live grid side by side, Preview shows the grid alone. The view mode is remembered per file. The grid keeps everything it had — the spreadsheet rendering, the “First row is a header” toggle, the row/column type profiler, sort/filter, ragged-row diagnostics, in-cell editing (writes back to the source line), click-a-cell → jump to the field, and the PDF / Excel (.xlsx) / ODF (.ods) / print exports. The oldcsvGridbottom tool-window stripe and itstool.csvGridcommand were retired; the Settings → Editor → CSV toggle and theview.toggleCsvGridpalette command now gate the in-editor preview.
Fixed
- Structured / XML tree preview no longer recolors its text when you select a row. The JSON/YAML/TOML
and XML preview rows are
TextFlows of coloredTextruns, whose fill AtlantaFX flips to the selection foreground on a selected cell — so clicking a line made the syntax-colored tokens visibly flash to the selection color and back (a “redraw”). Added selected-state CSS rules pinning each token’s-fx-fillto its normal color (the same fix.completion-listalready uses for itsTextFlowlabels), so selection only changes the row background now. CSS-only. - Spell-check and Personal-Notes overlays no longer pin an idle full-viewport GPU texture. Both
SpellCheckOverlayandNoteHighlightOverlaykept theirCanvassized to the whole viewport whenever the feature was on (the default), even on a buffer with zero visible misspellings/notes — retaining a viewport-sized RTTexture that other overlays (Todo/Search/LSP/…) already release. They now shrink the canvas to 1×1 when there’s nothing visible to paint and grow it back on demand (with no per-scroll size flapping), matching the established overlay convention — trimming idle Prism-texture footprint on every spell-checked/annotated buffer. Purely a resource fix; no visible behavior change. - Structured / XML tree preview no longer blanks silently on a render error. The off-thread
PREVIEW_POOL.submit(...)tasks that parse a JSON/YAML/TOML/XML buffer now catchThrowable(not just the parser’sException), so anErrorsuch as aNoClassDefFoundError/LinkageErrorwhile loading a Jackson format factory on the pool thread is surfaced as a visible error label in the preview + aWARNINGin the Debug Log, instead of being swallowed into the task’sFutureand leaving a blank pane. Aligns these two branches with the project’s “catchThrowableinexec.submittasks” convention.
[0.9.2] - 2026-07-08
Added
- Maven support — a toolbar icon + actions popup that reads the active project’s pom.xml, IntelliJ-style.
A new Maven toolbar button (hidden until a pom.xml is actually detected for the active file/project;
refreshed on tab switch, window focus-regain, and save) opens a searchable popup listing the standard
lifecycle phases (clean, validate, compile, test, package, verify, install, site, deploy), the pom’s
declared
<profiles>(checkable — composing with a run via-P<id>, without closing the popup — and marking anyactiveByDefaultone), and each declared<build>/<plugins>entry’s explicitly-bound<executions>goals as<prefix>:<goal>rows (e.g.spotless:check,jacoco:report) using Maven’s own plugin-prefix naming convention; profile-scoped plugins (<profiles>/<profile>/<build>/<plugins>) are nested under their own profile once checked. Discovery is pom.xml-only — parsed directly with the JDK’s own hardened DOM parser (java.xml, XXE disallowed outright; no third-party XML dependency, no shelling out tomvn help:effective-pom) — so it’s instant and offline; a plugin with no<executions>is omitted from the goal list (the “Run custom goal(s)…” freeform prompt covers those). Runs prefer the project’s own./mvnw/mvnw.cmdwrapper when present, falling back tomvnresolved on PATH (or a Settings override), streaming output to a dedicated Maven console tool window (default-hidden, auto-opens on a run). New pure packagecom.editora.maven(PomModel/PomParser/MavenPluginPrefix/MavenLifecycle/MavenExecutable/MavenArgs) +MavenService(mirrorsRunService’s streaming shape)ui/MavenPanel/ui/MavenActionsPopup/ui/MavenCoordinator. Gated bySettings.mavenSupport(default on — inert until a pom.xml is found) +Settings.mavenCommandoverride (schema 61→62), with a Settings → Languages & Tools → Maven page; off in Simple UI mode and for remote (SFTP) files. Palette commandsview.toggleMavenSupport,maven.showActions,maven.runCustom,maven.stop,maven.rerunLast,maven.refresh,tool.maven. Also generalizesOverlayHostwith ashowBelow(...)variant (mirroring the existingpositionAbove) so a popup can drop below a top-of-window anchor like a toolbar button, rather than only above a status-bar-style anchor.
- A dedicated “AI” group in Settings, with a master Enable/Disable AI switch (off by default).
The AI Agent and AI Actions pages — previously scattered under separate groups — now live together
under a new AI sidebar group, alongside a new landing page holding a single master checkbox
(
Settings.aiEnabled, schema 60→61 additive). Turning it off disables every AI feature regardless of the AI Agent / AI Actions pages’ own settings below it: the AI Agent chat window, commit-message generation, explain/rewrite selection, and inline ghost-text completion —AgentCoordinator.isEnabled()andAiCoordinator.isEnabled()both now gate on it. It does not affect the MCP server (a separate feature). Also a palette command,view.toggleAiEnabled(“Toggle AI”). Defaults to off, so a fresh install has no AI usage until explicitly turned on. - Clickable links in the Markdown preview. A rendered link now shows a hand cursor and opens in the
system default browser on click (the same
Consumer<String>already used by Ctrl/Cmd-click-in-source and the Welcome page’s links), rather than being inert.MarkdownRenderer.renderDocumentgained an overload taking an optional click handler; the print/PDF writers and the hover/completion-doc popups keep using the existing no-handler path, so only the interactive live preview is affected. - Loading indicator while an AI explanation streams into the Markdown preview.
ai.explainSelectionopens a newexplanation.mdbuffer in Preview mode and streams the response in, but the preview’s re-render is debounced ~250ms after the text stops changing — a fast, continuous stream can go the whole generation without a quiet gap, leaving the preview blank with no feedback until it finally finishes.EditorBuffergained a centered spinner + message overlay (setPreviewLoading), shown from just before the request starts until it ends (success or error).
Changed
- The gutter Run play glyph is 20% bigger (scale 1.014 → 1.217), making the clickable green run target easier to see and click.
Fixed
-
Windows installs failed to launch: “Failed to find JVM in …\runtime directory”. The AOT-cache build step (
scripts/aot_build.java) strips the bundled runtime’sbin/after training to reclaim footprint — safe on macOS/Linux, where the JVM shared library lives inruntime/lib/server/andbin/holds only launcher executables. But the Windows JDK layout puts the JVM itself insidebin/(runtime\bin\server\jvm.dllplus the bootstrapjli.dll/java.dll/jvm.cfg), whichEditora.exeloads from there — so deletingbin/removed the JVM, and every Windows install since the AOT cache landed failed to start. Thebin/strip is now guarded to non-Windows only; the Windowsbin/is kept intact (its launcher exes are a negligible footprint). -
AI Agent header buttons (History / New Session / Stop) took up too much width. They’re now icon-only (matching the Debug panel’s icon-only toolbar buttons), with the translated label moved to a tooltip instead of being lost — hover to see what each does.
-
CSV grid preview could stay open on a non-CSV file after a restart. If the CSV/TSV grid tool window was left open in the previous session, it was force-reopened at startup (session restore) before the first real buffer loaded — at that point every buffer-gated tool window is provisionally marked “unavailable”, so the reopened window’s tracked availability and its actual open state diverged. The very next gating pass (on the restored tab) saw the tracked flag as already matching and skipped closing it, leaving the grid visibly open over a file that wasn’t CSV until some other state change happened to flip it.
ToolWindowManager.setAvailablenow always reconciles the actual open/closed + stripe-button state instead of short-circuiting when its tracked flag already matches, so it self-heals regardless of how it got out of sync (mirrors the same gating used by the Commit/Markdown-Lint/TODO windows).
[0.9.1] - 2026-07-06
First tagged release. (v0.9.0 was cut and pushed first, but a JReleaser misconfiguration published it
as an immutable GitHub Release before the asset uploads failed; GitHub permanently reserves a tag once
it has backed an immutable release, even after both the release and the tag are deleted, so v0.9.0
itself can never be reused on this repository — see the fixes below. This release ships as 0.9.1
instead.)
Fixed
-
macOS release build failure on a pre-1.0 version. jpackage rejects an
--app-versionwhose first number is zero or negative, so a0.x.ypom.xmlversion (like this release’s0.9.1) failed the macOS legs of the release build with “The first number in an app-version cannot be zero or negative” — on both the app-image build and the DMG wrap step, since jpackage enforces the rule on every invocation. The macOS build now passes jpackage a bumped placeholder (a leading0.to1., e.g.1.9.1) purely to satisfy that CLI check, then immediately rewrites the built app’sInfo.plist(CFBundleVersion/CFBundleShortVersionString) back to the true version before wrapping the DMG — so the placeholder never reaches the delivered app: Finder “Get Info”,mdls, System Settings, and the app’s own--version/About dialog all correctly show0.9.1. Linux and Windows are untouched (jpackage accepts a leading-zero version there). -
Release job failed to check out branch
main. JReleaser defaults to a branch namedmainwhen none is configured; this repo’s default branch ismaster, so the final “Release” job failed with “Could not checkout branch main” even after every platform’s installer build succeeded.jreleaser.yml’srelease.githubnow setsbranch: masterexplicitly. -
Release job failed with “Cannot upload assets to an immutable release.” GitHub’s Immutable Releases feature (GA October 2025) rejects any asset upload once a release is published, but JReleaser’s default flow publishes the release before uploading each installer — so every asset upload 422’d and the whole release failed after all five platform builds had already succeeded.
jreleaser.yml’srelease.githubnow setsimmutableRelease: true, which makes JReleaser create the release as a draft, upload every asset while it’s still a mutable draft, then publish — the flow GitHub’s own docs prescribe. This is mutually exclusive with the previousoverwrite: true(removed), so re-running on the same tag now needs the prior release deleted first (gh release delete v<version>) rather than overwritten in place. -
AI connection check no longer hangs on a slow endpoint. The Settings → AI Actions health check is now a non-streaming request whose 30 s timeout bounds the entire exchange — so a local server that sends response headers immediately but is slow to produce the first token (LM Studio warming up a model) can no longer leave the status stuck on “Checking connection…”. It resolves to green Connected or a red Not working: … (with the HTTP error / “timed out”). Real streaming AI requests also gained a 120 s time-to-first-response cap (unchanged for a healthy generation).
Added
-
Open a
.patch/.difffile in the structured diff viewer. A right-click “Open in Diff Viewer” item (shown only for a.patch/.difffile, plus the palette-onlydiff.openPatchFilecommand) parses the active buffer’s unified diff and opens its first file-section as a read-only side-by-side diff — with word-level highlighting and prev/next-change navigation — instead of just plain syntax-highlighted diff text. Works on the buffer’s own (possibly unsaved) text; a multi-file patch shows a status note and just the first file. -
Configurable TODO per-part colors. The highlight color for a structured TODO comment’s
[tag]and each(priority)level (critical/high/medium/low) is now user-configurable — a TODO Part Colors section in Settings → Editor → TODO with five color pickers, plus a palette commandtodo.setPartColor(pick a part →#RRGGBB). Applies live to every buffer. -
AceJump line-mode (avy-goto-line). A new
nav.aceJumpLinecommand (M-g L) labels every visible line at once — type a label to jump the caret to that line’s first non-whitespace character. Complements the existing character-mode AceJump (M-g j). -
MCP:
list_tabsandtodo_scantools. The MCP bridge gains two read tools —list_tabs(every open tab including non-editor tabs — Welcome/image/hex/diff — with each tab’s type + which is active) andtodo_scan(scans the project + open buffers for the configured TODO/FIXME highlight patterns, returning file/line/col/keyword/tag/priority/text) — bringing the tool count to fourteen. -
External Tools: editor right-click submenu. The editor context menu now shows an External Tools submenu listing every enabled tool, so you can run one on the active file without the palette or a keybinding (hidden when no tool is enabled or in Simple UI mode).
-
Find-in-Files popup: include/exclude globs. The keyboard-first Find in Files popup now has the same comma-separated include / exclude glob fields as the tool window (e.g.
*.java, src/**/**/test/**), narrowing the search live as you type. -
Remote-file (SFTP) status indicator. When the active file lives on a remote host, the status bar now shows a ⇅ SFTP badge (with the
host:/pathin its tooltip; click it to manage connections), so it’s clear at a glance that you’re editing over SFTP. (The tab already showed a cloud icon + path tooltip.) -
Macro recording indicator. While recording a keyboard macro (
F3), the status bar now shows a red ● REC badge so it’s obvious you’re recording; click it to stop (orF4). -
Log viewer: jump to next/previous error. Two palette commands — Log: Next Error / Log: Previous Error — move the caret to the next/previous line at WARNING level or higher in a
.logfile (wrapping around), so you can hop between problems without scrolling. -
AI connection status (green/red). The Settings → AI Actions page now shows a live health-check indicator under the enable checkbox: green Connected when the configured provider/endpoint/key (and model) actually accept a request, red Not working: … with the error otherwise (a wrong endpoint path, a refused connection, a bad key, an unknown model). It checks when the page opens and re-checks — debounced — after you edit the provider, endpoint, key, or model, so a misconfigured local server (LM Studio/Ollama) or a typo’d URL is obvious immediately instead of failing silently at first use. Also a palette command AI: Test Connection that reports to the status bar. The probe is a tiny one-token request and never disturbs a real generation.
-
View: Open as Text — a palette command that opens the current file as plain text, bypassing the image/hex viewer. The counterpart to Open as Hex: use it when a text file is mis-detected as binary, or to read a binary’s raw text.
-
Local LLMs for the AI features (LM Studio, Ollama, vLLM). The AI actions and inline completion can now talk to a local OpenAI-compatible server instead of the Anthropic API — pick Local (OpenAI-compatible) under Settings → AI Actions → Provider. The default endpoint is LM Studio’s local server (
http://127.0.0.1:1234/v1/chat/completions) and no API key is required; the endpoint is configurable for Ollama/vLLM/llama.cpp. Leave the model fields blank to use whatever model the server has loaded, or name one explicitly. The OpenAI chat-completions streaming dialect (SSEdata:chunks →[DONE],choices[].delta.content,finish_reason) is handled alongside the Anthropic Messages format; commit-message generation, explain/rewrite selection, and ghost-text completion all work against either. Palette: AI: Set Provider / AI: Set Endpoint. Schema 56→57, additive. -
AI inline completion (ghost text). After a short typing pause with the caret at the end of a line, a muted single-line AI suggestion appears at the caret — Tab accepts it, Esc or typing dismisses it (the same ghost-text presentation prose autocomplete already uses). Requests are strictly bounded: one per ~600 ms idle pause, a windowed prefix/suffix prompt, a one-line stop sequence, and a small output cap — and each keystroke supersedes the in-flight request, so stale suggestions never appear. Runs on its own fast model (default
claude-haiku-4-5, configurable) independent of the AI-actions model, and never competes with the local snippet/dictionary popup. Off by default (Settings → AI Actions → “Enable AI inline completion”; palette “Toggle AI Inline Completion”); needs the AI Actions feature + an API key; inert in large files and read-only buffers. -
AI actions — one-shot Anthropic-API features, no agent required. Three streamed, palette-driven actions that call the Anthropic Messages API directly (a hand-rolled
java.net.httpclient — no SDK, no new dependency): AI: Generate Commit Message turns the staged diff into a commit message and drops it into the Commit window for review; AI: Explain Selection streams an explanation of the selected code into a new Markdown buffer; AI: Rewrite Selection… rewrites the selection per your instruction as a single undoable edit (aborted safely if the buffer changed while generating), and AI: Cancel stops an in-flight generation. Off by default (Settings → AI Actions, Beta) with a configurable model (defaultclaude-opus-4-8) and an API key fromANTHROPIC_API_KEYor a Settings override (stored in plain text — the environment variable is preferred). Separate from (and complementary to) the AI Agent chat window. -
Auto Close Tags. Typing the
>that completes an HTML/XML open tag now inserts the matching closing tag after the caret (<body+>→<body>|</body>), with the caret left between the tags. Closers, self-closing/>, doctypes/comments, HTML void elements (<br>,<img>, …) and a>typed inside an attribute string are all left alone. Works in macro replay too. On by default for.html/.xml-family files (Settings → Editor, palette “Toggle Auto Close Tags”). -
Embedded AI agent (ACP) — chat with Claude Code inside Editora. A new AI Agent tool window drives any Agent Client Protocol agent over stdio — the default command is
claude-code-acp(Claude Code’s ACP adapter;npm i -g @zed-industries/claude-code-acp), and Gemini CLI or any other ACP agent works by changing the command. The transcript streams the agent’s replies and tool activity; Enter sends, Shift+Enter inserts a newline; Stop cancels a turn and New Session starts fresh. The integration is editor-aware: the agent’s file reads serve open buffers’ unsaved text, and its edits to open files apply as undoable buffer edits (oneC-zreverts; you review and save) — writes to files you don’t have open go to disk and refresh the Project tree. Agent permission requests pop a dialog with the agent’s own options. Off by default (Settings → AI Agent, Beta) with a configurable command; the agent process is tracked like the LSP servers (killed on window close / app exit, reaped after a crash). Commands: AI Agent window toggle, Agent: New Session, Agent: Stop, Toggle AI Agent, Agent: Set Command. -
MCP server: six new tools — an AI agent can now edit, save, and navigate, not just read. The embedded Model Context Protocol server grows from six to twelve tools:
edit_bufferapplies an undoable text edit to an open buffer (exact-match str-replace — the old text must occur exactly once unlessreplace_all— or a whole-buffer rewrite; oneC-zreverts it),save_bufferwrites an open buffer to disk (untitled buffers are refused rather than popping a Save-As dialog),open_fileopens a file and optionally jumps to a line/column,get_selectionreports the active buffer’s caret and selection,document_symbolsreturns a file’s LSP symbol outline, andgit_statusreports the branch, ahead/behind counts, and changed files. The security-notice dialog now discloses that a connected agent can edit and save files, not just read them. Everything stays loopback-only + bearer-token-gated and off by default (Settings → MCP Server). -
Auto Rename Tag (the VS Code behavior). Editing an HTML/XML tag name now renames the paired open/close tag live as you type — nested and same-name sibling tags pair correctly, real-world HTML’s unclosed optional-close tags (
<li>,<p>, …) don’t break the pairing, comments, CDATA, doctypes, quoted attribute values, self-closing tags, HTML void elements (<br>,<img>, …) and<script>/<style>content are all skipped, and an old-name guard makes sure typing a brand-new tag never renames an unrelated one. On by default for.html/.xml-family files (Settings → Editor, palette “Toggle Auto Rename Tag”); inert in read-only and very large files. -
String manipulation commands (the JetBrains String Manipulation plugin family). Case-style conversions on the selection — or the identifier at the caret — to camelCase / PascalCase / snake_case / SCREAMING_SNAKE_CASE / kebab-case / dot.case, plus Cycle Case Style (repeated invocations step the same token through the styles) and Swap Case; and whole-line transforms on the selected lines (or the whole file): sort ascending/descending (numeric-aware —
file2beforefile10— and case-insensitive), sort by length, reverse, shuffle, remove duplicate lines, remove empty lines, and trim trailing whitespace. Every operation is its own palette command and key-bindable; Edit: String Manipulation… (C-c x) opens one filterable picker over all of them. Each transform applies as a single undoable edit and re-selects its result. -
Hex viewer for binary files. Opening a binary file (an executable, archive,
.class,.pdf, …) now shows a read-onlyoffset | hex | ASCIIdump instead of dumping its bytes as garbage text — completing the image-viewer story. Binaries are detected by content (a NUL byte / high control-character density, with a BOM guard so UTF-16 text isn’t misrouted), large files show their first slice with a truncation note, and the View: Open as Hex command force-opens any file’s bytes as hex. Hex tabs restore across sessions. -
More default TODO keywords + structured comments (Comment Manager). The built-in TODO highlight keywords now include HACK, NOTE, XXX and DONE alongside TODO/FIXME (existing custom patterns are kept; the defaults grow on upgrade). Editora now recognizes the IntelliJ-style
KEYWORD [tag] (priority) descriptioncomment format — e.g.// TODO [auth] (high) fix token refresh— and colors each part in the editor: the keyword in its pattern color, the[tag]in blue (underlined), and the(priority)by level (critical→red, high→orange, medium→amber, low→cyan). The TODO tool window now shows each match with those parts colored and a “Group by” selector — File, Priority, Tag, or Keyword — so you can, for example, see every critical item across the project together. Right-click a match to edit it directly in your source: Mark Done (rewrites the keyword toDONE) / Reopen, set the priority, or edit the description — each applied as one undoable edit to the file. -
Image viewer. Opening a raster image (
.png,.jpg/.jpeg,.gif,.bmp) now renders the picture in a read-only tab instead of dumping its binary bytes into a text buffer. A small top bar zooms out / in / fit-to-window / actual size (and Ctrl+mouse-wheel zooms); large images scroll, small ones center. SVG is deliberately left as editable text. Image tabs are restored across sessions like any other tab. -
Filter/search box focused on open. Opening the Project, Structure, Bookmarks, or Personal Notes tool window now puts keyboard focus on its filter/search box so you can type to filter immediately; Down moves into the results and Enter opens the selected (or first) one.
-
Project filter skips .gitignore. The Project tool window’s filter now skips
.gitignored files and folders (target/,node_modules/,*.log, …) by default — governed by the same “respect .gitignore” search setting. The full project tree still shows every file. -
Active file first in Problems & TODO. In the Problems and TODO tool windows, the file you’re currently editing now sorts to the top of the list, so its diagnostics / TODOs are the first thing you see (matching the IDE convention). Re-orders automatically as you switch tabs.
-
macOS “Open With” support. The packaged
Editora.appnow registers as a handler for common text and source files, so Editora appears in Finder’s right-click Open With menu (and the “Always Open With” picker). Opening a file that way launches or focuses Editora and opens the file — even though macOS delivers it as an Apple Event rather than a command-line argument. It’s offered as an alternate handler, so it doesn’t take over your existing default editor. (MoveEditora.appto/Applicationsso macOS registers it.) -
More plain-text developer formats. Syntax highlighting for unified diffs (
.diff/.patch— added/removed lines tint green/red, hunk headers stand out), Makefiles (Makefile/GNUmakefile/.mk), justfiles, Protocol Buffers (.proto), GraphQL (.graphql/.gql), and.gitattributes(including a repo’s.git/info/attributes). Java.propertiesfiles get a dedicated grammar (:and=separators, Unicode escapes, backslash line-continuations) instead of borrowing the INI one. Proto and GraphQL also fold on braces and auto-indent like C-family code, and comment toggling (M-;) works in all of them. -
Compare / Annotate in the Project tree. The Project tool window’s file Git submenu now also offers Compare with Branch… (diff the file against its version on any local or remote branch), Compare with Revision… (diff against a commit from the file’s history), and Annotate (open the file and show inline Git blame) — alongside the existing Compare with HEAD.
-
Git actions in the Project tree. Right-clicking a file in the Project tool window now offers a Git submenu with Stage, Unstage, Revert… (discard changes — untracked files are removed, tracked files reset to HEAD, with a confirmation), and Add to .gitignore, alongside Compare with HEAD and Show File History. Folders offer Stage and Revert… of the whole subtree. Actions enable or disable based on the file’s real Git status, and the whole submenu is hidden when the folder isn’t a Git repository.
-
Git status colors in the Project tree. Files in the Project tool window are now colored by their Git status — added (green), modified (blue), deleted (gray), renamed (violet), and untracked (olive) — and a folder that contains changes is tinted, just like IntelliJ. Updates as you edit, stage, commit, or switch branches; rides the Git integration (nothing to turn on).
-
References tool window. Find References now shows its results in a dedicated, browsable References tool window — grouped by file, with a line + preview per reference — instead of a one-shot picker. Enter or double-click jumps to a reference; if there’s only a single reference, it jumps straight there.
-
Go to Symbol in Workspace. A new command opens a live search box that finds any symbol (class, function, method, …) across the whole project by name and jumps to it — the LSP
workspace/symbolfeature, like VS Code’sCtrl-T/ IntelliJ’sCtrl-N. Available from the command palette in a code file with a running language server. -
Ctrl/Cmd-click to go to definition. In a code file with a running language server, holding Ctrl (or ⌘ on macOS) and clicking a symbol now jumps to its definition — the same gesture as VS Code / IntelliJ. It reuses the existing Go to Definition, so it also works across files and records a jump you can step back from (Back /
nav.back). -
Markdown table: export to CSV / Excel / ODF file. The editor’s right-click Table submenu (and the command palette) now export the Markdown table under the cursor to a
.csv, Excel.xlsx, or OpenDocument.odsfile — alongside the existing “Copy as CSV” (clipboard). -
CSV grid: columns fit their content. The CSV Grid now sizes each column to the width of its widest cell (or header), so you rarely need to drag a column edge to read the data. Very long values are capped so one giant field can’t push the rest of the table off-screen, and you can still resize any column by hand.
-
CSV: align / shrink columns. Two new commands reformat a
.csv/.tsvfile so it’s easy to read as a table: CSV: Align Columns pads each field with spaces so the delimiters line up in the editor, and CSV: Shrink Columns removes that padding again (fully reversible). Both preserve quoted fields, blank lines, and the trailing newline, and are undoable. (Files with a quoted field that spans multiple lines are left alone, since aligning them would split a record across lines.) -
Rainbow CSV columns.
.csv/.tsvfiles are now colored per column in the editor — each column gets a distinct color that cycles every eight, so field boundaries are easy to follow (like VS Code’s Rainbow CSV). On by default; toggle in Settings → Editor → CSV or the Toggle Rainbow CSV Columns command. -
CSV grid: sort, filter, and inconsistent-row highlighting. The CSV Grid now has a filter box that live-filters rows as you type, a right-click Sort Ascending / Descending / Clear Sort on any column (numbers sort numerically), and rows whose field count doesn’t match the header are tinted red with an “N inconsistent” count in the summary line — so ragged CSVs are easy to spot. Sorting and filtering only change the view; editing a cell and jumping to the editor still land on the right line.
-
CSV grid: export menu. Right-click the CSV Grid to Export to PDF, Print, Export to Excel (.xlsx), or Export to an ODF spreadsheet (.ods) (also available as palette commands). Numbers are written as real spreadsheet numbers where safe — leading-zero codes (like ZIP codes) and thousands-separated values stay text so they aren’t corrupted — and the header row is bold.
-
CSV grid: editable headers + spreadsheet polish. Column headers in the CSV Grid are now editable — double-click a header, type a new name, and it’s written back to the file’s first row. The grid also got a more spreadsheet-like look: a ”#” row-number gutter, zebra striping, bold headers, and right-aligned numeric columns.
-
CSV / TSV syntax highlighting.
.csvand.tsvfiles now open as a proper “csv” language instead of plain text: quoted fields are colored as strings (with the""escape), bare numeric fields as numbers, and the field separators (comma / semicolon / tab / pipe) get a muted tint so column boundaries stand out. First step of broader CSV support (a column readout, a table/grid preview, and grid editing are planned next). -
CSV / TSV column readout. For a
.csv/.tsvfile the status bar shows a “Field N of M” segment — which column the caret is in and how many the row has (RFC-4180 quote-aware, delimiter auto-detected). Clicking the segment, or the new CSV: Copy as Markdown Table command, copies the whole file to the clipboard as a GitHub-flavored Markdown table. -
CSV / TSV grid preview. A new CSV Grid tool window shows the active
.csv/.tsvfile as a read-only spreadsheet: a first-row-as-header toggle, a summary line (row/column counts + how many columns are numeric vs. text), and double-click / Enter on a cell jumps the editor caret to that field. Refreshes as you edit; on by default for CSV/TSV files (Settings → Editor → CSV, or the Toggle CSV Grid Preview command). -
Editable CSV grid. The CSV Grid is now editable: double-click a cell, type a new value, and it writes back to the file (undoable, with proper RFC-4180 quoting). Editing is available when the file is writable and has no quoted multi-line fields; otherwise the grid stays read-only. Right-click Reveal in editor jumps to a cell’s location either way.
Changed
- Local File History is now entirely in its tool window — no more separate window. Opening a revision’s diff used to spawn a standalone “Local History” window alongside the File History tool window. The tool window is now a split: the revision list on the left, and the live side-by-side diff of the selected revision vs the current file on the right — with the Ignore-whitespace / Highlight-words toggles, the “N differences” count, the per-hunk apply chevrons (selective restore), and the whole-file Revert, all in one place. Selecting a revision shows its diff inline; nothing pops out into a second window.
Fixed
-
Blank editor after switching tabs. Clicking a tab could occasionally show a blank editor until you clicked it a second time to give it focus. The editor area now repaints on the layout pulse right after the tab is shown, so its content appears on the first click (without moving the scroll position or stealing focus from a Find-in-Files preview).
-
Markdown print preview: tables no longer get pushed to their own page. When printing (or print- previewing) a Markdown file, a table could be bumped onto the next page even when it easily fit under the preceding heading, leaving the first page mostly blank. The page-layout measurement was collapsing the table’s columns and over-estimating its height; it now measures at the real page width, so the printed output matches the on-screen preview.
Added
-
Find in Files popup. A keyboard-first overlay for multi-file content search (command “Find in Files (Popup)”, palette-discoverable) — type a query, see results grouped by file, and press Enter to jump straight to a match (Enter on a file header opens that file); Esc closes. It has case / regex / whole-word toggles (no replace) and an editable search-folder field that defaults to the project root in a project window, or the current file’s folder otherwise, and can be pointed at any folder. Reuses the same fast (ripgrep-backed) search engine as the Find in Files tool window and shares its search history, and shows a ripgrep badge when that backend is active.
-
Bundled “Shell Script” and “Zsh Script” file templates. New File From Template now includes shell starters: a Bash one (
#!/usr/bin/env bash,set -euo pipefail, amain/main "$@"scaffold →${baseName:script}.sh) and a Zsh one (#!/usr/bin/env zsh,emulate -L zsh+setopt errexit nounset pipefail, the samemainscaffold →${baseName:script}.zsh); both prompt for the file name. Available from the template picker, the toolbar, and the project-tree “New From Template…” menu. -
Doc comments in the Structure tool window. Each item in the Structure outline now shows its leading documentation comment as a hover tooltip when one is present — Javadoc/JSDoc/PHPDoc and plain block comments (
/** … */,/* … */), and runs of//,#, or--line comments above the declaration. Annotation/decorator lines (@Override,#[derive(...)], …) between the comment and the declaration are skipped, and long comments are trimmed. Works for both LSP-served and heuristic outlines.
Fixed
-
New File From Template no longer doubles the extension. When a template’s file-name pattern ends in an extension (e.g.
${baseName}.md) and you typed a name that already included it (notes.md), the new file came out asnotes.md.md. The engine now collapses a doubled trailing extension — but only when the two extensions are identical, sotypes.d.ts,foo.tar.gz, andapp.min.jsare left alone. -
Release builds package from a clean tree, fixing a rare “dead keyboard” in the installer. The native-installer build fed jlink from an incrementally-compiled
target/, where a synthetic$SwitchMapclass (emitted for aswitchover an enum, e.g. the key dispatcher’s) could go missing if a background compiler or an interrupted build lefttarget/classesinconsistent — javac won’t regenerate it while the source looks up-to-date. The packaged app then threw aClassNotFoundExceptionon the first keypress. The release workflow now runscleanbefore-Pdist, guaranteeing every class is regenerated. (Only the native installers could be affected, and only when built from a dirty tree;mvn javafx:runand the fat jar were never affected.) -
Markdown “Insert Table” size picker no longer fills the editor. The grid picker was stretched to cover the whole code area; it now hugs the grid (with padding) and is centered.
-
Structure tool window: methods no longer show a doubled
(). When a language server (e.g. jdtls) already includes the parameter list in a method’s name (setX(Foo)), the outline no longer appended a redundant empty()— so it readssetX(Foo)instead ofsetX(Foo)().
Added
-
User-defined themes from the config folder. Drop a theme CSS in your config directory and it shows up in the theme pickers, named from the file (
my-cool-theme.css→ “My Cool Theme”). Two folders:<configDir>/themes/*.cssfor a full AtlantaFX theme (a self-contained stylesheet defining-color-*) — it becomes both the app theme and its editor colors via the adaptive syntax palette (base colors parsed from the CSS); and<configDir>/editor-themes/*.cssfor a hand-authored editor override (.editor-area+.text.<class>rules, like the bundled ones) that appears in the Editor Theme picker only. Themes load at startup; the palette command “Theme: Reload User Themes” re-scans without a restart. -
Per-hunk selective restore in the Local History window. The window’s diff is now editable on the current-file side — the IntelliJ-style apply chevrons are back, so you can copy an individual fragment from a past revision into the file (single chevron per changed row, double at each hunk start), routed through the undoable buffer with Undo/Save. The diff re-computes after each apply so the remaining chevrons update; the whole-file Revert button still does a full restore.
-
IntelliJ-style Local History window. Opening a file’s local history (double-click a revision in the File History tool window, or “Compare with Current”) now opens a dedicated window with the revision list on the left and a live side-by-side diff of the selected revision vs. the current file on the right — selecting a revision re-diffs instantly. A toolbar shows the revision timestamp, an “N differences” count, Ignore whitespace and Highlight words toggles (both re-diff), and a Revert button (undoable whole-file restore); a Search by content box filters the revision list. The diff pane keeps its side-by-side/unified toggle and prev/next-change navigation. Backed by a new
DiffEngine.DiffOptions(ignore-whitespace normalizes lines one-to-one so the original text still renders; word-level toggles the intra-line emphasis). -
19 new color themes. The theme picker gains the MIT-licensed AtlantaFX community themes from the DLSC collection — Blue, Navy, Army, and the four seasonal sets (Spring, Summer, Fall, Winter) in light/dark pairs, plus Autumn, Browny, News (dark) and Yacht (light) — bringing the total to 26 app themes. Each ships a matching syntax-highlighting theme: a single adaptive editor palette draws every token color from the active theme’s own AtlantaFX
-color-*variables, so the editor surface and syntax colors always coordinate with the chrome. Pick one from Settings → Appearance or theappearance.setTheme/appearance.setEditorThemepalette commands. (The GitHub colorblind/tritanopia variants are intentionally excluded — an adaptive red/green token palette would defeat their accessibility purpose, and GitHub’s default duplicates the existing Primer Light.)
Performance
- Dirty-state tracking no longer allocates the whole document on every keystroke. The editor’s modified-flag listener was bound to RichTextFX’s
textProperty(), which forces the full documentStringto be materialized on each change — an O(n) allocation per character on a very large single buffer (e.g. minified JS/CSS or a one-line JSON/log that slips past the line-count heavy-file tier). It now runs offplainTextChangeswith a cheapgetLength()gate, so the full text is only built in the rare near-clean state, never while typing. Behavior is unchanged (reverting an edit still clears the marker).
Internal
- Broadened the headless-FX harness over the Run + External-Tool console panels. New
RunPanelFxTestandExternalToolPanelFxTestexercise the console lifecycle (started → appendOutput stdout/stderr → finished/failed, Stop-button enablement) and the one-shot tool console (command header + stdout + stderr + status line, clear). - Broadened the headless-FX harness over the Notes + TODO panels. New
NotesPanelFxTestandTodoPanelFxTestfeed an in-memory note bucket / aTodoService.Outcomeinto the panels and assert the rendered file→note / file→match grouping, empty-input handling, the note-body filter, and the TODO summary label. - Broadened the headless-FX harness over the Structure + Bookmarks panels. New
StructurePanelFxTestandBookmarksPanelFxTestfeed an LSPSymbolNodetree / an in-memory bookmark bucket into the panels and assert the rendered hierarchy — Structure’s class→methods outline (and that symbols for a non-attached buffer are ignored); Bookmarks’ file→bookmark grouping, empty-file skipping, and the name filter. - Broadened the headless-FX harness over the Git + Problems tool-window panels. New
GitPanelFxTestandProblemsPanelFxTestfeed pure-record data (GitStatus/LspDiagnosticmaps) into the panels and assert the rendered grouping — Git’s Staged/Changes/Untracked tree, branch label, and commit-button enablement (incl. clean / not-a-repo states); Problems’ language→file→diagnostic tree plus empty-input handling — using no-opActionsstubs (no live git / LSP). - Broadened the headless-FX harness over
MainControllercommand dispatch. A newMainControllerCommandsFxTestdrives service-free commands through the realCommandRegistryagainst a fully-wired window (text zoom, the read-only toggle, line-numbers/minimap/whitespace view toggles, select-all), covering theuicommand-execution surface that the palette + keymap share. - Broadened the headless-FX harness over
EditorBuffer. A newEditorBufferFxTestexercises Markdown view-mode switching (incl. the minimap-hidden-in-preview rule), read-only / view mode, programmatic typing through the macro-replay path (with auto-close), per-language run-glyph detection (compact Java / Python / gated shell), and display-name/dirty/EOL bookkeeping — raisingeditor-package coverage with no new production code beyond two helper-visibility tweaks. - Test coverage: added unit tests + ratcheted the JaCoCo floors. New tests for
configmodels/stores (Breakpoint,BreakpointStore,RecentFiles, plusFileIdentitynull/empty branches) raisedconfigline coverage to ~88%, and newInstallServicetests coverfindBinary/makeExecutable/Result. The per-packagejacoco-checkfloors were ratcheted to sit a few points below the measured coverage (migration·diff 0.85→0.90, config split out at 0.86, template·editorconfig 0.80→0.82, completion·http·pdf 0.68→0.70).
Changed
- Indent detection now defaults to spaces when a file has no indentation, matching VSCode/IntelliJ (“spaces unless the file is detected to use tabs”). A file whose first indented line uses a tab still gets tabs, and spaces still get spaces; only the no-evidence case (empty or flat file) flipped from tab to spaces. This affects the default
detectindent style; an explicitspace/tabsetting or.editorconfigstill wins. - Settings → Snippets: the language picker is now a plain dropdown (no longer an editable/typing combo box) — it lists the curated languages plus any that already have a user snippet file, and you select from it rather than typing.
- The Structure and Bookmarks tool-window filter fields now have a clear (✕) button, matching the Personal Notes and Project panels. It appears only while the filter has text and clears it on click.
- Settings → Keymaps → Customize shortcuts now reveals the Record/Reset buttons only for the selected row. Every command row used to show both buttons, cluttering the long list; a row’s actions now appear only when you click to select it (the selected row is highlighted), so the list reads as a clean command + chord table.
- Save As from the command palette (or a keybinding) now prompts for the path in-scene instead of opening the native file chooser, keeping the keyboard flow mouse-free. The field is pre-filled with the current file’s path (or the project folder + suggested name for an untitled buffer); a bare name resolves against that folder and
~expands to home. The toolbar Save-As button still opens the native file chooser (it’s a mouse action), and overwriting a different existing file asks for confirmation.
Fixed
-
File templates now prompt for a variable used in the file-name pattern. A single-file template whose file name was e.g.
${baseName:Main}.javanever asked forbaseNameat creation time, so it silently used the default and always producedMain.java— even though the same variable worked in the body. The file-identity built-ins (baseName/fileName/extension) can’t be derived for a brand-new file, so when they appear in the file-name pattern they’re now prompted in the wizard (the answer flows into both the file name and the body); used only in the body, they’re still auto-derived as before. -
Markdown-preview code blocks stay readable when the preview theme is toggled independently. Code-block syntax colors followed the editor theme, so forcing the preview to the opposite brightness (the sun/moon toggle) left dark tokens on a light code background (or vice versa). They now follow the preview’s own light/dark — a fixed GitHub Primer palette matching the preview’s prose — while still tracking the editor theme when the preview follows the app.
-
The 80-column ruler no longer jumps to the left edge when word wrap is on. The ruler derived its per-column width from the caret x at the start vs. the end of the longest visible line; with wrap on, that line spans multiple visual rows, so the two points sat on different rows and the width collapsed — planting the ruler near column 13 instead of 80. It now measures the advance on a single visual row (falling back to an adjacent-column advance when the line wraps), so the ruler stays at the correct column in both modes.
-
Settings labels no longer truncate. A long settings-row label (e.g. “Max size / project (MB)” under Workspace → Local History) was clipped to ”…”; the label column now keeps its alignment floor but grows to fit longer labels (and translations). The Settings window also has a minimum size now, so wide rows (e.g. the large-file threshold row + its unit) can’t be clipped by shrinking it.
-
Flaky FX test fixed. The headless-FX harness could intermittently error with
NoSuchFileException: settings.toml.tmp— it now flushes pending async config writes before deleting the temp config dir (FxWindowFixture). -
Clearer Mermaid render error when headless Chrome is missing. A failed
mmdcrender whose error is the Puppeteer “Could not find Chrome” stack now leads with a one-line fix (thenpx puppeteer browsers install chrome-headless-shellcommand). -
Multi-line
$$…$$block math now exports as an image to PDF/DOCX/ODT. A display-math block with the$$delimiters on their own lines was exported as raw text (the export’ssoleDisplayMathbailed on the soft line breaks); single-line$$…$$and inline$…$were unaffected. -
Markdown preview: multi-line
$$…$$block math now renders. A display-math block with the$$delimiters on their own lines was shown as raw text (the paragraph scanner bailed on the soft line breaks); single-line$$…$$and inline$…$were unaffected. -
PDF export now works in packaged builds. PDFBox + pdfbox-io + fontbox all log via commons-logging, which jlink was silently pruning (jdeps dropped the
requiresunder--ignore-missing-deps), so every packaged PDF export failed withNoClassDefFoundError: org/apache/commons/logging/LogFactory. Fixed with hand-written module descriptors; also made the PDF/print/office export services catchThrowableso a failure reports instead of hanging the status. -
Minimap now shows in Markdown SPLIT view. It had been hidden whenever the preview was on (gated to
EDITORmode only); it now hides only in fullPREVIEW(no editor surface), and stays visible in SPLIT where the editor is on screen. -
Markdown → PDF export no longer fails on SVG images. A badge/image served as SVG (e.g. shields.io) made the whole export throw (
IllegalArgumentException: Image type UNKNOWN); SVG images are now rasterized to PNG (via the same JSVG path the on-screen preview uses) and any undecodable image degrades to alt text instead of aborting. -
Tool-window consoles really match the editor font now. The
.run-output/.debug-consoleCSS carried a-fx-font-sizethat overrode the programmatic font on every CSS pass, so the External Tools / Run / Debug consoles kept rendering at 12px regardless. Removed the font from those CSS rules so the editor-matchedsetFontsticks (guarded by a newConsoleFontFxTest). -
Tool-window consoles now actually pick up the editor font. The previous attempt set
-fx-font-sizeviasetStyle, which a JavaFXTextAreaignores; the consoles (External Tools / Run / Debug) now set the control’sfontproperty directly so they match the editor’s family + size. -
Command palette: clicking a result now runs it. A card-wide
MOUSE_CLICKEDconsume filter was swallowing the result cells’ click-to-run handler (only Enter worked). -
Command palette: removed the stray separator line above the command description.
-
Project / Current-Folder filter now finds top-level files in a large root. The filter-box search walked the tree depth-first, so under a big root (e.g. a home directory with huge
.cache/.m2/node_modulessubtrees) it exhausted its visit budget deep inside one subtree before ever reaching a shallow file like.profile— which then never appeared in the results. The search is now breadth-first, visiting every shallower entry before descending, so top-level matches are never starved by a deep sibling subtree. -
Hardened several spots against NullPointerExceptions from corrupted config or non-conforming servers (found via a full-codebase null-flow audit).
Macronow defaults a missingnameto"", so a hand-edited/corruptedmacros.jsonentry without a name can’t NPE macro lookups (find/save/delete).SnippetManagerskips a snippet file whose content is the literalnullinstead of throwing.LspManagergo-to-definition / find-references guard aLocation/LocationLinkrange that a non-conforming language server leaves null.DapClient.evaluatetolerates a null adapter response likeevaluateFullalready did. (The wider audit found theui/editor/MainControllerpackages already guard nullable returns consistently.) -
Hardened the Bookmarks drag-reorder against a theoretical NPE.
handleDropdereferenced a dragged mark row’sgetParent()directly; it’s always non-null today (a mark is always under a file group), but a defensive guard now returns early if a detached row is ever dropped, so a future tree-shape change can’t turn it into a crash. -
Markdown preview no longer renders HTML comments.
<!-- … -->blocks (and inline comments) were shown as visible text/code in the preview (and PDF/print export); they’re now hidden, matching GitHub and every other Markdown renderer. -
Markdown preview hides the editor minimap. In Split/Preview mode the minimap was wedged between the editor and the preview; it’s now hidden while the preview is shown (the preview is the overview) and restored when you return to the plain editor view.
-
The “install language support?” banner no longer shows when the language server is actually installed and running. It was reading the server-availability flag before startup detection finished (and never re-checking), so it could falsely claim e.g. Java support was missing while jdtls was serving the file. The banner now only appears once a server is confirmed absent (not merely “not probed yet”), never when a live session is already serving the file, and re-evaluates as soon as detection settles. Also fixed the banner text dropping its apostrophe (“isnt”) — the message runs through
MessageFormat, where'must be doubled.
Added
-
Drag a raw image out of a web browser into a Markdown file. Alongside dropping image files and pasting a clipboard image, you can now drag an image straight from a web page onto a Markdown editor — Editora downloads it (off the UI thread) into the sibling
assets/folder and inserts. It resolves the source from the drag’s URL, an<img src>in the dragged HTML, or adata:URI; a raw dropped image is encoded to PNG; if the download fails it falls back to inserting the remote URL. (Requires the Markdown file to be saved, soassets/has a home.) -
Syntax highlighting in Markdown-preview code blocks. A fenced code block with a language (
```java,```python,```js, …) is now tokenized with the same TextMate engine + theme the editor uses, so the preview shows colored code instead of flat monospace. Unknown/absent languages and indented blocks stay plain; very large blocks skip highlighting. -
Save as Administrator (Linux & macOS). Editing a file you lack write permission for (e.g. one under
/etc) now offers to save it via the OS’s own authentication prompt — Editora never sees the password. On Linuxpkexec/polkit handles it; on macOS AppleScript’sdo shell script … with administrator privilegesshows the native Touch ID / password prompt. The View-mode banner on a read-only-on-disk file shows Edit as Administrator, andCtrl-S(or theFile: Save as Administratorpalette command) then writes through the elevated helper, which rewrites the file in place so its owner and permissions are preserved. Off by default (Settings → Editor → Saving; paletteToggle Save as Administrator); Windows isn’t supported; each elevated save prompts for authentication. -
Shebang language detection. A file with no telling extension (e.g.
shellscript,myscript) whose first line is an interpreter shebang now gets the right language — syntax highlighting, folding, run gutter, and LSP all light up. Recognizes direct paths (#!/bin/bash),envlookups (#!/usr/bin/env python3), andenv -Swith args, mapping the interpreter (version-stripped:python3.12→python) to shell (bash/zsh/sh/dash/ksh/…), python, javascript (node), typescript (deno/bun/ts-node/tsx), ruby, php, lua, groovy, or powershell. A#!/usr/bin/env -S java --source NNline is treated as a Java compact source file (JEP 512) — highlighting plus a Run ▶ that launchesjava --source NN <file>. Only applies when the extension didn’t already decide the language (never overrides a real type), and the status-bar language picker still overrides manually. -
Back / forward navigation (jump list). A browser-style history of caret jumps — going to a definition, a search hit, a file-history location, a diagnostic, or any go-to-line records where you were, and
Go: Back/Go: Forward(palette commands, user-bindable) walk the trail. A new jump after going back forks the history (drops the forward stack), consecutive same-file+line jumps are deduped, and the trail is capped. -
Word wrap. Toggle soft wrapping of long lines (
View: Toggle Word Wrap, Settings → Editor → “Word wrap”; default off, per buffer/window). The 80-column ruler stays visible when wrap is on. -
Plugin API Javadoc build.
./mvnw -Papidocs javadoc:javadocgenerates thecom.editora.pluginAPI docs and doclint-validates their references/HTML (a broken@linkin the public API fails the build). Also added the missing class header onApp(100% type-level Javadoc). -
Inline math (
$…$) exports as an image to PDF, DOCX and ODT. Previously only block$$…$$math embedded; inline math now renders to a PNG inline in all three exporters (falls back to literal text when math support is off). -
Markdown table of contents.
markdown.tocinserts a TOC (nested heading links, GitHub-style anchors) wrapped in<!-- toc -->markers, or regenerates it in place on re-run — palette command + the right-click Markdown menu. (Moves the formermarkdown-tocplugin into the editor core.) -
Markdown table ↔ CSV. Convert selected CSV (or the clipboard) into a GFM table (
markdown.tableFromCsv; delimiter auto-detected, RFC-4180 quoting) and copy the table at the caret to the clipboard as CSV (markdown.tableToCsv) — palette commands + the right-click Table menu. -
Smart Backspace clears an empty Markdown list/quote marker. Pressing Backspace at the end of an empty
-/1./>/- [ ]item now deletes the whole marker (→ a blank line), instead of removing one space at a time. -
Export the Markdown preview to MS Word (
.docx) and OpenDocument Text (.odt). From the preview right-click menu or thepreview.exportDocx/preview.exportOdtcommands — headings, text styling, lists, quotes, code, GFM tables, and local images..docxuses Apache POI;.odtis generated natively. Mermaid diagrams,$$…$$math, and images (incl. SVG badges) embed as pictures; the packaged installer build is supported (device-tested). -
Markdown table tools. Insert a table from a Typora/Word-style grid size-picker (format-bar button +
markdown.insertTable), add/delete the caret’s row or column, and set a column’s alignment (left/center/right) — via the editor right-click Table submenu and palette commands. Tab-to-next-cell and add-row-on-Enter already worked; tables stay auto-aligned after every edit. The command-palette Insert-Table is a keyboard-onlyRxCsize prompt (e.g.4x4), while the format-bar button and right-click menu use the mouse grid picker. -
Markdown format bar now uses icons (the same glyphs as the editor right-click menu) instead of letter labels, and gained a checkbox / task-list button that toggles a GFM
- [ ]item (commandmarkdown.taskList,C-c C-s k). -
Generic
rcconfig files now highlight as INI. An unrecognized runtime-config file whose name ends inrc(extension-less or a single-dot dotfile — e.g..vimrc,.inputrc,.screenrc,.curlrc,/etc/inputrc) is parsed as INI. Specific rc formats still win (.bashrc=shell,.npmrc=ini,.babelrc=json,.condarc=yaml). -
Jump to next/previous TODO —
todo.next/todo.previous(M-g ]/M-g [) move the caret between TODO/FIXME (highlight-pattern) matches in the active file, wrapping around. -
Clickable inline note marker — click a personal note’s amber start triangle to edit it (restores the click-to-edit the old gutter marker had).
-
notes.toggleResolved— resolve/reopen the personal note at the caret from the keyboard (was panel-only). -
Keybindings for
notes.editNote(C-c e),notes.search(M-g s), andundoHistory.jump(M-g v) — previously palette-only. -
Undo History popup. Besides the Undo History tool window, the new
undoHistory.jumppalette command opens the active buffer’s edit checkpoints as a filterable popup (caret-line preview + capture time); pick one to jump back to that state (undoable). Same checkpoints, a faster keyboard-driven path. -
Clear (✕) button on the Project / Current-Folder filter. A small clear button appears at the right of the filter box once you’ve typed something; clicking it empties the filter (restoring the full tree) and returns focus to the field. It’s hidden whenever the filter is empty.
-
“Beta” pills in the Settings sidebar. Still-beta features (Language Servers, Debugger, Web — HTTP client + HTML preview, Templates, Git, Remote, MCP) now show a small Beta pill beside their name in the Settings category list, so their maturity is clear at a glance.
-
One-click install completed for the binary-only language servers — C/C++ (
clangd), XML (lemminx), Kotlin (kotlin-language-server), Terraform (terraform-ls), and Lua (lua-language-server). Editora downloads the right per-OS/architecture release archive (from GitHub releases, or releases.hashicorp.com for terraform-ls), extracts it into~/.editora/plugins/lsp/<server>/, and points that server’s command at the extracted binary — so every supported language server now has an in-app installer (Install… button / banner / Install: Language Server… picker). With this, all 21 language servers across the npm, toolchain, and binary tiers are covered. -
One-click install extended to toolchain-based language servers: Go (
gopls), SQL (sqls), Ruby (ruby-lsp), C# (csharp-ls), Rust (rust-analyzer), and PHP (phpactor) join the Install… buttons / banner / Install: Language Server… picker. Each installs via that language’s own package manager —go install,gem install,dotnet tool install,rustup component add,composer global require— so they only run when the relevant toolchain (Go / Ruby / .NET / rustup / Composer) is already on your PATH; otherwise Editora tells you which toolchain to install first. -
One-click install extended to more language servers (npm batch): JSON, HTML, CSS (one package,
vscode-langservers-extracted), Bash/Shell, YAML, Dockerfile, and TOML now have Install… buttons on Settings → Language Servers, an in-editor install banner (when LSP is on but the server is missing), and a new Install: Language Server… palette command that lists every installable server. All install globally vianpm(needs Node). -
In-editor “install language support?” banner. When you open a Java/Python/JavaScript file (with the Language Servers feature on) whose language server isn’t installed — or a Mermaid file with the Mermaid feature on but no
mmdc— a slim banner appears above the editor offering Install (one click, with a progress spinner) or Not now (dismissed for that file this session). It only appears when the relevant feature is enabled, and you can turn the nudge off globally via the new Offer to install missing language support checkbox (Settings → Language Servers) or theview.toggleInstallPromptspalette command. -
Install buttons on the Settings → Language Servers and Mermaid pages. Each installable language server row (Java/Python/JavaScript) and the Mermaid page now has an Install… button that runs the same one-click install as the palette commands (downloading that language’s server + debugger, or the Mermaid CLI). The button shows Installed and disables once the tool is detected, updating live right after an install finishes.
-
One-click install of language support (LSP + debugger) for Java, Python, and JavaScript/Node, plus the Mermaid diagram CLI — cross-platform (macOS/Linux/Windows). New palette commands Install: Java / Python / JavaScript-Node Language Support and Install: Mermaid Diagram Tools download and set up each ecosystem’s language server and debug adapter for you, instead of hand-running scripts. Node-based tools (Pyright, typescript-language-server, mermaid-cli) install globally via
npm; debugpy installs viapipinto the config folder; java-debug downloads from Open VSX; the Java language server (Eclipse JDT-LS) and the Node debugger (js-debug) download into~/.editora/plugins/. After installing, the tools are auto-detected and activate without a restart. Editora never installs the underlying runtimes — if Node/Python/taris missing it tells you which one to install first. (An in-editor “install support?” banner follows.) -
“Compare with Commit…” in the editor tab’s right-click menu. Sits next to “Compare with HEAD” and opens the same commit picker as the
diff.vsCommitcommand (diffs the tab’s file against a commit you choose). Like the other Git items in the menu, it’s shown only when the file is inside a repo with Git enabled. -
--single-window[=project]command-line flag. Opens exactly one window at startup instead of restoring every window that was open at last quit: bare--single-windowopens the no-project window;--single-window=MyProjectopens that project’s window (falling back to the no-project window if no project matches that name). It’s session-only — it does not change your saved multi-window layout, so a normal launch afterwards restores all your windows as before (and quitting a one-window run won’t shrink the saved set). CLI file targets /--zen/--new-file/--simpleapply to the single window. -
Syntax highlighting for many extension-less dotfiles / named config files (matched by name via
ConfigFileType, so they also get the matching language server when enabled):- Shell —
.bashrc,.bash_profile,.bash_aliases,.zshrc,.zshenv,.zprofile,.zsh_aliases,.profile,.aliases,.kshrc, the.<rc>.localoverrides, and X-session scripts (.xprofile/.xinitrc/.xsession). (.bash_aliasesis standard — Debian/Ubuntu sources it;.zsh_aliasesisn’t auto-sourced by zsh but is a common user-chosen name.) - Ruby —
Gemfile,Rakefile,Guardfile,Capfile,Thorfile,Berksfile,Brewfile,Podfile,Vagrantfile,Fastfile,Appfile,Dangerfile(lockfiles likeGemfile.lockare intentionally left alone). - Groovy —
Jenkinsfile(andJenkinsfile.<env>). - INI —
.npmrc,pylintrc/.pylintrc,.flake8,.coveragerc,.pep8,.gitlint,.hgrc; git-config —.gitmodules. - YAML —
.clang-format,.clang-tidy,.condarc,.gemrc,.yamllint. - JSON —
.babelrc,.eslintrc,.prettierrc,.stylelintrc,.swcrc,.bowerrc(the bare forms; the explicit.json/.yaml/.jsvariants were already handled by extension).
- Shell —
-
The window title bar now shows the active file’s name + path. Format:
<file path> — <project> — Editora(e.g.~/src/app/Main.java — MyProject — Editora), with a•prefix for unsaved changes; an untitled buffer shows its name, and with no file open it’s just the project/app name. The path is home-collapsed (~) and updates on tab switch, save, Save-As, and rename. -
Linux
.deb: aneditoracommand onPATH, plus a proper menu entry + app icon. The Debian package now (1) creates a/usr/bin/editorasymlink to the installed launcher (jpackage otherwise installs it only at/opt/editora/bin/Editora, offPATH), so you can start Editora from a terminal with arguments — e.g.editora some/file.java:42; and (2) registers the bundled.desktopinto/usr/share/applications(injectingStartupWMClassso the running window matches it), so the application menu and dock show the Editora icon instead of the generic Java icon. Both are removed on uninstall. Implemented via custom DEB maintainer scripts (packaging/linux/postinst/postrm) passed throughjpackage --resource-dirin the installer-wrap step — these replace jpackage’s generated scripts, so they reproduce jpackage’s menu/icon registration in addition to the new symlink. The build (scripts/aot_build.java) also overwrites the app image’slib/Editora.pngwith our branding PNG before wrapping — jpackage’s app-image phase left its default Java icon there andjpackage --app-imageignores--iconat wrap time, so the menu/dock were showing the generic icon..rpmis unaffected (run/opt/editora/bin/Editora).
Fixed
- A “Compare with HEAD” diff no longer blanks its HEAD pane on refresh. In a window with no project open, opening a file’s vs-HEAD diff and then triggering a refresh (clicking an “apply change” chevron, or regaining window focus) could empty the left (HEAD) pane and render the whole working side as all-added. The diff’s HEAD side was re-fetched against the live repo root, which the Git status machine drops to “none” when the active tab is a diff tab with no backing buffer (a No-Project window has no project root to fall back to) — so the re-fetch ran against a null root and returned nothing. The diff now captures the repo root when it’s opened and reuses it on every refresh. Affected all Git-backed diffs (vs HEAD, vs commit, Git-panel staged/unstaged rows, a commit’s file, and the Local File History revision diff). Covered by a new FX regression test.
Changed
-
Perf: the Markdown minimap now coalesces scroll-driven repaints to one per pulse (was a synchronous repaint per scroll event); the completion popup’s prefix check reads a bounded window before the caret instead of materializing the whole document on every caret move.
-
Installing Mermaid support now also installs the headless Chrome it needs.
install.mermaidSupport(Settings → Mermaid → Install…) runsnpx puppeteer browsers install chrome-headless-shellfrom mmdc’s own dir afternpm i -g @mermaid-js/mermaid-cli, so diagrams render on a fresh Linux box without the “Could not find Chrome” Puppeteer error. -
Snippet choice dropdown (
${1|a,b|}) now uses the same keys as autocomplete:C-n/C-pto move,Tab/Enterto accept,C-gto cancel (Down/Up/Enter/Esc still work). -
Command palette ranks results by relevance. Matches are ordered exact > whole-word > word-start > substring > scattered-subsequence (tie-broken by shorter title), so e.g. typing
undonow listsEdit: Undofirst instead of a scattered match likeUnsplit Editor. -
Clearer unsaved marker in the window title. A modified file now shows a prominent leading
●in the title bar (was a small•), so the modified state is visible in Zen mode where the tab strip is hidden. (Native title bars are plain text, so the tab’s colour/italics can’t be reproduced there.) -
The tool-window consoles (External Tools, Run, Debug) now use the editor’s code-area font (family + effective size, tracking the zoom) instead of a fixed JetBrains Mono 12.
-
The Personal Notes inline indicator is 25% larger (the amber corner triangle at a note’s start), making it easier to spot; its click target grew to match.
-
Personal Notes tool window: bold file headers + a clear-filter button. File-name rows are now bold (matching Find-in-Files), and the filter has a trailing
✕button shown only when there’s text (like the Project panel). -
Personal Note editor: Markdown hint + delete confirmation. The note edit popup now shows a hint that the body supports Markdown formatting, and its Delete button asks for confirmation first (the body already rendered as Markdown in the hover tooltip).
-
The External Tools tool-window stripe is off by default. It’s registered default-hidden (like Undo History and Remote Sites); reach it via the
tool.externalToolscommand, theexternalTool.runpicker, or Settings → Tool Windows. The feature is unaffected. -
Refactor: pure text-editing operation cores moved to
com.editora.editops. The 10 toolkit-free editing/indent/navigation cores (Indenter,AutoClose,BraceMatcher,Commenter,Transposer,LineOps,EmacsEdits,SexpNav,Filler,LineIndent) and their tests moved out ofcom.editora.editor. Internal-only — no behavior, dependency, ormodule-infochange. -
Refactor: pure Markdown logic moved to a
com.editora.markdownpackage. The 11 toolkit-free Markdown cores (editing, lint, outline, image-paste) and their tests moved out ofcom.editora.editorinto their own package; the FX renderer and editor overlays stay. Internal-only — no behavior, dependency, ormodule-infochange. -
The “New File From Template” picker is bigger. Widened (and given a taller minimum) so the longer template descriptions are no longer clipped (the default width showed a horizontal scrollbar).
-
Personal Notes show an inline marker at the note’s start instead of a gutter icon. A small amber corner-triangle now marks where a note begins (the selected character/word/range), reinforcing that notes are anchored to the selection — not the line. The gutter note slot is gone; edit the note at the caret with the new
notes.editNotecommand (the inline highlight + hover tooltip + Notes tool window are unchanged). -
The Undo History tool-window stripe button is now off by default. With the new Undo History popup (
undoHistory.jump) as the primary entry point, the side-stripe icon is hidden until you enable it (Settings → Tool Windows, or thetool.undoHistorycommand). Existing sessions keep whatever visibility you had. -
Internal: moved the last Git blame UI bits into
GitCoordinator. The blame annotation engine already lived inGitCoordinator; the three remaining UI methods — thegit.toggleBlametoggle, the caret-lineblameShowCommit, and the gutter-annotation click (onGutterBlameClick) — moved there too, so the whole inline-blame feature now resides with the engine. The two surfaces those touch outside the coordinator route through newWindowOpscallbacks:syncBlameCheck()(re-sync the Settings checkbox) andopenCommitFileDiff()(open the line’s commit diff — routed through the window to reachDiffCoordinatorwithout a circular dependency).MainControllershed ~34 lines (8,716 → ~8,682); its blame command + gutter-click wiring delegate togit::toggleBlame/git::blameShowCommit/git::onGutterBlameClick. No behavior change — part of the ongoingMainControllerdecomposition. -
Internal: moved the Git clone flow into
GitCoordinator. Thegit.cloneUI — the URL + destinationOverlayInputform (with the<home>/<repo>auto-fill and the Browse picker), thegit clonecall, and opening a file from the fresh clone (openClonedEntry) — moved intoGitCoordinatoralongside the engine it already used (GitService.clone/gitError), ascloneRepo()(renamed offcloneto avoid clashing withObject.clone()). The pure/unit-testedrepoNameFromUrlhelper moved with it (GitCoordinator.repoNameFromUrl).MainController’s three clone callbacks — thegit.clonecommand, the empty-GitPanelbutton, and theBranchPopupNo-VCS action — now delegate togit::cloneRepo.MainControllershed ~131 lines (8,847 → ~8,716). Verified byGitStateFxTest(a new case asserts the clone form renders as an in-scene overlay through the coordinator wiring) + the moved unit tests. No behavior change — part of the ongoingMainControllerdecomposition (the remaining Git-Log / blame / branch-popup wiring is a follow-up). -
Internal: extracted
RemoteCoordinatorfromMainController. The Remote file access (SFTP) feature — the SSH connect form, the saved-connection picker, mounting a remote folder as the Project tree root, single-file open from ansftp://URI, and disconnect — moved into its ownCoordinatorHost-style coordinator. It owns theRemoteFileSystemsengine (the SSH client +Vfsresolver), theRemoteConnectionsPanel, and the mount state;MainControllerkeeps theremoteToolWindow(built with the coordinator’s panel), queries the coordinator for the current-folder view, and wires theremote.*/tool.remotecommands + the Welcome-page quick-connect callback to it.MainControllershed ~172 lines (9,019 → ~8,847). Verified by a new FX test that drives the connect flow on a real window and asserts the SFTP form renders as an in-scene overlay (no network). No behavior change — part of the ongoingMainControllerdecomposition. -
Internal: extracted
PluginCoordinatorfromMainController. The Plugin support feature — discovery/apply/install/uninstall plus the per-windowPluginContextadapter that lets a plugin contribute commands, keybindings, tool windows, status-bar segments, and editor menu items — moved into its ownCoordinatorHost-style coordinator. It owns the per-windowPluginRegistry/PluginInstaller, the started-plugin instances, the plugin-contributed tool windows + editor menu items, the “Browse Plugins” picker, and the two inner adapters (PluginContextImpl/ActiveEditorImpl). The sharedPluginManager(owned byWindowManager) is passed in;MainControllerkeeps thepluginManagerfield + thindisposePlugins()/pluginsEnabled()delegates and wiresapplyPlugins/ the tool-window gating / the editor-menu contributor / theplugins.*+view.togglePluginscommands to the coordinator.MainControllershed ~574 lines (9,578 → ~9,004). Device-tested live (MCP bridge): the example plugin’s Java-registered command, its tool-window toggle, and its declarative external command all loaded + ran through the movedPluginContextImpl.startpath. No behavior change — part of the ongoingMainControllerdecomposition (the largest single extraction in the campaign). -
Internal: extracted
HistoryCoordinatorfromMainController. The Local File History feature (per-file save/label/delete snapshots stored outside the file) moved into its ownCoordinatorHost-style coordinator: it owns theHistoryService(off-thread snapshot/blob engine) + theFileHistoryPanel, and the whole flow — record (save / autosave / external / pre-delete), the per-file + folder-history views, label / recent-changes pickers, and revision diff/restore (reusing the sharedDiffCoordinator).MainControllerkeeps the File HistoryToolWindow(built with the coordinator’s panel) and wires the record hooks + thehistory.*/tool.fileHistorycommands + the project-tree “Show Local History” item to it.MainControllershed ~300 lines (9,881 → ~9,578). Device-tested live (MCP bridge): saving a file recorded a revision intohistory/index.json, and the history commands ran clean. No behavior change — part of the ongoingMainControllerdecomposition. -
Internal: moved the Git operations into
GitCoordinator. The user-facing Git mutation operations that were still inMainController—gitOp/gitCommit/discardChanges, branch checkout/create, fetch/pull/push (gitSync), the stash commands, the active-file stage/unstage/discard, the commit-focus action, and thegitErrordialog — moved intoGitCoordinator(which already owned theGitService+ repo state), so the whole Git engine + its mutations now live in one cohesive coordinator.MainControllerkeeps the panels/tool-windows/branch-popup + command registrations (delegating togit.*).MainControllershed ~294 lines (10,175 → ~9,881). No behavior change. (The commit/branch/log/blame/clone UI wiring is a follow-up.) -
Internal: extracted
DiffCoordinatorfromMainController. The diff + merge-conflict viewer (theDiffService, the diff-tab open/refresh machinery, the “apply change” hunk flow through an undoable buffer with Undo/Save, the compare entry points — vs HEAD / vs commit / compare-with-file / Git-panel rows / a commit’s file — patch export, and merge-conflict resolution) moved into its ownCoordinatorHost-style coordinator. Git-backed diffs reach the repo via the sharedGitCoordinator;openDiff+ theDiffSideinterface stay package-visible so the Local File History flow reuses them.MainControllershed ~317 lines (10,492 → ~10,175). No behavior change — continuing theMainControllerdecomposition. -
Internal: extracted
DebugCoordinatorfromMainController. The whole DAP debugging integration (theDebugPanel, breakpoint persistence + gutter gating, the DAP event sink + panel actions, the inline-values / hover / execution-line editor surfaces, the start/attach/step/run-to-cursor/jump flows, and thedebug.toggleAdapter/debug.setAdapterPathpickers) moved into its ownCoordinatorHost-style coordinator. TheDapManagerstays aMainControllerfield (it’s built on the LSPlspManager, andSettingsWindow+ window-dispose reach it) and is passed to the coordinator, mirroring the LSP split; Java debugging layers on the jdtls session, so the coordinator also takes thelspManager+LspCoordinator.MainControllershed ~565 lines (11,057 → ~10,492). This completes theMainControllerdecomposition campaign (13,017 → ~10,492). No behavior change. -
Internal: extracted
LspCoordinatorfromMainController. The whole LSP integration moved into a newCoordinatorHost-style coordinator across three slices: the navigation/format flows (go-to-definition, find-references, hover, document formatting + the hover popup); the diagnostics routing (the per-open-fileproblemsmap, theProblemsPanel, thepublishDiagnosticscallback); and the configure/detect/gating + per-buffer lifecycle (applySupport/server detection/the per-server enable+command switches/syncBufferopen-activate-close/the status-barLSP:segment/the Structure outline/semantic tokens/the per-buffer hook wiring/thelsp.toggleServer+lsp.setServerCommandpickers). TheLspManagerstays owned byMainController(Debug layers on the jdtls session, the MCP bridge reads its diagnostics) and is passed to the coordinator; its callbacks route through thin method-ref delegates.MainControllershed ~685 lines (11,742 → ~11,057). No behavior change — part of the ongoing decomposition. -
Internal: extracted
BookmarkCoordinatorfromMainController. The Bookmarks feature (the panel + jump picker, per-buffer persist/restore, the gutter-click flow, and thebookmarks.*commands) moved into its ownCoordinatorHost-style coordinator. No behavior change — part of the ongoingMainControllerdecomposition. -
Internal: extracted
NotesCoordinatorfromMainController. The Personal Notes feature (the panel + cross-file pickers, per-buffer persist/restore with anchor re-keying, the note editor dialog, and thenotes.*flow) moved into its ownCoordinatorHost-style coordinator. No behavior change — part of the ongoingMainControllerdecomposition. -
Internal: documented soft code-size limits in
CLAUDE.md. Added review heuristics for class/method size (target ≤1000 lines/class, ≤50/method; refactor-smell beyond ~2000/~100) and codified theCoordinatorHostdecomposition as the way to keepMainController(and future features) in check. Docs only — no tooling/behavior change. -
Internal: extracted
RunCoordinatorfromMainController. The run-a-file feature (the run service + console panel, the run/rerun/stop flow, and the launcher-command building) moved into its ownCoordinatorHost-style coordinator. No behavior change — part of the ongoingMainControllerdecomposition. -
Internal: extracted
SearchCoordinatorfromMainController. The Find-in-Files feature (the multi-file search service + results panel, scope tracking, replace-in-files, and the ripgrep/walker backend selection) moved into its ownCoordinatorHost-style coordinator. No behavior change — part of the ongoingMainControllerdecomposition. -
Internal: extracted
TodoCoordinatorfromMainController. The TODO / highlight-pattern feature (service + tool-window panel + the in-editor highlight matcher + the project/open-files scan +todo.*commands) moved into its ownCoordinatorHost-style coordinator. No behavior change — part of the ongoingMainControllerdecomposition. -
Internal: extracted
MacroCoordinatorfromMainController. The keyboard-macro feature (per-window service + record/replay capture hooks +macro.*commands) moved into its ownCoordinatorHost-style coordinator. No behavior change — part of the ongoingMainControllerdecomposition. -
Internal: extracted
ExternalToolCoordinatorfromMainController. The External Tools feature (service + console panel + run/output logic +externalTool.*commands) moved into its ownCoordinatorHost-style coordinator, trimming theMainControllergod-class. No behavior change — first step of an ongoingMainControllerdecomposition. -
The Remote Sites tool window is off by default and opens on the right. Its stripe button is now hidden until you enable it (Settings → Tool Windows, or the
tool.remotecommand /M-g r), and when shown it docks on the right. The remote (SFTP) functionality is unchanged — only the tool window’s default visibility and side moved. Existing layouts (if you’d already shown or moved it) are preserved.
Added
-
Open the session log from Settings → Advanced. Added a “Session log” link next to the settings-file path that opens
editora-session.log(the captured logging + uncaught exceptions for the current session) in the editor — handy for grabbing diagnostics for a bug report. -
Open the dictionary files from Settings → Spell Check. Two links (grouped near the top of the page): “Open the technical.txt dictionary” opens the built-in technical dictionary read-only in a new tab so you can browse what it covers, and “Open dictionary.txt…” opens your personal dictionary file in the editor (created empty if you haven’t added a word yet).
-
Large source files stay responsive (intermediate large-file tier). A very long single file (e.g. a 13k-line source) below the 5 MB hard cutoff used to get the full treatment — syntax highlighting plus the minimap (which redraws every line) plus a language server churning over a huge AST — which made it slow to load and scroll. There’s now an intermediate tier: above a configurable line count (Settings → Editor → Performance → “Disable minimap & LSP above”, default 10000 lines,
0= never), the minimap and language server auto-disable while syntax highlighting and full editing stay on. A status note reports it on open, and you can flip it per file from the palette (Toggle Large-File Mode). Paletteeditor.setLargeFileThresholdchanges the threshold;view.toggleLargeFileModeoverrides per buffer. -
The TODO tool window shows the folder it’s scanning. Like Find in Files, the TODO panel now has a “Scanning in:” line at the top showing the scope — the project root when a project is open, otherwise open files only (full path on hover).
-
The TODO scan skips
.gitignored files and folders. Scanning a project for TODO/FIXME markers now excludes the paths your.gitignoreexcludes (target/,node_modules/, build output,*.log, …), so the TODO panel stops filling with generated-code noise. Governed by the same Settings → Search → “Exclude .gitignore files and folders from search” toggle as Find in Files. -
Find in Files shows the folder it’s searching. The Find-in-Files panel now has a “Searching in:” line at the top showing the exact scope — the project root when a project is open, otherwise the current file’s folder (with the full path on hover). Non-project searches now actually scan that folder on disk (previously they only matched open buffers), and the scope updates as you switch tabs.
-
Find in Files skips
.gitignored files and folders. Searching a project now excludes the paths your.gitignoreexcludes —target/,node_modules/,build/,dist/,*.log, etc. — so build output and dependencies stop cluttering results. New setting Settings → Search → “Exclude .gitignore files and folders from search” (default on; paletteview.toggleSearchGitignore). The ripgrep backend already honored.gitignore; the built-in walker now does too (via a new pragmatic.gitignoreparser), and turning the setting off searches everything (rg--no-ignore). -
Mexican Spanish spell-check dictionary (
es_MX). Added a Spanish (Mexico) dictionary alongside the existing Spanish (Spain) one — both from the RLA / LibreOffice project, redistributed under MPL-1.1. Choose it per file via Spell Check: Set Language… or as the default in Settings → Spell Check; the existingesentry is now labelled “Spanish (Spain)”. -
Technical-terms dictionary for spell check. A bundled list of common programming / web / data / devops words that standard prose dictionaries flag as misspellings (
config,async,boolean,middleware,kubernetes,stdin,json, …) is now accepted by the spell checker, so code-adjacent prose stops getting peppered with red squiggles. Toggle it in Settings → Spell Check → “Enable technical dictionary” (default on) or via the palette commandToggle Technical Dictionary(view.toggleTechnicalDictionary). It layers on top of the language dictionary and your personal dictionary, each independently switchable. -
Command-palette parity for actions that were UI-only. Every remaining click-only action now also has a registered, keybindable palette command:
- Git Log commit operations (Checkout / New Branch / Revert / Cherry-Pick / Reset… / Copy Hash) →
git.log.*, acting on the selected commit. - Debugger data inspection →
debug.evaluate(open console + focus the eval field) /debug.addWatch/debug.setValue. - Diff viewer toolbar →
diff.toggleView(unified ↔ side-by-side) /diff.applyAll/diff.nextChange/diff.previousChange, acting on the active diff tab. - Find/Replace bar →
find.next/find.previous/find.replaceCurrent/find.replaceAll(open the bar if it’s closed). - Git working-file ops →
git.unstageFile/git.discardFile(active file; discard confirms first), mirroring the existinggit.stageFile. - Console Clear →
run.clear/externalTool.clearOutput.
- Git Log commit operations (Checkout / New Branch / Revert / Cherry-Pick / Reset… / Copy Hash) →
-
Macros management in Settings. A new Settings → Macros page lists your saved keyboard macros and lets you rename them, edit/reorder/remove their steps (add command or text steps), delete them, and assign a keybinding inline — all in one place.
-
Snippet & template body editors got syntax highlighting + Emacs keys. The Snippets and File Templates pages in Settings now edit the body in a real code editor (RichTextFX
CodeArea) with live syntax highlighting for the chosen language and basic Emacs caret movement (C-a/C-e/C-f/C-b/C-n/C-p/M-f/M-b/C-d/C-k), instead of a plain text box. Also fixed the form-field labels that could collapse to “…” on the Snippets/Templates/External-Tools pages. -
Reorganized the Settings window. The flat 22-item sidebar is now grouped into five sections (General · Editor · Languages & Tools · Version Control · System) with non-selectable headers and indented items. The overloaded Editor page was split (Code Completion and TODO are their own pages), the Application grab-bag became Interface (window chrome + Simple/Zen modes) and Workspace (projects, notes, local history), HTTP Client + HTML Preview merged into Web, and the author field moved to Templates. Search now also hides empty group headers. Every setting is preserved — only the organization and presentation changed.
-
Saved remote sites UI. Your saved SFTP connections (in
connections.json) now have three visible surfaces beyond the palette picker: a Remote Sites tool window (M-g r) listing every saved site with New / Connect / Remove, a Settings → Remote page to add/edit/remove them (label, host, port, user, auth, key path), and a Remote Sites quick-connect list on the Welcome page. Picking a site opens the connection form pre-filled (your password/passphrase is still prompted and is never stored). CommandsRemote Sites(tool.remote) andManage Remote Sites…(remote.settings). -
Personal dictionary editor. Settings → Spell Check now has a Personal Dictionary section listing your accepted spell-check words (from
dictionary.txt) with add/remove and an Enable personal dictionary checkbox (on by default; off re-flags words you’ve added) — so you can prune, add, or temporarily ignore words without editing the file by hand. Opens via the paletteSpell: Manage Personal Dictionary…(spell.manageDictionary) and the toggleview.togglePersonalDictionary; changes apply globally across all languages. -
File template management GUI. A new Settings → Templates page lists your file templates — the bundled (shipped) ones too, tagged read-only — alongside your own. Editing a bundled template writes a user override to
<configDir>/templates/<id>.json(the shipped one is never modified); add/remove your own with an id / name / description / language / file-name / body form. Opens via the paletteTemplates: Manage File Templates…(template.manage); multi-file templates are shown read-only (edit viaEdit User Templates). The raw-JSON command still works. Both the Snippets and Templates pages also got a taller list and an explicit Save button (edits still auto-save on Enter/focus-loss). -
Undo History tool window (
M-g u) — an in-session timeline of document checkpoints captured as you edit (one per typing burst); double-click or Enter to jump back to any recent state (a single undoable restore). Finer-grained than save-based Local History, session-only, and disabled for very large files. -
Word/line-level undo. Undo no longer collapses an entire typing burst into one step — one
C-znow undoes a word or line. The undo manager starts a new group at word/whitespace/newline boundaries and after a short typing pause (idle break), matching VS Code/IntelliJ. (First of a planned undo arc; an undo tree and a history panel are next.) -
Snippet management GUI. A new Settings → Snippets page lists the snippets for a language — the bundled (shipped) ones too, tagged read-only — alongside your own. Editing a bundled snippet writes a user override to
<configDir>/snippets/<lang>.json(the shipped one is never modified); add/remove your own snippets with a name / trigger / description / body form, applied live. Opens via the paletteSnippets: Manage Snippets…(snippets.manage); the raw-JSONEdit User Snippetscommand still works. -
Sample corpus for manual feature testing. A new top-level
samples/folder holds small, curated text samples organized by feature (syntax per language, folding, indent, markdown, mermaid, todo, spell, search, editorconfig, http, log, diff, encodings) — open the relevant file to smoke-test a feature.samples/README.mdis the manifest;SamplesCorpusTestkeeps it in sync with the files on disk. Large perf inputs are generated on demand byjava scripts/GenSamples.java(git-ignored, never committed), and a scoped.gitattributespreserves the encoding/EOL samples verbatim. -
Syntax highlighting for common project config files.
.editorconfig(INI),.gitignore(and other.*ignorefiles, via a newignoregrammar),mvnw/gradlew(shell), and Eclipse.classpath/.project(XML) are now recognized by name and highlighted — wired through the sharedConfigFileTyperesolver so the grammar and language registries stay in sync. -
Local History, closer to IntelliJ. Three additions to the File History tool window: selective restore — the snapshot-vs-current diff is now editable on the current side, so the apply-chevrons copy individual fragments from a revision back into the file (undoable) instead of only a whole-file revert; named snapshots — a “Put Label” command (
history.putLabel) records a labeled point in time (shown bold in the list, even when the content is unchanged); and search + recent changes — a filter field over the revision list (by label/reason/time) plus ahistory.recentChangespicker of the most recent revisions across the whole project. (history/index.jsonschema 1→2, additive.) -
Local History: folder view + recover a deleted file. Right-click a folder in the Project tool window → Show Local History lists every file under it that has recorded revisions, with deleted files badged; a revision’s “Restore This Version” writes it back to disk (recreating the file). Deleting a file from the project tree now snapshots it into history first, so an accidental delete is recoverable. (Recovery covers files deleted in Editora or that already had edit history — not files deleted by an external tool that were never opened.)
Fixed
- Tool-window sizes are now remembered when you quit with them open. Resizing a tool window (e.g. the Project panel) and quitting with it still open used to reset its divider to the default on next launch — the position was only captured when a tool window was hidden, never on quit. The session now captures the live divider positions on quit, so the layout comes back as you left it. (Window size/position already persisted.)
- Minimap no longer shows a second, stale viewport box on large files. The minimap’s content render calls
canvas.snapshot(), which forces a synchronous layout pass that could settle the editor’s viewport and re-enter the minimap’s redraw mid-render — painting a duplicate viewport highlight that lingered. The minimap now guards against re-entrant paints, so exactly one viewport box is drawn. - Tidier, better-fitting Settings → Templates page. It showed “Templates” three times (page title + two section headers) and its body editor was tall enough to overflow the window. The section headers are now “Author” and “Manage templates”, and the template/snippet body editors use a more modest height (they still grow to fill a taller window).
- Settings page titles no longer shave the bold heading. The big bold heading on each Settings page (e.g. “Markdown”) looked like it was missing slivers of its letters — a JavaFX
Labelclips its text to the glyphs’ logical (advance-based) bounds, which cuts the outer strokes of bold faces (pipeline- and font-independent). The heading is now aTextnode withTextBoundsType.VISUAL, which renders the full glyph ink without clipping. - EditorConfig BOM charset no longer truncates files on open. When a project’s
.editorconfigdeclaredcharset = utf-8-bom(orutf-16le/utf-16be) but a file on disk had no actual byte-order mark, the first 2–3 content bytes were silently dropped on open — and saving re-added a BOM, corrupting the file.EditorConfigCharset.decodenow strips the BOM only when the bytes really start with it. - Typing a closing bracket over a selection no longer silently deletes it. With text selected, typing
)/]/}where the next character was that same bracket “typed over” it and discarded the selection without inserting anything. It now replaces the selection like normal typing. (AutoClose.decidegates skip-over on having no selection.) - Regex “Replace All” now respects the whole-word toggle. With both Regex and Whole word enabled, Replace All replaced every unbounded occurrence instead of the
\b-bounded matches it had highlighted and counted. It now uses the sameSearchMatcher.compileRegexas the search/highlight path. - A hung
javaccan no longer freeze debugging. The loose-Java compile-and-debug fallback now runs the compile throughProcessRunnerwith a 60-second timeout (and concurrent pipe draining), so a stuck compiler can’t pin the single-threaded debug-adapter executor and block every later debug start.
Changed
-
Headless FX tests now use JavaFX 26’s built-in headless platform. The
@Tag("fx")harness boots over the Headless Glass platform that ships insidejavafx.graphics(-Dglass.platform=Headless) instead of the self-built Monocle backend. The vendoredopenjfx-monoclejar (and the rebuild-on-every-JavaFX-bump ritual it required) are gone — nothing extra to download or maintain, and the backend can no longer go stale on a JavaFX bump. Test-scope only; no runtime, packaging, ormodule-infoimpact. The harness only uses TestFX to boot the toolkit (never the robot), so the prototype platform’s input/rendering limits don’t apply. -
Command docs now open the version-matched page. The command palette’s “open docs” (C-h) now links to
…/docs/v-<appVersion>/commands/<command-id>, matching the website’s new versioned docs layout, so each build opens the docs for the version that’s actually running. -
De-duplicated the plugin examples. The full plugin catalog lives canonically in the adriandeleon/editora-plugins repo (source + signed registry index), so the editor repo no longer keeps copies under
examples/— those had already drifted from the registry. Onlyexamples/example-plugin/remains as the in-tree SDK reference;docs/plugins.md, the README, andCLAUDE.mdnow point the catalog at the registry repo.
Added
-
Plugin API:
needsBufferopt-out for tool windows. Plugin tool windows are gated on an open editor buffer by default (they act on the active editor), but a newregisterToolWindow(…, icon, needsBuffer)overload lets a self-contained tool window (scratchpad, calculator, color picker, …) passneedsBuffer = falseso its stripe button stays available on every tab, including Welcome. The existing overloads are unchanged (they default toneedsBuffer = true). -
Markdown lint, substantially expanded. The Markdown linter now covers more of the markdownlint rule set — MD001 (heading-level increment), MD010 (hard tabs), MD022 (headings surrounded by blank lines), MD023 (heading indent), MD026 (heading trailing punctuation), MD031 (fenced code surrounded by blank lines), MD034 (bare URLs), and MD041 (first line should be a top-level heading) — joining the existing MD009/012/018/019/025/040/047/052. New capabilities:
- Per-rule configuration. Turn individual rules off in Settings → Markdown (a per-rule
checklist) or via the
markdownLint.toggleRulepalette picker; persisted insettings.toml(markdownLintDisabledRules, schema 39→40). - Inline disable comments.
<!-- markdownlint-disable [MDxxx] -->/enable/disable-line/disable-next-lineare honored, matching markdownlint. .markdownlint.jsondiscovery. The nearest config file (walking up from the file) is merged into the disabled-rule set ({"MD013": false}/{"default": false}forms).- Auto-fix.
markdownLint.fix(palette) corrects the safely-mechanical issues in the active file (trailing whitespace, tabs→spaces, blank-line runs, heading-marker spacing, heading indent, heading trailing punctuation, final newline) as one undoable edit. - Overview stripe + minimap ticks. Lint warnings now appear as an IntelliJ-style stripe over the scrollbar (click to jump, hover for the rule + message) and as ticks on the minimap, beside the TODO and LSP-diagnostic stripes.
- Dedicated Settings page. The Markdown options (format bar, math preview, lint enable + per-rule checklist) moved out of Editor into their own Settings → Markdown page.
- Per-rule configuration. Turn individual rules off in Settings → Markdown (a per-rule
checklist) or via the
-
Export to HTML opens the result in a tab. After Preview: Export to HTML writes the file, the generated
.htmlnow opens in a new editor tab (re-selecting it if already open). -
Project tool window can show hidden files. A new Settings → Tool Windows → “Show hidden files in the project tree” checkbox (and the
view.toggleProjectHiddenpalette command) reveals dot-files/folders in the project tree and its filter search. Off by default. Persisted insettings.toml(schema 38→39).
Fixed
-
“Add to Dictionary” now persists to
dictionary.txt. The added word was applied for the current session but never written to disk, so it reappeared as misspelled after a restart. The buffer added the word to the shared dictionary set before calling the persist callback, andaddUserWordonly writes the file when the word is newly added to that (same) set — so the write was always skipped. The word is now persisted on the first add. -
Structure tool window no longer jumps the editor back while navigating. A language server that re-announces readiness repeatedly (jdtls re-sends
language/status“ServiceReady” on workspace events) re-pushed the same document symbols, rebuilding the outline tree, resetting the selection to the first row, and snapping the editor back to the top of the file as you clicked around. Re-pushing an unchanged outline is now a no-op, and a genuine rebuild preserves the symbol you had selected. -
Java LSP (jdtls) now initializes reliably; completion works. jdtls was launched without a
-dataworkspace, so every project shared one default Eclipse workspace and deadlocked on its.lock(contending with a leaked previous run or another editor) — the server never finishedinitialize, leaving the status-bar loading bar spinning forever and autocomplete dead. Each project root now gets its own jdtls workspace under<config>/jdtls-workspaces/<hash>(its index persists across sessions, so later opens are faster). A user-supplied-datain the configured command is respected. A language server’s stderr is also now captured into the Debug Log (it was discarded), so when a server fails to start the reason — missing JDK, a locked workspace, a bad command — is finally visible. -
Opening a large source file no longer freezes the UI. Computing fold regions on open recreated a gutter graphic for every fold header in the file (thousands, for a big file like a 12k-line Java source) on the JavaFX thread, blocking it for several seconds. The fold recompute now only refreshes the chevrons of currently-visible rows — offscreen rows get the correct fold state when the gutter factory builds them on scroll — cutting the per-open cost from seconds to a few milliseconds.
-
Breadcrumb dropdown uses the same file icons as the Project tool window. The folder dropdown on a breadcrumb crumb now shows per-type file glyphs (and the boxed folder icon) via
FileIcons, instead of a single generic file icon — matching the Project tree. -
The Git Log tool window refreshes when opened. Opening it — via the stripe icon (which toggles the window directly) or a command — now reloads the commit history, so it no longer shows stale commits from a previous open. (Opening via the stripe button previously bypassed the load entirely.)
-
Tool windows that act on the active file now hide on non-buffer tabs. The Git Commit and Git Log stripe buttons — and plugin tool windows (e.g. GnuPG, the Regex tester) — only appear when there’s an open editor buffer to act on; on the Welcome tab (or any non-buffer tab) they’re hidden, matching how the Structure/File-Info/TODO windows already behave. Commit/Log additionally stay gated on being inside a repo.
-
Find/Replace bar buttons disable when there’s no search term. Prev, Next, Replace, Replace-All, and the case/regex/whole-word toggles are now disabled while the Find field is empty — nothing they do is meaningful without a query (an empty replacement still works as “delete matches”).
-
Buffer-only tool windows are hidden when there’s no buffer to act on. The Structure, File Information, TODO, Markdown Lint, and External Tools stripe buttons no longer show on the Welcome page (or other non-buffer tabs) — they now gate their availability on an actionable active editor buffer (Markdown Lint additionally needs a Markdown buffer; External Tools needs a buffer and non-Simple mode), mirroring how Run / Debug / HTTP / Commit already gate their buttons.
-
Settings → Editor → “Enable log viewer” checkbox now reflects the actual setting. It was omitted from the Settings window’s
load()(the value was only synced insyncViewChecks(), whichload()doesn’t call), so the box always rendered unchecked even though the log viewer was on. The feature itself was unaffected — only the checkbox display.
Changed
- More features on out of the box (new installs only). The default
settings.tomlnow enables: Breadcrumbs, Personal Notes, Git integration (self-gating — inert untilgitis on PATH, so effectively “on when Git is installed”), HTML Live Preview, the HTTP Client, Render LaTeX math in Markdown, and Mermaid diagrams (self-gating — inert until themmdcCLI is found). The server-log viewer was already on. The built-in TODO/FIXME highlight patterns are now case-sensitive (the uppercase/lowercase check is on), so onlyTODO/FIXMEmatch, nottodo/fixme. These are defaults for a fresh config — existingsettings.tomlfiles keep their saved values; flip any of these in Settings or via the palette toggles.
Added
-
HTTP tool window auto-shows for
.httpfiles. Opening (or switching to) a.httpbuffer now automatically opens the HTTP Client tool window, which defaults to the right side. Switching to another tab auto-hides it (restoring any right-side window it displaced) and returning re-opens it; a manual close is remembered per file — once you close it for a.httpbuffer it stays closed until you reopen it (running a request or toggling it back on re-enables auto-show). -
Find in Files improvements. The Find-in-Files tool window (
C-S-f,M-6) gains: include/exclude file globs (comma-separated,.gitignore-style — e.g. include*.java, src/**, exclude**/test/**), applied by ripgrep (-g/-g !) and by the Java-walker fallback + open-buffer overlay alike; query history (the query field is now an editable dropdown of recent searches, persisted insearch-history.json); regex capture groups in replace ($1/${name}in regex mode viaMatcher.replaceAll, with a bad-reference guard; literal mode still inserts$verbatim); a ripgrep badge shown in the panel when that backend is the active one; bold file-name rows in the results tree; single-click / keyboard selection opens the match (a focus-keeping preview, like the Structure tool window — double-click / Enter still dives into the editor); and the window now defaults to the right side (existing sessions keep their saved position).
Changed
-
Refactor: extract the
git pushargv decision into a pure, testedGitService.pushArgs(developer-facing). The first-push-of-an-upstream-less-branch logic (--set-upstream origin <branch>so the first push “just works”, else a plainpush) — including the guard that a blank/detached branch never emits--set-upstream origin <empty>— moved out ofMainController.gitPushinto a pure static helper with a focusedGitPushArgsTest(6 cases). No behavior change; the decision is now unit-covered instead of buried in the UI layer. -
Refactor: centralize the “no repo” guard on the
GitCoordinator(developer-facing). Every repo-only Git operation opened with the same four-line guard —if (repoRoot == null) { echo "not a repo" / "git not installed"; return; }— duplicated 11×. It’s now one engine method,git.reportIfNoRepo(), and each call site is a one-liner (if (git.reportIfNoRepo()) return;). The status string lives in exactly one place. Pure consolidation, no behavior change; full suite green. -
Refactor: move inline Git blame into the
GitCoordinator(developer-facing). The blame annotation engine — eligibility (isBlameEnabled), the off-thread per-file fetch (applyBlame/refreshBlame), and the blame→BlameInfomapping (author/date label, full-commit tooltip, age-heatmap tint) — moves out ofMainControllerintoGitCoordinator, whereapplyStatenow drives it directly. That removes theWindowOps.refreshBlameHost hop added when the engine was extracted: the coordinator owns blame end to end and needs nothing new from the window (only the sharedCoordinatorHost). The click→commit-diff actions (blameShowCommit/onGutterBlameClick) stay inMainControllersince they open a diff tab. No behavior change; full suite green. -
Refactor: extract the stateful Git core into a
GitCoordinator(developer-facing). Under the net added in the previous step, the engine of the Git integration moves out ofMainControllerintoui/GitCoordinator: theGitServiceCLI facade, the repo state (root / branch / upstream), and the state machine that keeps the status bar, the Commit / Log tool windows, and the active buffer’s gutter change bars in sync (isEnabled/isAvailable/ifEnabled/applySupport/refresh/applyState/afterMutation/contextPath). It reaches the window through the sharedCoordinatorHostplus a small git-specificWindowOps(status-bar/tool-window/blame/diff surfaces). The user-facing Git operations (commit, branch, log, blame, stash, clone, diff) stay inMainControllerand reach the engine viagit.service()/git.repoRoot(). No behavior change;GitStateFxTestre-pointed at the new home with the same assertions, full suite green. -
Refactor: a characterization-test net + pure helpers for the Git integration (developer-facing). First step of the Git extraction (the largest, least-tested subsystem): before moving any stateful code, this adds a safety net —
GitStateFxTestdrives theapplyGitState/applyGitSupportcore directly with a syntheticRepoStateand pins the repo-root/branch/upstream state machine, and a new pure, unit-testedgit/GitChangeBars(the diff→CSS-class mapping + the background-buffer re-diff predicate) dedups the gutter change-bar logic at its two call sites. No behavior change; sets up the subsequentGitCoordinatorextraction under the net. -
Refactor: extract the HTTP Client into an
HttpClientCoordinator(developer-facing). The fourth (and most window-entangled) feature coordinator peeled offMainController: the request runner, the response tool-window panel, response-tab opening, clipboard curl import/export, and.envscanning move toui/HttpClientCoordinator. It takes the sharedCoordinatorHostplus a smallWindowOpsextension (open a tab / open-toggle the tool window / re-gate the run affordance / persist the selected environment);MainControllerkeeps only theToolWindowregistration + availability plumbing. Thanks to the shared host, this dropped ~210 lines fromMainController(vs ~60 for the earlier ones). Behavior is identical; covered by a newHttpClientCoordinatorFxTestagainst fake host + ops. -
Refactor: a shared
CoordinatorHostfor the feature coordinators (developer-facing). The three feature coordinators (log viewer / HTML preview / Mermaid) had a bespoke ~60-line anonymousHostadapter each inMainController. They now share oneui/CoordinatorHostinterface and a singleMainController.Servicesadapter, dropping the per-feature boilerplate — and future coordinators reuse it for free (zero adapter code). Test fakes extend a no-opCoordinatorHostStuband override only what they exercise. No behavior change; full suite green. -
Refactor: extract Mermaid into a
MermaidCoordinator(developer-facing). The third feature coordinator peeled offMainController: themmdc/maidservice, the detected-tool availability, and the apply/gating/export logic now live inui/MermaidCoordinator, behind a narrowHostinterface. The few mermaid reads other features need are exposed as coordinator methods (mmdcCommandOrNull()for PDF/print,effectiveAutocomplete()for the completion config,exportDiagram()for PDF export). Behavior is identical; covered by a newMermaidCoordinatorFxTestagainst a fakeHost. Continues de-goddingMainControllerone feature at a time. -
Refactor: extract HTML Live Preview into an
HtmlPreviewCoordinator(developer-facing). The second feature coordinator peeled offMainController(afterLogViewerCoordinator): the loopback HTTP server, the detected-browser list, and the browser picker now live inui/HtmlPreviewCoordinator, reaching the window only through a narrowHostinterface (an anonymous adapter inMainController+ one-line delegations at the call sites). Behavior is identical; covered by a newHtmlPreviewCoordinatorFxTestagainst a fakeHost. Continues de-goddingMainControllerone feature at a time. -
Refactor: extract the log viewer into a
LogViewerCoordinator(developer-facing). The first feature coordinator split out of the ~13k-lineMainController: the server-log-viewer state (tail service + per-buffer follow/offset/control maps) and all its logic now live inui/LogViewerCoordinator, which reaches back into the window only through a narrowHostinterface.MainControllersupplies an anonymousHostadapter and keeps one-line delegations at the call sites (open/save-as/rename, tab close, settings apply, huge-file load, command registration), dropping ~240 lines. Behavior is identical; the payoff is a smaller controller and a unit-testable seam — the newLogViewerCoordinatorFxTestexercises the coordinator against a hand-written fakeHost. This establishes the template for de-goddingMainControllerone feature at a time. -
Upgraded to JavaFX 26 + enabled the macOS Metal rendering pipeline. On Apple silicon (e.g. M-series), the deprecated es2/OpenGL backend JavaFX 25 used could render parts of the UI as blank/garbled rectangles in the packaged app (the file breadcrumb and tool-window stripe icons). JavaFX 26 adds the Metal pipeline, now selected first on macOS (
-Dprism.order=mtl,es2,sw; es2/sw fallback). Windows keeps Direct3D, Linux keeps es2 — the pipeline order is set per-OS. The JDK stays 25 (JavaFX 26 runs on JDK 24+). -
Performance: config writes happen off the JavaFX thread (developer-facing). The frequent, coalesced in-session save (
requestSave— fired by settings toggles, view changes, tab/fold changes) now serializes a consistent snapshot on the FX thread and writes it on a dedicatedconfig-writerdaemon thread, instead of blocking the UI onsettings.toml+workspace-state.jsondisk I/O. A newconfig/ConfigWritercoalesces writes per file (latest wins) and keeps them ordered through one thread, with an atomic temp-file- move so a crash never leaves a half-written config. Durable saves (quit, one-off actions, export) and a
JVM-shutdown flush block until written, and all settings/session writes funnel through the one writer
queue, so an async write can never land after and clobber a durable one. No behavior change; covered by a
new unit test (
ConfigWriterTest).
- move so a crash never leaves a half-written config. Durable saves (quit, one-off actions, export) and a
JVM-shutdown flush block until written, and all settings/session writes funnel through the one writer
queue, so an async write can never land after and clobber a durable one. No behavior change; covered by a
new unit test (
-
Performance: lazy-attach feature overlays (developer-facing). Each editor buffer no longer eagerly builds the five overlays that are inert for most files — find-match highlight, log-level, Mermaid-lint, LSP-diagnostic, and AceJump. They’re constructed (Canvas + scroll/edit subscriptions) only on first activation and inserted in their original z-order, so a buffer with LSP off, no diagram, not a log, and never searched/ace-jumped allocates none of them. Cuts per-buffer memory and per-pulse listener fan-out when many tabs are open; no behavior change. Covered by a new headless-FX test (
LazyOverlayFxTest).
Added
-
Server log viewer. Opening a
.logfile now gets log-aware treatment: level highlighting (ERROR/ WARN/INFO/DEBUG/TRACE coloured inline and as a left-edge bar that works even on huge files where syntax highlighting is off), Follow tail (tail -f— a floating toggle streams new lines as the file grows on disk and auto-scrolls to the bottom), open-the-tail for very large logs (a multi-GB log opens read-only at its end instead of its start), and live level + regex filtering (a floating control narrows the view to e.g.WARN+or lines matching a pattern as you type — the query is a case-insensitive regex that falls back to a literal substring when it isn’t valid one; a stack trace inherits its record’s level so it stays visible). Detection covers Logback/Log4j,java.util.logging, syslog, nginx, structuredlevel=…/JSON logs, zerolog, and Apache/Nginx access logs (5xx→error, 4xx→warn). Log files open in View mode (read-only with an Enable-Editing banner) by default, since they’re for reading — follow still streams while read-only. On by default for.logfiles (Settings → Editor → Logs, orView: Toggle Log Viewer); the rest is per-buffer via the floating control or the palette (Log: Toggle Follow,Log: Filter by Level,Log: Filter by Pattern,Log: Clear Filter,Log: View as Log). No new dependency. -
Ripgrep backend for Find in Files. When ripgrep (
rg) is installed, project-wide Find in Files uses it for a much faster,.gitignore-aware search; otherwise the built-in walker is used unchanged. It’s automatic (on whenrgis detected) and falls back silently when rg is absent, the regex is one rg can’t compile, or the project is remote (SFTP). Unsaved edits in open buffers still win (their in-memory text is searched). Configurable in Settings → Search (toggle + command path + a found/not-found status) and via the palette (Toggle Ripgrep Search,Set Ripgrep Command…). No new dependency —rgis an external tool resolved on PATH. -
Headless-JavaFX test harness (developer-facing).
ui/controller behavior is now covered by real, headless FX tests — TestFX over a self-built JavaFX 25 Monocle backend (vendored inm2-repo, test scope only), so they run on CI with no display/xvfb. Tests lock down Zen mode hiding/restoring chrome, Simple-mode toolbar stripping, chrome-visibility prefs, the tab/buffer lifecycle, the floating toolbar-restore/Zen-exit buttons, tab pinning + Close-Others/Close-All, and command-palette feature gating. FX tests are taggedfx(run the pure suite alone with-DexcludedGroups=fx). No change to the app, thedist/fatjarbuilds, ormodule-info— Monocle is never shipped. -
Window/tool-window decision logic extracted + unit-tested (developer-facing). The multi-window restore-set / primary-window / orphan-session-GC logic (where a quit-vs-close drain bug once lived) moved into the pure
ui/WindowKeys, and the tool-window stripe/button visibility rules intoui/ToolWindowVisibility;WindowManager/ToolWindowManagernow delegate to them. No behavior change. -
External Tools (IntelliJ-style). Define your own CLI commands in Settings → External Tools and run them on the current file/buffer. Command and arguments support
$Name$macros ($FilePath$,$FileDir$,$FileName$,$FileNameWithoutExtension$,$SelectedText$,$LineNumber$,$ColumnNumber$,$ProjectFileDir$), and a tool can pipe the selection or whole buffer to the command’s stdin. Each tool chooses what happens with its output: show it in a read-only console tool window, replace the selection, replace the whole buffer (undoable), or insert at the caret — so the same feature covers both “run a command and see the output” and text transforms (formatters/filters likejq,sort,sed). Every defined tool becomes its own palette command (and is bindable to a key via Settings → Keymaps); there’s also External Tools: Run… (a picker) and Rerun Last. Tools run off the UI thread with a 2-minute timeout. Available by default (the tool list is empty until you add one) and disabled in Simple UI mode. -
Markdown support improvements. A broad set of upgrades to the Markdown experience:
- More CommonMark extensions in the preview — YAML front matter (rendered as a metadata block),
footnotes, heading anchors, and
++inserted++text, on top of the existing GFM tables / task lists / strikethrough / autolinks. - Heading outline — the Structure tool window now shows a Markdown document’s
#…######heading tree (ATX + Setext), so long documents are navigable; click a heading to jump. - Markdown linting — high-confidence rules (trailing whitespace, multiple blank lines, ATX hash spacing, multiple top-level H1, fenced block without a language, missing final newline, broken reference links) shown as inline squiggles with hover messages and listed in a Markdown Lint tool window. On by default; toggle with View: Toggle Markdown Lint.
- Image paste & drag-drop — paste an image from the clipboard, or drop image files onto a saved
Markdown buffer, and the image is written into a sibling
assets/folder with anlink inserted. - Smart link paste — paste a URL over a selection to wrap it as
[selection](url). - Table editing — Tab / Shift-Tab move between pipe-table cells and Enter on the last row adds a row, reflowing the table as you go.
- LaTeX math — render inline
$…$and display$$…$$math in the preview (and block formulas in PDF export) via JLaTeXMath, with GitHub-style delimiter rules so prose dollar amounts are left alone. Off by default (Settings → Editor → Render LaTeX math, or View: Toggle Math Rendering). - Export to HTML — export the rendered preview to a standalone, self-contained
.htmlfile (embedded stylesheet, heading anchors, math as images) via Preview: Export to HTML.
- More CommonMark extensions in the preview — YAML front matter (rendered as a metadata block),
footnotes, heading anchors, and
-
More Emacs editing & movement commands. Filled the remaining gaps versus a standard Emacs
global-map: backward-kill-word (M-DEL); case commands upcase/downcase/capitalize-word (M-u/M-l/M-c) and upcase/downcase-region (C-x C-u/C-x C-l); whitespace and line surgery — join-line / delete-indentation (M-^), delete-horizontal-space (M-\), just-one-space (M-SPC), delete-blank-lines (C-x C-o), open-line (C-o), kill-whole-line (C-S-DEL), and zap-to-char (M-z); structural navigation — forward/backward-sexp (C-M-f/C-M-b), mark-sexp (C-M-SPC), kill-sexp (C-M-k), beginning/end-of-defun (C-M-a/C-M-e, heuristic in a language-agnostic editor), and mark-paragraph (M-h); plus mark-whole-buffer (C-x h) and move-to-window-line-top-bottom (M-r). All are palette-discoverable and rebindable like any command. Kill-ring behavior (yank-pop, consecutive-kill accumulation) remains deferred — these “kill” commands delete, and cut/copy/paste still use the system clipboard. -
Keyboard macros (Emacs-style). Record a sequence of editor actions and replay it. Recording captures the faithful interleaved stream of invoked commands and literally typed text, and replay reproduces the exact sequence — replayed typing runs through the same auto-close / auto-indent assists as live typing. Start with F3, stop with F4, replay the last macro with C-x e; the palette adds Replay Last N Times, Name and Save Last (saved macros persist across sessions in
macros.json), Run Saved, and Delete Saved. A saved macro becomes its own palette command, so it can be bound to a keyboard shortcut via Settings → Keymaps like any other command. The recording hooks are inert (a single boolean check) when not recording, so there’s no idle cost. Known limitation: a recorded command that opens a modal picker/prompt pauses replay for input (it isn’t scripted). -
New Window command. A palette command Window: New Window (
C-x 5 2) opens an additional editor window without having to load a project — previously a second window only appeared when a project was opened. Each new window has its own session (independent open files, layout, and bounds — windows no longer share or clobber one state file) and is restored on the next launch like a project window. Works whether or not the Projects feature is enabled. -
TODO / highlight patterns (IntelliJ-style). Configurable regex patterns (TODO and FIXME by default, each with its own color) are highlighted everywhere they match in the editor, and listed in a new TODO tool window (
M-g o) grouped by file — scanning the open project’s tree when a project is open, else the open files; double-click a result to jump to it. Matches also show as overview stripes over the scrollbar and on the minimap edge (each in its pattern’s color; click to jump, hover for the line). On by default. Add/edit/remove patterns (name, regex, a color picker, case-sensitivity, enabled) in Settings → Editor → TODO Highlighting; commandstool.todo,todo.refresh,todo.addPattern, andview.toggleTodoHighlight. Highlighting runs off the UI thread and is debounced; the project scan is lazy (only when the tool window is open or refreshed). -
LSP-powered Structure tool window. For files served by a language server, the Structure outline is now built from the server’s
textDocument/documentSymbolresponse — a precise hierarchy of classes, methods, fields, enums, etc. with proper kinds and per-kind icons, and methods showing their signature (parameters and return type, in a muted colour) — instead of the fold/TextMate heuristic (which remains the fallback for non-LSP languages, when LSP is off, or for remote files). The outline refreshes live as you type and once the server finishes starting, and clicking an entry still navigates to it. New toolbar controls: a sort selector (Position / Name / Kind) and a kind filter to hide categories. -
Project tool window auto-refreshes on filesystem changes. A watcher on the project tree (the root + currently-expanded folders) refreshes the view when files are created, deleted, renamed, or modified on disk — by another app, a build, git, or Editora itself — preserving expanded folders and the selection. Local projects only; on macOS (where the JDK uses a polling watcher) external changes may take a few seconds to appear. Deleting a file from the tree’s right-click menu now also removes its row immediately.
-
EditorConfig status-bar indicator. When an
.editorconfiggoverns the current file, the status bar shows an EditorConfig segment; clicking it (or the EditorConfig: Open Active File palette command) opens the nearest governing.editorconfig. Hidden when no config applies and in Simple UI mode. -
Global indent-style preference. Settings → Editor → “Indent style” (and the Editor: Set Indent Style… palette command) lets you force Spaces or Tabs for Tab/Enter indentation, or keep Detect from file (the default — per-file auto-detection from the file’s existing indentation). A file’s
.editorconfigindent_stylestill wins over this when EditorConfig is enabled; the global preference is the fallback, and per-file detection is the fallback below that. Applies live to every open buffer. -
EditorConfig (
.editorconfig) support. Opening a file now resolves the nearest.editorconfigchain (walking up toroot = true, nearest-directory-wins) and applies its style to the buffer:indent_style/indent_size/tab_widthdrive Tab/Enter indentation (tabs vs. N spaces) and the minimap/indent math, overriding Editora’s auto-detection.end_of_line(lf/crlf/cr) sets the line ending used on save; a manual EOL change still wins for the session.charset(utf-8, utf-8-bom, latin1, utf-16le, utf-16be) is honored on read and save — files are decoded with the right charset (BOM-detected first, else the.editorconfigvalue) and written back in the same charset, BOM preserved. The status bar shows the active charset.max_line_lengthsets the column-ruler position (offhides it).trim_trailing_whitespaceandinsert_final_newlineare applied on save (disk bytes only — the on-screen document, caret, and undo history are untouched).- Glob sections support
***?[seq][!seq]{a,b}{n1..n2}; unknown keys/sections are ignored. - On by default; toggle via Settings → Editor → “Enable EditorConfig” or the
view.toggleEditorConfigpalette command. Local files only.
-
Tab re-indents the current line to the language server’s convention. In an LSP language whose server supports range formatting (e.g. Java via jdtls), pressing Tab snaps the current line’s indentation to what the formatter would produce — only the leading whitespace changes, the rest of the line is left alone. It still accepts an open completion / advances a snippet tab-stop first, indents a multi-line selection, and on a blank line or when the server can’t format it falls back to the normal smart-Tab indent. Uses
textDocument/rangeFormatting(resolved off-thread). -
IntelliJ-style code completion popup. The autocomplete list now looks and behaves much closer to IntelliJ IDEA:
- Per-kind icons — each row shows a glyph for its kind (method, field, variable, class, interface,
enum, keyword, module, snippet, …), mapped from the language server’s
CompletionItemKind. - Matched-character bolding — the characters you’ve typed are bolded within each candidate (contiguous prefix/substring, or camelCase/subsequence).
- Relevance ordering — LSP candidates are ordered by the server’s relevance (preselect, then
sortText), and the server’s preselected item starts highlighted, instead of raw server order. - Documentation side-popup — a panel beside the list shows the selected symbol’s documentation (and signature), resolved lazily from the server and rendered as Markdown. It auto-shows (toggle the default in Settings → Editor) and Ctrl+Q shows/hides it for the current item. It positions to the right of the list and flips left at the screen edge.
- Deprecated APIs are struck through; PageUp/PageDown page the list.
- The type/detail column is enriched from the server’s
labelDetails. Negligible cost on the typing hot path — the icon/label/ordering work is cheap and per-render, and the documentation is resolved off the FX thread and debounced per selection.
- Per-kind icons — each row shows a glyph for its kind (method, field, variable, class, interface,
enum, keyword, module, snippet, …), mapped from the language server’s
Performance
-
Smoother scrolling in files with many lines. The line-number gutter set each row’s number via a per-row reactive binding that subscribed/unsubscribed as cells recycled during a scroll — measurable churn on the hot path. The number is now set directly from the live line count, and re-pads only when the count crosses a power of 10 (rare). A layout-forced scroll sweep over a ~375-line Markdown file was ~12–21% faster in the gutter (measured A/B in one process). Line numbers, folding, and all gutter markers are unchanged.
-
Cheaper spell-check overlay redraw while scrolling prose. The red-squiggle overlay re-runs each scroll pulse over the visible words. It now checks the (cached) spelling verdict before the per-word syntax-style lookup, so that lookup runs only for the few misspelled words instead of every visible word, and it hoists the per-word offset query out of the loop. Redrawing a ~375-line Markdown file was ~16–22% faster (measured A/B in one process); squiggle placement is unchanged.
Changed
-
Git blame is now an IntelliJ-style “Annotate” gutter column instead of a single inline annotation. Turning on blame (
M-g a, or Settings → Git) now shows a per-line column on the left of the editor — author and commit date on every line, with an age-heatmap background (recent commits tinted warmer, older ones faded), a hover tooltip with the full commit (author / date / relative time / message / hash), and click to open that line’s commit. This replaces the previous GitLens-style annotation that only appeared after the current caret line. Same toggle and setting; only the active file is annotated, and the blame still runs once per file off the UI thread (no per-keystroke cost). -
Tool windows and status segments now follow the active file’s capabilities. The Problems stripe button shows only when the active file is served by a language server; the Debug stripe button only for a debuggable file (Java/Python/JS) or while a debug session is live; and the Git Log stripe button plus the status-bar branch segment only when the active file is inside a git repo. Each is hidden (not greyed) when it doesn’t apply — so a Welcome/Markdown/plain-text tab no longer shows Problems, Debug, the branch segment, or a git context inherited from the working directory. The
LSP:status segment already cleared on non-LSP files. -
Find and Find in Files pre-fill from the selected text. Select a word or phrase on a single line, then open the in-editor Find/Replace bar (the toolbar icon,
C-s/C-r, or Replace) or Find in Files (its toolbar icon orC-S-f) and the selection becomes the search term — the field is also selected so you can type over it. A multi-line selection is left alone (it isn’t a sensible find term) and whatever query was already there is kept. Matches the VS Code / browser convention. -
Markdown split view now keeps the editor and preview scrolled together. In SPLIT mode (editor + rendered preview side by side), scrolling one pane scrolls the other by the same fraction of its content, so the two stay roughly aligned even though their heights differ. The pane the mouse is over drives the other, which keeps the sync one-directional at any moment so it can’t oscillate; entering SPLIT aligns the preview to the editor’s current position. Negligible cost — it rides existing scroll-position listeners with no new per-keystroke or per-pulse work.
-
The Project tool window shows the current file’s folder when no project is open. In the global (“No Project”) window it no longer just shows an empty “No project open” placeholder — it roots the tree at the active file’s parent directory and titles the tool window “Current Folder”, so it doubles as a file explorer that follows you as you switch tabs. With a project open it’s unchanged (the tree stays the project root, titled “Project”); a mounted remote folder is also left as-is. An unsaved/Welcome tab (no folder) falls back to the placeholder.
-
File-type icons now line up in the Project tree (and other file lists). Each glyph’s node bounds equal its ink, which varies per file type, so the icon column had a ragged width and file names jumped left/right (and the taller document glyphs sat high). Every file/folder glyph is now centered in a fixed-size box, so names start at the same x and icons are vertically centered. Applies wherever file icons appear — the tree, tabs, the Open-Files / Recent pickers, and the Switcher.
-
Distinct icons for the tab right-click close menu. The six close commands (Close, Close Other Tabs, Close All Tabs, Close Unmodified Tabs, Close Tabs to the Left/Right) all shared the plain ✕. They now each carry a variant of the ✕ so they’re distinguishable at a glance: one small square (other), three squares (all), a check (unmodified/saved), and a left/right triangle (direction). “Close” keeps the plain ✕.
-
The Find in Files toolbar icon now toggles the panel — one click opens it (focusing the query field), another click closes it (matching
C-s’s in-file find and theM-6tool-window toggle).C-S-ftoggles too, since it shares the command.
Fixed
-
Zen mode is now per-window and restores chrome reliably. Zen mode used to hide the toolbar, status bar, tab bar, breadcrumb, and editor view options by flipping the shared, app-wide view preferences and snapshotting/restoring them — so entering Zen in one window changed every window (and the saved settings), and the restore could desync. Zen is now a per-window effective overlay (like Simple UI mode): it lives in the window’s own session state and is layered on top of the saved prefs without mutating them, so one window can be in Zen while another isn’t, leaving Zen always restores exactly what was showing, and your saved view preferences are never touched.
-
Git menu items are hidden/disabled when there’s no VCS. The tab right-click menu no longer shows Compare with HEAD and Git: Show File History when the file isn’t under Git (feature off, or not inside a repo) — only “Compare With…” (which isn’t a Git action) remains. The Project tool window’s Git submenu is greyed out in the same case. Both now key off “Git available” (enabled and inside a repo) rather than just the feature toggle.
-
HTTP Client: you can now right-click the response body to copy it. The response body is a read-only RichTextFX editor, which (unlike the headers’ text area) has no built-in context menu, so right-clicking the JSON/XML/etc. output did nothing. It now has a Copy / Select All menu — Copy puts the selection, or the whole body when nothing is selected, on the clipboard.
-
Loading a large file (5–50 MB) with a language server enabled no longer freezes the editor. Large-file mode already turns off syntax highlighting and the minimap, but LSP stayed on — so opening, say, a big
.json/.xml/.logwith its server enabled sent the whole document to the server and then mapped and rendered the flood of diagnostics it returned (the Problems tree, minimap, and scrollbar stripes) on the UI thread, locking up the editor. LSP is now disabled in large-file mode too (like highlighting, the minimap, Git change bars, and inline blame), so the file just opens as fast, plain text. Autocomplete is likewise disabled in large-file mode — it allocated the whole document on every keystroke to find the word prefix (the same per-cursor-move cost brace matching already avoids), which made typing in a large file stutter. -
Diff viewer: next/previous-change no longer makes the scroll jump erratically. The two side-by-side panes were kept in sync by copying the absolute pixel scroll position from one to the other, in both directions. That value is a RichTextFX estimate refined as lines are measured, so each pane settled to a slightly different value and pushed the other back — a feedback loop, worst when navigation jumped into an unmeasured region. Now only the focused (actively scrolled) pane drives the other, so interactive sync is strictly one-directional and cannot oscillate. Next/previous-change navigation bypasses the sync entirely: it pins both panes to the same top row explicitly (the rows are 1:1 aligned, so this can’t desync) and puts the selection caret at the block’s top so caret-follow agrees with the scroll.
-
Spell check no longer flags commands, URLs, paths, and identifiers. Tokens like
./mvnw,javafx:run,https://github.com/…,path/to/file.txt,snake_case, anda@b.comwere getting red squiggles — in Markdown prose, in fenced```code blocks, and in comments/strings of Java/Python/Shell and other code files. The checker now recognizes a flagged word that’s part of a URL, path, e-mail, or dotted/underscored/colon-joined token and leaves it alone (ordinary words, including hyphenated ones likewell-known, are still checked). In Markdown it additionally skips whole fenced code blocks and link targets. The check runs only for the few already-flagged words, so it doesn’t affect scroll performance. -
Language servers and debug adapters no longer pile up as orphaned processes. Previously the external servers Editora spawns (e.g.
jdtls) were only killed when a window was closed through the app — so force-quitting, a crash, an OS quit, or akillleft them running, where they accumulate and hold their workspace locks. Now a centralProcessRegistryreaps them three ways: a JVM shutdown hook force-kills every spawned server tree on exit (covers normal quit and SIGTERM/kill); the per-server teardown escalates (SIGTERM the whole tree, then force-kill anything that ignores it after a grace period); and a small on-disk ledger lets a fresh launch reap any server leaked by a previous run that died hard (SIGKILL / power loss), matched by PID + start time + executable so it can never kill an unrelated process. -
Silenced a spurious LSP “Internal error” log. Language servers that support pull diagnostics (the HTML/CSS/JSON servers) send a
workspace/diagnostic/refreshrequest; the client didn’t implement it, so lsp4j logged a SEVEREUnsupportedOperationExceptioneach time (visible in the debug log / dev console). The client now acknowledges the request. Diagnostics were never actually affected — they already refresh on each edit, on save, and when a server reports ready. -
Find/Replace highlights now stay in sync when you edit the buffer. With the find bar open, editing the text left the match highlights painted at their old offsets (no longer aligned with the searched text). The bar now re-runs the search on each (debounced) edit and re-highlights at the new positions — without moving the caret or selection, so it doesn’t interfere with typing.
-
Right-click Cut/Copy/Paste now act on a column (box) selection. The editor context menu’s Cut/Copy/Paste called the native single-caret operations, so on an Alt+drag column/box selection they only touched the primary row — even though the
edit.cut/copy/pastecommands already worked (they route through the multi-caret-aware path). The menu items now use that same path (falling back to the native op when there’s a single caret), Cut/Copy are enabled on a box selection even when the primary selection is empty, and the mutating Cut/Paste are gated to editable buffers. -
Every window that was open at quit now reopens. With multiple project windows open, a Cmd-Q / OS quit fired each window’s close handler in turn, and the eager “mark closed” drained the saved open-window set down to just the last window — so only one reopened on the next launch. Closing a window is now reconciled on a short debounce that can’t elapse during a quit’s close-handler burst: a genuine single close persists the reduced set (that window is forgotten), but a quit exits the JVM before the timer fires, leaving the full pre-quit set on disk so every window reopens. (
app.quitalready worked — it fires no close handlers.)
Added
-
Open a command’s online docs from the palette (C-h). In the command palette, press C-h to open the highlighted command’s documentation page (
https://editora-project.dev/docs/commands/<id>) in your system default browser. The palette footer now listsC-h docsalongside the other shortcuts. -
Format Document command (LSP). A new command-palette command LSP: Format Document reformats the whole active file via its language server (
textDocument/formatting) — available when a server is running for the file’s language and advertises formatting. Edits apply through the undoable buffer (so a single Undo reverts), and it reports when no formatter is available or the file is already formatted. Also available as Format Document in the editor right-click menu, shown only when the server advertises formatting. -
Right-click a tool-window icon → Hide. Right-clicking a button in the tool-window stripe now offers Hide, which removes that icon from the stripe (persisted). Bring it back from Settings → Tool Windows.
-
Reveal in File Manager / Open Terminal Here from the breadcrumb. Clicking a folder or file crumb in the navigation bar now lists Reveal in File Manager and Open Terminal Here for that crumb (above the folder-contents dropdown) — Reveal selects the file or opens the folder; Open Terminal opens at the folder (the file’s parent for a file crumb). Shown for local files only. This also fixes the crumb dropdown not opening at all: the AtlantaFX breadcrumb skin owns each crumb button’s action, so the click is now caught via the control’s crumb-action event instead of an
onActionthe skin overrode. -
“Comment / Uncomment” in the editor right-click menu. For files whose language has comment syntax, the code-area context menu now offers Comment / Uncomment (the same action as the
M-;keybinding and the command palette) — it line-comments the caret’s line or block/line-comments the selection, and toggles back off when already commented. Hidden for plaintext (no comment syntax) and while read-only. -
MCP server (Model Context Protocol). Editora can run a small Model Context Protocol server so an LLM agent (Claude Code, etc.) can observe live editor state and drive the command registry. It’s a loopback-only HTTP/JSON-RPC server with bearer-token auth that writes its endpoint to
<configDir>/mcp-endpoint.jsonfor discovery, and exposes six tools:list_open_files,read_buffer,get_diagnostics,find_in_files,list_commands, andexecute_command. A status-bar MCP indicator shows when it’s running (click to copy the connection command) and is hidden in Simple UI mode. Off by default and gated by a security-notice dialog — enable it under Settings → MCP Server (or the Toggle MCP Server command,view.toggleMcp;mcp.copyEndpointcopies the endpoint). No new dependency (the JDK’s built-inHttpServer+ Jackson). -
Reveal in File Manager / Open Terminal Here (built-in). Revealing a file in Finder/Explorer/your file manager and opening a terminal at a folder are now core features (previously an example plugin), reachable from the command palette (
file.revealInFileManager/file.openTerminal), the tab right-click menu, and the Project tool window menu (both files and folders). Local saved files only; per-OS argv (Finder/Explorer select,xdg-openon Linux) launched off the UI thread. (The breadcrumb crumb menu — see below — surfaces the same two actions.) -
Show Local History + a Git submenu in the Project tree menu. The Project tool window’s per-file right-click menu gained Show Local History and a Git submenu (Show File History, Compare with HEAD, Stage File), mirroring the editor tab menu but acting on the clicked tree path. The Git items grey out when Git is off, and Local History when it’s off.
-
HTTP Client — request chaining, multipart, dynamic vars, and a richer viewer. The
.http/.restclient moved much closer to IntelliJ’s: reference an earlier named request’s response in a later one ({{name.response.body.$.path}}/.headers.H/.status, threaded across a run-all), multipart/ form-data bodies (inline +< ./fileparts), external request bodies (< ./fileraw,<@ ./filesubstituted), response-to-file redirects (>> file/>>! file), per-request directives (# @no-redirect/@no-cookie-jar/@no-log/@timeout N), a per-run cookie jar, and the full dynamic-variable family ({{$random.*}},{{$datetime "fmt"}},{{$processEnv.X}},{{$dotenv.X}}). The response viewer is now a content-type-highlighted read-only editor with split status/headers/body, an in-session history picker, Copy as cURL, Import cURL, and Open in editor tab. No new dependency. -
HTTP Client — Digest auth, URL auto-encoding, date math, and
$sharedenv. A second IntelliJ-parity pass: theAuthorization: Digest user passshorthand now performs the 401-challenge round-trip (RFC 2617); request URLs are percent-encoded automatically after substitution (disable with# @no-auto-encoding);{{$datetime}}/{{$localDatetime}}/{{$timestamp}}take signed offsets (y/M/w/d/h/m/s/ms); and a$sharedsection inhttp-client.env.jsonmerges its variables under every environment (and is hidden from the picker). New directive# @connection-timeout N. -
Per-file-type icons. File icons now reflect the file’s type instead of a single generic document glyph. Editing
Main.javashows the Java logo,app.pythe Python logo,style.cssthe CSS logo, an image its picture glyph, a.zipan archive glyph, and so on — covering every language/format Editora supports (Java, Python, JS/TS, Go, PHP, Ruby, C/C++, C#, Kotlin, HTML, CSS, Markdown, YAML, JSON, XML, shell, Docker, Terraform, Mermaid, SQL, INI/TOML, images, PDFs, archives, CSV, …), with the generic document glyph as a fallback for unknown types. The icons appear everywhere a file is listed — editor tabs, the Project tool window tree, the Jump to Open File and Jump to Recent File pickers, the Switcher (Ctrl-Tab), and the file/folder finders. They are monochrome single-path glyphs in the existing toolbar-icon style, so they track the light/dark theme automatically (no per-theme overrides). Brand logos are from Simple Icons (CC0); category glyphs from Material Design Icons (Apache-2.0). -
Local file history. Editora now silently snapshots local files over time — on save, auto-save, and before an external-change reload — so you can recover earlier versions independently of any VCS. Browse them in a new File History tool window (
M-g lor the palette File History: Show): each revision shows its date/time, reason, and size, with the latest tagged Current. Double-click a revision for a read-only diff against the current file, or restore one (an undoable whole-file replace). Snapshots are deduped by content, stored gzip-compressed and content-addressed under<configDir>/history/, and pruned by configurable limits (max revisions/file, max age, max size per project — Settings → Application → Local History). On by default; local-only (no SFTP) and off in Simple UI mode. -
Emacs fill commands. Re-wrap paragraphs to a fill column: Fill Paragraph (
M-q) reflows the paragraph at the caret, Fill Region reflows every paragraph in the selection, and Set Fill Column (C-x f) sets the target width (default 70, also in Settings → Editor). Filling preserves the paragraph’s indentation and an adaptive fill prefix — line comments (//,#,;, …), Markdown blockquotes (>), and Javadoc (*) — so code comments and quoted text wrap correctly; a word longer than the column is never broken. (Auto Fill mode and justification are deferred.)
Fixed
- LSP completion now triggers on each server’s own trigger characters (e.g.
<for HTML,:/ space for CSS), not just on.or a 2-character word prefix. This makes HTML/CSS/JSON/YAML autocomplete behave like VS Code — typing<in an HTML file now pops up tag completions. The trigger set is read from the server’s advertised capabilities, so every language server gets its correct triggers. - LSP pull diagnostics (
textDocument/diagnostic, LSP 3.17) are now supported. Servers that deliver diagnostics on request rather than by pushingpublishDiagnostics— vscode-html/css/json and others — now surface their warnings/errors as squiggles and in the Problems panel. Push-only servers (jdtls, Pyright, tsserver) are unaffected.
Added
-
HTML Live Preview. A floating browser-globe button now appears at the top-right of any
.html/.htm/.xhtmleditor (mirroring the Markdown preview toggle). Click it to open the current file in a detected browser — Safari, Chrome, Firefox, Edge, or the system default. The file is served over a tiny embedded web server bound to loopback only, so its sibling CSS, JS, and images load, and a small injected script reloads the page live as you type (serving the buffer’s in-memory text, so unsaved edits show instantly). Off by default — enable it under Settings → HTML Preview (or the Toggle HTML Live Preview command). Palette commands: Open in Browser / Open in Browser… (pick a browser). No new dependency (uses the JDK’s built-inHttpServer). -
Command descriptions in the palette. Every command now has a one-line description, shown as a detail line between the results list and the navigation hint in the command palette (
M-x) — it updates live as you arrow through the list, so you can tell what an unfamiliar command does before running it. All ~231 descriptions are fully localized in every bundled language (en/it/es/fr/pt/de); a build test guarantees every command has one. The website’s Commands page reads the same descriptions, so the app and the site never drift. -
Configurable shortcuts (keybinding editor). Settings → Keymaps now lists every command with its current shortcut and a filter box: Record a new chord (multi-key sequences like
C-x C-sare captured live), Reset one command to the keymap default, or Reset all shortcuts. Rebinding replaces the keymap’s default (with a conflict warning before stealing another command’s chord) and applies live, no restart, across all windows; overrides persist insettings.tomland layer on top of whichever keymap theme is active. -
Select All, Duplicate Line, Move Line Up/Down commands (
edit.selectAll/edit.duplicateLine/edit.moveLineUp/edit.moveLineDown), bound in the CUA/Sublime/VSCode/IntelliJ keymaps (Ctrl/Cmd-A, Alt/Option-Up/Down, etc.) and available in the command palette + the new keybinding editor. -
Keybinding themes (CUA, Sublime Text, VSCode, IntelliJ IDEA). Beyond the default Emacs keymap, Editora now bundles four familiar non-modal keymaps. Pick one in Settings → Keymaps (or the Keymap: Select… command) — switching is live, no restart, across all open windows. Each keymap ships a Ctrl-based (Windows/Linux) and a Cmd-based (macOS) variant, auto-selected per platform. User and plugin keybinding overrides are re-applied on top of whichever keymap is active. (Modal Vim is deferred — it needs a mode state machine the current resolver doesn’t model.)
-
Signed plugin registry (authenticity). The registry
index.jsonis verified against a bundled Ed25519 public key via a detached signature (index.json.sig) before Editora trusts it, and “Require signed plugins” (Settings → Plugins, default on) blocks installs from a registry that doesn’t verify — closing the registry-tampering / silent-malicious-update gap that SHA-256 alone can’t. A non-default registry must be signed with its own key or have the setting turned off. JDK-only (no dependency); sign withscripts/PluginSigningTool.java. Signing proves who published — not a sandbox. -
Plugin security hardening (consent + integrity). Enabling a plugin now shows a capability disclosure first — whether it runs executable code, the exact external commands it declares, and any keybindings it remaps — at every arming point (Browse install, install-from-file, and the per-plugin enable checkbox). The registry index and plugin downloads are read with a hard size cap (a hostile registry can’t OOM the app), registry-entry fields are length-capped, and a non-default registry URL is flagged with its host. (Consent/integrity, not a sandbox — plugins remain full-trust.)
-
Plugin support — extend Editora without forking it. A plugin is a folder under
<configDir>/plugins/<id>/with aplugin.jsonmanifest plus, optionally, a Java jar and asset dirs. Two flavors, usable together: a Java SPI (implementcom.editora.plugin.Plugin; aPluginContextlets it register palette commands, keybindings, dockable tool windows, editor right-click items, and status-bar segments, and edit the active buffer) and a declarative manifest (keymap bindings, palette commands that run an external program, andsnippets//templates/dirs merged into the registries). Plugins are loaded once at startup by a childURLClassLoader(parent = the app loader), so the same jar works in dev and inside the packaged jlink installers — no module path,ServiceLoader, orModuleLayerneeded. Off by default and full-trust (no sandbox): a master Enable plugins gate plus a per-plugin enable list live in Settings → Plugins (with a security note, the plugins-folder path, a Reload button, and load-error reporting); enabling/disabling takes effect on the next launch. Simple UI mode disables plugins along with the other local-process features. Palette:view.togglePlugins. Seedocs/plugins.mdandexamples/example-plugin/(a complete example with a build script). -
Plugin registry & install — install plugins without hand-copying folders. Settings → Plugins → Browse plugins… (palette
plugins.browse) fetches a curated GitHub-hostedindex.jsonover HTTPS and lists installable plugins; picking one downloads its.zip, verifies the SHA-256, and unpacks it into the plugins dir (with a zip-slip guard + size caps). Install from file… (plugins.installFromDisk) installs a local.zip, and each Settings row has a Remove button. The registry URL is configurable (a baked-in default you can override); entries show Installed / Update available / Install / Requires Editora ≥ x. The distribution format is a.zipwhose top level is the plugin folder contents;examples/example-plugin/build.shnow emits one (+ its SHA-256) andexamples/editora-plugins-registry/is a ready-to-host sample index. All network/zip/disk work is off the FX thread; no new dependency (java.net.http). Seedocs/plugins.md→ Publishing & installing. -
More example plugins +
ActiveEditor.setText— the pluginActiveEditorfacade gainedsetText(whole-buffer, undoable replace) for whole-file transforms. New real example plugins underexamples/: lorem-ipsum (text generator), text-tools (case convert / sort / unique / reverse / trim, on the selection or whole document), insert-tools (UUID / timestamp inserters), scratchpad (a persistent tool window backed by the plugin’s data dir), and format-runner (run an external formatter — prettier/black/gofmt/rustfmt/clang-format — over stdin→stdout). Plus encode-tools (Base64/URL/HTML/ ROT13/hex), hash-tools (MD5/SHA-1/SHA-256), markdown-toc (insert a TOC from headings), and regex-tester (a live regex tester tool window); Text Tools also gained Squeeze Blank Lines (v1.1.0). All are published in theadriandeleon/editora-pluginsregistry, which carries each plugin’s source. -
Git history, blame & stash (IntelliJ/VSCode parity) — a Git Log tool window (
M-g h, or Show File History on a tab) lists commits; select one to see its changed files, double-click a file for a read-only diff against its parent, and right-click a commit to Copy Hash / Checkout / Reset (soft·mixed·hard) / Revert / Cherry-Pick / New Branch. Inline blame (M-g a, GitLens-style) shows a faint “author, time ago • summary” after the current line, following the caret; toggle it from Settings → Git or the palette. Stash push (with an optional message) / pop / apply / drop via the palette or the status-bar branch dropdown. All require Git enabled (and are off in Simple UI mode); blame is off by default. -
Simple UI mode — a one-toggle minimal layout that strips the editor to the essentials: it hides the extra toolbar groups (new-from-template, recent, find-in-files, split, project selector), the tool-window stripe, the file breadcrumb, the entire gutter (line numbers, fold chevrons, and bookmark/note/run/breakpoint/git markers — any collapsed regions are unfolded first so nothing is stranded), the minimap, and most status-bar segments (git, the LSP server indicator, language, tab size, line endings, encoding), while keeping the tabs, core toolbar icons (including Open), and the echo / read-only / zoom / Ln-Col / file-size status. It also disables the heavier features — language servers (LSP), debugging, the HTTP client, Git, and multiple cursors / column selection — for a quiet, plain editor (the saved enable settings are untouched and restored on exit). Toggle it from the toolbar icon, the command palette (View: Toggle Simple UI Mode), or Settings → Application; it persists. The
--simplecommand-line flag starts a session in Simple mode without changing the saved preference. Your own line-number/minimap preferences are preserved and return when you toggle it off. -
Remote file access (SFTP) — connect to a server over SSH/SFTP and edit its files as if they were local. Remote: Connect to SFTP… opens a connection form (host/port/user, and auth via your default
~/.sshkeys, a chosen key file, or a password); on connect the remote folder is mounted in the Project tool window so you can browse it, and opening a file edits + saves it directly over SFTP. Saved connections are remembered (metadata only — never a password) and reachable via Remote: Saved Connections…; Remote: Open File… opens a singlesftp://URI and Remote: Disconnect returns to your local project. Features that need a local process (language servers, debugging, Git, Run, the HTTP client) are automatically disabled for remote files. Built on Apache MINA SSHD; no external tools needed. -
HTTP Client (
.http/.restfiles) — open a.httpfile with syntax highlighting and a green ▶ on every request; click it to run that request and see the response (status, headers, pretty-printed JSON body, timing/size) in an “HTTP Client” tool window (M-0). Uses Editora’s built-in HTTP client — no external tools needed (off by default; enable in Settings → HTTP Client). Supports in-file@variable/{{var}}substitution (incl.{{$uuid}}/{{$timestamp}}), environment files (http-client.env.json+.private.env.json, with an environment picker), running the whole file, and saving the response. Commands:HTTP: Run Request at Caret,HTTP: Run All Requests in File,HTTP: Select Environment. -
File templates — “New File From Template” creates a file (or set of files) from a reusable template with
${variable}substitution, a${cursor}placement, and a variable-entry wizard:- A template picker (palette
Template: New File From Template…, C-c C-n, and a toolbar button) lists bundled + user templates; a wizard prompts for any variables the template references (e.g.${className:Main}), pre-filled with defaults. Built-ins (${author},${projectName},${date},${baseName}, …) are filled automatically. - Single-file templates open as an untitled buffer with the caret at
${cursor}; multi-file templates (afiles[]array) and the project tree’s folder right-click New From Template… write the files to disk and open the primary one. A traversal guard rejects paths that escape the target folder; existing files are never overwritten. - User templates live in
<configDir>/templates/*.json(Template: Edit User Templates…,Template: Reload Templates); a new Author name setting (Settings → Application → Templates, defaulting to the OS user) feeds${author}. Always-on, like snippets.
- A template picker (palette
-
Diff viewer — compare files visually in a dedicated tab:
- Side-by-side (default) with synchronized scrolling, original line numbers, syntax highlighting, per-line added/removed/changed backgrounds, and intra-line word emphasis on changed lines; a toolbar toggle switches to a unified (+/−) view.
- Prev/next change navigation and Export patch… (writes a
.patch). - Entry points: Compare with HEAD for the current file (
C-x v =, also the tab and Git-panel menus), Compare With… any other file, Compare with Commit… (pick from the file’s history), and Show Diff on a Git-panel row (staged → index↔HEAD, unstaged → working↔index). - Merge-conflict resolution — open a file with Git conflict markers in a merge view and accept
Ours / Theirs / Both per conflict, then save the resolved file (
Merge: Resolve Conflicts). - Commands
diff.vsHead,diff.compareWith,diff.vsCommit,merge.resolve(palette-discoverable).
-
Better Compact Source File (JDK 25) support — running and debugging single-file programs is now much more complete:
- Console input — the Run window has an input field: type a line and press Enter to send it to
the program’s stdin (so
IO.readln(...)-style interactive programs work instead of hanging). - Program arguments — Run File with Arguments… prompts for arguments (quote-aware), remembers them per file, and both Run and Debug pass them to the program. Rerun Last Run repeats the previous run.
- Clickable stack traces — double-click a Java/Python/Node stack-trace line in the Run or Debug console to jump straight to that file and line.
- JDK version check — running a
.javafile with an older JDK on PATH now says “Compact source files need JDK 25 or newer — found Java N” instead of a cryptic launcher error. - Quieter diagnostics — implicit-class complaints from a Java language server that doesn’t know JDK 25 yet are filtered out for compact files (real errors still show).
- Snippets —
cmain(compactvoid main()skeleton),ioprintln, andioreadln.
- Console input — the Run window has an input field: type a line and press Enter to send it to
the program’s stdin (so
-
IntelliJ-style debugger UI — the Debug tool window and the editor now match IntelliJ IDEA’s debugger as closely as practical:
- New look — an icon-only grouped toolbar (resume / pause / stop / restart | step over / into /
out / run to cursor, with full titles in tooltips), a thread selector dropdown over the call
stack, rich frame cells (
method:line, File), and type-colored variable values (strings green, numbers blue, booleans amber, null muted) with a muted type suffix. - Pause (
C-c C-d p) suspends a running program; Run to Cursor (C-c C-d u) resumes and stops at the caret line via a temporary breakpoint that is cleaned up automatically. - Watches — a “Watches” node at the top of the variables tree with a ”+ Add watch…” row; add/edit/remove via the context menu or double-click. Watches re-evaluate on every stop and frame selection, expand when the result is structured, and persist across restarts (per project).
- Set Value — change a variable’s value mid-session from the context menu, F2, or double-click; the tree updates in place and watches re-evaluate.
- Jump to Line (
C-c C-d j) — move the execution pointer to the caret line without executing the code in between (like the IntelliJ “Jump to Line” plugin). Works with Python (debugpy); the Java and JS adapters don’t support it yet and say so. - Inline values — while suspended, grey italic
name: valueannotations appear at the end of each visible line that mentions a variable of the current frame, and vanish on resume. - Hover values — hovering a variable in the editor while suspended shows its value in a tooltip.
- New look — an icon-only grouped toolbar (resume / pause / stop / restart | step over / into /
out / run to cursor, with full titles in tooltips), a thread selector dropdown over the call
stack, rich frame cells (
-
Multiple cursors & column/box selection — VS Code–style multi-caret editing, powered by a personal RichTextFX fork. Alt+drag makes a vertical column/box selection; Cmd/Ctrl+click adds a caret at another spot; Cmd/Ctrl+D selects the next occurrence of the current selection; Esc collapses back to a single caret. While multiple carets exist, typing, Enter/Tab, Backspace/Delete, paste (one line per caret), and arrow/Home/End movement apply to every caret as a single undoable step. On by default; toggle under Settings → Editor → “Enable multiple cursors & column selection” (or the
view.toggleMultiCaretcommand). The palette also lists Add Caret at Next Occurrence / Above / Below and Collapse to Single Caret (unbound by default — bind them in your keymap if you like). With a single caret everything behaves exactly as before. (Note: Emacs movement chords likeC-f/C-nact on the primary caret only — use the arrow keys to move all carets together.) -
Java debugging (DAP) — a full debugger for Java, complementing the existing Java LSP support, off by default (Settings → Debugging → “Enable Java debugging”). It layers on the running jdtls language server plus the Microsoft java-debug plugin jar (not bundled — auto-detected from a VS Code Java extension / nvim mason install, or set its path in Settings) and speaks the Debug Adapter Protocol over a socket. Features:
- Breakpoints in a clickable leftmost gutter strip (red dot), persisted per project in
breakpoints.jsonand re-anchored to their line’s content across edits; conditional breakpoints and logpoints via Edit Breakpoint (C-c C-d e). - Launch a standalone Java file (incl. JEP 512 compact source) or a project main class (a picker
appears when several are found), or attach to a running JVM (
host:port). - Stepping (continue / over / into / out), a current-execution-line highlight that follows
execution, and a Debug tool window (
M-g d) with the call stack, a lazily-expanding variables tree, an output console with an evaluate REPL, and exception breakpoints. - Commands
debug.start(C-c C-d d),debug.continue/stepOver/stepInto/stepOut(C-c C-d c/n/i/o),debug.stop(C-c C-d k),debug.attach(C-c C-d a),debug.toggleBreakpoint(C-c C-b),debug.restart,debug.toggleExceptionBreakpoints, and a status-bar “Debug” segment. Requires the Java LSP server enabled + jdtls and the plugin detected; everything no-ops cleanly otherwise.
- Breakpoints in a clickable leftmost gutter strip (red dot), persisted per project in
-
Python & JavaScript debugging (DAP) — the debugger now also debugs Python (via debugpy over stdio) and JavaScript / Node (via vscode-js-debug over a socket), reusing the same breakpoints, Debug tool window, stepping, variables, console, and execution-line highlight. Each is enabled per-language under Settings → Debugging (default on, only effective when the master debugging toggle is on). Like the Java adapter, the runtimes are not bundled — install them with the new
scripts/install-debugpy.shandscripts/install-js-debug.sh(auto-detected from~/.editora/plugins/dap/{python,javascript}), or point Settings at an existing install. The breakpoint gutter now appears only for debuggable files (Java / Python / JavaScript). JavaScript is Node-only for now (no browser); TypeScript and remote attach for Python/JS are not yet supported. -
Debug Log viewer — a new in-app window that shows the application log (java.util.logging output + uncaught exceptions), so you can see errors/warnings in a packaged build (DMG/MSI/DEB) where stderr isn’t visible. Open it via the Debug Log command in the palette or Settings → Advanced → Show Debug Log…; it has Refresh / Copy / Clear / Export. The same log is also mirrored to
editora-session.login your config folder, so it survives a crash and can be attached to a bug report. -
Icons on every right-click menu item — all context menus (editor surface + Markdown preview, tab strip, Project / Git / Bookmarks / Notes / Message-log panels) now show a leading Material-style glyph next to each item, matching the toolbar and tool-window icons. Editor-package menus use a new
MenuIconsglyph factory (so theeditorpackage stays independent ofui); colors track the theme via the existing.toolbar-iconstyle, so there’s no new CSS. -
Markdown preview right-click menu — the preview pane now has a context menu with Select All, Copy (both copy the preview’s rendered plain text — markup stripped, via commonmark
TextContentRenderer), Export to PDF, and Print. Copy puts the visible text on the clipboard. -
Markdown selection format bar — selecting text in a Markdown buffer shows a floating, IntelliJ-style bar above the selection with Bold / Italic / Strikethrough / Inline code / Link / Bulleted list buttons and a Normal / H1–H6 paragraph-style dropdown. Each action toggles the wrapping (a second press removes it). It never steals typing focus, repositions on scroll, and hides on Escape / empty selection. Toggle it in Settings → Editor → Markdown (“Show format bar on text selection”, default on) or the
markdown.toggleFormatBarcommand. -
Markdown smart editing — the same actions are available as commands,
C-c-prefixed keybindings (C-c C-s b/i/s/cbold/italic/strike/code,C-c C-a llink,C-c C-h p/C-c C-h dheading promote/demote,C-c C-oopen link,C-c C-s treformat table), and a right-click Format group. Plus: list/blockquote continuation on Enter (-,*,1.(auto-incrementing),- [ ],>— Enter on an empty item ends the list), link helpers (wrap a selection as a link using a clipboard URL when present; Ctrl/Cmd-click ormarkdown.openLinkopens the link under the caret), and GFM table reflow (markdown.reflowTablepads/aligns a table’s pipes honoring:--/:-:/--:).
Changed
-
Keyboard popups are now in-scene overlays — the command palette, the “Jump to…” pickers (recent files, structure, open files, tool windows, bookmarks, notes, snippets, projects, LSP references, spell/app/editor-theme), the file/folder finder, the IntelliJ-style Switcher, the Git branch dropdown, and the status message-log popup no longer use separate
Popupwindows. They render as a card inside the main window over a dim, click-to-dismiss backdrop (a sharedOverlayHost). This fixes a Windows bug where aPopupdidn’t reliably take OS keyboard focus — opening the palette (or a picker) could leave the keyboard dead until restart. Anchored popups (branch dropdown, message log) still “drop” from their status-bar segment; clicking the segment again closes them cleanly. -
Input dialogs are now in-scene too — the text-input dialogs (Personal Note editor, file/bookmark rename, bookmark note, Git clone, new branch, go to line) are no longer separate native windows; they show as a form card in the main window over the same backdrop (a new shared
OverlayInputhelper).Esc/C-gcancel;Enteraccepts (Ctrl/Cmd+Enterfor the multi-line note editor). This continues the Windows keyboard-focus fix to every text prompt. (Simple yes/no confirmations and choice pickers — overwrite, delete, set language/tab-size/line-endings — stay native.) -
Status bar polish — the Git segment now toggles its branch dropdown (click to open, click again to close). The status message-log popup is 20% wider and gains a Copy button (copies the selected messages, or all of them when none are selected); its per-row right-click menu was removed in favor of the more discoverable button (⌘/Ctrl+C still works too).
-
Command palette polish — removed the separator line above the footer legend, and the legend now lists
C-galongside esc as a way to close the palette. -
C-a(beginning of line) is now “smart home” — the first press moves to the beginning of the line’s text (the first non-whitespace character); a second press toggles to the true line start (column 0). Shift-extends selection as before. Applies in the editor for every file type.
Fixed
- LSP (and DAP) failed to initialize in the packaged native build — in the modular
jlinkruntime,lsp4jis a real module that didn’t open its packages togson, so its JSON-RPCinitializethrewInaccessibleObjectExceptionthe moment a language server (or debugger) started. Thelsp4jmodules are now built as open modules. Only affected the installers / app image (notmvn javafx:run).
Added
-
LSP support for C/C++, C#, HTML, CSS, Kotlin, Lua, Dockerfile, SQL, Terraform, and TOML — ten more language servers (twenty-one total): clangd (one server for both C and C++), csharp-ls (C#), vscode-html-language-server, vscode-css-language-server, kotlin-language-server, lua-language-server, docker-langserver, sqls (SQL), terraform-ls, and taplo (TOML). Each has its own enable toggle + command field in Settings → LSP (auto-detected on PATH, never bundled, off by default with the rest of LSP). Lua, Dockerfile, Terraform, and TOML had no syntax support, so each gained a bundled TextMate grammar (from the shiki tm-grammars collection) — they now highlight, fold, and comment-toggle.
Dockerfile(andContainerfile) is recognized by filename, not just extension. TOML moved off the borrowed INI grammar onto its own. Settings schema 11→12→13 (additive-identity migrations). -
Lua auto-indent — Lua now has a dedicated indent style (
do/then/function(…)/repeat/{open a block;end/else/elseif/untilde-indent), so pressing Enter inside Lua blocks indents like the other keyword-block languages instead of just inheriting the previous line. -
LSP support for YAML, Go, Rust, PHP, and Ruby — five more language servers (eleven total): YAML via
yaml-language-server, Go viagopls, Rust viarust-analyzer, PHP via phpactor, and Ruby via ruby-lsp. Each has its own enable toggle + command field in Settings → LSP (auto-detected on PATH, never bundled, off by default with the rest of LSP). YAML/Go/ Rust/Ruby already had syntax highlighting + folding; PHP gained a bundled TextMate grammar (php.tmLanguage.json, from VS Code, MIT) so.phpfiles now get highlighting + folding + comment/indent support alongside the LSP. Settings schema 10→11 (additive-identity migration). -
LSP support for XML, JSON, and Bash/Shell — three more language servers alongside Java/TypeScript/ Python: XML via lemminx (
lemminx; schema-aware completion + validation for.xml/.xsd/.xsl/.svg/.fxml/…), JSON viavscode-json-language-server(schema completion + validation), and Bash/Shell via bash-language-server (bash-language-server start; diagnostics come fromshellcheckwhen it’s on your PATH). Each has its own enable toggle + command field in Settings → LSP (auto-detected on PATH; never bundled). Off by default with the rest of LSP. These ride the existing server-centric foundation — XML/JSON/shell already had syntax highlighting + folding, so no new grammars. The Settings → LSP page is now data-driven over a server-descriptor list. -
Gutter Run ▶ for shell scripts — a green play glyph in the gutter of
.sh/.bash/.zshfiles runs the script withbashand streams output into the Run tool window (reusing the Java/Python Run plumbing). Shown only when the Bash LSP server is enabled (Settings → LSP), gating it separately from the Java/Python Run glyphs. -
LSP support for Python (Pyright) — diagnostics, completion, go-to-definition/references, and hover for
.py/.pyw/.pyi. Configure the server command in Settings → LSP with its own enable toggle (auto-detected on PATH;npm i -g pyright). Off by default with the rest of LSP. (Python already had syntax highlighting, so this rides the existing server-centric LSP foundation.) -
Gutter Run ▶ for Python scripts — a green play glyph in the gutter (on the
if __name__ == "__main__":line, else the first line) runs the file withpython3and streams output into the Run tool window, reusing the Java compact-source Run plumbing. -
Python completion fixes — the popup now appears for fuzzy-matching servers (Pyright returns subsequence matches; the client falls back to those when no literal-prefix match exists). Also wired up auto-import support (declare
completionItem.resolveSupport/workspace.configuration, pushpython.analysis.autoImportCompletions=true) so accepting a cross-module symbol inserts itsimport— auto-import is wired but still being verified. -
LSP support for TypeScript & JavaScript (
typescript-language-server) alongside Java — diagnostics, go-to-definition/references, hover, and completion for.ts/.tsx/.js/.jsx/.mjs/.cjs/.mts/.cts. One TypeScript server serves all of them; the LSP registry is now server-centric (one session per server + project root). Also bundles TypeScript/TSX TextMate syntax highlighting (so JS/TS are no longer plain text) and supports auto-imports — accepting a completion that needs an import inserts theimportline viacompletionItem/resolve. Configure the server command in Settings → LSP (auto-detected on PATH;npm i -g typescript-language-server typescript). Off by default with the rest of LSP.
Fixed
-
Settings → Mermaid: the
mmdc/maiddetection status is now colored green (found) / red (missing), matching the LSP and Git status labels, instead of plain text. -
Windows: keyboard froze and the command palette wouldn’t open. Two distinct bugs (found via on-device key-event logging):
- Keyboard freeze on Alt. A bare
Altor an unboundAlt+<key>was interpreted by Windows as menu/mnemonic activation, putting the window into “menu mode” — which froze all keyboard input app-wide (mouse still worked, restart required) and broke the manyM-chords. Editora now consumes the bare Alt and any unbound plain-Alt key on Windows/Linux so menu mode never engages. BoundM-chords work as before; AltGr (Ctrl+Alt) is left alone so international layouts keep composing characters; macOS is unchanged. - Command palette (
M-x) never appeared. It was ajavafx.stage.Popup, which on Windows doesn’t reliably take OS keyboard focus — focus got orphaned and the keyboard went dead. It’s now an in-scene overlay (centered card + dim backdrop) that takes focus correctly on every platform.
(Note: if a specific chord like
Alt+Xstill does nothing on Windows, check for a global hotkey manager — e.g. XKeymaps — grabbing it before Editora; that’s external to the editor.) - Keyboard freeze on Alt. A bare
-
Diagnostic hover tooltip flickered while the mouse moved over a mark — the tooltip was re-shown (and re-positioned to the cursor) on every mouse-move event, so hovering a squiggle or a scrollbar diagnostic mark looked janky. It’s now shown once and left in place until you move to a different mark/message. Applies to the editor squiggle hover, the Mermaid-lint hover, and the scrollbar diagnostic stripe.
-
LSP error squiggles / Problems never appeared when the file path involved a symlink — a language server reports diagnostics under the file’s real (canonical) path (e.g.
/private/tmp/…for a/tmp/…symlink on macOS, or any symlinked project directory), but the open tab was matched by a non-symlink-resolved path, so every diagnostic was silently dropped. The match now uses the canonical (symlink-resolved) path on both sides. -
jdtls (and other wrapper-script servers) leaked orphaned processes and could hang on restart — the Homebrew
jdtlslauncher is a wrapper (jdtls→ python → java); Editora only killed the wrapper on shutdown, orphaning the real server JVM, which kept running and held its Eclipse workspace lock so the next session for that folder couldn’t start (stuck on “Starting…”, no completion/diagnostics). Editora now tears down the whole process tree. (Orphans left by an earlier version may need a one-time manual kill, e.g.pkill -f org.eclipse.jdt.ls.) -
A chatty language server could deadlock during startup — its stderr stream was never drained, so once the ~64 KB OS pipe buffer filled the server blocked. The server’s stderr is now discarded.
-
Language servers installed via a Node version manager (nvm/fnm/asdf) showed “not found” in the packaged app — a Finder-launched
.appinherits a strippedPATHthat omits the version manager’s bin dir (e.g. nvm’s~/.nvm/versions/node/<ver>/bin), so npm-global servers liketypescript-language-server,pyright-langserver,vscode-json-language-server,bash-language-server, andyaml-language-servercouldn’t be located even though they were installed.ProcessRunnernow resolves the user’s login-shellPATHonce (running$SHELL -l -i -cwith a timeout) and folds it into the PATH it gives every subprocess, recovering all version-manager and profile-set dirs at a stroke. This also helps Git (a Homebrewgit) and Mermaid (mmdc/maid). -
LSP loading bar in the status bar never stopped spinning — it was only cleared by incoming diagnostics or jdtls’s vendor-specific status message, so a file with no diagnostics (or any non-jdtls server) left it running indefinitely. It now clears as soon as the universal LSP
initializehandshake completes. -
Intermittent black screen / render glitches in the packaged macOS app after opening many files — JavaFX’s GPU texture pool defaults to a 512 MB ceiling; with enough open files (each holding editor + overlay + minimap textures, plus cached preview/diagram images) it filled up and the render thread threw
NullPointerExceptions (nullRTTexture/ mask texture), showing as a black window. Reduced retained GPU memory so it no longer scales with the number of open files — background tabs now drop their minimap snapshot (regenerated when shown), and the Markdown image + Mermaid diagram caches are LRU-bounded — and raised the Prism texture caps (prism.maxvram,prism.maxTextureSize) in the packaged app and dev run as a safety net. (Only reproduced in the jlink/jpackage build, not undermvn javafx:run.) -
Editor froze (render thread crashed) when opening a Markdown file straight into Preview/Split mode — the editor’s
Canvasoverlays (minimap, whitespace, spell-check, note-highlight, Mermaid lint) were sized directly from the editor pane, which is momentarily zero-width while a Markdown buffer opens into Preview/Split. A buffered draw on a zero/NaN-sized canvas made JavaFX allocate a null GPU texture and the render thread NPE’d inNGCanvas— stalling the whole pipeline so the UI looked hung. Canvas dimensions are now clamped to a finite, in-range size (new unit-testedCanvasGuards) and overlays skip painting into a collapsed surface. -
External tools not found in the installed app — a GUI-launched app (a macOS
.appfrom Finder, a Linux.desktop) inherits a strippedPATHthat omits Homebrew / npm / Node locations, sommdc,npx/maid(and a Homebrew-installedgit) showed up as “not found” even when installed. Editora now augments the subprocessPATHwith the usual install dirs (e.g./opt/homebrew/bin,/usr/local/bin) and resolves a bare command name to its absolute path before launching. Setting an absolute path in Settings still works as an override. -
App freeze with several Markdown previews open (macOS) — rendering SVG badges in the Markdown preview pulled in AWT/Java2D, whose native macOS pipeline contended with JavaFX for the AppKit run loop, an intermittent deadlock that grew more likely the more previews were open. AWT/Java2D now runs headless (software rasterization only), eliminating the conflict; SVG badges still render.
-
The Markdown preview table columns no longer collapse to one character wide (short headers like “Phase”/“Depends on” stacking letter-by-letter); columns now size proportionally to their content.
-
Thread leak on tab close — each open file’s two background worker threads (Markdown preview + syntax highlighter) are now shut down when its tab closes, instead of lingering for the rest of the session.
-
The message log opened from the status bar now toggles: clicking the status message again closes the popup.
-
The Welcome page now shows a scrollbar (vertical or horizontal) when its content doesn’t fit the window, and its tab can be dragged to reorder like any other tab.
Added
-
LSP support (Phase 1: Java) — language intelligence via the Language Server Protocol, backed by Eclipse JDT LS. Live diagnostics (severity-colored squiggles + hover tooltips + a Problems tool window,
M-8), go-to-definition (M-.), find references (M-?, results in a picker), hover documentation (C-c h, rendered Markdown), and LSP-backed completion merged into the existing completion popup. The server is auto-detected (jdtlson your PATH; a Settings command override is available) and never bundled — install it (e.g.brew install jdtls). Off by default: enable it in Settings → LSP (or theview.toggleLspcommand). The workspace root is the active Editora project, else the nearest build file (pom.xml/build.gradle/…), else the file’s folder. All server I/O runs off the UI thread; document sync is debounced. (Built on LSP4J; more languages, formatting, and refactoring are planned.) -
Toolbar refinements — the top icon bar’s edit buttons are now state-aware: Save is disabled unless the file has unsaved changes; Undo/Redo only when there’s something to undo/redo; Cut/Copy only with a selection (Cut also needs an editable buffer); Paste only when the buffer is editable and the clipboard holds text (all disabled on the Welcome tab). A new Find in Files icon sits beside the in-file Find icon. The toolbar can be hidden (Settings → Application “Show toolbar”, or the
view.toggleToolbarpalette command); when hidden, a small floating Tool button (top-right, like the Zen “Z”) brings it back. The decorative Switcher keybinding hint was removed from the bar. -
Search — a big upgrade across three areas. In the editor, the find bar (
C-s) now searches incrementally as you type, highlights every match (the current one accented), shows a “{n} of {total}” count, and adds a whole-word toggle (regex + case already existed). Find in Files (C-S-f, or the Search Results tool window onM-6) searches the project and open files off-thread and lists matches grouped by file — Enter/double-click jumps to one — with replace across files (open buffers edited undoably, closed files rewritten on disk, after a confirmation). AceJump (M-g j, avy-style): type a character, then a 1–2 char label drawn over any on-screen occurrence, to jump the caret there. -
Printing — two new commands show a print preview first, then send to the printer.
File: Print…(editor.print) previews/prints the active buffer’s source code — syntax-highlighted with an optional line-number gutter, paginated by whole lines, in a clean light theme.File: Print Preview…(preview.print) handles the rendered Markdown preview (block-aware pagination — a paragraph, table, or image is never split across a page boundary) or a standalone Mermaid.mmddiagram scaled to the page. A modal preview window lets you page through the exact output (scaled to fit, with page navigation); Print… then opens the native dialog (printer, copies, paper) to send, Close cancels. Reuses the Include line numbers / Syntax highlighting settings (the Settings → Editor section is now Export & Print); preparation runs off the UI thread. -
Export to PDF — two new commands.
File: Export to PDF(editor.exportPdf) writes the active buffer’s source as a real, searchable PDF: embedded JetBrains Mono, syntax highlighting, and a right-aligned line-number gutter (both toggleable), in a clean light theme regardless of the app/editor theme.File: Export Preview to PDF(preview.exportPdf) exports a previewable buffer: a Markdown document becomes native vector text (headings, lists, tables, block quotes, code blocks, links and images — including embedded Mermaid diagrams and SVG badges as images), and a standalone Mermaid.mmdfile exports via mmdc’s native vector PDF. New Settings → Editor → PDF Export: Include line numbers, Syntax highlighting, and a Page size selector (Letter / A4). PDF generation runs entirely off the UI thread. Powered by Apache PDFBox. -
Mermaid diagram support (Settings → Mermaid, off by default) — renders Mermaid diagrams in the preview: standalone
.mmdfiles (syntax-highlighted, with the Editor/Split/Preview toggle) and```mermaidfenced blocks inside Markdown. Uses the external mmdc (mermaid-cli) to render and to export a diagram to SVG/PNG/PDF (Mermaid: Export Diagramcommand), and maid (probelabs/maid) to validate — a failed diagram shows the error with its line/column in the preview. Configure themmdc/maidcommands in Settings (blank =mmdconPATHandnpx -y @probelabs/maid). Rendered diagrams are cached by content so editing stays responsive..mmdfiles also get live maid linting (red squiggly underlines + hover tooltip, debounced while typing) and autocomplete of Mermaid keywords + diagram snippets — both with their own toggles, automatically disabled when Mermaid is off or the tools aren’t detected. -
Export configuration — a new “Export Configuration…” button (Settings → Advanced) and the
Configuration: Export to Zipcommand zip your active config folder (~/.editora,~/.editora-dev, or a--config-diroverride) into a timestamped.zipin your home directory, for quick backups. -
Show/hide the tool stripe — a new “Show tool stripe” setting (Settings → Tool Windows) and the
View: Toggle Tool Stripecommand (command palette) hide the side icon bars. This is UI only — tool windows still open via their keyboard shortcuts (e.g.M-1) and the command palette. Hiding the stripe takes precedence over each tool window’s individual visibility toggle. -
Build commit in dev mode — when running with
--dev, the About dialog and the Welcome page now show the short git commit the app is running from, so it’s easy to tell which build you’re testing. Not shown in normal (non-dev) runs. -
Independent Markdown preview theme — the rendered preview can now be light or dark independently of the app/editor theme, so you can read docs in light while coding in a dark theme (or vice versa). Toggle it with the floating sun/moon button next to the preview’s −/+ zoom controls, or the Markdown: Toggle Preview Light/Dark command. The choice is remembered; until you first toggle it, the preview follows the app theme as before.
Changed
-
Unsaved-file marker everywhere — the dot + amber-italic style the tabs use for a file with unsaved changes now also appears in the Switcher, the Open Files picker, and the Project tool window tree, so every list of open files reads consistently. The Project tool window also refreshes when files/folders change outside Editora (on window focus), keeping its expanded folders and selection.
-
Markdown preview readability — the rendered preview now lays its content out as a centered, width-capped column (GitHub-style) with more generous margins and vertical spacing (line height, space between blocks and list items, heading separation), instead of stretching edge-to-edge across a wide window.
Added
-
SVG images in the Markdown preview — the preview now renders SVG images, which JavaFX can’t decode on its own. This makes shields.io / GitHub status badges (served as SVG) show up in the preview as they do on GitHub. Images load off the UI thread and are cached. (Uses the lightweight JSVG library.)
-
Keyboard scrolling in the Markdown preview — Space / PageDown (page down), Backspace / PageUp (page up), and the Emacs
C-v/M-vnow scroll the rendered preview. -
Status-bar message log — click the status message on the left of the status bar to see every message from the current session in a scrollable popup, newest first, each with an
HH:mm:sstime indicator. Messages are selectable (multi-select) and can be copied to the clipboard (Cmd/Ctrl+C or a right-click Copy). The log is in-memory only (not persisted), capped at the most recent 200 messages, with a Clear action. Also available as the Message Log command (view.messageLog). -
Welcome page — instead of opening an empty “Untitled” buffer, Editora now opens a VSCode-style Welcome page in its own tab at startup when there’s no session to restore. It offers Start actions — New File, Open File, then Open Folder as a Project and Clone Git Repository (each shown only when that feature is enabled), and the Command Palette last — each with its configured keybinding shown alongside, plus a Recent files list and a footer with the app version, a link to the project home page, and the license. Being a real tab, it activates, switches, and closes like any other tab; the Welcome Page command (
view.welcome) opens it (or re-selects it if already open) on demand even with files open. The About dialog now also shows the home-page link, copyright, and license. New CLI flag--new-file[=name]bypasses it by opening a fresh buffer instead:--new-file=notes.mdopens an unsaved buffer titlednotes.md(highlighted by its extension; first save prompts for a location), and bare--new-fileopens a blank untitled buffer. -
Personal Notes — private annotations attached to a file without modifying the file, for read-only / generated / shared code where you want knowledge stored separately. Three scopes (word / line / range), an optional body, tags, and a status (active / resolved / orphaned). Notes follow their content as you edit (live offset tracking) and re-anchor on reopen by the captured selection + surrounding context; a note whose anchor can no longer be found is marked orphaned (kept and recoverable, never silently lost). File identity is content-hash + path, so a note re-attaches even when the file is renamed/moved outside the app. Indicators: a gutter glyph, a soft in-editor highlight behind the anchored span, and a hover tooltip (the body is rendered as Markdown in the editor’s font) — the gutter/highlight is toggleable via Settings → Editor → Show note indicators. A Personal Notes tool window (
M-5) groups notes per file with a filter, edit/resolve/delete, and “delete all in file”; commands cover add (C-c n), next/previous, cross-file Jump to Note (M-g n), Search Notes (a picker that matches the full note body, tags, and file path), delete (the note on the caret line — also available from the panel and as a Delete button in the note editor), and export to JSON (resolve/reopen lives in the tool window’s context menu). Note bodies are edited in a multi-line text box (Enter inserts a newline; Ctrl/Cmd+Enter saves). Stored per project in a single versionednotes.json(reuses the bookmark per-project bucket model + the config migration framework). Bookmarks are unaffected (separate gutter slot, no layout shift). The whole feature is off by default — enable it via Settings → Application → Enable Personal Notes (when off, the tool window, commands, and editor menu items are hidden). -
Config schema versioning + migrations — every structured config file (
settings.toml,workspace-state.json,projects/<id>.json,projects.json,bookmarks.json,recent-files.json) now carries a per-file integerschemaVersion(baseline 1), and reads go through a small migration framework (config/migration/) so future releases can evolve a file’s format with one registeredv→v+1step. Existing unversioned files load unchanged and get stamped on the next save (no data loss);recent-files.json(previously a bare array) is migrated to a versioned object. If a file is newer than the running build (e.g. after a downgrade), it’s backed up to<name>.v<n>.bakand defaults are loaded rather than overwriting newer data. -
Autocomplete — appears as you type (debounced) and on demand (
C-M-i/M-/, command “Edit: Trigger Autocomplete”). Code files show a caret-anchored popup of snippet completions (accepting one expands the snippet with its tab stops); Enter/Tab accept, ↑/↓ navigate, Esc dismisses. Prose files (plain text / Markdown) instead show inline “ghost text” — a single greyed continuation drawn after the caret, completed from the bundled spell dictionary plus your personal dictionary; press Tab to accept, Esc (or just keep typing / move the caret) to dismiss. In the code popup, ↑/↓ and C-n/C-p move the selection. Settings → Editor has a master “Enable autocomplete” toggle plus per-source checkboxes — Words (prose) and Snippets (code) — all on by default (more sources can be added later), and there are command-palette toggles for each (“View: Toggle Autocomplete”, ”… Words (Prose)”, ”… Snippets”). The dictionary word list is parsed off-thread and cached; prefix lookup is a binary-search range, so typing/scrolling stay unaffected. -
Multi-language interface (i18n) — Editora’s UI can now run in English, Italian, Spanish, French, Portuguese, or German. Command-palette titles, toolbar tooltips, tool-window titles, and the full Settings window are translated; pick a language under Settings → Appearance → Language (default Automatic follows the system language, falling back to English). A language change applies on the next restart. Strings live in a
messages[_<lang>].propertiescatalog loaded by the newcom.editora.i18n.Messageshelper; a key-parity unit test keeps every translation complete. Essentially the entire interface is translated — command palette, toolbar tooltips, tool-window titles and panel contents (Project, Structure, Bookmarks, File Information, Commit/Git), the full Settings window, status-bar segments, echo-area status messages, every dialog (titles, bodies and buttons), context menus, and popups (branch dropdown, command palette, file finder, switcher). The chosen language also drivesLocale.setDefault, so JavaFX’s own OK/Cancel buttons match.
Changed
-
Settings window redesigned — a scalable left category sidebar (Appearance, Editor, Tool Windows, Spell Check, Application, Advanced, plus “coming soon” placeholders for Keymaps/Plugins/Git/AI) replaces the single stacked list, with a search box that filters settings + jumps to matches, a live preview on Appearance (sample code that recolors/re-fonts as you change theme/font), and a Reset to Defaults on the Advanced page. Changes still apply live. Tool-window placement moved to its own page; the new Tab size control lives on the Editor page. About moved out of Settings (it’s on the toolbar + the “About Editora” command); the settings-file link now lives on the Advanced page.
-
Markdown preview now renders closer to GitHub — task lists (
- [x]/- [ ]) show real checkboxes instead of bullets, inlinecodegets a rounded gray pill (instead of blue text),#/##headings get an underline rule, and links are no longer permanently underlined. Standalone images now render as block images (with the alt text / title as a tooltip), in addition to inline images; relative image paths resolve against the file’s folder, andhttp(s)/file/data:URLs are supported.
Fixed
- In-app file rename now carries bookmarks and personal notes to the new path — renaming an open file from the tab/right-click menu already moved its folds and recent-files entry; it now also re-keys that file’s bookmarks and notes, so they no longer get stranded under the old path.
Added
-
Emacs transpose commands — transpose characters (
C-t), words (M-t), and lines (C-x C-t), also in the command palette (“Edit: Transpose …”). At end of line,C-tswaps the two preceding characters (typo fix);C-x C-tswaps the current line with the one above. -
“Enable Git” setting (Settings → Git), off by default — Git integration is now opt-in. When off, the status-bar VCS segment is disabled, the Commit tool window is hidden, gutter change markers are cleared, and all Git commands/keybindings are inactive (and hidden from the palette). Turn it on in Settings → Git to use branch/status, change bars, commit, and sync. The Git page detects the
gitcommand: it shows the installed version when found, or “git command not found” and disables the checkbox when git isn’t onPATH. -
Save / Save As… in the tab right-click menu — save the right-clicked tab directly (Save is greyed out for an unchanged, already-saved file).
-
Git support (native CLI) — Editora now talks to your installed
git. The status bar shows the current branch with ahead/behind counts (⎇ main ↑2 ↓1, click for the branch dropdown — an IntelliJ-style searchable list of actions + Local/Remote branches, each local branch showing its upstream and incoming/outgoing (↓/↑) commit counts); the editor gutter draws change bars relative toHEAD(green added / blue modified / red deleted); and a Commit tool window (M-4) groups Staged / Changes / Untracked files with stage, unstage, discard, Stage All, a Push button, and a commit message box (Ctrl/Cmd+Enter to commit). Clone a remote repo with “Git: Clone Repository…” (or the button in the empty Commit tool window) — one dialog asks for the repo URL and the destination directory (with a Browse button; the directory auto-fills to<home>/<repo>from the URL), clones, and opens a file from it (its README, if any) so Git lights up; cloning is independent of projects (you don’t need project support to clone or use Git). Palette commands: “Git: Clone Repository…”, “Git: Commit…” (C-x g), “Git: Stage Current File”, “Git: Switch Branch…”, “Git: New Branch…”, “Git: Fetch”, “Git: Pull”, “Git: Push”, and “Git: Refresh Status”. Pushing a brand-new branch automatically sets its upstream (--set-upstream origin <branch>), and Git command failures are shown in a readable dialog rather than the one-line status bar. The Commit tool window is shown only when the active file is under Git; otherwise it’s hidden. The status-bar VCS segment is always present: outside a repo it reads No VCS, and clicking it offers Clone Git repository…. All Git calls run off the UI thread; everything degrades silently when a file isn’t in a repository orgitisn’t installed. (No new dependencies — Editora shells out to your real git, so credential helpers, SSH, and signing all work.) -
Theme commands in the palette — “Theme: Set App Theme…” and “Theme: Set Editor Theme…” open a fuzzy picker. The app-theme picker switches the chrome theme and the editor theme to match; the editor-theme picker changes only the editor colors (and pins it so it won’t follow the app theme).
-
Spell checking — misspelled words get a red wavy underline; right-click for suggestions (click one to replace), Add to Dictionary, or Ignore. In source files only comments and string literals are checked (identifiers aren’t flagged); plaintext and Markdown are checked in full. Toggle it with “View: Toggle Spell Check” (or the Settings window, where you can also choose the default dictionary language); pick the dictionary per file with “Spell Check: Set Language…” (ships English (en_US, en_GB), Spanish (es), and French (fr); your added words live in
dictionary.txt). Powered by Apache Lucene’s pure-Java Hunspell engine. -
Detect external file changes — when the file in the active tab is modified by another program, Editora notices (on window focus or when you switch to that tab) and offers to reload it, or keep your version (also when you have unsaved edits). The prompt only appears for the tab you’re looking at. Deleted files are left untouched. Editora’s own saves never trigger the prompt.
-
Smart backspace — pressing Backspace while the caret is in a line’s leading indentation deletes the whole indent in one press instead of one space at a time. On a blank, auto-indented line a single Backspace jumps back to the end of the previous line (undoing the Enter); on an indented line that has content, it clears the indent. Outside leading whitespace, Backspace behaves normally.
Changed
- Bookmarks now live in their own
bookmarks.json(in the config directory) instead of inside each session’sworkspace-state.json, so they’re all in one easy-to-find file. They remain scoped per project — switching projects shows only that project’s bookmarks (the global session has its own), and deleting a project deletes its bookmarks. Existing bookmarks are migrated automatically on first run (moved into the right per-project bucket; the old session files are cleaned up). This also fixes the cross-file “Jump to Bookmark” picker (M-g b) showing an empty list when a project was active. - Reorder bookmarks in the Bookmarks tool window — move a bookmark within its file, or a whole file
group, with Alt+Up/Down, the right-click Move Up/Down menu, or drag-and-drop (with the
same drag visuals as the editor tab strip: a translucent ghost, the dragged row dimmed, and an accent
insertion line on the drop target). The order you set is exactly the order the
M-g bjump picker uses, and it persists (and survives edits to the file).
Fixed
- The About dialog now shows the live config/settings path, so
--dev(~/.editora-dev/) and--config-dirare reflected (it previously always showed the default~/.editora/settings.toml).
Added
-
Zen-mode exit button — a small floating “Z” button appears at the top-right of the window while in Zen mode (same look as the Markdown preview controls); click it (tooltip: “Exit Zen mode”) to leave Zen. It’s hidden whenever Zen is off, and on Markdown files it sits just below the preview controls so the two don’t overlap.
-
Navigation key hints in the pickers — the Command Palette, the “Jump to…” pickers (recent files, bookmarks, structure, tool windows, snippets), and the file finder now show a footer legend of their relevant keys (move / select / cancel, plus Tab-complete in the file finder), matching the Switcher.
-
Focused tool window is highlighted — the panel that currently holds keyboard focus (Project, Structure, Bookmarks, File Information) gets an accent-tinted header, so it’s clear where you are when moving between panels and the editor.
-
--devcommand-line flag — runs Editora against a separate~/.editora-dev/config directory so a development instance can run alongside your everyday editor without sharing settings or session state. Config-dir precedence is now--config-dir>EDITORA_CONFIG_DIR>--dev>~/.editora/. A light-red “dev mode” badge appears in the toolbar (just left of the About icon) so the dev instance is visually distinct. -
Comment / uncomment (
M-;or the palette: “Edit: Toggle Comment”). A single line toggles a line comment; a multi-line selection toggles a block/region comment — using whichever the language has (e.g.//and/* */for Java/C-likes,#for Python/shell/YAML,<!-- -->for XML/HTML/Markdown,/* */for CSS,--for SQL). Falls back gracefully (block-only languages always wrap; line-only languages comment each line), preserves indentation, and is a no-op for languages without comments. -
Auto-close brackets and quotes — typing
(,[,{,",', or`inserts the matching closer and keeps the caret between; typing the closer (or a quote) when it’s already next to the caret types over it; typing an opener/quote with a selection wraps the selection; and Backspace inside an empty pair deletes both halves. Quotes are not auto-paired next to a word character (so apostrophes indon'tare left alone). -
Highlight matching brackets — when the caret is next to a
(),[], or{}, both it and its match are highlighted. -
Auto / smart indentation for all 21 languages. Enter keeps the current line’s indentation; a block-opener adds a level (braces
{ ( [;:in Python/YAML;do/then/inin shell;def/class/do/… in Ruby; an open tag in XML/HTML); pressing Enter between a matching pair ({}/()/[]or<a>|</a>) opens an indented stanza with the closer dropped below; and typing a closer (a)]}bracket alone on the line, or a keyword likeend/fi/done) re-aligns the line to its opener. The indent unit (tab vs spaces) is inferred from the file. -
Ctrl + mouse wheel zooms the Markdown preview while in Preview mode (scroll up/down to zoom in/out, driving the preview’s
−/+); the editor text zoom is left untouched there. In Editor and Split modes Ctrl+wheel still zooms the editor text. -
Snippets (VS Code / TextMate-style) — expand templates with interactive tab stops. Type a prefix and press Tab to expand (Tab still indents when nothing matches), or pick from the “Snippet: Insert…” fuzzy list (
C-c i). After expanding, Tab / Shift-Tab cycle the fields, placeholders are pre-selected to overtype, mirrored fields update live, and$0is the final caret. Bodies use the standard syntax —$1,${1:default}, mirrors,${1|a,b|}choices (a dropdown appears on the field), variables ($TM_FILENAME,$TM_DIRECTORY,$CLIPBOARD,$CURRENT_YEAR, the selection, …) and\$escapes. Snippets ship for all 21 highlighted languages — most from the MIT-licensed friendly-snippets collection (attributed inNOTICE), the rest written for Editora. Add your own in~/.editora/snippets/<language>.json(orglobal.json) — “Snippet: Edit User Snippets…” opens the file and “Snippet: Reload Snippets” picks up changes. User snippets override bundled ones. -
Read-only / View mode — toggle a buffer read-only with
C-x C-q(or the palette: “View: Toggle Read-Only”), so it can’t be edited by accident. Typing and the editor’s own edit commands are blocked (with a status-bar hint), while highlighting, minimap, folding, scrolling, and copy keep working. A file opens read-only automatically when it isn’t writable on disk, a status-bar toggle flips it either way (“Read-Only” ⇄ “Editable”) and the tab title is muted, and the per-file state is remembered across restarts. A Word-style “View Mode” banner docks above the editor with the navigation hint and an Enable Editing button (shown only when the file is writable). While read-only, Space pages down and Backspace pages up (pager-style, likeless/man). -
More command-line options —
--version/-Vand--help/-h(print and exit, no window); positionalFILE,FILE:LINE, andFILE:LINE:COLUMNto open a file (and jump);--project[=]<dir>to open a folder as a project (only when Projects are enabled, else ignored); and--zento start in Zen mode. File/project args are additive on top of the restored session. Cross-platform. -
Custom config folder — point Editora’s config directory somewhere other than the default
~/.editora/with the--config-dir <path>command-line argument (or--config-dir=<path>) or theEDITORA_CONFIG_DIRenvironment variable. Precedence:--config-dir>EDITORA_CONFIG_DIR>~/.editora/. Works on macOS, Linux, and Windows. -
Text zoom — quickly scale the editor text up/down independent of the configured font size (the set size is 100%). Use the status-bar
− 100% +control,C-=/C--(andCtrl+Plus) /C-0to reset, Ctrl + mouse-wheel, or the palette (“View: Zoom In / Out / Reset Text”). The zoom level persists across restarts and is separate from the font-size setting (it’s not shown in Settings). -
Markdown preview — an IntelliJ-style 3-mode view for Markdown files via a small floating control at the top-right of the editor: Editor, Editor + Preview (side-by-side), and Preview. The preview is rendered natively (no WebView) from CommonMark + GitHub-flavored Markdown (tables, task lists, strikethrough, autolinks), updates live as you type (debounced, off-thread), follows the active theme, and remembers its mode per file. The preview has −/+ buttons (top-left) to zoom the text in/out. Also available as palette commands (“Markdown: Editor / Editor and Preview / Preview”, and “Markdown: Zoom In / Out / Reset Preview”).
-
Bookmarks — mark lines and jump back to them, across files. Toggle a bookmark on the caret line with
C-c m, or click the gutter to add one (clicking an existing bookmark asks before removing it); a light-orange bookmark glyph appears in the line-number gutter. Each bookmark can carry an optional note (“Bookmarks: Edit Note…”). The Bookmarks tool window (M-2) lists every bookmark across all files (open or closed), grouped by file, with a filter box; Enter opens the file and jumps to the line, and right-click edits the note or deletes.C-c ]/C-c [cycle through the current file’s bookmarks, andM-g b(“Bookmarks: Jump…”) is a fuzzy cross-file picker. Bookmarks persist with the session (per project) and track their line as you edit above them. -
The Project tool window’s file tree now colors its icons to match the active theme: folders use the theme accent and files a muted tone. The colors are driven by two looked-up CSS variables (
-project-folder-color/-project-file-color) that default to the GUI theme (AtlantaFX) and are overridden per editor theme, so the tree tracks the chosen code theme. Files open with unsaved changes are marked in the tree like dirty tabs — a ”• ” prefix and amber italic — updating live as you edit and save. -
Reorder the tool-window stripe icons: drag an icon up/down within its side, or use the new ▲/▼ buttons next to each row in Settings → “Tool window placement”. The order persists in the session state (
workspace-state.json). -
Projects (VSCode single-folder-workspace style), off by default — enable via Settings → “Enable projects” (when off, the Project tool window, the toolbar open-folder icon + project switcher, and the project commands are all hidden). Each project is a root folder plus its own saved session (open files with carets/pins, active file, folds, tool-window layout). Open one via the toolbar’s open-folder icon (native dialog) or “Project: Open Folder…” (
C-x C-p, a keyboard folder picker likeC-x C-f); “Project: Switch…” (C-x p) saves the current session and restores another’s; “Project: Close” returns to the global session (with confirmation); “Project: Delete…” (also a trash button in the Project tool window) removes a project from your list — its folder and files on disk are kept, only the project entry and its saved session are removed. The active project is shown in the window title and a project switcher combobox in both the toolbar and the Project tool window (each with a “No Project” entry that returns to the global session without closing any project). The Project tool window shows the project’s files as a lazy tree with Emacs-style keyboard navigation (C-n/C-p, C-f/C-b, Enter to open), a filter box that runs a bounded project-wide filename search (like the Structure filter), and a right-click menu to rename or delete files (syncing open tabs). Opening a recent file that belongs to another project switches to that project first (restoring its session/tree) before opening the file. Settings stay global. -
Keyboard file finder (Emacs
find-filestyle) onC-x C-f: a path popup with a live directory listing that prefix-autocompletes as you type.Tabcompletes the common prefix, Enter descends into a folder or opens a file (a non-existent path opens a new buffer, written on save),C-n/C-pmove,Esccancels — no mouse needed. Also thefile.findpalette command. The toolbar Open icon (andfile.open) still uses the native OS dialog. -
Fold/unfold the single region at the caret:
view.fold(C-c C-f) collapses the innermost region around the caret,view.unfold(C-c C-u) expands it, andview.toggleFold(C-c C-t) toggles it — complementing the existing Fold All (C-c f) / Unfold All (C-c u). -
Keyboard “Jump to…” pickers — command-palette-style fuzzy popups: recent files (
C-x C-r), the active file’s structure/symbols (M-g i), open files/tabs (C-x b), and tool windows (M-g t). Type to filter,C-n/C-por ↑/↓ to navigate, Enter to act (open the file / jump to the symbol / switch tabs / open the tool window),Escto cancel. All are also in the command palette (“Recent Files: Jump”, “Structure: Jump”, “Open Files: Jump”, “Tool Windows: Jump”). -
More Emacs movement keys:
M-m(back to indentation),C-l(recenter the caret line in the viewport),M-{/M-}(backward/forward paragraph), andM-a/M-e(backward/forward sentence) — all registered commands, so they also appear in the palette. Note: on macOS the Option dead keys (Option+e/i/u/n/`) are intercepted by the OS for accent composition and never reach the app, soM-e(forward sentence) can’t be triggered by keyboard there — use the palette command “Go: Forward Sentence” instead. -
Auto save (VS Code-style), off by default. Choose a mode in Settings: “After delay” saves a file once it’s been idle for a configurable number of seconds (default 1), or “On focus change” saves when you switch editor tabs or the window loses focus. Only dirty, file-backed, writable buffers are saved (untitled/read-only ones are skipped); writes happen off the UI thread. Cycle the mode with the
file.toggleAutoSavepalette command orC-c a. -
File breadcrumb bar (IntelliJ-style) along the bottom, above the status bar: shows the active file’s path as clickable segments. Click a segment to drop down that folder’s contents (sub-folders first, then files); pick a folder to drill in (it becomes the trailing crumb and the dropdown reopens) or a file to open it. Long paths scroll, anchored to the file. Off by default; toggle via the Settings checkbox, the
view.toggleBreadcrumbpalette command, orC-c p. -
Runnable fat jar:
mvn -Pfatjar packagebuildstarget/Editora-<version>.jar, launchable withjava -jar(bundles JavaFX classes + natives for the build host’s platform via a non-ApplicationLaunchermain class). The release pipeline builds one per platform and attaches them to the GitHub release alongside the native installers. -
Zen mode (distraction-free): one toggle hides the editor view options (80-column ruler, current-line highlight, line numbers, minimap, hidden characters), the toolbar, status bar, and tab bar, and all open tool windows — leaving just the editor. While in Zen you can still switch individual items back on (e.g. line numbers or the status bar); toggling Zen off restores your previous configuration exactly. The mode persists across restarts. Via the Settings checkbox, the
view.toggleZenpalette command, orC-c z. -
Toggle the toolbar, the status bar, and the tab bar — via Settings checkboxes (“Show toolbar”, “Show status bar”, “Show tab bar”), the
view.toggleToolbar(C-c t) /view.toggleStatusBar(C-c s) /view.toggleTabBar(C-c b) palette commands, and persisted in settings. -
Application icon: the Editora logo now appears as the window/dock/taskbar icon, in the About dialog, and as the native installer icon (macOS
.icns, Windows.ico, Linux.png), all generated frombranding/editora-icon.svg. -
Emacs-style text selection:
C-SPCsets the mark, then any caret-movement chord (C-f/C-b/C-n/C-p/M-f/M-b/C-a/C-e/M-</M->/C-v/M-v) extends the selection from it.C-x C-xexchanges point and mark;C-g(and a mouse click, cut/copy/paste) clears the mark. The region works withC-w/M-w/C-y. -
Bundled monospace fonts (no install needed): JetBrains Mono (the new default), Cascadia Code, Fira Code, IBM Plex Mono, and Source Code Pro. They’re loaded at startup and listed first in the Settings font picker. (Existing configs keep their saved font; the new default applies to fresh installs.)
-
Editor color themes (syntax tokens + editor surface) chosen in Settings under “Editor theme”: Primer Light/Dark, Nord Light/Dark, Cupertino Light/Dark, Dracula (these match the AtlantaFX themes), plus JetBrains-style Islands Light and Islands Dark. Selecting an AtlantaFX app theme switches to the matching editor theme automatically, until you pick an editor theme yourself.
-
Editor tabs can be reordered by dragging them with the mouse. Pinned tabs stay grouped at the front (a drag is clamped to the dragged tab’s group).
-
Very large files (50 MB or larger) now open read-only with a capped load — at most the first 50 MB is read (so a multi-GB log can’t exhaust memory) and editing/undo are disabled; the status bar notes the truncation.
-
Undo history is now bounded (300 entries per view) instead of unlimited, and is disabled entirely in large/huge-file mode, capping undo memory.
-
Large-file mode: opening a file 5 MB or larger skips syntax highlighting and the minimap (regardless of view settings) to stay responsive, and the status bar announces this when it happens.
-
The main window now remembers its size, position, and maximized state across launches (stored in
workspace-state.json). A saved position that no longer lands on a connected screen falls back to the default centered window. -
Release pipeline: pushing a
vX.Y.Ztag builds native installers for Linux (x64/arm64), macOS (x64/arm64), and Windows (x64) on per-platform GitHub runners and publishes a GitHub Release via JReleaser (jreleaser.yml+release.yml). Installers are currently unsigned. -
Session restore: on launch, reopens the files that were open at last exit (in tab order), reselects the active tab, and restores each file’s caret position (scrolled into view). Missing files are skipped; an empty session opens a fresh buffer.
-
Editor split view: toolbar icons (and
view.splitVertical/view.splitHorizontal/view.unsplitcommands,C-x 3/C-x 2/C-x 1) toggle a second synced view of the current file — side by side or stacked. Edits/highlighting stay in sync; scroll, caret, and minimap are per-pane. The icons reflect the active split state. -
“Show hidden characters” view option: renders markers for spaces (·), tabs (→), and line ends (¶) on a transparent overlay without altering the document. Toggle via
view.toggleWhitespace(C-c w) or Settings; off by default. -
The About dialog now shows the build date/time (baked in at build time).
-
Editor tab right-click context menu: Close, Close Other Tabs, Close All Tabs, Close Unmodified Tabs, Close Tabs to the Left/Right, Copy Path, Pin/Unpin Tab, and Rename File…. Pinned tabs are marked with a pin icon, kept grouped at the front, and skipped by the bulk-close actions. Each action is also a palette command (
buffer.closeOthers,buffer.closeAll,buffer.copyPath,buffer.togglePin,buffer.rename, …). Tabs now show a full-path tooltip on hover. -
Status bar segments: live cursor position/selection, language, indentation (tab size), line endings, file size, and encoding, shown right of the message area. Segments are clickable and dispatch commands —
nav.goToLine(M-g g),buffer.setLanguage(override the syntax grammar),buffer.setTabSize, andbuffer.convertLineEndings(LF/CRLF). Tab size now affects the minimap. -
Syntax highlighting via bundled TextMate grammars (using tm4e) for 21 languages: Java, XML, shell, PowerShell, DOS batch, Python, Groovy, Kotlin, Ruby, C, C++, Rust, Go, C#, Markdown, JSON, CSS, HTML, YAML, INI, and SQL. Stateful tokenization carries grammar state across lines so multi-line constructs (block comments, heredocs, fenced code) highlight correctly.
-
Code folding now covers more languages (C, C++, Rust, Go, Kotlin, Groovy, C#, CSS) in addition to Java, JSON, XML/HTML, and Markdown.
-
NOTICEfile attributing the bundled TextMate grammars and third-party libraries. -
Structure tool window: a collapsible tree of the active file’s foldable regions with a search filter and Emacs-style keyboard navigation. Selecting an entry navigates the editor and anchors the target line at the top of the viewport. Bound to
M-7. -
C-x o(Window: Other) to cycle keyboard focus between the editor and open tool windows. -
Code/text folding with
view.foldAll(C-c f) andview.unfoldAll(C-c u); fold state persists across sessions. -
Editor view options for line numbers and a minimap.
-
Theme switching and a recent-files list.
-
IntelliJ-style left/right/bottom tool windows, a buffer Switcher, and a File Information panel.
-
Toolbar icons, a settings window, and an expanded Emacs keymap.
-
Command palette (
M-x) with fuzzy matching, driven by the command registry. -
GitHub Actions CI workflow with a status badge, plus a Maven wrapper.
-
README, MIT license, and
CLAUDE.mdproject guide.
Changed
- Opening a tool window now moves keyboard focus into it and selects its first item (the first
folder/file in Project, the first bookmark, the first symbol in Structure), so you can navigate it
with the keyboard immediately. Restoring tool windows on startup/session-switch does not steal focus.
While a tool window is focused, global commands still work —
M-x, the tool-window toggles (M-1/M-2/…), theM-g …jumps, andC-x …prefixes; the panel only intercepts the editor navigation chords it reuses (C-n/C-p,C-f/C-b, …). - The Switcher (
C-x C-b) now lists only the open files — the Tool Windows column was removed. It’s a single column in tab order, with a bold header and a fixed width (sized to the longest file path on open) so it no longer resizes while you navigate. Tool windows are still reachable via their stripe icons and the “Tool Windows: Jump” picker (M-g t). - Silenced the benign tm4e/Oniguruma grammar warnings that flooded the console
(
'…]' without escape,No grammar source for scope …) by raising theorg.eclipse.tm4elog level toSEVEREat startup. They were harmless bundled-grammar quirks; real errors still surface. - The Switcher (
C-x C-b) now lists all open files (labeled “Open Files”, most-recently-used first) instead of only previously-activated ones. It is also larger with bigger column headers; the selection bar now tracks the active column (vivid in the focused column, dimmed in the other), and the highlighted file’s full path is shown at the bottom of the popup, above a legend of the navigation keys. A “Switcher: C-x C-b” hint on the toolbar makes the keybinding discoverable. - Syntax highlighting is now incremental: an edit re-tokenizes only from the changed line to the end of the document (reusing stored per-line grammar states for the unchanged prefix) instead of the whole file, lowering highlight latency on larger files. Still runs off the UI thread with stale-result discarding.
- App chrome (toolbar/tool-window icons, status bar, tool windows, command palette, switcher, find bar, File Information) now uses AtlantaFX theme variables instead of hardcoded light colors, so it adapts to dark themes (icons and text stay legible).
- Clearing the recent-files list now asks for confirmation; each entry in the recent-files menu has an inline ✕ icon to remove just that file (no confirmation).
- Go to Line (
M-g g) now accepts an optional column asline:column(e.g.342:35); a bare number still goes to the line’s start. The dialog notes the column is optional and the column is clamped to the target line’s length. If the target line is inside a collapsed fold, the region is unfolded so the line is shown. - File Information tool window: values are now selectable/copyable (read-only text fields), with tighter padding and without the boxed section borders. Long values (e.g. the full path) no longer overflow the panel, which previously truncated the key labels to ”…” and showed scrollbars.
- The Settings font-family picker now lists only monospaced fonts, with a note explaining the filter (a previously saved non-monospaced font stays selectable).
- Quitting now asks for confirmation first (the Quit button,
C-x C-c, and the window close button), in addition to the existing per-buffer unsaved-changes prompts. - User preferences are now stored as TOML in
~/.editora-v2/settings.toml(wassettings.json). Session state (fold regions, tool-window layout) moved to a separateworkspace-state.json; recent files stay inrecent-files.json. No migration — a pre-existingsettings.jsonis ignored. - Closing a pinned tab now asks for confirmation first (the X, the Close menu item, and the close command); bulk-close actions still skip pinned tabs.
- The Structure tool window now labels entries with the symbol name and kind (functions, types/classes, namespaces, Markdown headings, XML tags) derived from the TextMate grammar, and hides trivial blocks (if/for/while/…), instead of showing the raw brace line. Works across all bundled languages; clicking a node jumps to the symbol’s line.
- Folded regions now shade their header line (in addition to the gutter chevron) so collapsed code is easier to spot.
- Opening a file that is already open now switches to its existing tab instead of opening a duplicate.
- The recent-files picker is now a toolbar menu button (with a per-entry remove action) instead of a combo box.
- The key dispatcher now yields only single-key chords to windows that own
their keys (e.g. the Structure tool window); multi-key Emacs chords such as
C-x ostay global so window switching andC-x/C-ccommands work from anywhere.
Fixed
- Bookmarks no longer drift to the wrong line when a file is edited outside the editor. Bookmarks followed their content only for edits made inside Editora, so changing a file in another program left its markers on stale lines. Each bookmark now re-anchors to its saved line text when the file is opened (re-found at the nearest matching line), and the corrected position is saved back so the session self-heals.
- Adding a bookmark no longer shifts that line’s text indentation. The gutter now reserves a fixed-width bookmark column on every line, so the marker glyph no longer widens only the bookmarked row (which had pushed its text rightward).
- A bookmark’s gutter marker now follows its line when an edit moves it. Inserting or deleting lines above a bookmark already moved the bookmark (and its Bookmarks-panel entry), but the gutter glyph wasn’t repainted onto the new line; it is now.
- Switching projects (or “No Project”), or closing/deleting a project, while in Zen mode no longer leaves the UI permanently mangled. Zen stores its “everything off” view state in the global settings while keeping the restore snapshot in the per-session state; swapping sessions orphaned that snapshot. The editor now exits Zen (restoring the real view settings) before a session switch and lands the incoming session in normal view.
- The
M-</M->(document start/end) andM-%(find & replace) keybindings now work. They were stored in Emacs notation (M-<) but the dispatcher emits shifted symbols asM-S-,etc., so the entries never matched; the keymap now uses the dispatcher’s token form. - No more light-gray flash on startup with a dark theme: the scene is pre-filled with the theme’s background color, so the first frame paints in the theme color instead of JavaFX’s default background before the CSS is applied.
- Much faster startup when restoring a session: all tab headers are created first (so they appear at once), then each file’s content and folds are loaded one per pulse — the active file first — keeping the UI responsive. Previously a session with a heavily-folded file could freeze the window for several seconds on launch.
- Folding no longer smears the hidden lines’ numbers onto the fold-header row in the gutter: a collapsed paragraph’s gutter cell is laid out at zero height, but its line-number label did not clip to it; the gutter is now clipped to its bounds.
- “Show hidden characters” no longer piles stray space/tab/EOL markers onto the fold-header row when a region is collapsed: folded (hidden) paragraphs are now skipped by the whitespace overlay (and the 80-column ruler’s measurement).
- Running a command via a chord that opens a dialog no longer types a stray
character afterwards (e.g.
M-g gthen OK inserted ag): the dispatcher now also swallows the character event paired with a key press it handled, which a modal dialog (showAndWait) would otherwise deliver to the editor after closing. M-(Meta) chords no longer insert a stray character on macOS. Option is the Meta key, andOption+<key>also emits a special character (e.g.M-f→ “ƒ”,M-x→ ”≈”) whose KEY_TYPED was inserted into the editor (or palette) after the command ran. Characters typed with Alt held are now swallowed on macOS — globally by the key dispatcher and in the command palette popup. (Scoped to macOS so AltGr-composed characters keep working on other platforms.)- Command palette entries can now be run with the mouse — clicking a command runs it (previously only Enter on the keyboard-selected row worked).
- Next/previous line (
C-n/C-p) now move the caret like Emacs: they were bound to RichTextFX’s scroll-basednextLine/prevLine(which move relative to the viewport and no-op when the caret is off-screen). They now move by paragraph and preserve a goal column, so passing through short lines keeps the original column. - Syntax highlighting no longer intermittently fails to load when multiple files of the same language are open. They share one TextMate grammar instance, and tm4e’s tokenizer is not thread-safe, so concurrent highlighting on each buffer’s background thread could throw and silently drop a file’s highlighting; tokenization is now serialized per grammar.
- The minimap no longer stretches short files across its full height (scattering sparse blocks): line height is capped, so short documents fill from the top and long documents still compress to fit.
- Pinned tabs are now remembered across sessions: a tab’s pinned state is saved on exit and restored on the next launch (alongside the open files and carets).
- The 80-column ruler now lands exactly on column 80. It was positioned from a font-probe character width and ignored the line-number gutter, so it drifted off the text grid; the column is now derived from the editor’s live layout (caret positions, glyph-independent). It is hidden when column 80 is outside the visible text width (window too narrow, or scrolled past it).
- The 80-column ruler now tracks horizontal scroll instead of staying pinned to a fixed x-offset when a horizontal scrollbar is present.
- The Structure tool window no longer shows “No structure” for documents whose regions were computed before the panel attached.
- The editor context menu now dismisses on left-click.