aboutsummaryrefslogtreecommitdiff
path: root/src/app/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/mod.rs')
-rw-r--r--src/app/mod.rs187
1 files changed, 172 insertions, 15 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 7391d7d3..a77f2536 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -13,6 +13,7 @@ mod config_io;
mod creation;
mod ids;
mod input;
+mod popup;
mod runtime;
mod runtime_mutations;
mod session;
@@ -659,6 +660,7 @@ impl App {
pane_graphics_layers: std::collections::HashMap::new(),
pane_graphics_streams: std::collections::HashMap::new(),
pane_graphics_revision: 0,
+ popup_pane: None,
plugin_command_logs: Vec::new(),
next_plugin_command_log_id: 1,
plugin_commands_in_flight: 0,
@@ -1577,7 +1579,8 @@ impl App {
let key_id = repeat_key_identity(&key);
match key.kind {
crossterm::event::KeyEventKind::Press => {
- if self.state.mode == Mode::Terminal {
+ if self.state.popup_pane.is_some() || self.state.mode == Mode::Terminal
+ {
self.suppressed_repeat_keys.remove(&key_id);
self.handle_terminal_key_headless(key);
} else {
@@ -1586,7 +1589,8 @@ impl App {
}
}
crossterm::event::KeyEventKind::Repeat => {
- if self.state.mode == Mode::Terminal
+ if (self.state.popup_pane.is_some()
+ || self.state.mode == Mode::Terminal)
&& !self.suppressed_repeat_keys.contains(&key_id)
{
self.handle_terminal_key_headless(key);
@@ -1600,7 +1604,7 @@ impl App {
}
}
crate::raw_input::RawInputEvent::Mouse(mouse) => {
- if self.state.mouse_capture {
+ if self.state.popup_pane.is_some() || self.state.mouse_capture {
self.handle_mouse_event_headless(mouse);
} else {
self.state
@@ -1608,7 +1612,8 @@ impl App {
}
}
crate::raw_input::RawInputEvent::Paste(text) => {
- if self.state.mode != Mode::Terminal {
+ if self.try_route_paste_to_popup(&text) {
+ } else if self.state.mode != Mode::Terminal {
self.paste_into_active_text_input(&text);
} else {
if let Some(ws_idx) = self.state.active {
@@ -1619,17 +1624,7 @@ impl App {
ws_idx,
focused,
) {
- let _ = runtime.try_send_bytes(bytes::Bytes::from(
- if runtime
- .input_state()
- .map(|s| s.bracketed_paste)
- .unwrap_or(false)
- {
- format!("\x1b[200~{text}\x1b[201~")
- } else {
- text
- },
- ));
+ let _ = runtime.try_send_paste(text);
}
}
}
@@ -4918,6 +4913,168 @@ last_pane = "prefix+tab"
);
}
+ #[tokio::test]
+ async fn route_client_events_pastes_only_into_popup() {
+ let mut app = test_app();
+ let mut workspace = Workspace::test_new("tiled");
+ let focused = workspace.focused_pane_id().unwrap();
+ let (tiled_runtime, mut tiled_rx) = TerminalRuntime::test_with_channel(80, 24);
+ workspace.tabs[0].runtimes.insert(focused, tiled_runtime);
+ app.state.workspaces = vec![workspace];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.mode = Mode::Terminal;
+
+ let (popup_runtime, mut popup_rx) = TerminalRuntime::test_with_channel(40, 12);
+ app.install_test_popup_runtime(popup_runtime);
+ assert!(app
+ .state
+ .should_capture_host_mouse_from(&app.terminal_runtimes));
+
+ app.route_client_events(
+ vec![crate::raw_input::RawInputEvent::Paste("popup-only".into())],
+ true,
+ );
+
+ assert_eq!(
+ popup_rx.try_recv().unwrap(),
+ bytes::Bytes::from_static(b"popup-only")
+ );
+ assert!(tiled_rx.try_recv().is_err());
+
+ app.route_client_events(
+ vec![raw_key(
+ KeyCode::Char('x'),
+ KeyModifiers::NONE,
+ KeyEventKind::Press,
+ )],
+ true,
+ );
+ assert_eq!(
+ popup_rx.try_recv().unwrap(),
+ bytes::Bytes::from_static(b"x")
+ );
+ assert!(tiled_rx.try_recv().is_err());
+
+ app.state.mode = Mode::Settings;
+ assert!(
+ app.handle_raw_input_event(raw_key(
+ KeyCode::Char('y'),
+ KeyModifiers::NONE,
+ KeyEventKind::Repeat,
+ ))
+ .await
+ );
+ assert_eq!(
+ popup_rx.try_recv().unwrap(),
+ bytes::Bytes::from_static(b"y")
+ );
+ assert!(tiled_rx.try_recv().is_err());
+ }
+
+ #[tokio::test]
+ async fn route_client_events_discards_paste_when_popup_runtime_is_missing() {
+ let mut app = test_app();
+ let mut workspace = Workspace::test_new("tiled");
+ let focused = workspace.focused_pane_id().unwrap();
+ let (tiled_runtime, mut tiled_rx) = TerminalRuntime::test_with_channel(80, 24);
+ workspace.tabs[0].runtimes.insert(focused, tiled_runtime);
+ app.state.workspaces = vec![workspace];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.mode = Mode::Terminal;
+ let install_missing_popup = |app: &mut App| {
+ let popup_terminal_id = crate::terminal::TerminalId::alloc();
+ app.state.terminals.insert(
+ popup_terminal_id.clone(),
+ crate::terminal::TerminalState::new(
+ popup_terminal_id.clone(),
+ std::path::PathBuf::from("/popup"),
+ ),
+ );
+ app.state.popup_pane = Some(state::PopupPaneState {
+ pane_id: crate::layout::PaneId::alloc(),
+ terminal_id: popup_terminal_id,
+ width: None,
+ height: None,
+ });
+ };
+ install_missing_popup(&mut app);
+
+ app.route_client_events(
+ vec![crate::raw_input::RawInputEvent::Paste("discard-me".into())],
+ true,
+ );
+
+ assert!(tiled_rx.try_recv().is_err());
+ assert!(app.state.popup_pane.is_none());
+
+ install_missing_popup(&mut app);
+ assert!(
+ app.handle_raw_input_event(crate::raw_input::RawInputEvent::Paste(
+ "discard-monolithic".into(),
+ ))
+ .await
+ );
+ assert!(tiled_rx.try_recv().is_err());
+ assert!(app.state.popup_pane.is_none());
+ }
+
+ #[tokio::test]
+ async fn route_client_events_routes_popup_mouse_when_global_capture_is_disabled() {
+ let mut app = test_app();
+ let mut workspace = Workspace::test_new("tiled");
+ let focused = workspace.focused_pane_id().unwrap();
+ let (tiled_runtime, mut tiled_rx) = TerminalRuntime::test_with_channel(80, 24);
+ tiled_runtime.test_process_pty_bytes(b"\x1b[?1000h\x1b[?1006h");
+ workspace.tabs[0].runtimes.insert(focused, tiled_runtime);
+ app.state.workspaces = vec![workspace];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.mode = Mode::Terminal;
+ app.state.mouse_capture = false;
+ app.state.view.terminal_area = ratatui::layout::Rect::new(0, 0, 80, 24);
+
+ let (popup_runtime, mut popup_rx) = TerminalRuntime::test_with_channel(40, 12);
+ popup_runtime.test_process_pty_bytes(b"\x1b[?1000h\x1b[?1006h");
+ app.install_test_popup_runtime(popup_runtime);
+ let (_, inner) =
+ crate::ui::popup_pane_rects(&app.state, app.state.view.terminal_area).unwrap();
+
+ app.route_client_events(
+ vec![crate::raw_input::RawInputEvent::Mouse(
+ crossterm::event::MouseEvent {
+ kind: crossterm::event::MouseEventKind::Down(
+ crossterm::event::MouseButton::Left,
+ ),
+ column: inner.x,
+ row: inner.y,
+ modifiers: crossterm::event::KeyModifiers::NONE,
+ },
+ )],
+ true,
+ );
+
+ assert!(popup_rx.try_recv().is_ok());
+ assert!(tiled_rx.try_recv().is_err());
+
+ assert!(
+ app.handle_raw_input_event(crate::raw_input::RawInputEvent::Mouse(
+ crossterm::event::MouseEvent {
+ kind: crossterm::event::MouseEventKind::Down(
+ crossterm::event::MouseButton::Left,
+ ),
+ column: inner.x + 1,
+ row: inner.y,
+ modifiers: crossterm::event::KeyModifiers::NONE,
+ },
+ ))
+ .await
+ );
+ assert!(popup_rx.try_recv().is_ok());
+ assert!(tiled_rx.try_recv().is_err());
+ }
+
#[test]
fn route_client_input_closes_release_notes_modal() {
let mut app = test_app();