From 22c2858b9bb9772fb10e2e986680549dffd968c6 Mon Sep 17 00:00:00 2001 From: Mostafa Ashraf Date: Thu, 25 Dec 2025 16:25:53 +0200 Subject: [PATCH 1/3] feat: Add keyboard enhancement flags, enable Shift+Enter for newlines, and update newline hint based on terminal. --- tui/src/event.rs | 8 +++++++- tui/src/event_loop.rs | 10 ++++++++-- tui/src/services/hint_helper.rs | 9 +++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/tui/src/event.rs b/tui/src/event.rs index bd45e389..ba5059a3 100644 --- a/tui/src/event.rs +++ b/tui/src/event.rs @@ -96,7 +96,13 @@ pub fn map_crossterm_event_to_input_event(event: Event) -> Option { Some(InputEvent::InputBackspace) } } - KeyCode::Enter => Some(InputEvent::InputSubmitted), + KeyCode::Enter => { + if key.modifiers.contains(KeyModifiers::SHIFT) { + Some(InputEvent::InputChangedNewline) + } else { + Some(InputEvent::InputSubmitted) + } + } KeyCode::Esc => Some(InputEvent::HandleEsc), KeyCode::Up => Some(InputEvent::Up), KeyCode::Down => Some(InputEvent::Down), diff --git a/tui/src/event_loop.rs b/tui/src/event_loop.rs index 610ab30a..c6c87f3d 100644 --- a/tui/src/event_loop.rs +++ b/tui/src/event_loop.rs @@ -12,6 +12,7 @@ use crate::services::message::Message; use crate::view::view; use crossterm::event::{ DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, + KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags, }; use crossterm::{execute, terminal::EnterAlternateScreen}; use ratatui::{Terminal, backend::CrosstermBackend}; @@ -61,7 +62,11 @@ pub async fn run_tui( execute!( std::io::stdout(), EnterAlternateScreen, - EnableBracketedPaste + EnableBracketedPaste, + PushKeyboardEnhancementFlags( + KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES + | KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES + ) )?; #[cfg(unix)] @@ -276,7 +281,8 @@ pub async fn run_tui( std::io::stdout(), crossterm::terminal::LeaveAlternateScreen, DisableBracketedPaste, - DisableMouseCapture + DisableMouseCapture, + PopKeyboardEnhancementFlags )?; Ok(()) } diff --git a/tui/src/services/hint_helper.rs b/tui/src/services/hint_helper.rs index 15071209..88e15d3d 100644 --- a/tui/src/services/hint_helper.rs +++ b/tui/src/services/hint_helper.rs @@ -185,8 +185,13 @@ pub fn render_hint_or_shortcuts(f: &mut Frame, state: &AppState, area: Rect) { // detect if terminal is vscode let terminal_info = detect_terminal(); let terminal_name = terminal_info.emulator; - let is_iterm2 = terminal_name == "iTerm2"; - let new_line_hint = if !is_iterm2 { "ctrl+j" } else { "shift+enter" }; + let is_terminal_app_or_vscode = + terminal_name == "VS Code Terminal" || terminal_name == "Terminal.app"; + let new_line_hint = if is_terminal_app_or_vscode { + "ctrl+j" + } else { + "shift+enter" + }; let hint = Paragraph::new(Span::styled( format!( "{} new line | {} | ctrl+o toggle auto-approve", From 58ec424688d80ffc78b8c7943c67d25e1574c493 Mon Sep 17 00:00:00 2001 From: Mostafa Ashraf Date: Mon, 29 Dec 2025 18:33:01 +0200 Subject: [PATCH 2/3] feat: Improve keyboard event reporting by enabling event types and alternate keys. --- tui/src/event_loop.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tui/src/event_loop.rs b/tui/src/event_loop.rs index c6c87f3d..8e98ed91 100644 --- a/tui/src/event_loop.rs +++ b/tui/src/event_loop.rs @@ -65,7 +65,8 @@ pub async fn run_tui( EnableBracketedPaste, PushKeyboardEnhancementFlags( KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES - | KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES + | KeyboardEnhancementFlags::REPORT_EVENT_TYPES + | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS ) )?; From 2bb700202dbf45aedcf8bf7b2746f26a73ad87c5 Mon Sep 17 00:00:00 2001 From: Mostafa Ashraf Date: Mon, 29 Dec 2025 23:16:09 +0200 Subject: [PATCH 3/3] refactor: improve `if let` chaining and conditionally enable keyboard enhancements on Unix. --- Cargo.lock | 2 +- .../file_scratchpad_context_manager.rs | 37 +++++++++---------- tui/src/event_loop.rs | 9 ++++- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9197c63..ad3cdce2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5490,7 +5490,7 @@ dependencies = [ [[package]] name = "stakpak-popup-widget" -version = "0.3.1" +version = "0.3.4" dependencies = [ "crossterm 0.29.0", "ratatui", diff --git a/libs/api/src/local/context_managers/file_scratchpad_context_manager.rs b/libs/api/src/local/context_managers/file_scratchpad_context_manager.rs index d7de187f..8e3e7bed 100644 --- a/libs/api/src/local/context_managers/file_scratchpad_context_manager.rs +++ b/libs/api/src/local/context_managers/file_scratchpad_context_manager.rs @@ -73,17 +73,17 @@ impl FileScratchpadContextManager { } fn load_file(&self, path: &Path) -> String { - fs::read_to_string(&path).unwrap_or_default() + fs::read_to_string(path).unwrap_or_default() } fn resolve_path(&self, base_path: &Path, session_id: Option) -> PathBuf { - if let Some(session_id) = session_id { - if let Some(parent) = base_path.parent() { - // If there's a session ID, put files in a subdirectory named after the session ID - // e.g. .stakpak/session/scratchpad.md -> .stakpak/session//scratchpad.md - let session_dir = parent.join(session_id.to_string()); - return session_dir.join(base_path.file_name().unwrap_or_default()); - } + if let Some(session_id) = session_id + && let Some(parent) = base_path.parent() + { + // If there's a session ID, put files in a subdirectory named after the session ID + // e.g. .stakpak/session/scratchpad.md -> .stakpak/session//scratchpad.md + let session_dir = parent.join(session_id.to_string()); + return session_dir.join(base_path.file_name().unwrap_or_default()); } base_path.to_path_buf() } @@ -197,17 +197,16 @@ impl FileScratchpadContextManager { if let (Some(old_str), Some(new_str)) = ( args.get("old_str").and_then(|s| s.as_str()), args.get("new_str").and_then(|s| s.as_str()), - ) { - if let Some(content) = current_content { - let replace_all = args - .get("replace_all") - .and_then(|b| b.as_bool()) - .unwrap_or(false); - if replace_all { - *content = content.replace(old_str, new_str); - } else { - *content = content.replacen(old_str, new_str, 1); - } + ) && let Some(content) = current_content + { + let replace_all = args + .get("replace_all") + .and_then(|b| b.as_bool()) + .unwrap_or(false); + if replace_all { + *content = content.replace(old_str, new_str); + } else { + *content = content.replacen(old_str, new_str, 1); } } } diff --git a/tui/src/event_loop.rs b/tui/src/event_loop.rs index 18cb5c87..62eee006 100644 --- a/tui/src/event_loop.rs +++ b/tui/src/event_loop.rs @@ -63,6 +63,11 @@ pub async fn run_tui( std::io::stdout(), EnterAlternateScreen, EnableBracketedPaste, + )?; + + #[cfg(unix)] + execute!( + std::io::stdout(), PushKeyboardEnhancementFlags( KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES | KeyboardEnhancementFlags::REPORT_EVENT_TYPES @@ -283,8 +288,10 @@ pub async fn run_tui( crossterm::terminal::LeaveAlternateScreen, DisableBracketedPaste, DisableMouseCapture, - PopKeyboardEnhancementFlags )?; + + #[cfg(unix)] + execute!(std::io::stdout(), PopKeyboardEnhancementFlags)?; Ok(()) }