@fresh-editor/fresh-editor 0.3.1 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +138 -0
- package/package.json +1 -1
- package/plugins/config-schema.json +48 -6
- package/plugins/diagnostics_panel.ts +4 -0
- package/plugins/diff_nav.ts +20 -1
- package/plugins/find_references.ts +4 -0
- package/plugins/flash.ts +11 -3
- package/plugins/git_explorer.ts +9 -1
- package/plugins/lib/finder.ts +81 -10
- package/plugins/lib/fresh.d.ts +40 -4
- package/plugins/live_diff.i18n.json +450 -0
- package/plugins/live_diff.ts +946 -0
- package/plugins/live_grep.i18n.json +42 -14
- package/plugins/live_grep.ts +491 -41
- package/plugins/markdown_compose.ts +17 -3
- package/plugins/schemas/theme.schema.json +510 -12
- package/plugins/search_replace.ts +5 -0
- package/plugins/theme_editor.i18n.json +224 -0
- package/plugins/theme_editor.ts +58 -50
- package/plugins/tsconfig.json +1 -0
- package/themes/dark.json +4 -0
- package/themes/dracula.json +2 -0
- package/themes/high-contrast.json +4 -0
- package/themes/light.json +4 -0
- package/themes/nord.json +7 -0
- package/themes/nostalgia.json +4 -0
- package/themes/solarized-dark.json +7 -0
- package/themes/terminal.json +128 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,143 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## 0.3.4
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **Live Grep floating overlay + Utility Dock** (#1796): Live Grep now opens as a centered floating overlay with results on the left and a real-buffer file preview on the right (full syntax highlighting, gutter, soft-wrap). `Esc` returns you to your prior layout exactly. **Resume** (`Alt+r`) reopens the last query with cached results. **Export to Quickfix** (`Alt+M`) sends results into a dockable list.
|
|
8
|
+
|
|
9
|
+
* New **Utility Dock** at the workspace root hosts the terminal (`` Alt+` ``), Quickfix, Diagnostics, and Find References — they share one pane spanning the full width instead of nesting under whichever split was focused.
|
|
10
|
+
|
|
11
|
+
* **Pluggable Live Grep providers**: Built-in chain is now ripgrep → **git-grep (default in repos)** → grep, with `ag` / `ack` available via plugin registration. `Alt+P` cycles to the next available provider; the active one shows in the overlay's title bar. Plugins can register custom backends via `editor.getPluginApi("live-grep")`.
|
|
12
|
+
|
|
13
|
+
* **Settings tree-view**: The left category list is now an expandable tree — categories with multiple sections show chevrons, expanding reveals jumpable section rows, and the tree cursor follows scrolling so you can see where you are in the body. Section jumps snap to the top of the section. Toggle controls render as a chip-style `[ ✓ ACTIVE ]` indicator.
|
|
14
|
+
|
|
15
|
+
* **HDL language support** (#1528, reported by @bqinTT): Syntax highlighting for **Verilog** (`.v` left mapped to vlang for compatibility, `.vh`/`.verilog`), **SystemVerilog** (`.sv`/`.svh`/`.svi`/`.svp`), and **VHDL** (`.vhd`/`.vhdl`/`.vho`). `svls` wired as the default LSP for Verilog/SystemVerilog (opt-in per project).
|
|
16
|
+
|
|
17
|
+
* **New `terminal` built-in theme** (#1457, #1798, reported by @AmethystGosling169 and @BrettKinny): Colors come from your terminal's own palette instead of hard-coded RGB — backgrounds use `Default` so transparency and your terminal's background show through; accents use ANSI named colors that remap to whatever your terminal colorscheme defines. Selection uses reverse-video so it inverts whatever colors are already on screen.
|
|
18
|
+
|
|
19
|
+
* **Theme inheritance with `extends`**: User themes can now `extends: "builtin://light"` (or `dark` / `high-contrast` / `nostalgia` / `terminal`) and layer overrides on top — the same model VSCode/Helix/Sublime/Zed use. With no `extends`, an explicit `editor.bg` triggers luminance-based auto-inference (bright bg → light base, dim → dark), so partial light themes no longer end up with dark UI chrome (#1281, reported by @nico2004444).
|
|
20
|
+
|
|
21
|
+
* **File explorer context-menu additions** (#1576, reported by @RandomGHUser): **Duplicate** (creates `name copy[.ext]` next to the source, multi-select supported), **Copy Full Path** and **Copy Relative Path** (newline-joined for multi-select). The new entries don't appear on the project root.
|
|
22
|
+
|
|
23
|
+
* **Distribute clipboard across cursors** (#1057, reported by @graphixillusion): With N cursors and an N-line clipboard, paste now gives each cursor one line in top-to-bottom order — VSCode / Notepad++ "column-mode paste" semantics. A block-selected copy/paste round-trip preserves its rectangular shape. Behavior is unchanged when counts don't match.
|
|
24
|
+
|
|
25
|
+
* **Discard option in quit prompt** (#1839, reported by @turkishmaid): With `hot_exit` on (the default), the unsaved-changes prompt now offers "(d)iscard and quit" so accidental edits no longer require disabling hot_exit globally to throw away. Picking "save" on quit also chains a Save As prompt for each dirty unnamed buffer instead of silently dropping it.
|
|
26
|
+
|
|
27
|
+
* **Project name in window title** (#1793, reported by @dAnjou): Title is now `<file> — <project> — Fresh` so multiple Fresh sessions in different projects are distinguishable in your taskbar/window list.
|
|
28
|
+
|
|
29
|
+
* **Cursor-jump animation toggle** (#1788): New `editor.cursor_jump_animation` setting lets you keep ambient animations (tab slides, dashboard) while disabling just the cursor-jump trail. The master `editor.animations` setting still wins.
|
|
30
|
+
|
|
31
|
+
### Improvements
|
|
32
|
+
|
|
33
|
+
* **Search & Replace across project no longer hangs on large binary files** (#1342, reported by @dragonfyre13): Hardcoded extension fast-path skips known-binary files (compiled artifacts, archives, media, ML weights, fonts) before any I/O. Per-file size cap and stronger header sniff (PNG, ZIP-based archives like `.pth`, ELF) catch the formats whose first bytes can look text-like.
|
|
34
|
+
|
|
35
|
+
* **POSIX ACL writability** (#1765, reported by @cherouize): A file granted write access via `setfacl -m u:NAME:rw` is no longer reported read-only — Fresh now asks the kernel via `faccessat(W_OK)` instead of walking inode mode bits, so ACLs, capabilities, and read-only mounts are all honored.
|
|
36
|
+
|
|
37
|
+
* **LSP status popup doesn't auto-show**: Auto-popping on first file open stole focus and swallowed keystrokes for users who hadn't asked to enable LSP. The `LSP` indicator is now a manual click target; its `Off` state (configured but not running) is rendered with a more prominent attention-grabbing color so discoverability isn't lost.
|
|
38
|
+
|
|
39
|
+
* **Hover popup no longer flickers on mouse moves** (#692) inside the editor (gutter, end-of-line, between words). It only dismisses when the mouse leaves the editor area entirely. New hover responses replace the existing popup instead of stacking.
|
|
40
|
+
|
|
41
|
+
* **Multi-cursor `Ctrl-D` after substring search** (#1697, reported by @dtwilliamson): When the cursor is inside an active search match, "Add cursor at next match" selects the next *search match* instead of expanding to the surrounding word.
|
|
42
|
+
|
|
43
|
+
* **JavaScript syntax highlighting** (#899, reported by @comesuccingfuccsloot): Routed through tree-sitter, so template literals containing arrow functions or `${expr}` no longer leak `@string` styling across the rest of the file.
|
|
44
|
+
|
|
45
|
+
* **Smarter auto-indent for Lua / Ruby / Bash / Pascal**: Tree-sitter `indents.scm` is now the single source of truth for keyword-delimited languages, so `(` opening a function call no longer gets treated as a block-opening delimiter.
|
|
46
|
+
|
|
47
|
+
* **Enter at column 0 doesn't push the line right anymore** (#1425, reported by @goszlanyi): Auto-indent now detects "cursor at column 0 of a non-empty line" and inserts a bare newline. Closing-delimiter lines still get the established indent-before-close behavior.
|
|
48
|
+
|
|
49
|
+
* **Live Diff virtual lines soft-wrap** (#1787) instead of being truncated at the right edge.
|
|
50
|
+
|
|
51
|
+
* **Per-workspace hot-exit recovery** (#1550, reported by @goszlanyi): Standalone-mode recovery files are now scoped per working directory. Quitting Fresh in folder B no longer wipes folder A's recovered unnamed-buffer state.
|
|
52
|
+
|
|
53
|
+
* **Terminal PTY resyncs on tab reveal** (#1795): Resizing the host while a terminal was hidden behind another tab now correctly forwards `SIGWINCH` when you switch back — `$COLUMNS` / `stty size` stay accurate.
|
|
54
|
+
|
|
55
|
+
* **Open File dialog scrolls correctly on small terminals** (#245): Selection no longer slides past the bottom of the visible list.
|
|
56
|
+
|
|
57
|
+
* **Keybinding editor scrollbar responds to mouse** (#1593, reported by @Kodiak-01): Click and drag both work; wheel scrolls the viewport instead of moving the selection (so a scrollbar drag isn't undone by the next wheel tick).
|
|
58
|
+
|
|
59
|
+
* **Settings Number controls** (#1825, e.g. Tab Size): Tab now commits and exits the input; clicking the value cell enters edit mode (matches Enter).
|
|
60
|
+
|
|
61
|
+
* **Plugin keybinding labels refresh on every prompt open** so plugins surfacing key hints ("`Alt+P` to cycle", overlay headers, etc.) reflect mid-session rebinds without restart.
|
|
62
|
+
|
|
63
|
+
* **New plugin hook `after_file_explorer_change`** fires on FS-mutating explorer actions (Duplicate, Paste, New File, Rename, Delete) so plugins like git_explorer can refresh badges immediately.
|
|
64
|
+
|
|
65
|
+
* **Theme picker consistency**: The Theme Editor plugin's picker now shows the same set of themes as the native `Select Theme` prompt — no more divergence from a separate `cwd/themes` scan or normalization mismatches. New plugin API `editor.getAllThemes()` returns the cached map directly so plugins can drop their own filesystem walks.
|
|
66
|
+
|
|
67
|
+
### Bug Fixes
|
|
68
|
+
|
|
69
|
+
* **Crash fixes**: `Option::unwrap()` panic when pasting in the Theme Editor (event apply used the wrong split). `DeleteBackward` panics on stale cursor state in vi-mode count prefixes and plugin action batches. Theme editor crash on the new `terminal` theme's modifier fields. Embedded-plugin extraction race across concurrent test processes.
|
|
70
|
+
|
|
71
|
+
* **Search** (#1537, reported by @pstahle): `Find Selection Next/Previous` on a non-word character (e.g. `}` after goto-matching-bracket) no longer hijacks the search query — it now navigates the existing search instead.
|
|
72
|
+
|
|
73
|
+
* **OpenLine (Emacs `C-o`)**: Cursor stays on the original line instead of advancing — was previously indistinguishable from Enter.
|
|
74
|
+
|
|
75
|
+
* **Markdown compose** (#1789, #1790): Wrap budget widened by one column to prevent orphan-word re-wrapping on Windows; current-line highlight now extends across soft-wrapped sub-rows.
|
|
76
|
+
|
|
77
|
+
* **Viewport** (#1794): Popup anchoring counts true visual rows under wrap, so completion popups appear next to the cursor instead of several rows above in heavily-wrapped buffers.
|
|
78
|
+
|
|
79
|
+
### Under the Hood
|
|
80
|
+
|
|
81
|
+
* **Smaller release binaries**: New `dist` cargo profile (`strip=true`, `lto=fat`, `codegen-units=1`, `opt-level=z`) is now applied to release builds. Binary shrinks from 62.4 MB → ~46 MB (-26%). Backtraces are still included in panics.
|
|
82
|
+
|
|
83
|
+
* **Build performance**: `oxc` and `rquickjs` now build at `opt-level=3` in dev/test profiles to keep iteration fast despite their size.
|
|
84
|
+
|
|
85
|
+
* **Semantic test framework + ~250 migrated cases**: A new "scenario" test layer dispatches `Action`s straight against an isolated editor instance, skipping plugin loading where the assertion surface (buffer text + caret) can't observe it — about 440 ms saved per test harness. ~250 e2e claims have been migrated across multicursor, block selection, auto-indent, paste, undo/redo, search-modal flows, multibyte handling, and many regression repros (issues #191, #1147, #1305, #1574, #1697, etc.). Property-based theorems over generated action sequences caught two real production crashes in no-render dispatch paths (vi-mode count prefixes, plugin action batches) — both fixed in this release. A pre-existing race in concurrent embedded-plugin extraction (could leave half-written plugins for parallel test processes) is also fixed.
|
|
86
|
+
|
|
87
|
+
## 0.3.2
|
|
88
|
+
|
|
89
|
+
### Features
|
|
90
|
+
|
|
91
|
+
* **Live Diff plugin** (experimental): Unified-diff overlay rendered live in the editable buffer. If your file is unmodified in the editor, it updates as the file changes on disk when auto-revert kicks in - great for watching an agent edit your file. Opt-in via `Live Diff: Toggle (Global)` / `Live Diff: Toggle (Buffer)`. Reference selectable per buffer: `vs HEAD` / `vs Disk` / `vs Branch...` / `vs Default Branch`.
|
|
92
|
+
|
|
93
|
+
* **New Startup section in Settings** (open Settings and search "Startup") groups everything that fires on launch:
|
|
94
|
+
- **Blank-workspace flow** (#1753) — *Auto Create Empty Buffer On Last Buffer Close* (Editor) and *Auto Open On Last Buffer Close* (File Explorer). With both off, closing the last buffer leaves a truly blank pane (no `[No Name]`, no gutter, no `~`); buffer-specific status-bar items and menu entries are suppressed, and a subdued centered hint shows the keys to escape (`Ctrl+P` / `Ctrl+O` / `Ctrl+E`).
|
|
95
|
+
- *Skip Session Restore When Files Passed* — `fresh src/main.rs` opens just that file; bare `fresh` and `fresh some/dir` still restore. Hot-exit recovery still runs. `--restore` overrides.
|
|
96
|
+
- *Restore Previous Session* (existing, moved into Startup).
|
|
97
|
+
|
|
98
|
+
* **File explorer side** (thanks @paveloparev!): *Side* under File Explorer in Settings — left or right.
|
|
99
|
+
|
|
100
|
+
* **Prompt Line now hidden by default**: *Show Prompt Line* now defaults off — the prompt line only appears while a prompt is active. Turn it back on via Settings.
|
|
101
|
+
|
|
102
|
+
* **Mark mode preserved through Go to Line** so you can extend selections across the jump. Use **Set Mark** command followed by **Goto Line** to start a selection and extend it to the target line.
|
|
103
|
+
|
|
104
|
+
* **Copy File Path commands**: New commands: "Copy File Path" and "Copy Relative File Path" to copy current buffer's path to your clipboard. Also available by right-clicking on a tab name.
|
|
105
|
+
|
|
106
|
+
* **CLI Help Localization**: The `--help` output is now fully localized using runtime i18n lookups.
|
|
107
|
+
|
|
108
|
+
* **Relative +/- Goto Line**: Infers absolute vs relative jumps from a leading sign (e.g., `:+10` jumps 10 lines down, `:10` jumps to line 10).
|
|
109
|
+
|
|
110
|
+
* **Rust Toolchain Update**: Updated to Rust 1.95 in `rust-toolchain.toml` to fix compatibility issues with newer LLVM/clang versions on systems like Arch Linux (#1782).
|
|
111
|
+
|
|
112
|
+
### Improvements
|
|
113
|
+
|
|
114
|
+
* **Plugin loading deferred off the boot critical path** — another ~225 ms saved. Same load order, same hooks, just async.
|
|
115
|
+
|
|
116
|
+
* **Popup focus**: LSP popups that auto-show on file open (status popup, hover, signature help, plugin Text overlays) no longer steal the next keystroke. They show unfocused with an `[Alt+T to focus]` hint; user-invoked popups (Completion, code actions, status-bar `{remote}`, LSP-status menu) still grab focus on show. Settings / Menu / Prompt modals take precedence over unfocused buffer popups for `Esc` / `popup_focus`.
|
|
117
|
+
|
|
118
|
+
* **Status Bar visual integration** (#1711): The "Palette: Ctrl+P" hint and "LSP (on)" indicator now blend into the status bar by default. Built-in themes have been updated with coherent prominent colors for these indicators.
|
|
119
|
+
|
|
120
|
+
* **Prompt interaction and scrolling** (#1660):
|
|
121
|
+
- Minimal scrolling: the suggestion list no longer recenters the selection on every move, preventing "row jumping" during navigation.
|
|
122
|
+
- Clicks no longer cause accidental list shifts; double-click correctly confirms selections.
|
|
123
|
+
- Preview-on-click supported for "Reload with encoding".
|
|
124
|
+
|
|
125
|
+
* **Global Menu Bar**: "Toggle Menu Bar" state is now persisted globally across all workspaces.
|
|
126
|
+
|
|
127
|
+
* **Windows integration**: High-quality app icon applied to the running window; app manifest and version info embedded in the binary.
|
|
128
|
+
|
|
129
|
+
### Bug Fixes
|
|
130
|
+
|
|
131
|
+
* **Windows subprocesses**: Transient console windows are now hidden when spawning subprocesses (e.g., formatters, linters).
|
|
132
|
+
|
|
133
|
+
* **Terminal CWD**: Fixed shell spawning failure on Windows when the current directory has a `\\?\` UNC prefix.
|
|
134
|
+
|
|
135
|
+
* **Live Diff stability**: Fixed crashes on surrogate-pair content (emojis) and corrected gutter rendering for empty lines inside added blocks.
|
|
136
|
+
|
|
137
|
+
### Under the Hood
|
|
138
|
+
|
|
139
|
+
* **Syntax Highlight Caching**: New multi-phase caching system (memoised scope lookups and whole-file cache for small files) significantly reduces CPU usage during rendering.
|
|
140
|
+
|
|
3
141
|
## 0.3.1
|
|
4
142
|
|
|
5
143
|
### Features
|
package/package.json
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"$ref": "#/$defs/EditorConfig",
|
|
32
32
|
"default": {
|
|
33
33
|
"animations": true,
|
|
34
|
+
"cursor_jump_animation": true,
|
|
34
35
|
"line_numbers": true,
|
|
35
36
|
"relative_line_numbers": false,
|
|
36
37
|
"highlight_current_line": true,
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"{palette}"
|
|
64
65
|
]
|
|
65
66
|
},
|
|
66
|
-
"show_prompt_line":
|
|
67
|
+
"show_prompt_line": false,
|
|
67
68
|
"show_vertical_scrollbar": true,
|
|
68
69
|
"show_horizontal_scrollbar": false,
|
|
69
70
|
"show_tilde": true,
|
|
@@ -103,6 +104,8 @@
|
|
|
103
104
|
"auto_save_interval_secs": 30,
|
|
104
105
|
"hot_exit": true,
|
|
105
106
|
"restore_previous_session": true,
|
|
107
|
+
"skip_session_restore_when_files_passed": true,
|
|
108
|
+
"auto_create_empty_buffer_on_last_buffer_close": true,
|
|
106
109
|
"recovery_enabled": true,
|
|
107
110
|
"auto_recovery_save_interval_secs": 2,
|
|
108
111
|
"auto_revert_poll_interval_ms": 2000,
|
|
@@ -128,7 +131,9 @@
|
|
|
128
131
|
"show_gitignored": false,
|
|
129
132
|
"custom_ignore_patterns": [],
|
|
130
133
|
"width": "30%",
|
|
131
|
-
"preview_tabs": true
|
|
134
|
+
"preview_tabs": true,
|
|
135
|
+
"side": "left",
|
|
136
|
+
"auto_open_on_last_buffer_close": true
|
|
132
137
|
}
|
|
133
138
|
},
|
|
134
139
|
"file_browser": {
|
|
@@ -243,7 +248,8 @@
|
|
|
243
248
|
"dark",
|
|
244
249
|
"light",
|
|
245
250
|
"high-contrast",
|
|
246
|
-
"nostalgia"
|
|
251
|
+
"nostalgia",
|
|
252
|
+
"terminal"
|
|
247
253
|
]
|
|
248
254
|
},
|
|
249
255
|
"LocaleOptions": {
|
|
@@ -276,6 +282,12 @@
|
|
|
276
282
|
"default": true,
|
|
277
283
|
"x-section": "Display"
|
|
278
284
|
},
|
|
285
|
+
"cursor_jump_animation": {
|
|
286
|
+
"description": "Enable the cursor-jump trail animation on long cursor moves\n(search jumps, go-to-definition, pane switches). Has no effect\nwhen `animations` is `false`.",
|
|
287
|
+
"type": "boolean",
|
|
288
|
+
"default": true,
|
|
289
|
+
"x-section": "Display"
|
|
290
|
+
},
|
|
279
291
|
"line_numbers": {
|
|
280
292
|
"description": "Show line numbers in the gutter (default for new buffers)",
|
|
281
293
|
"type": "boolean",
|
|
@@ -389,9 +401,9 @@
|
|
|
389
401
|
"x-section": "Status Bar"
|
|
390
402
|
},
|
|
391
403
|
"show_prompt_line": {
|
|
392
|
-
"description": "Whether the prompt line is visible
|
|
404
|
+
"description": "Whether the prompt line is always visible.\nThe prompt line is the bottom-most line used for search, file open, and other prompts.\nWhen `false` (the default), the prompt line auto-hides — it only appears\nwhile a prompt is active and disappears again once the prompt closes.\nWhen `true`, the prompt line is always reserved at the bottom of the screen.\nDefault: false",
|
|
393
405
|
"type": "boolean",
|
|
394
|
-
"default":
|
|
406
|
+
"default": false,
|
|
395
407
|
"x-section": "Display"
|
|
396
408
|
},
|
|
397
409
|
"show_vertical_scrollbar": {
|
|
@@ -643,7 +655,19 @@
|
|
|
643
655
|
"description": "Whether to auto-open previously opened files (session restore) when\nstarting Fresh in a directory. When enabled (the default), tabs,\nsplits, cursor positions and the file explorer state are restored\nfrom the last clean exit in the same working directory. When\ndisabled, Fresh starts with a clean workspace. The workspace file\non disk is still written on exit, so re-enabling this setting picks\nup whatever state was saved at the most recent clean exit. The\n`--no-restore` CLI flag is a stronger override: it skips both\nrestoring and saving the workspace.\nDefault: true",
|
|
644
656
|
"type": "boolean",
|
|
645
657
|
"default": true,
|
|
646
|
-
"x-section": "
|
|
658
|
+
"x-section": "Startup"
|
|
659
|
+
},
|
|
660
|
+
"skip_session_restore_when_files_passed": {
|
|
661
|
+
"description": "When Fresh is launched with one or more file arguments (e.g.\n`fresh src/main.rs README.md`), skip the workspace session restore\nand open only the files passed on the command line. Hot-exit\ncontent (unsaved modified files and unnamed `[No Name]` buffers\nwith content) is still restored so in-progress work is never lost.\nPure-directory invocations (`fresh some/dir`) and bare invocations\n(`fresh` with no args) still restore the previous session normally.\nDisable this option to keep the legacy behavior of always\nrestoring the previous session even when files are passed.\nDefault: true",
|
|
662
|
+
"type": "boolean",
|
|
663
|
+
"default": true,
|
|
664
|
+
"x-section": "Startup"
|
|
665
|
+
},
|
|
666
|
+
"auto_create_empty_buffer_on_last_buffer_close": {
|
|
667
|
+
"description": "Whether to auto-create a fresh empty `[No Name]` buffer when the\nlast open buffer is closed. When `false`, the editor still creates\nan internal placeholder buffer (it always needs at least one) but\nhides it from the tab bar so the workspace looks blank. Combined\nwith `file_explorer.auto_open_on_last_buffer_close = false`, this\ngives a fully blank workspace where nothing opens automatically.\nDefault: true",
|
|
668
|
+
"type": "boolean",
|
|
669
|
+
"default": true,
|
|
670
|
+
"x-section": "Startup"
|
|
647
671
|
},
|
|
648
672
|
"recovery_enabled": {
|
|
649
673
|
"description": "Whether to enable file recovery (Emacs-style auto-save)\nWhen enabled, buffers are periodically saved to recovery files\nso they can be recovered if the editor crashes.",
|
|
@@ -918,6 +942,16 @@
|
|
|
918
942
|
"description": "Open files in a \"preview\" (ephemeral) tab on single-click in the\nfile explorer. The preview tab is replaced by the next single-click\ninstead of accumulating tabs. Editing the file, double-clicking\n(or pressing Enter) on it in the explorer, or dragging its tab\npromotes the tab to a permanent tab.\nDefault: true",
|
|
919
943
|
"type": "boolean",
|
|
920
944
|
"default": true
|
|
945
|
+
},
|
|
946
|
+
"side": {
|
|
947
|
+
"description": "Which side of the screen to show the file explorer on.\nDefault: left",
|
|
948
|
+
"$ref": "#/$defs/FileExplorerSide",
|
|
949
|
+
"default": "left"
|
|
950
|
+
},
|
|
951
|
+
"auto_open_on_last_buffer_close": {
|
|
952
|
+
"description": "Automatically focus the file explorer when the last buffer is\nclosed. Set to `false` for a \"blank workspace\" workflow where\nnothing opens automatically and the user explicitly invokes the\nfile explorer (e.g. via keybinding or command palette).\nDefault: true",
|
|
953
|
+
"type": "boolean",
|
|
954
|
+
"default": true
|
|
921
955
|
}
|
|
922
956
|
}
|
|
923
957
|
},
|
|
@@ -926,6 +960,14 @@
|
|
|
926
960
|
"type": "string",
|
|
927
961
|
"pattern": "^(100%|[1-9]?[0-9]%|\\d+)$"
|
|
928
962
|
},
|
|
963
|
+
"FileExplorerSide": {
|
|
964
|
+
"description": "Side placement for the file explorer panel.",
|
|
965
|
+
"type": "string",
|
|
966
|
+
"enum": [
|
|
967
|
+
"left",
|
|
968
|
+
"right"
|
|
969
|
+
]
|
|
970
|
+
},
|
|
929
971
|
"FileBrowserConfig": {
|
|
930
972
|
"description": "File browser configuration (for Open File dialog)",
|
|
931
973
|
"type": "object",
|
|
@@ -126,6 +126,10 @@ const finder = new Finder<DiagnosticItem>(editor, {
|
|
|
126
126
|
groupBy: "file",
|
|
127
127
|
syncWithEditor: true,
|
|
128
128
|
navigateOnCursorMove: true,
|
|
129
|
+
// Diagnostics is a generic "list of locations" UX — route into
|
|
130
|
+
// the shared Utility Dock so it shares space with Quickfix,
|
|
131
|
+
// search-replace results, etc. See issue #1796.
|
|
132
|
+
useUtilityDock: true,
|
|
129
133
|
onClose: () => {
|
|
130
134
|
isOpen = false;
|
|
131
135
|
sourceBufferId = null;
|
package/plugins/diff_nav.ts
CHANGED
|
@@ -22,6 +22,13 @@ interface DiffHunk {
|
|
|
22
22
|
lineCount: number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** Hunk shape published by live_diff.ts on `live_diff_hunks` view state. */
|
|
26
|
+
interface LiveDiffHunk {
|
|
27
|
+
kind: "added" | "removed" | "modified";
|
|
28
|
+
newStart: number; // 0-indexed
|
|
29
|
+
newCount: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
/** A jump target with a byte position for sorting/deduplication */
|
|
26
33
|
interface JumpTarget {
|
|
27
34
|
bytePos: number;
|
|
@@ -47,7 +54,19 @@ async function collectTargets(bid: number): Promise<JumpTarget[]> {
|
|
|
47
54
|
}
|
|
48
55
|
}
|
|
49
56
|
|
|
50
|
-
// Source 2:
|
|
57
|
+
// Source 2: live-diff hunks (head/disk/branch comparison from live_diff.ts)
|
|
58
|
+
const liveHunks = editor.getViewState(bid, "live_diff_hunks") as LiveDiffHunk[] | null;
|
|
59
|
+
if (liveHunks && liveHunks.length > 0) {
|
|
60
|
+
for (const hunk of liveHunks) {
|
|
61
|
+
const line = Math.max(0, hunk.newStart);
|
|
62
|
+
const pos = await editor.getLineStartPosition(line);
|
|
63
|
+
if (pos !== null) {
|
|
64
|
+
targets.push({ bytePos: pos, line });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Source 3: saved-diff (unsaved changes)
|
|
51
70
|
const diff = editor.getBufferSavedDiff(bid);
|
|
52
71
|
if (diff && !diff.equal) {
|
|
53
72
|
for (const [start, _end] of diff.byte_ranges) {
|
|
@@ -40,6 +40,10 @@ const finder = new Finder<ReferenceLocation>(editor, {
|
|
|
40
40
|
},
|
|
41
41
|
preview: true,
|
|
42
42
|
maxResults: 100,
|
|
43
|
+
// Find References is a generic "list of locations" UX — share
|
|
44
|
+
// the Utility Dock with Diagnostics, Quickfix, search-replace
|
|
45
|
+
// results, etc. See issue #1796.
|
|
46
|
+
useUtilityDock: true,
|
|
43
47
|
});
|
|
44
48
|
|
|
45
49
|
// Pending references for the current prompt
|
package/plugins/flash.ts
CHANGED
|
@@ -493,10 +493,19 @@ async function flashJump(): Promise<void> {
|
|
|
493
493
|
// enough to survive status-bar truncation. Includes the current
|
|
494
494
|
// pattern so tests (and careful users) can confirm the plugin has
|
|
495
495
|
// accepted each typed key.
|
|
496
|
+
//
|
|
497
|
+
// The banner doubles as a synchronization barrier for tests: as long
|
|
498
|
+
// as setStatus runs AFTER redraw within the same loop iteration, any
|
|
499
|
+
// observer that sees `Flash[<pattern>]` on screen is guaranteed to
|
|
500
|
+
// also see the conceals/labels for that same pattern — they were
|
|
501
|
+
// committed in the redraw immediately before. Setting the banner
|
|
502
|
+
// earlier (e.g. in the keypress handler before `continue`) breaks
|
|
503
|
+
// this invariant: the new banner reaches the screen while the
|
|
504
|
+
// previous iteration's conceals are still painted, so a renderer
|
|
505
|
+
// tick in that window shows banner=N with conceals=N-1.
|
|
496
506
|
const setStatusForPattern = (): void => {
|
|
497
507
|
editor.setStatus("Flash[" + state.pattern + "]");
|
|
498
508
|
};
|
|
499
|
-
setStatusForPattern();
|
|
500
509
|
|
|
501
510
|
try {
|
|
502
511
|
while (true) {
|
|
@@ -522,6 +531,7 @@ async function flashJump(): Promise<void> {
|
|
|
522
531
|
if (m.label) state.prevLabelByKey.set(matchKey(m), m.label);
|
|
523
532
|
}
|
|
524
533
|
redraw(state.matches, views);
|
|
534
|
+
setStatusForPattern();
|
|
525
535
|
|
|
526
536
|
const ev = await editor.getNextKey();
|
|
527
537
|
|
|
@@ -538,7 +548,6 @@ async function flashJump(): Promise<void> {
|
|
|
538
548
|
if (state.pattern.length > 0) {
|
|
539
549
|
state.pattern = state.pattern.slice(0, -1);
|
|
540
550
|
}
|
|
541
|
-
setStatusForPattern();
|
|
542
551
|
continue;
|
|
543
552
|
}
|
|
544
553
|
|
|
@@ -551,7 +560,6 @@ async function flashJump(): Promise<void> {
|
|
|
551
560
|
break;
|
|
552
561
|
}
|
|
553
562
|
state.pattern += ev.key;
|
|
554
|
-
setStatusForPattern();
|
|
555
563
|
continue;
|
|
556
564
|
}
|
|
557
565
|
|
package/plugins/git_explorer.ts
CHANGED
|
@@ -117,9 +117,14 @@ async function refreshGitExplorerDecorations() {
|
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// -z gives NUL-terminated, raw (unquoted) paths. Without it git
|
|
121
|
+
// wraps any path containing spaces or special chars in double
|
|
122
|
+
// quotes (e.g. `?? "name copy.txt"`), which the parser would then
|
|
123
|
+
// key the decoration against — meaning the actual on-disk path
|
|
124
|
+
// never matches and the badge never appears next to the file.
|
|
120
125
|
const statusResult = await editor.spawnProcess(
|
|
121
126
|
"git",
|
|
122
|
-
["status", "--porcelain"],
|
|
127
|
+
["status", "--porcelain", "-z"],
|
|
123
128
|
repoRoot
|
|
124
129
|
);
|
|
125
130
|
if (statusResult.exit_code !== 0) {
|
|
@@ -155,6 +160,9 @@ editor.on("after_file_open", () => {
|
|
|
155
160
|
editor.on("after_file_save", () => {
|
|
156
161
|
refreshGitExplorerDecorations();
|
|
157
162
|
});
|
|
163
|
+
editor.on("after_file_explorer_change", () => {
|
|
164
|
+
refreshGitExplorerDecorations();
|
|
165
|
+
});
|
|
158
166
|
editor.on("editor_initialized", () => {
|
|
159
167
|
refreshGitExplorerDecorations();
|
|
160
168
|
});
|
package/plugins/lib/finder.ts
CHANGED
|
@@ -118,6 +118,21 @@ export interface FinderConfig<T> {
|
|
|
118
118
|
|
|
119
119
|
/** Called when the panel or prompt is closed (e.g. via Escape) */
|
|
120
120
|
onClose?: () => void;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* When true, panels created by this Finder are routed into the
|
|
124
|
+
* shared Utility Dock (issue #1796 / Section 2 of
|
|
125
|
+
* `docs/internal/tui-editor-layout-design.md`). The first
|
|
126
|
+
* dock-aware utility creates the dock leaf; subsequent ones swap
|
|
127
|
+
* the dock's active buffer instead of spawning new splits.
|
|
128
|
+
*
|
|
129
|
+
* Defaults to `false` so panels with bespoke layouts (e.g.
|
|
130
|
+
* `theme_editor`'s buffer groups, `pkg`'s side-by-side panes)
|
|
131
|
+
* keep their independent split. Plugins that present a generic
|
|
132
|
+
* "list of locations" UX (Diagnostics, Find References, Live
|
|
133
|
+
* Grep Quickfix) should opt in.
|
|
134
|
+
*/
|
|
135
|
+
useUtilityDock?: boolean;
|
|
121
136
|
}
|
|
122
137
|
|
|
123
138
|
/**
|
|
@@ -128,6 +143,14 @@ export interface PromptOptions<T> {
|
|
|
128
143
|
source: SearchSource<T> | FilterSource<T>;
|
|
129
144
|
/** Initial query value */
|
|
130
145
|
initialQuery?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Render the prompt as a centred floating overlay with an
|
|
148
|
+
* embedded preview pane (issue #1796). When true, the editor
|
|
149
|
+
* draws the input + results + preview inside one floating frame
|
|
150
|
+
* over the editor area; the underlying split tree is not
|
|
151
|
+
* mutated. Defaults to false.
|
|
152
|
+
*/
|
|
153
|
+
floatingOverlay?: boolean;
|
|
131
154
|
}
|
|
132
155
|
|
|
133
156
|
/**
|
|
@@ -277,7 +300,16 @@ export function defaultFuzzyFilter<T>(
|
|
|
277
300
|
// ============================================================================
|
|
278
301
|
|
|
279
302
|
/**
|
|
280
|
-
* Parse a grep-style output line
|
|
303
|
+
* Parse a grep-style output line.
|
|
304
|
+
*
|
|
305
|
+
* Accepts both `file:line:column:content` (ripgrep, ag, git-grep with
|
|
306
|
+
* `--column`, GNU grep with `--column`) and `file:line:content`
|
|
307
|
+
* (POSIX grep, BSD grep, plenty of custom wrappers that omit the
|
|
308
|
+
* column). When the column is missing it defaults to 1.
|
|
309
|
+
*
|
|
310
|
+
* Returns null only if the line lacks even a `file:line:` prefix —
|
|
311
|
+
* pure header lines (ripgrep without `--no-heading`, blank lines)
|
|
312
|
+
* are filtered upstream by the caller's `if (!line.trim()) continue`.
|
|
281
313
|
*/
|
|
282
314
|
export function parseGrepLine(line: string): {
|
|
283
315
|
file: string;
|
|
@@ -285,13 +317,25 @@ export function parseGrepLine(line: string): {
|
|
|
285
317
|
column: number;
|
|
286
318
|
content: string;
|
|
287
319
|
} | null {
|
|
288
|
-
|
|
289
|
-
|
|
320
|
+
// Try the four-field shape first so a content payload that
|
|
321
|
+
// happens to start with digits (e.g. `42 = solve(...)`) doesn't
|
|
322
|
+
// get mistaken for a column number under the three-field path.
|
|
323
|
+
const four = line.match(/^([^:]+):(\d+):(\d+):(.*)$/);
|
|
324
|
+
if (four) {
|
|
290
325
|
return {
|
|
291
|
-
file:
|
|
292
|
-
line: parseInt(
|
|
293
|
-
column: parseInt(
|
|
294
|
-
content:
|
|
326
|
+
file: four[1],
|
|
327
|
+
line: parseInt(four[2], 10),
|
|
328
|
+
column: parseInt(four[3], 10),
|
|
329
|
+
content: four[4],
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const three = line.match(/^([^:]+):(\d+):(.*)$/);
|
|
333
|
+
if (three) {
|
|
334
|
+
return {
|
|
335
|
+
file: three[1],
|
|
336
|
+
line: parseInt(three[2], 10),
|
|
337
|
+
column: 1,
|
|
338
|
+
content: three[3],
|
|
295
339
|
};
|
|
296
340
|
}
|
|
297
341
|
return null;
|
|
@@ -465,20 +509,42 @@ export class Finder<T> {
|
|
|
465
509
|
}
|
|
466
510
|
|
|
467
511
|
// Start the prompt
|
|
512
|
+
const overlay = options.floatingOverlay === true;
|
|
468
513
|
if (options.initialQuery) {
|
|
469
514
|
this.editor.startPromptWithInitial(
|
|
470
515
|
options.title,
|
|
471
516
|
this.config.id,
|
|
472
|
-
options.initialQuery
|
|
517
|
+
options.initialQuery,
|
|
518
|
+
overlay
|
|
473
519
|
);
|
|
474
520
|
} else {
|
|
475
|
-
this.editor.debug(`[Finder] calling startPrompt with title="${options.title}", id="${this.config.id}"`);
|
|
476
|
-
const result = this.editor.startPrompt(options.title, this.config.id);
|
|
521
|
+
this.editor.debug(`[Finder] calling startPrompt with title="${options.title}", id="${this.config.id}", overlay=${overlay}`);
|
|
522
|
+
const result = this.editor.startPrompt(options.title, this.config.id, overlay);
|
|
477
523
|
this.editor.debug(`[Finder] startPrompt returned: ${result}`);
|
|
478
524
|
}
|
|
479
525
|
this.editor.setStatus("Type to search...");
|
|
480
526
|
}
|
|
481
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Re-run the current search against `lastQuery`, bypassing the
|
|
530
|
+
* "skip-if-same-query" dedup. Useful when the *backend* has
|
|
531
|
+
* changed (e.g. user cycled Live Grep providers) and the same
|
|
532
|
+
* query needs to produce different results.
|
|
533
|
+
*
|
|
534
|
+
* No-op for filter-mode sources (results are already correct
|
|
535
|
+
* client-side) and when no prompt is open.
|
|
536
|
+
*/
|
|
537
|
+
async refresh(): Promise<void> {
|
|
538
|
+
if (!this.isPromptMode || !this.currentSource) return;
|
|
539
|
+
if (this.currentSource.mode !== "search") return;
|
|
540
|
+
const query = this.promptState.lastQuery;
|
|
541
|
+
// Reset dedup so runSearch doesn't short-circuit on the
|
|
542
|
+
// unchanged query.
|
|
543
|
+
this.promptState.lastQuery = "";
|
|
544
|
+
if (query.length === 0) return;
|
|
545
|
+
await this.runSearch(query, this.currentSource);
|
|
546
|
+
}
|
|
547
|
+
|
|
482
548
|
/**
|
|
483
549
|
* Show static results in panel
|
|
484
550
|
*/
|
|
@@ -1095,6 +1161,11 @@ export class Finder<T> {
|
|
|
1095
1161
|
ratio,
|
|
1096
1162
|
direction: "horizontal",
|
|
1097
1163
|
panelId: this.config.id,
|
|
1164
|
+
// Per-finder opt-in via `useUtilityDock` — many Finder
|
|
1165
|
+
// consumers (theme_editor's buffer groups, pkg's side-by-
|
|
1166
|
+
// side panes, …) need their own independent splits and
|
|
1167
|
+
// would break if routed into the shared dock.
|
|
1168
|
+
...(this.config.useUtilityDock ? { role: "utility_dock" } : {}),
|
|
1098
1169
|
showLineNumbers: false,
|
|
1099
1170
|
showCursors: true,
|
|
1100
1171
|
editingDisabled: true,
|
package/plugins/lib/fresh.d.ts
CHANGED
|
@@ -821,6 +821,13 @@ type CreateVirtualBufferInSplitOptions = {
|
|
|
821
821
|
* Initial content entries with optional properties
|
|
822
822
|
*/
|
|
823
823
|
entries?: Array<TextPropertyEntry>;
|
|
824
|
+
/**
|
|
825
|
+
* Split role tag. When set to `"utility_dock"`, the dispatcher
|
|
826
|
+
* routes this buffer to the existing dock leaf if one exists,
|
|
827
|
+
* instead of creating a new split. See
|
|
828
|
+
* `docs/internal/tui-editor-layout-design.md` Section 2.
|
|
829
|
+
*/
|
|
830
|
+
role?: string;
|
|
824
831
|
};
|
|
825
832
|
type CreateVirtualBufferOptions = {
|
|
826
833
|
/**
|
|
@@ -1458,6 +1465,11 @@ interface EditorAPI {
|
|
|
1458
1465
|
*/
|
|
1459
1466
|
getBuiltinThemes(): unknown;
|
|
1460
1467
|
/**
|
|
1468
|
+
* Full theme registry (builtins + user themes + packages + bundles).
|
|
1469
|
+
* Keyed by canonical registry key; each value carries `_key` / `_pack`.
|
|
1470
|
+
*/
|
|
1471
|
+
getAllThemes(): unknown;
|
|
1472
|
+
/**
|
|
1461
1473
|
* Delete a custom theme (alias for deleteThemeSync)
|
|
1462
1474
|
*/
|
|
1463
1475
|
deleteTheme(name: string): boolean;
|
|
@@ -1668,9 +1680,15 @@ interface EditorAPI {
|
|
|
1668
1680
|
*/
|
|
1669
1681
|
prompt(label: string, initialValue: string): Promise<string | null>;
|
|
1670
1682
|
/**
|
|
1671
|
-
* Start an interactive prompt
|
|
1683
|
+
* Start an interactive prompt.
|
|
1684
|
+
*
|
|
1685
|
+
* When `floatingOverlay` is true, the editor renders the prompt
|
|
1686
|
+
* and its suggestions inside a centred floating frame instead of
|
|
1687
|
+
* the bottom minibuffer row (issue #1796 — Live Grep). The flag
|
|
1688
|
+
* is rendering-only; confirm/cancel/hooks behave identically to a
|
|
1689
|
+
* non-overlay prompt of the same `promptType`.
|
|
1672
1690
|
*/
|
|
1673
|
-
startPrompt(label: string, promptType: string): boolean;
|
|
1691
|
+
startPrompt(label: string, promptType: string, floatingOverlay?: boolean): boolean;
|
|
1674
1692
|
/**
|
|
1675
1693
|
* Begin a key-capture window for the calling plugin.
|
|
1676
1694
|
*
|
|
@@ -1704,9 +1722,10 @@ interface EditorAPI {
|
|
|
1704
1722
|
*/
|
|
1705
1723
|
getNextKey(): Promise<KeyEventPayload>;
|
|
1706
1724
|
/**
|
|
1707
|
-
* Start a prompt with initial value
|
|
1725
|
+
* Start a prompt with initial value. See `startPrompt` for the
|
|
1726
|
+
* meaning of `floatingOverlay`.
|
|
1708
1727
|
*/
|
|
1709
|
-
startPromptWithInitial(label: string, promptType: string, initialValue: string): boolean;
|
|
1728
|
+
startPromptWithInitial(label: string, promptType: string, initialValue: string, floatingOverlay?: boolean): boolean;
|
|
1710
1729
|
/**
|
|
1711
1730
|
* Set suggestions for the current prompt
|
|
1712
1731
|
*
|
|
@@ -1715,6 +1734,13 @@ interface EditorAPI {
|
|
|
1715
1734
|
setPromptSuggestions(suggestions: PromptSuggestion[]): boolean;
|
|
1716
1735
|
setPromptInputSync(sync: boolean): boolean;
|
|
1717
1736
|
/**
|
|
1737
|
+
* Set the title shown in the floating-overlay prompt's frame
|
|
1738
|
+
* header (issue #1796). Pass `null` or omit the argument to
|
|
1739
|
+
* clear the title and fall back to the default. Has no
|
|
1740
|
+
* visible effect on non-overlay prompts.
|
|
1741
|
+
*/
|
|
1742
|
+
setPromptTitle(title?: string | null): boolean;
|
|
1743
|
+
/**
|
|
1718
1744
|
* Define a buffer mode (takes bindings as array of [key, command] pairs)
|
|
1719
1745
|
*/
|
|
1720
1746
|
defineMode(name: string, bindingsArr: string[][], readOnly?: boolean, allowTextInput?: boolean, inheritNormalBindings?: boolean): boolean;
|
|
@@ -2099,6 +2125,16 @@ interface HookEventMap {
|
|
|
2099
2125
|
path: string;
|
|
2100
2126
|
buffer_id: number;
|
|
2101
2127
|
};
|
|
2128
|
+
/**
|
|
2129
|
+
* Fired by the file explorer after a paste/duplicate/etc. mutates
|
|
2130
|
+
* the filesystem without going through a buffer save. Plugins that
|
|
2131
|
+
* surface FS-derived state (git status badges, etc.) should
|
|
2132
|
+
* subscribe in addition to `after_file_save` to refresh on
|
|
2133
|
+
* explorer-driven changes too.
|
|
2134
|
+
*/
|
|
2135
|
+
after_file_explorer_change: {
|
|
2136
|
+
path: string;
|
|
2137
|
+
};
|
|
2102
2138
|
// ── text edits ───────────────────────────────────────────────────────────
|
|
2103
2139
|
before_insert: {
|
|
2104
2140
|
buffer_id: number;
|