From db08840b6425879a2952a04f5671ae76e1d75e43 Mon Sep 17 00:00:00 2001 From: cinco euzebio Date: Sun, 1 Mar 2026 05:07:00 -0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20ask=20for=20layout=20in?= =?UTF-8?q?=20interactive=20wizard=20when=20window=20has=20multiple=20pane?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `render_layout_prompt` and `parse_layout_input` to ui.rs so that the first-run wizard asks the user to choose a tmux layout (1–5 or by name) for each window that has 2 or more panes. Previously, layout was always silently set to None. Also update `render_config_created` to display the chosen layout in the post-setup summary. Closes: layout never being set during interactive setup --- src/config.rs | 23 ++++++++++- src/ui.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index a6a2eac..2aa01ae 100644 --- a/src/config.rs +++ b/src/config.rs @@ -181,14 +181,19 @@ impl Config { window_names }; - // Configure panes for each window + // Configure panes and layout for each window let mut windows = Vec::new(); for name in names { let panes = Self::prompt_for_panes(&name)?; + let layout = if panes.len() > 1 { + ui::render_layout_prompt(&name, panes.len())? + } else { + None + }; windows.push(crate::session::Window { name, panes, - layout: None, + layout, }); } @@ -348,6 +353,20 @@ mod tests { assert_eq!(result, vec!["editor", "terminal", "server"]); } + #[test] + fn should_use_ui_parse_functions_for_layout() { + assert_eq!(ui::parse_layout_input(""), None); + assert_eq!( + ui::parse_layout_input("1"), + Some("main-horizontal".to_string()) + ); + assert_eq!( + ui::parse_layout_input("main-vertical"), + Some("main-vertical".to_string()) + ); + assert_eq!(ui::parse_layout_input("invalid"), None); + } + #[test] fn should_parse_config_with_windows_and_panes() { let toml_str = r#" diff --git a/src/ui.rs b/src/ui.rs index f36828a..c4f1a48 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -133,6 +133,12 @@ pub fn render_config_created( println!("{}", label_style.render(" 🪟 Default Windows:")); for window in windows { println!(" {}", window_style.render(&format!("◦ {}", window.name))); + if let Some(layout) = &window.layout { + println!( + "{}", + info_style.render(&format!(" └─ layout: {}", layout)) + ); + } if !window.panes.is_empty() { for (i, pane) in window.panes.iter().enumerate() { let pane_display = if pane.is_empty() { @@ -299,6 +305,67 @@ pub fn render_panes_prompt(window_name: &str) -> Result { Ok(input.trim().to_string()) } +/// Renders a prompt for the layout of a window with multiple panes +pub fn render_layout_prompt(window_name: &str, pane_count: usize) -> Result> { + let prompt_style = Style::new().bold(true).foreground(color_green()); + let hint_style = Style::new().italic(true).foreground(color_dark_gray()); + let window_style = Style::new().bold(true).foreground(color_purple()); + let label_style = Style::new().foreground(color_blue()); + + println!(); + println!( + " Layout for window {} ({} panes):", + window_style.render(window_name), + label_style.render(&pane_count.to_string()) + ); + println!( + "{}", + hint_style.render(" Choose a pane layout (leave empty for no layout):") + ); + println!( + "{}", + hint_style.render(" 1. main-horizontal — main pane on top, others below") + ); + println!( + "{}", + hint_style.render(" 2. main-vertical — main pane on left, others on right") + ); + println!( + "{}", + hint_style.render(" 3. tiled — all panes tiled equally") + ); + println!( + "{}", + hint_style.render(" 4. even-horizontal — all panes side by side") + ); + println!( + "{}", + hint_style.render(" 5. even-vertical — all panes stacked vertically") + ); + print!(" {} ", prompt_style.render("❯ Layout (1-5 or name):")); + io::stdout().flush().context("Failed to flush stdout")?; + + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .context("Failed to read input")?; + + Ok(parse_layout_input(input.trim())) +} + +/// Parse layout input: accepts number (1-5) or layout name; returns None for empty/invalid +pub fn parse_layout_input(input: &str) -> Option { + match input.trim() { + "" => None, + "1" | "main-horizontal" => Some("main-horizontal".to_string()), + "2" | "main-vertical" => Some("main-vertical".to_string()), + "3" | "tiled" => Some("tiled".to_string()), + "4" | "even-horizontal" => Some("even-horizontal".to_string()), + "5" | "even-vertical" => Some("even-vertical".to_string()), + _ => None, + } +} + /// Renders a prompt for a pane command pub fn render_pane_command_prompt(pane_name: &str) -> Result { let prompt_style = Style::new().bold(true).foreground(color_green()); @@ -531,6 +598,50 @@ mod tests { render_config_created(&vec!["~/Projects".to_string()], 5, true, 24, &windows); } + #[test] + fn should_return_none_for_empty_layout_input() { + assert_eq!(parse_layout_input(""), None); + assert_eq!(parse_layout_input(" "), None); + } + + #[test] + fn should_parse_layout_by_number() { + assert_eq!(parse_layout_input("1"), Some("main-horizontal".to_string())); + assert_eq!(parse_layout_input("2"), Some("main-vertical".to_string())); + assert_eq!(parse_layout_input("3"), Some("tiled".to_string())); + assert_eq!(parse_layout_input("4"), Some("even-horizontal".to_string())); + assert_eq!(parse_layout_input("5"), Some("even-vertical".to_string())); + } + + #[test] + fn should_parse_layout_by_name() { + assert_eq!( + parse_layout_input("main-horizontal"), + Some("main-horizontal".to_string()) + ); + assert_eq!( + parse_layout_input("main-vertical"), + Some("main-vertical".to_string()) + ); + assert_eq!(parse_layout_input("tiled"), Some("tiled".to_string())); + assert_eq!( + parse_layout_input("even-horizontal"), + Some("even-horizontal".to_string()) + ); + assert_eq!( + parse_layout_input("even-vertical"), + Some("even-vertical".to_string()) + ); + } + + #[test] + fn should_return_none_for_invalid_layout_input() { + assert_eq!(parse_layout_input("6"), None); + assert_eq!(parse_layout_input("0"), None); + assert_eq!(parse_layout_input("unknown"), None); + assert_eq!(parse_layout_input("horizontal"), None); + } + #[test] fn render_config_created_with_disabled_cache_should_not_panic() { let windows = vec![Window {