Skip to content

Commit 24a96d6

Browse files
authored
Sort tab configs alphabetically by name (#9258)
Closes #9132 Description Tab configs loaded from ~/.warp/tab_configs/ appeared in non-deterministic order in the + menu and the default session mode dropdown in settings. The root cause is WalkDir returning directory entries in filesystem order, which varies across platforms and runs. Added a case-insensitive alphabetical sort by name in load_tab_configs() (app/src/user_config/native.rs), so all consumers receive a stable, predictable ordering without needing per-site sorting. Testing - cargo check -p warp passes with no errors. - No new tests added. The change is a single sort_by call on an existing Vec<TabConfig> — the sorting behavior is deterministic and trivially correct. Manual verification: create multiple .toml files in ~/.warp/tab_configs/ with names like "Zebra", "alpha", "Beta" and confirm they appear as alpha, Beta, Zebra in the + menu. Agent Mode - Warp Agent Mode - This PR was created via Warp's AI Agent Mode Changelog Entries for Stable CHANGELOG-BUG-FIX: Tab configs in the + menu and default session mode dropdown are now sorted alphabetically by name instead of appearing in random order.
1 parent 2d0d88f commit 24a96d6

3 files changed

Lines changed: 69 additions & 0 deletions

File tree

app/src/user_config/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use std::path::PathBuf;
1717
use warp_core::ui::theme::WarpTheme;
1818
use warpui::{Entity, ModelContext, SingletonEntity};
1919

20+
#[cfg(test)]
21+
pub(crate) use imp::load_tab_configs;
2022
#[cfg(feature = "local_fs")]
2123
pub use imp::load_workflows;
2224
pub use imp::{load_launch_configs, load_theme_configs};

app/src/user_config/mod_test.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::*;
22
use std::collections::HashMap;
3+
use std::io::Write;
34
use std::path::Path;
45

56
use crate::launch_configs::launch_config::PaneTemplateType;
@@ -112,3 +113,64 @@ fn test_sanitize_toml_base_name_replaces_spaces_and_dots() {
112113
fn test_sanitize_toml_base_name_falls_back_for_empty_result() {
113114
assert_eq!(sanitize_toml_base_name("..."), "worktree");
114115
}
116+
117+
fn write_tab_config_toml(dir: &Path, file_name: &str, config_name: &str) {
118+
let path = dir.join(file_name);
119+
let mut f = std::fs::File::create(path).unwrap();
120+
write!(f, "name = \"{}\"", config_name).unwrap();
121+
}
122+
123+
#[cfg(feature = "local_fs")]
124+
#[test]
125+
fn test_load_tab_configs_sorts_case_insensitive() {
126+
let dir = tempfile::tempdir().unwrap();
127+
write_tab_config_toml(dir.path(), "zebra.toml", "Zebra");
128+
write_tab_config_toml(dir.path(), "alpha.toml", "alpha");
129+
write_tab_config_toml(dir.path(), "beta.toml", "Beta");
130+
131+
let (configs, errors) = load_tab_configs(dir.path());
132+
133+
assert!(errors.is_empty());
134+
let names: Vec<&str> = configs.iter().map(|c| c.name.as_str()).collect();
135+
assert_eq!(names, vec!["alpha", "Beta", "Zebra"]);
136+
}
137+
138+
#[cfg(feature = "local_fs")]
139+
#[test]
140+
fn test_load_tab_configs_deterministic_tie_breaking() {
141+
let dir = tempfile::tempdir().unwrap();
142+
write_tab_config_toml(dir.path(), "upper.toml", "Alpha");
143+
write_tab_config_toml(dir.path(), "lower.toml", "alpha");
144+
145+
let (configs, errors) = load_tab_configs(dir.path());
146+
147+
assert!(errors.is_empty());
148+
let names: Vec<&str> = configs.iter().map(|c| c.name.as_str()).collect();
149+
assert_eq!(names, vec!["Alpha", "alpha"]);
150+
}
151+
152+
#[cfg(feature = "local_fs")]
153+
#[test]
154+
fn test_load_tab_configs_empty_directory() {
155+
let dir = tempfile::tempdir().unwrap();
156+
157+
let (configs, errors) = load_tab_configs(dir.path());
158+
159+
assert!(configs.is_empty());
160+
assert!(errors.is_empty());
161+
}
162+
163+
#[cfg(feature = "local_fs")]
164+
#[test]
165+
fn test_load_tab_configs_skips_non_toml_files() {
166+
let dir = tempfile::tempdir().unwrap();
167+
write_tab_config_toml(dir.path(), "real.toml", "Real");
168+
std::fs::write(dir.path().join("readme.md"), "not a config").unwrap();
169+
std::fs::write(dir.path().join("data.json"), "{}").unwrap();
170+
171+
let (configs, errors) = load_tab_configs(dir.path());
172+
173+
assert!(errors.is_empty());
174+
assert_eq!(configs.len(), 1);
175+
assert_eq!(configs[0].name, "Real");
176+
}

app/src/user_config/native.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ pub fn load_tab_configs(tab_config_path: &Path) -> (Vec<TabConfig>, Vec<TabConfi
204204
Err(error) => errors.push(error),
205205
}
206206
}
207+
configs.sort_by(|a, b| {
208+
let a_name = a.name.to_lowercase();
209+
let b_name = b.name.to_lowercase();
210+
a_name.cmp(&b_name).then_with(|| a.name.cmp(&b.name))
211+
});
207212
(configs, errors)
208213
}
209214

0 commit comments

Comments
 (0)