Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/fresh-editor/plugins/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@
"shell": null
}
},
"live_diff": {
"description": "Live Diff plugin settings.\nControls the inline-diff view rendered against a git reference.",
"$ref": "#/$defs/LiveDiffConfig",
"default": {
"enabled_on_startup": false
}
},
"keybindings": {
"description": "Custom keybindings (overrides for the active map)",
"type": "array",
Expand Down Expand Up @@ -1053,6 +1060,17 @@
"command"
]
},
"LiveDiffConfig": {
"description": "Live Diff plugin configuration.\n\nThe Live Diff plugin renders a unified-diff view directly inside the\neditable buffer (gutter glyphs, virtual deletion lines, change\nhighlights) against a git reference. By default it is opt-in: the\nuser toggles it on via the `Live Diff: Toggle (Global)` command.\nThis struct exposes settings that take effect at startup, before\nany toggle command has been issued.",
"type": "object",
"properties": {
"enabled_on_startup": {
"description": "Enable the Live Diff view globally when Fresh starts.\nWhen `true`, the editor begins each session with Live Diff on\n— equivalent to invoking `Live Diff: Toggle (Global)` once\nfrom the command palette. The user can still toggle it off\nfor the current session; the next startup re-enables it.\nDefault: false (opt-in via command palette).",
"type": "boolean",
"default": false
}
}
},
"Keybinding": {
"description": "Keybinding definition",
"type": "object",
Expand Down
25 changes: 25 additions & 0 deletions crates/fresh-editor/plugins/live_diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1236,13 +1236,15 @@ registerHandler("live_diff_set_default", live_diff_set_default);
// =============================================================================

editor.on("after_file_open", (args) => {
applyStartupConfigOnce();
const state = ensureState(args.buffer_id);
if (!state) return true;
recompute(args.buffer_id).catch((e) => editor.error(`live-diff: ${e}`));
return true;
});

editor.on("buffer_activated", (args) => {
applyStartupConfigOnce();
const state = ensureState(args.buffer_id);
if (!state) return true;
// Indicators stick around across activations; only repaint if we never
Expand Down Expand Up @@ -1345,8 +1347,31 @@ editor.exportPluginApi("live-diff", {
// Initialization
// =============================================================================

// Honor `live_diff.enabled_on_startup` from user config: if the user has
// opted in, force the global toggle on at the start of every session,
// regardless of the persisted value. The user can still toggle off
// mid-session via the command palette; the next startup re-enables it.
//
// The check is one-shot per session, gated by `startupConfigApplied`. It
// runs lazily — on the first buffer event (open, activate) or the first
// recompute — rather than at plugin load. `editor.getConfig()` reads
// from a snapshot the editor refreshes between ticks, so it can return
// `{}` if the plugin loads before the snapshot has been populated; the
// lazy check guarantees we look after the editor has settled.
let startupConfigApplied = false;
function applyStartupConfigOnce(): void {
if (startupConfigApplied) return;
startupConfigApplied = true;
const config = editor.getConfig() as Record<string, unknown> | null;
const section = (config?.live_diff as Record<string, unknown> | undefined) ?? undefined;
if (section?.enabled_on_startup === true && !isGlobalEnabled()) {
setGlobalEnabled(true);
}
}

const initBid = editor.getActiveBufferId();
if (initBid !== 0) {
applyStartupConfigOnce();
const state = ensureState(initBid);
if (state) {
recompute(initBid).catch((e) => editor.error(`live-diff: ${e}`));
Expand Down
34 changes: 34 additions & 0 deletions crates/fresh-editor/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,11 @@ pub struct Config {
#[serde(default)]
pub terminal: TerminalConfig,

/// Live Diff plugin settings.
/// Controls the inline-diff view rendered against a git reference.
#[serde(default)]
pub live_diff: LiveDiffConfig,

/// Custom keybindings (overrides for the active map)
#[serde(default)]
pub keybindings: Vec<Keybinding>,
Expand Down Expand Up @@ -1854,6 +1859,34 @@ pub struct TerminalShellConfig {
pub args: Vec<String>,
}

/// Live Diff plugin configuration.
///
/// The Live Diff plugin renders a unified-diff view directly inside the
/// editable buffer (gutter glyphs, virtual deletion lines, change
/// highlights) against a git reference. By default it is opt-in: the
/// user toggles it on via the `Live Diff: Toggle (Global)` command.
/// This struct exposes settings that take effect at startup, before
/// any toggle command has been issued.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct LiveDiffConfig {
/// Enable the Live Diff view globally when Fresh starts.
/// When `true`, the editor begins each session with Live Diff on
/// — equivalent to invoking `Live Diff: Toggle (Global)` once
/// from the command palette. The user can still toggle it off
/// for the current session; the next startup re-enables it.
/// Default: false (opt-in via command palette).
#[serde(default = "default_false")]
pub enabled_on_startup: bool,
}

impl Default for LiveDiffConfig {
fn default() -> Self {
Self {
enabled_on_startup: false,
}
}
}

/// Warning notification configuration
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct WarningsConfig {
Expand Down Expand Up @@ -2417,6 +2450,7 @@ impl Default for Config {
file_browser: FileBrowserConfig::default(),
clipboard: ClipboardConfig::default(),
terminal: TerminalConfig::default(),
live_diff: LiveDiffConfig::default(),
keybindings: vec![], // User customizations only; defaults come from active_keybinding_map
keybinding_maps: HashMap::new(), // User-defined maps go here
active_keybinding_map: default_keybinding_map_name(),
Expand Down
43 changes: 41 additions & 2 deletions crates/fresh-editor/src/partial_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

use crate::config::{
ClipboardConfig, CursorStyle, FileBrowserConfig, FileExplorerConfig, FormatterConfig,
Keybinding, KeybindingMapName, KeymapConfig, LanguageConfig, LineEndingOption, OnSaveAction,
PluginConfig, TerminalConfig, ThemeName, WarningsConfig,
Keybinding, KeybindingMapName, KeymapConfig, LanguageConfig, LineEndingOption, LiveDiffConfig,
OnSaveAction, PluginConfig, TerminalConfig, ThemeName, WarningsConfig,
};
use crate::types::LspLanguageConfig;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -82,6 +82,7 @@ pub struct PartialConfig {
pub file_browser: Option<PartialFileBrowserConfig>,
pub clipboard: Option<PartialClipboardConfig>,
pub terminal: Option<PartialTerminalConfig>,
pub live_diff: Option<PartialLiveDiffConfig>,
pub keybindings: Option<Vec<Keybinding>>,
pub keybinding_maps: Option<HashMap<String, KeymapConfig>>,
pub active_keybinding_map: Option<KeybindingMapName>,
Expand All @@ -107,6 +108,7 @@ impl Merge for PartialConfig {
merge_partial(&mut self.file_browser, &other.file_browser);
merge_partial(&mut self.clipboard, &other.clipboard);
merge_partial(&mut self.terminal, &other.terminal);
merge_partial(&mut self.live_diff, &other.live_diff);
merge_partial(&mut self.warnings, &other.warnings);
merge_partial(&mut self.packages, &other.packages);

Expand Down Expand Up @@ -414,6 +416,20 @@ impl Merge for PartialTerminalConfig {
}
}

/// Partial Live Diff plugin configuration.
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(default)]
pub struct PartialLiveDiffConfig {
pub enabled_on_startup: Option<bool>,
}

impl Merge for PartialLiveDiffConfig {
fn merge_from(&mut self, other: &Self) {
self.enabled_on_startup
.merge_from(&other.enabled_on_startup);
}
}

/// Partial warnings configuration.
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(default)]
Expand Down Expand Up @@ -859,6 +875,24 @@ impl PartialTerminalConfig {
}
}

impl From<&LiveDiffConfig> for PartialLiveDiffConfig {
fn from(cfg: &LiveDiffConfig) -> Self {
Self {
enabled_on_startup: Some(cfg.enabled_on_startup),
}
}
}

impl PartialLiveDiffConfig {
pub fn resolve(self, defaults: &LiveDiffConfig) -> LiveDiffConfig {
LiveDiffConfig {
enabled_on_startup: self
.enabled_on_startup
.unwrap_or(defaults.enabled_on_startup),
}
}
}

impl From<&WarningsConfig> for PartialWarningsConfig {
fn from(cfg: &WarningsConfig) -> Self {
Self {
Expand Down Expand Up @@ -988,6 +1022,7 @@ impl From<&crate::config::Config> for PartialConfig {
file_browser: Some(PartialFileBrowserConfig::from(&cfg.file_browser)),
clipboard: Some(PartialClipboardConfig::from(&cfg.clipboard)),
terminal: Some(PartialTerminalConfig::from(&cfg.terminal)),
live_diff: Some(PartialLiveDiffConfig::from(&cfg.live_diff)),
keybindings: Some(cfg.keybindings.clone()),
keybinding_maps: Some(cfg.keybinding_maps.clone()),
active_keybinding_map: Some(cfg.active_keybinding_map.clone()),
Expand Down Expand Up @@ -1188,6 +1223,10 @@ impl PartialConfig {
.terminal
.map(|e| e.resolve(&defaults.terminal))
.unwrap_or_else(|| defaults.terminal.clone()),
live_diff: self
.live_diff
.map(|e| e.resolve(&defaults.live_diff))
.unwrap_or_else(|| defaults.live_diff.clone()),
keybindings: self
.keybindings
.unwrap_or_else(|| defaults.keybindings.clone()),
Expand Down
45 changes: 45 additions & 0 deletions crates/fresh-editor/tests/e2e/plugins/live_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,51 @@ fn enable_live_diff_globally(harness: &mut EditorTestHarness) {
// Tests
// =============================================================================

/// `live_diff.enabled_on_startup = true` in the user config replaces the
/// usual "open command palette, run Toggle (Global)" handshake — the
/// plugin force-enables the global toggle on init, so a modified file
/// shows diff decorations on first render with no user input. Regression
/// guard for the `enabled_on_startup` config field added in
/// fresh#1950.
#[test]
#[cfg_attr(target_os = "windows", ignore)]
fn test_live_diff_enabled_on_startup_config_renders_without_manual_toggle() {
let repo = GitTestRepo::new();
repo.setup_typical_project();
repo.setup_live_diff_plugin();

let original_dir = repo.change_to_repo_dir();
let _guard = DirGuard::new(original_dir);

// Same diff shape as the "added line" test below, but we never
// invoke `Live Diff: Toggle (Global)`. If the config wiring is
// broken the gutter stays empty and `wait_until` will block until
// the external test timeout fires.
repo.modify_file(
"src/utils.rs",
r#"// brand new top line added by the agent
pub fn format_output(msg: &str) -> String {
format!("[INFO] {}", msg)
}

pub fn validate_config(config: &Config) -> bool {
config.port > 0 && !config.host.is_empty()
}
"#,
);

let mut config = Config::default();
config.live_diff.enabled_on_startup = true;
let mut harness =
EditorTestHarness::with_config_and_working_dir(120, 40, config, repo.path.clone()).unwrap();

open_file(&mut harness, &repo.path, "src/utils.rs");

harness
.wait_until(|h| has_glyph(&h.screen_to_string(), '+'))
.unwrap();
}

/// vs HEAD: an added line shows `+` in the gutter once the file is opened.
/// Live-diff fetches `git show HEAD:<path>` and diffs against the on-disk
/// content (which has one new line vs HEAD), so the new line should be
Expand Down
Loading