diff --git a/crates/fresh-editor/src/config.rs b/crates/fresh-editor/src/config.rs index 9cc1b826a0..6448d2ff33 100644 --- a/crates/fresh-editor/src/config.rs +++ b/crates/fresh-editor/src/config.rs @@ -4600,6 +4600,9 @@ impl Config { "launch.json".to_string(), "extensions.json".to_string(), "argv.json".to_string(), + // Bun's text lockfile is JSONC (trailing commas), not + // strict JSON (#2921). + "bun.lock".to_string(), ], grammar: "jsonc".to_string(), comment_prefix: Some("//".to_string()), @@ -9432,6 +9435,25 @@ mod tests { } } + /// Well-known JSONC filenames must route to the `jsonc` language so + /// they get JSON highlighting instead of falling through to plain + /// text. Regression test for #2921: `bun.lock` is JSONC (trailing + /// commas) but no default language claimed it. + #[test] + fn test_default_languages_map_jsonc_filenames() { + use crate::services::lsp::manager::detect_language; + use std::path::Path; + + let languages = Config::default_languages(); + for filename in ["bun.lock", "tsconfig.json", "devcontainer.json"] { + assert_eq!( + detect_language(Path::new(filename), &languages), + Some("jsonc".to_string()), + "expected `{filename}` to be detected as jsonc" + ); + } + } + #[test] #[cfg(feature = "runtime")] fn test_gdscript_lsp_disabled_by_default() { diff --git a/crates/fresh-editor/src/init_script.rs b/crates/fresh-editor/src/init_script.rs index d57233277a..e1ee2ae34b 100644 --- a/crates/fresh-editor/src/init_script.rs +++ b/crates/fresh-editor/src/init_script.rs @@ -233,12 +233,15 @@ const editor = getEditor(); /// `tsconfig.json` for the user's init.ts. Matches the plugin-dev /// workspace (no DOM, no ambient types) so LSP behaviour is consistent -/// with plugins. +/// with plugins. `moduleResolution` must be `bundler` (matching +/// `plugins/tsconfig.json` and `check-types.sh`): TypeScript 7 removed +/// the old `node` (node10) resolution mode, so a tsserver reading this +/// file would fail with TS5108 (#2872). const INIT_TSCONFIG: &str = r#"{ "compilerOptions": { "target": "ES2020", "module": "ES2020", - "moduleResolution": "node", + "moduleResolution": "bundler", "strict": true, "noEmit": true, "skipLibCheck": true, @@ -682,6 +685,20 @@ mod tests { assert_eq!(p, PathBuf::from("/tmp/fresh/init.ts")); } + /// TypeScript 7 removed `moduleResolution: node` (node10); a tsserver + /// reading a generated tsconfig that still uses it fails with TS5108. + /// The generated config must use `bundler`, matching + /// `plugins/tsconfig.json` and `check-types.sh` (#2872). + #[test] + fn generated_tsconfig_does_not_use_removed_module_resolution() { + let parsed: serde_json::Value = serde_json::from_str(INIT_TSCONFIG) + .expect("generated tsconfig.json must be valid JSON"); + assert_eq!( + parsed["compilerOptions"]["moduleResolution"], "bundler", + "moduleResolution must be `bundler`; `node` (node10) was removed in TypeScript 7" + ); + } + #[test] fn crash_fuse_trips_after_threshold_consecutive_failures() { let tmp = TempDir::new().unwrap(); diff --git a/crates/fresh-editor/src/services/plugins/plugin_dev_workspace.rs b/crates/fresh-editor/src/services/plugins/plugin_dev_workspace.rs index 1a45e76d50..94f04480b1 100644 --- a/crates/fresh-editor/src/services/plugins/plugin_dev_workspace.rs +++ b/crates/fresh-editor/src/services/plugins/plugin_dev_workspace.rs @@ -23,11 +23,15 @@ pub struct PluginDevWorkspace { /// - No DOM lib (plugins run in QuickJS, not a browser) /// - `types: []` prevents picking up @types/node or other ambient types /// - `skipLibCheck: true` avoids checking fresh.d.ts itself +/// - `moduleResolution: bundler` (matching `plugins/tsconfig.json` and +/// `check-types.sh`): TypeScript 7 removed the old `node` (node10) +/// resolution mode, so a tsserver reading this file would fail with +/// TS5108 (#2872) const TSCONFIG_CONTENT: &str = r#"{ "compilerOptions": { "target": "ES2020", "module": "ES2020", - "moduleResolution": "node", + "moduleResolution": "bundler", "strict": true, "noEmit": true, "skipLibCheck": true, @@ -114,3 +118,22 @@ impl Drop for PluginDevWorkspace { self.cleanup(); } } + +#[cfg(test)] +mod tests { + use super::*; + + /// TypeScript 7 removed `moduleResolution: node` (node10); a tsserver + /// reading a generated tsconfig that still uses it fails with TS5108. + /// The generated config must use `bundler`, matching + /// `plugins/tsconfig.json` and `check-types.sh` (#2872). + #[test] + fn generated_tsconfig_does_not_use_removed_module_resolution() { + let parsed: serde_json::Value = serde_json::from_str(TSCONFIG_CONTENT) + .expect("generated tsconfig.json must be valid JSON"); + assert_eq!( + parsed["compilerOptions"]["moduleResolution"], "bundler", + "moduleResolution must be `bundler`; `node` (node10) was removed in TypeScript 7" + ); + } +} diff --git a/docs/blog/editing/index.md b/docs/blog/editing/index.md index 346a89eaab..d4c53b4d40 100644 --- a/docs/blog/editing/index.md +++ b/docs/blog/editing/index.md @@ -21,7 +21,7 @@ Place your cursor on a word, press **Ctrl+W** to select it, then **Ctrl+D** to s ## Search & Replace -**Ctrl+H** opens find-and-replace with live highlighting as you type. Supports regex with capture groups (`$1`, `$2`), find-in-selection, and a confirm-each toggle. +**Ctrl+R** opens find-and-replace with live highlighting as you type. Supports regex with capture groups (`$1`, `$2`), find-in-selection, and a confirm-each toggle. (Ctrl+H is deliberately not used: terminals transmit it as Backspace, so Fresh binds it to delete-word-backward instead.)
diff --git a/docs/configuration/keyboard.md b/docs/configuration/keyboard.md
index bbb7ae2e9e..aeddef64a7 100644
--- a/docs/configuration/keyboard.md
+++ b/docs/configuration/keyboard.md
@@ -57,13 +57,13 @@ The macOS keymap is designed around these constraints:
**Ctrl+Shift combinations don't work.** Some macOS terminals cannot reliably send Ctrl+Shift sequences. For example, Ctrl+Shift+Z produces a caron character (ˇ) instead of being recognized as a key chord. The macOS keymap uses Ctrl+Alt as an alternative modifier.
-**Some Ctrl keys are ASCII control characters.** In terminal protocols, Ctrl+J is Line Feed (newline), Ctrl+M is Carriage Return (Enter), and Ctrl+I is Tab. Binding actions to these keys causes erratic behavior. The macOS keymap avoids these collisions.
+**Some Ctrl keys are ASCII control characters.** In terminal protocols, Ctrl+J is Line Feed (newline), Ctrl+M is Carriage Return (Enter), Ctrl+I is Tab, and Ctrl+H is Backspace. Binding actions to these keys causes erratic behavior. The macOS keymap avoids these collisions. This is also why find-and-replace is on **Ctrl+R** rather than Ctrl+H: most terminals transmit Ctrl+H as Backspace, so Fresh treats it as Ctrl+Backspace (delete previous word) in every keymap.
**International keyboards use Alt for essential characters.** On German, French, and other ISO layouts, Alt (Option) combined with letters produces characters like @, [, ], {, and }. The macOS keymap avoids Alt+letter combinations that would block character input.
**Unix readline conventions are preserved.** Terminal users expect Ctrl+Y to "yank" (paste from the kill ring), Ctrl+K to kill to end of line, and Ctrl+U to kill to start of line. The macOS keymap respects these conventions rather than overriding them with GUI editor shortcuts.
-Use the **Command Palette** (Ctrl+P) or **Show Keybindings** (Ctrl+H) to discover the actual key bindings, or view the keymap file directly at `keymaps/macos.json`.
+Use the **Command Palette** (Ctrl+P) or run **Show Keyboard Shortcuts** from it to discover the actual key bindings, or view the keymap file directly at `keymaps/macos.json`.
#### Recommended Terminal Emulators
diff --git a/docs/features/lsp.md b/docs/features/lsp.md
index a04d16d460..f5ebe15cb8 100644
--- a/docs/features/lsp.md
+++ b/docs/features/lsp.md
@@ -209,7 +209,7 @@ Some LSP servers expect a different `languageId` than Fresh's internal language
### Rust LSP Mode Switching
-Use "Switch Rust Analyzer Mode" from the command palette to toggle between Full and Reduced Memory modes for rust-analyzer.
+Use "Rust LSP: Configure Mode" from the command palette to toggle between Full and Reduced Memory modes for rust-analyzer.
## Configuring Language Detection via Settings UI