From 825dfc07222307ab0df753a17c8368887d5653f8 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Mon, 13 Oct 2025 22:23:36 +0200 Subject: [PATCH] =?UTF-8?q?feat(tui):=20add=20Ctrl+=E2=86=91/=E2=86=93=20s?= =?UTF-8?q?hortcuts=20to=20resize=20chat/thinking=20split?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update help UI to show “Ctrl+↑/↓ → resize chat/thinking split”. - Introduce `ensure_ratio_bounds` and `nudge_ratio` on `LayoutNode` to clamp and adjust split ratios. - Ensure vertical split favors the thinking panel when it becomes focused. - Add `adjust_vertical_split` method in `ChatApp` and handle Ctrl+↑/↓ in normal mode to modify the split and update status messages. --- crates/owlen-tui/src/chat_app.rs | 37 +++++++++++++++++++++++++ crates/owlen-tui/src/state/workspace.rs | 25 +++++++++++++++++ crates/owlen-tui/src/ui.rs | 2 ++ 3 files changed, 64 insertions(+) diff --git a/crates/owlen-tui/src/chat_app.rs b/crates/owlen-tui/src/chat_app.rs index 23b9dce..cc915f3 100644 --- a/crates/owlen-tui/src/chat_app.rs +++ b/crates/owlen-tui/src/chat_app.rs @@ -2407,6 +2407,12 @@ impl ChatApp { } else if !order.contains(&self.focused_panel) { self.focused_panel = order[0]; } + if let FocusedPanel::Thinking = self.focused_panel { + // Ensure the vertical split favours thinking panel if chat collapsed entirely + if let Some(tab) = self.workspace_mut().active_tab_mut() { + tab.root.ensure_ratio_bounds(); + } + } } pub fn cycle_focus_forward(&mut self) { @@ -2461,6 +2467,12 @@ impl ChatApp { configure_textarea_defaults(&mut self.textarea); } + pub fn adjust_vertical_split(&mut self, delta: f32) { + if let Some(tab) = self.workspace_mut().active_tab_mut() { + tab.root.nudge_ratio(delta); + } + } + async fn process_slash_submission(&mut self) -> Result { let raw = self.controller.input_buffer().text().to_string(); if raw.trim().is_empty() { @@ -4169,6 +4181,16 @@ impl ChatApp { key.code, KeyCode::Right | KeyCode::Char('l') | KeyCode::Char('L') ); + let is_resize_up = key.modifiers.contains(KeyModifiers::CONTROL) + && matches!( + key.code, + KeyCode::Up | KeyCode::Char('k') | KeyCode::Char('K') + ); + let is_resize_down = key.modifiers.contains(KeyModifiers::CONTROL) + && matches!( + key.code, + KeyCode::Down | KeyCode::Char('j') | KeyCode::Char('J') + ); if is_reveal_active && matches!(self.mode, InputMode::Normal) { self.reveal_active_file(); @@ -4208,6 +4230,21 @@ impl ChatApp { return Ok(AppState::Running); } + if (is_resize_up || is_resize_down) && matches!(self.mode, InputMode::Normal) { + let delta = if is_resize_up { -0.05 } else { 0.05 }; + self.adjust_vertical_split(delta); + self.status = format!( + "Vertical split adjusted ({})", + if delta.is_sign_positive() { + "down" + } else { + "up" + } + ); + self.error = None; + return Ok(AppState::Running); + } + if is_symbol_search_key && matches!(self.mode, InputMode::Normal) { self.set_input_mode(InputMode::SymbolSearch); self.symbol_search.clear_query(); diff --git a/crates/owlen-tui/src/state/workspace.rs b/crates/owlen-tui/src/state/workspace.rs index d2eed3a..670581e 100644 --- a/crates/owlen-tui/src/state/workspace.rs +++ b/crates/owlen-tui/src/state/workspace.rs @@ -85,6 +85,31 @@ pub enum LayoutNode { } impl LayoutNode { + pub fn ensure_ratio_bounds(&mut self) { + match self { + LayoutNode::Split { + ratio, + first, + second, + .. + } => { + *ratio = ratio.clamp(0.1, 0.9); + first.ensure_ratio_bounds(); + second.ensure_ratio_bounds(); + } + LayoutNode::Leaf(_) => {} + } + } + + pub fn nudge_ratio(&mut self, delta: f32) { + match self { + LayoutNode::Split { ratio, .. } => { + *ratio = (*ratio + delta).clamp(0.1, 0.9); + } + LayoutNode::Leaf(_) => {} + } + } + fn replace_leaf(&mut self, target: PaneId, replacement: LayoutNode) -> bool { match self { LayoutNode::Leaf(id) => { diff --git a/crates/owlen-tui/src/ui.rs b/crates/owlen-tui/src/ui.rs index 658218f..7e9f4b5 100644 --- a/crates/owlen-tui/src/ui.rs +++ b/crates/owlen-tui/src/ui.rs @@ -3070,6 +3070,7 @@ fn render_help(frame: &mut Frame<'_>, app: &ChatApp) { Line::from(" :h, :help → open help from command mode"), Line::from(" :files, :explorer → toggle files panel"), Line::from(" Ctrl+←/→ → resize files panel"), + Line::from(" Ctrl+↑/↓ → resize chat/thinking split"), ], 1 => vec![ // Editing @@ -3183,6 +3184,7 @@ fn render_help(frame: &mut Frame<'_>, app: &ChatApp) { Line::from(" F1 or ? → toggle help overlay"), Line::from(" :files, :explorer → toggle files panel"), Line::from(" Ctrl+←/→ → resize files panel"), + Line::from(" Ctrl+↑/↓ → resize chat/thinking split"), Line::from(" :quit → quit application"), Line::from(" Ctrl+C twice → quit application"), Line::from(" :reload → reload configuration and themes"),