diff --git a/src/config.rs b/src/config.rs index 0240671..c6dddd5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -56,11 +56,13 @@ pub struct Config { } impl Config { - /// Discover launchable deployments on disk under `deployments_base`, one - /// entry per (deployment, virtualenv) pair, labelled `"/"`. + /// Discover launchable deployments on disk under `deployments_base`. /// /// These are the "custom" deployments that are present on the machine but - /// not named in the config. Entry points use the configured defaults. + /// not named in the config; entry points use the configured defaults. Each + /// is labelled by its deployment name, with the virtualenv kept inside the + /// entry — only when a deployment has more than one virtualenv is the venv + /// name appended (`"/"`) to keep the labels unique. pub fn discovered_entries(&self) -> Vec<(String, LauncherEntry)> { let mut found = Vec::new(); let Ok(dirs) = fs::read_dir(&self.deployments_base) else { @@ -74,13 +76,21 @@ impl Config { let dir = deployment.path(); let name = deployment.file_name().to_string_lossy().into_owned(); - for venv in detect_venvs(&dir).unwrap_or_default() { - let venv_name = venv - .file_name() - .map(|n| n.to_string_lossy().into_owned()) - .unwrap_or_default(); + let venvs = detect_venvs(&dir).unwrap_or_default(); + let disambiguate = venvs.len() > 1; + + for venv in venvs { + let label = if disambiguate { + let venv_name = venv + .file_name() + .map(|n| n.to_string_lossy().into_owned()) + .unwrap_or_default(); + format!("{name}/{venv_name}") + } else { + name.clone() + }; found.push(( - format!("{name}/{venv_name}"), + label, LauncherEntry { python: venv.join("bin/python"), gui_file: join_relative(&dir, &self.default_gui_file), diff --git a/src/main.rs b/src/main.rs index d8151fe..baf6546 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,7 +83,6 @@ fn main() -> Result<()> { let mut config = load_config(&json)?; check_base_exists("deployments_base", &config.deployments_base, &config_path)?; - check_base_exists("venvs_base", &config.deployments_base, &config_path)?; // Populate the config with deployments found on disk so they are available // to both `lookup_entry` and the TUI. Configured entries take precedence. diff --git a/src/tui/entries.rs b/src/tui/entries.rs index 46c92c6..b28f294 100644 --- a/src/tui/entries.rs +++ b/src/tui/entries.rs @@ -27,7 +27,10 @@ pub struct EntriesTab { } impl EntriesTab { - pub fn new(entries: Vec<(String, LauncherEntry)>) -> Self { + pub fn new(mut entries: Vec<(String, LauncherEntry)>) -> Self { + // Production deployments first, then the rest, alphabetical within each. + entries.sort_by(|a, b| sort_key(&a.0).cmp(&sort_key(&b.0))); + let mut list_state = ListState::default(); if !entries.is_empty() { list_state.select(Some(0)); @@ -117,7 +120,7 @@ impl EntriesTab { let items: Vec = self .entries .iter() - .map(|(label, _)| ListItem::new(display_label(label))) + .map(|(label, _)| ListItem::new(label.as_str())) .collect(); let list = List::new(items) .block(Block::default().borders(Borders::ALL).title("Entries")) @@ -168,10 +171,12 @@ impl EntriesTab { } } -/// The label shown in the list: the default `/.venv` virtualenv suffix (used by -/// discovered deployments) is dropped for readability. -fn display_label(label: &str) -> &str { - label.strip_suffix("/.venv").unwrap_or(label) +/// Production deployment labels, pinned to the top of the list. +const PRODUCTION: [&str; 3] = ["x06da", "x06sa", "x10sa"]; + +/// Sort key placing production deployments first, then alphabetical by label. +fn sort_key(label: &str) -> (bool, &str) { + (!PRODUCTION.contains(&label), label) } /// A rectangle centered within `area`, sized as a percentage of it. @@ -187,20 +192,17 @@ fn centered_rect(width_pct: u16, height_pct: u16, area: Rect) -> Rect { #[cfg(test)] mod tests { - use super::display_label; + use super::sort_key; #[test] - fn strips_default_venv_suffix() { - assert_eq!(display_label("mydeploy/.venv"), "mydeploy"); + fn production_sorts_before_others() { + // Production wins even though its label sorts later alphabetically. + assert!(sort_key("x10sa") < sort_key("aaa")); } #[test] - fn keeps_named_venv() { - assert_eq!(display_label("mydeploy/pxii"), "mydeploy/pxii"); - } - - #[test] - fn leaves_plain_labels_untouched() { - assert_eq!(display_label("x10sa"), "x10sa"); + fn production_ordered_alphabetically() { + assert!(sort_key("x06da") < sort_key("x06sa")); + assert!(sort_key("x06sa") < sort_key("x10sa")); } }