@fresh-editor/fresh-editor 0.3.1 → 0.3.2

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 CHANGED
@@ -1,5 +1,59 @@
1
1
  # Release Notes
2
2
 
3
+ ## 0.3.2
4
+
5
+ ### Features
6
+
7
+ * **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`.
8
+
9
+ * **New Startup section in Settings** (open Settings and search "Startup") groups everything that fires on launch:
10
+ - **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`).
11
+ - *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.
12
+ - *Restore Previous Session* (existing, moved into Startup).
13
+
14
+ * **File explorer side** (thanks @paveloparev!): *Side* under File Explorer in Settings — left or right.
15
+
16
+ * **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.
17
+
18
+ * **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.
19
+
20
+ * **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.
21
+
22
+ * **CLI Help Localization**: The `--help` output is now fully localized using runtime i18n lookups.
23
+
24
+ * **Relative +/- Goto Line**: Infers absolute vs relative jumps from a leading sign (e.g., `:+10` jumps 10 lines down, `:10` jumps to line 10).
25
+
26
+ * **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).
27
+
28
+ ### Improvements
29
+
30
+ * **Plugin loading deferred off the boot critical path** — another ~225 ms saved. Same load order, same hooks, just async.
31
+
32
+ * **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`.
33
+
34
+ * **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.
35
+
36
+ * **Prompt interaction and scrolling** (#1660):
37
+ - Minimal scrolling: the suggestion list no longer recenters the selection on every move, preventing "row jumping" during navigation.
38
+ - Clicks no longer cause accidental list shifts; double-click correctly confirms selections.
39
+ - Preview-on-click supported for "Reload with encoding".
40
+
41
+ * **Global Menu Bar**: "Toggle Menu Bar" state is now persisted globally across all workspaces.
42
+
43
+ * **Windows integration**: High-quality app icon applied to the running window; app manifest and version info embedded in the binary.
44
+
45
+ ### Bug Fixes
46
+
47
+ * **Windows subprocesses**: Transient console windows are now hidden when spawning subprocesses (e.g., formatters, linters).
48
+
49
+ * **Terminal CWD**: Fixed shell spawning failure on Windows when the current directory has a `\\?\` UNC prefix.
50
+
51
+ * **Live Diff stability**: Fixed crashes on surrogate-pair content (emojis) and corrected gutter rendering for empty lines inside added blocks.
52
+
53
+ ### Under the Hood
54
+
55
+ * **Syntax Highlight Caching**: New multi-phase caching system (memoised scope lookups and whole-file cache for small files) significantly reduces CPU usage during rendering.
56
+
3
57
  ## 0.3.1
4
58
 
5
59
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fresh-editor/fresh-editor",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "A modern terminal-based text editor with plugin support",
5
5
  "repository": {
6
6
  "type": "git",
@@ -63,7 +63,7 @@
63
63
  "{palette}"
64
64
  ]
65
65
  },
66
- "show_prompt_line": true,
66
+ "show_prompt_line": false,
67
67
  "show_vertical_scrollbar": true,
68
68
  "show_horizontal_scrollbar": false,
69
69
  "show_tilde": true,
@@ -103,6 +103,8 @@
103
103
  "auto_save_interval_secs": 30,
104
104
  "hot_exit": true,
105
105
  "restore_previous_session": true,
106
+ "skip_session_restore_when_files_passed": true,
107
+ "auto_create_empty_buffer_on_last_buffer_close": true,
106
108
  "recovery_enabled": true,
107
109
  "auto_recovery_save_interval_secs": 2,
108
110
  "auto_revert_poll_interval_ms": 2000,
@@ -128,7 +130,9 @@
128
130
  "show_gitignored": false,
129
131
  "custom_ignore_patterns": [],
130
132
  "width": "30%",
131
- "preview_tabs": true
133
+ "preview_tabs": true,
134
+ "side": "left",
135
+ "auto_open_on_last_buffer_close": true
132
136
  }
133
137
  },
134
138
  "file_browser": {
@@ -389,9 +393,9 @@
389
393
  "x-section": "Status Bar"
390
394
  },
391
395
  "show_prompt_line": {
392
- "description": "Whether the prompt line is visible by default.\nThe prompt line is the bottom-most line used for command input, search, file open, etc.\nWhen hidden, the prompt line only appears when a prompt is active.\nCan be toggled at runtime via command palette or keybinding.\nDefault: true",
396
+ "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
397
  "type": "boolean",
394
- "default": true,
398
+ "default": false,
395
399
  "x-section": "Display"
396
400
  },
397
401
  "show_vertical_scrollbar": {
@@ -643,7 +647,19 @@
643
647
  "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
648
  "type": "boolean",
645
649
  "default": true,
646
- "x-section": "Recovery"
650
+ "x-section": "Startup"
651
+ },
652
+ "skip_session_restore_when_files_passed": {
653
+ "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",
654
+ "type": "boolean",
655
+ "default": true,
656
+ "x-section": "Startup"
657
+ },
658
+ "auto_create_empty_buffer_on_last_buffer_close": {
659
+ "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",
660
+ "type": "boolean",
661
+ "default": true,
662
+ "x-section": "Startup"
647
663
  },
648
664
  "recovery_enabled": {
649
665
  "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 +934,16 @@
918
934
  "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
935
  "type": "boolean",
920
936
  "default": true
937
+ },
938
+ "side": {
939
+ "description": "Which side of the screen to show the file explorer on.\nDefault: left",
940
+ "$ref": "#/$defs/FileExplorerSide",
941
+ "default": "left"
942
+ },
943
+ "auto_open_on_last_buffer_close": {
944
+ "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",
945
+ "type": "boolean",
946
+ "default": true
921
947
  }
922
948
  }
923
949
  },
@@ -926,6 +952,14 @@
926
952
  "type": "string",
927
953
  "pattern": "^(100%|[1-9]?[0-9]%|\\d+)$"
928
954
  },
955
+ "FileExplorerSide": {
956
+ "description": "Side placement for the file explorer panel.",
957
+ "type": "string",
958
+ "enum": [
959
+ "left",
960
+ "right"
961
+ ]
962
+ },
929
963
  "FileBrowserConfig": {
930
964
  "description": "File browser configuration (for Open File dialog)",
931
965
  "type": "object",
@@ -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: saved-diff (unsaved changes)
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) {
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