diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 1babdf37d..04dee7cc4 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -775,10 +775,16 @@ impl UiState { crate::edit_toolbar::sanitize(&mut self.toolbar_edits); self.docking = self.docking.filter(crate::panel_docking::valid); self.docking_icons.retain(|id| ICON_PANELS.iter().any(|entry| entry.0 == id)); - // Changes kept for workspaces that still exist, one per workspace (a hand-edited file). + // Changes kept for workspaces that still exist, under their saved spelling and one per + // workspace whatever the case (a hand-edited file). let mut seen = std::collections::BTreeSet::new(); - let custom: Vec = self.custom_workspaces.iter().map(|w| w.name.clone()).collect(); - self.workspace_changes.retain(|w| (crate::workspaces::is_builtin(&w.name) || custom.contains(&w.name)) && seen.insert(w.name.clone())); + let saved: Vec = + self.custom_workspaces.iter().map(|w| w.name.clone()).chain(crate::workspaces::builtins().into_iter().map(|w| w.name)).collect(); + self.workspace_changes.retain_mut(|w| { + let Some(name) = saved.iter().find(|n| n.eq_ignore_ascii_case(&w.name)) else { return false }; + w.name = name.clone(); + seen.insert(name.to_ascii_lowercase()) + }); self.workspace_changes.truncate(64); // Overrides that can't fire (modifier-only chords recorded by older versions, #487) give // the default back. diff --git a/crates/ui-egui/src/workspaces.rs b/crates/ui-egui/src/workspaces.rs index 1d9fa4d19..eec49ed45 100644 --- a/crates/ui-egui/src/workspaces.rs +++ b/crates/ui-egui/src/workspaces.rs @@ -216,12 +216,10 @@ pub fn apply(ui: &mut UiState, w: &Workspace) { /// Keep the layout of the current workspace as it is now, so switching back brings it back. fn remember(ui: &mut UiState) { - let current = ui.workspace.clone(); - if find_saved(ui, ¤t).is_none() { - return; - } - let w = capture(ui, ¤t); - ui.workspace_changes.retain(|x| x.name != current); + // Kept under the saved workspace's spelling, which `find_saved` matches whatever the case. + let Some(saved) = find_saved(ui, &ui.workspace.clone()) else { return }; + let w = capture(ui, &saved.name); + ui.workspace_changes.retain(|x| !x.name.eq_ignore_ascii_case(&saved.name)); ui.workspace_changes.push(w); // Over the cap, the oldest go: the layout just kept is the newest. let over = ui.workspace_changes.len().saturating_sub(64); @@ -232,7 +230,7 @@ fn remember(ui: &mut UiState) { pub fn switch(ui: &mut UiState, name: &str) -> Result { let saved = find_saved(ui, name).ok_or_else(|| format!("unknown workspace (one of {})", names(ui).join(", ")))?; remember(ui); - let w = ui.workspace_changes.iter().find(|w| w.name == saved.name).cloned().unwrap_or(saved); + let w = ui.workspace_changes.iter().find(|w| w.name.eq_ignore_ascii_case(&saved.name)).cloned().unwrap_or(saved); apply(ui, &w); Ok(w.name) } @@ -240,7 +238,7 @@ pub fn switch(ui: &mut UiState, name: &str) -> Result { /// Reset the current workspace to the way it was saved, dropping its changes. pub fn reset(ui: &mut UiState) -> String { let w = find_saved(ui, &ui.workspace.clone()).unwrap_or_default(); - ui.workspace_changes.retain(|x| x.name != w.name); + ui.workspace_changes.retain(|x| !x.name.eq_ignore_ascii_case(&w.name)); apply(ui, &w); w.name } @@ -275,12 +273,13 @@ pub fn save_as(ui: &mut UiState, name: &str) -> Result<(), String> { } // The changes go into the workspace saved, which starts with none; the one left keeps the // changes it had (none of these). + // A user workspace of that name in another case is replaced, as `find_saved` would find it. let w = capture(ui, name); - match ui.custom_workspaces.iter_mut().find(|x| x.name == name) { + match ui.custom_workspaces.iter_mut().find(|x| x.name.eq_ignore_ascii_case(name)) { Some(x) => *x = w, None => ui.custom_workspaces.push(w), } - ui.workspace_changes.retain(|x| x.name != name); + ui.workspace_changes.retain(|x| !x.name.eq_ignore_ascii_case(name)); ui.workspace = name.into(); Ok(()) } @@ -626,6 +625,31 @@ mod tests { assert!(ui.control_bar, "the layout just left is kept"); } + /// Workspace names match whatever their case, as `find` does: "Mine" saved over "mine" replaces + /// it, and changes made under one spelling come back under the other. + #[test] + fn workspace_names_match_whatever_their_case() { + let mut ui = UiState::default(); + save_as(&mut ui, "mine").unwrap(); + save_as(&mut ui, "Mine").unwrap(); + assert_eq!(ui.custom_workspaces.len(), 1, "one workspace, not two spellings"); + ui.control_bar = true; + switch(&mut ui, ESSENTIALS).unwrap(); + assert_eq!(switch(&mut ui, "MINE").unwrap(), "Mine"); + assert!(ui.control_bar, "its changes came back"); + // A preferences file with a remembered layout under another spelling, twice: it maps to the + // saved one, once. + ui.control_bar = false; + switch(&mut ui, ESSENTIALS).unwrap(); + let mut odd = capture(&ui, "MINE"); + odd.control_bar = true; + ui.workspace_changes = vec![odd.clone(), Workspace { name: "mine".into(), ..odd }]; + let mut ui = ui.sanitized(); + assert_eq!(ui.workspace_changes.iter().map(|w| w.name.as_str()).collect::>(), ["Mine"]); + switch(&mut ui, "Mine").unwrap(); + assert!(ui.control_bar); + } + /// Saving a workspace takes the current layout into it, which then starts with no changes. #[test] fn saving_a_workspace_moves_the_changes_into_it() {