diff --git a/mux/src/localpane.rs b/mux/src/localpane.rs index cc4586574..47365e405 100644 --- a/mux/src/localpane.rs +++ b/mux/src/localpane.rs @@ -22,14 +22,15 @@ use std::io::{Result as IoResult, Write}; use std::ops::Range; use std::sync::Arc; use std::time::{Duration, Instant}; -use termwiz::escape::DeviceControlMode; +use termwiz::escape::csi::{Sgr, CSI}; +use termwiz::escape::{Action, ControlCode, DeviceControlMode}; use termwiz::input::KeyboardEncoding; -use termwiz::surface::{Line, SequenceNo, SEQ_ZERO}; +use termwiz::surface::{Line, SequenceNo}; use url::Url; use wezterm_term::color::ColorPalette; use wezterm_term::{ - Alert, AlertHandler, CellAttributes, Clipboard, DownloadHandler, KeyCode, KeyModifiers, - MouseEvent, SemanticZone, StableRowIndex, Terminal, TerminalConfiguration, TerminalSize, + Alert, AlertHandler, Clipboard, DownloadHandler, KeyCode, KeyModifiers, MouseEvent, + SemanticZone, StableRowIndex, Terminal, TerminalConfiguration, TerminalSize, }; #[derive(Debug)] @@ -112,28 +113,11 @@ impl Pane for LocalPane { } fn get_lines(&self, lines: Range) -> (StableRowIndex, Vec) { - let (first, mut lines) = terminal_get_lines(&mut self.terminal.borrow_mut(), lines); - - if self.tmux_domain.borrow().is_some() { - let cursor = terminal_get_cursor_position(&mut self.terminal.borrow_mut()); - let idx = cursor.y as isize - first as isize; - if idx > 0 { - if let Some(line) = lines.get_mut(idx as usize) { - line.overlay_text_with_attribute( - 0, - "This pane is running tmux control mode. Press q to detach.", - CellAttributes::default(), - SEQ_ZERO, - ); - } - } - } - - (first, lines) + crate::pane::impl_get_lines_via_with_lines(self, lines) } fn get_logical_lines(&self, lines: Range) -> Vec { - terminal_get_logical_lines(&mut self.terminal.borrow_mut(), lines) + crate::pane::impl_get_logical_lines_via_get_lines(self, lines) } fn get_dimensions(&self) -> RenderableDimensions { @@ -681,6 +665,27 @@ struct LocalPaneDCSHandler { tmux_domain: Option>, } +fn emit_output_for_pane(pane_id: PaneId, message: &str) { + let mut actions = vec![ + Action::CSI(CSI::Sgr(Sgr::Reset)), + Action::Control(ControlCode::CarriageReturn), + Action::Control(ControlCode::LineFeed), + ]; + for c in message.chars() { + actions.push(Action::Print(c)); + } + actions.push(Action::Control(ControlCode::CarriageReturn)); + actions.push(Action::Control(ControlCode::LineFeed)); + + promise::spawn::spawn_into_main_thread(async move { + let mux = Mux::get().unwrap(); + if let Some(pane) = mux.get_pane(pane_id) { + pane.perform_actions(actions); + } + }) + .detach(); +} + impl wezterm_term::DeviceControlHandler for LocalPaneDCSHandler { fn handle_device_control(&mut self, control: termwiz::escape::DeviceControlMode) { match control { @@ -705,6 +710,11 @@ impl wezterm_term::DeviceControlHandler for LocalPaneDCSHandler { pane.tmux_domain .borrow_mut() .replace(Arc::clone(&tmux_domain)); + + emit_output_for_pane( + self.pane_id, + "[This pane is running tmux control mode. Press q to detach]", + ); } self.tmux_domain.replace(tmux_domain); diff --git a/mux/src/pane.rs b/mux/src/pane.rs index 2f6cd67fa..084c9cfff 100644 --- a/mux/src/pane.rs +++ b/mux/src/pane.rs @@ -224,6 +224,7 @@ pub trait Pane: Downcast { /// Because of this, we also return the adjusted StableRowIndex for /// the first row in the range. fn get_lines(&self, lines: Range) -> (StableRowIndex, Vec); + fn with_lines_mut(&self, lines: Range, with_lines: &mut dyn WithPaneLines); fn for_each_logical_line_in_stable_range_mut( @@ -232,88 +233,7 @@ pub trait Pane: Downcast { for_line: &mut dyn ForEachPaneLogicalLine, ); - fn get_logical_lines(&self, lines: Range) -> Vec { - // NOTE: see terminal_get_logical_lines() for the implementation that is - // actually used by most panes; this particular method is the fallback - // implementation for other Panes. - let (mut first, mut phys) = self.get_lines(lines); - - // Avoid pathological cases where we have eg: a really long logical line - // (such as 1.5MB of json) that we previously wrapped. We don't want to - // un-wrap, scan, and re-wrap that thing. - // This is an imperfect length constraint to partially manage the cost. - const MAX_LOGICAL_LINE_LEN: usize = 1024; - let mut back_len = 0; - - // Look backwards to find the start of the first logical line - while first > 0 { - let (prior, back) = self.get_lines(first - 1..first); - if prior == first { - break; - } - if !back[0].last_cell_was_wrapped() { - break; - } - if back[0].len() + back_len > MAX_LOGICAL_LINE_LEN { - break; - } - back_len += back[0].len(); - first = prior; - for (idx, line) in back.into_iter().enumerate() { - phys.insert(idx, line); - } - } - - // Look forwards to find the end of the last logical line - while let Some(last) = phys.last() { - if !last.last_cell_was_wrapped() { - break; - } - if last.len() > MAX_LOGICAL_LINE_LEN { - break; - } - - let next_row = first + phys.len() as StableRowIndex; - let (last_row, mut ahead) = self.get_lines(next_row..next_row + 1); - if last_row != next_row { - break; - } - phys.append(&mut ahead); - } - - // Now process this stuff into logical lines - let mut lines = vec![]; - for (idx, line) in phys.into_iter().enumerate() { - match lines.last_mut() { - None => { - let logical = line.clone(); - lines.push(LogicalLine { - physical_lines: vec![line], - logical, - first_row: first + idx as StableRowIndex, - }); - } - Some(prior) => { - if prior.logical.last_cell_was_wrapped() - && prior.logical.len() <= MAX_LOGICAL_LINE_LEN - { - let seqno = prior.logical.current_seqno().max(line.current_seqno()); - prior.logical.set_last_cell_was_wrapped(false, seqno); - prior.logical.append_line(line.clone(), seqno); - prior.physical_lines.push(line); - } else { - let logical = line.clone(); - lines.push(LogicalLine { - physical_lines: vec![line], - logical, - first_row: first + idx as StableRowIndex, - }); - } - } - } - } - lines - } + fn get_logical_lines(&self, lines: Range) -> Vec; fn apply_hyperlinks(&self, lines: Range, rules: &[Rule]) { struct ApplyHyperLinks<'a> { @@ -495,6 +415,150 @@ pub trait ForEachPaneLogicalLine { ) -> bool; } +pub fn impl_with_lines_via_get_lines( + pane: &P, + lines: Range, + with_lines: &mut dyn WithPaneLines, +) { + let (first, mut lines) = pane.get_lines(lines); + let mut line_refs = vec![]; + for line in lines.iter_mut() { + line_refs.push(line); + } + with_lines.with_lines_mut(first, &mut line_refs); +} + +pub fn impl_for_each_logical_line_via_get_logical_lines( + pane: &P, + lines: Range, + for_line: &mut dyn ForEachPaneLogicalLine, +) { + let mut logical = pane.get_logical_lines(lines); + + for line in &mut logical { + let num_lines = line.physical_lines.len() as StableRowIndex; + let mut line_refs = vec![]; + for phys in line.physical_lines.iter_mut() { + line_refs.push(phys); + } + let should_continue = for_line + .with_logical_line_mut(line.first_row..line.first_row + num_lines, &mut line_refs); + if !should_continue { + break; + } + } +} + +pub fn impl_get_logical_lines_via_get_lines( + pane: &P, + lines: Range, +) -> Vec { + let (mut first, mut phys) = pane.get_lines(lines); + + // Avoid pathological cases where we have eg: a really long logical line + // (such as 1.5MB of json) that we previously wrapped. We don't want to + // un-wrap, scan, and re-wrap that thing. + // This is an imperfect length constraint to partially manage the cost. + const MAX_LOGICAL_LINE_LEN: usize = 1024; + let mut back_len = 0; + + // Look backwards to find the start of the first logical line + while first > 0 { + let (prior, back) = pane.get_lines(first - 1..first); + if prior == first { + break; + } + if !back[0].last_cell_was_wrapped() { + break; + } + if back[0].len() + back_len > MAX_LOGICAL_LINE_LEN { + break; + } + back_len += back[0].len(); + first = prior; + for (idx, line) in back.into_iter().enumerate() { + phys.insert(idx, line); + } + } + + // Look forwards to find the end of the last logical line + while let Some(last) = phys.last() { + if !last.last_cell_was_wrapped() { + break; + } + if last.len() > MAX_LOGICAL_LINE_LEN { + break; + } + + let next_row = first + phys.len() as StableRowIndex; + let (last_row, mut ahead) = pane.get_lines(next_row..next_row + 1); + if last_row != next_row { + break; + } + phys.append(&mut ahead); + } + + // Now process this stuff into logical lines + let mut lines = vec![]; + for (idx, line) in phys.into_iter().enumerate() { + match lines.last_mut() { + None => { + let logical = line.clone(); + lines.push(LogicalLine { + physical_lines: vec![line], + logical, + first_row: first + idx as StableRowIndex, + }); + } + Some(prior) => { + if prior.logical.last_cell_was_wrapped() + && prior.logical.len() <= MAX_LOGICAL_LINE_LEN + { + let seqno = prior.logical.current_seqno().max(line.current_seqno()); + prior.logical.set_last_cell_was_wrapped(false, seqno); + prior.logical.append_line(line.clone(), seqno); + prior.physical_lines.push(line); + } else { + let logical = line.clone(); + lines.push(LogicalLine { + physical_lines: vec![line], + logical, + first_row: first + idx as StableRowIndex, + }); + } + } + } + } + lines +} + +pub fn impl_get_lines_via_with_lines( + pane: &P, + lines: Range, +) -> (StableRowIndex, Vec) { + struct LineCollector { + first: StableRowIndex, + lines: Vec, + } + + let mut collector = LineCollector { + first: 0, + lines: vec![], + }; + + impl WithPaneLines for LineCollector { + fn with_lines_mut(&mut self, first_row: StableRowIndex, lines: &mut [&mut Line]) { + self.first = first_row; + for line in lines.iter_mut() { + self.lines.push(line.clone()); + } + } + } + + pane.with_lines_mut(lines, &mut collector); + (collector.first, collector.lines) +} + #[cfg(test)] mod test { use super::*; @@ -549,7 +613,11 @@ mod test { lines: Range, for_line: &mut dyn ForEachPaneLogicalLine, ) { - unimplemented!(); + crate::pane::impl_for_each_logical_line_via_get_logical_lines(self, lines, for_line) + } + + fn get_logical_lines(&self, lines: Range) -> Vec { + crate::pane::impl_get_logical_lines_via_get_lines(self, lines) } fn get_lines(&self, lines: Range) -> (StableRowIndex, Vec) { @@ -654,88 +722,6 @@ mod test { physical_lines } - #[test] - fn logical_lines_terminal() { - use wezterm_term::{Terminal, TerminalConfiguration}; - - #[derive(Debug)] - struct TermConfig {} - impl TerminalConfiguration for TermConfig { - fn color_palette(&self) -> ColorPalette { - ColorPalette::default() - } - } - - let mut terminal = Terminal::new( - TerminalSize { - rows: 24, - cols: 20, - pixel_width: 0, - pixel_height: 0, - dpi: 0, - }, - Arc::new(TermConfig {}), - "WezTerm", - "o_O", - Box::new(Vec::new()), - ); - - let text = "Hello there this is a long line.\r\nlogical line two\r\nanother long line here\r\nlogical line four\r\nlogical line five\r\ncap it off with another long line"; - terminal.advance_bytes(text.as_bytes()); - - let logical = terminal_get_logical_lines(&mut terminal, 0..9); - - snapshot!( - summarize_logical_lines(&logical), - r#" -[ - ( - 0, - "Hello there this is a long line.", - ), - ( - 2, - "logical line two", - ), - ( - 3, - "another long line here", - ), - ( - 5, - "logical line four", - ), - ( - 6, - "logical line five", - ), - ( - 7, - "cap it off with another long line", - ), -] -"# - ); - - // Now try with offset bounds - let offset = terminal_get_logical_lines(&mut terminal, 1..3); - snapshot!( - summarize_logical_lines(&offset), - r#" -[ - ( - 0, - "Hello there this is a long line.", - ), - ( - 2, - "logical line two", - ), -] -"# - ); - } - fn summarize_logical_lines(lines: &[LogicalLine]) -> Vec<(StableRowIndex, Cow)> { lines .iter() diff --git a/mux/src/renderable.rs b/mux/src/renderable.rs index 309e87ce8..94a2faca3 100644 --- a/mux/src/renderable.rs +++ b/mux/src/renderable.rs @@ -1,4 +1,4 @@ -use crate::pane::{ForEachPaneLogicalLine, LogicalLine, WithPaneLines}; +use crate::pane::{ForEachPaneLogicalLine, WithPaneLines}; use luahelper::impl_lua_conversion_dynamic; use rangeset::RangeSet; use serde::{Deserialize, Serialize}; @@ -85,39 +85,6 @@ pub fn terminal_for_each_logical_line_in_stable_range_mut( }); } -pub fn terminal_get_logical_lines( - term: &mut Terminal, - lines: Range, -) -> Vec { - let screen = term.screen(); - let mut result = vec![]; - screen.for_each_logical_line_in_stable_range(lines.clone(), |sr, lines| { - let mut physical_lines: Vec = lines - .iter() - .map(|line| { - let line = (*line).clone(); - line - }) - .collect(); - - let mut logical = physical_lines[0].clone(); - for line in &mut physical_lines[1..] { - let seqno = line.current_seqno(); - logical.set_last_cell_was_wrapped(false, seqno); - logical.append_line((*line).clone(), seqno); - } - - result.push(LogicalLine { - physical_lines, - logical, - first_row: sr.start, - }); - - true - }); - result -} - /// Implements Pane::with_lines for Terminal pub fn terminal_with_lines(term: &mut Terminal, lines: Range, mut func: F) where diff --git a/mux/src/tab.rs b/mux/src/tab.rs index c50c5a8fa..0241b666e 100644 --- a/mux/src/tab.rs +++ b/mux/src/tab.rs @@ -2009,16 +2009,16 @@ mod test { fn with_lines_mut( &self, - stable_range: Range, - with_lines: &mut dyn WithPaneLines, + _stable_range: Range, + _with_lines: &mut dyn WithPaneLines, ) { unimplemented!(); } fn for_each_logical_line_in_stable_range_mut( &self, - lines: Range, - for_line: &mut dyn ForEachPaneLogicalLine, + _lines: Range, + _for_line: &mut dyn ForEachPaneLogicalLine, ) { unimplemented!(); } @@ -2027,6 +2027,10 @@ mod test { unimplemented!(); } + fn get_logical_lines(&self, _lines: Range) -> Vec { + unimplemented!(); + } + fn get_dimensions(&self) -> RenderableDimensions { unimplemented!(); } diff --git a/mux/src/termwiztermtab.rs b/mux/src/termwiztermtab.rs index 4e9c3344a..47b91d70c 100644 --- a/mux/src/termwiztermtab.rs +++ b/mux/src/termwiztermtab.rs @@ -5,7 +5,7 @@ use crate::domain::{alloc_domain_id, Domain, DomainId, DomainState}; use crate::pane::{ - alloc_pane_id, CloseReason, ForEachPaneLogicalLine, Pane, PaneId, WithPaneLines, + alloc_pane_id, CloseReason, ForEachPaneLogicalLine, LogicalLine, Pane, PaneId, WithPaneLines, }; use crate::renderable::*; use crate::tab::Tab; @@ -153,6 +153,10 @@ impl Pane for TermWizTerminalPane { ); } + fn get_logical_lines(&self, lines: Range) -> Vec { + crate::pane::impl_get_logical_lines_via_get_lines(self, lines) + } + fn with_lines_mut(&self, lines: Range, with_lines: &mut dyn WithPaneLines) { terminal_with_lines_mut(&mut self.terminal.borrow_mut(), lines, with_lines) } diff --git a/wezterm-client/src/pane/clientpane.rs b/wezterm-client/src/pane/clientpane.rs index 6c3f91b88..f9af258cd 100644 --- a/wezterm-client/src/pane/clientpane.rs +++ b/wezterm-client/src/pane/clientpane.rs @@ -7,8 +7,8 @@ use codec::*; use config::configuration; use mux::domain::DomainId; use mux::pane::{ - alloc_pane_id, CloseReason, ForEachPaneLogicalLine, Pane, PaneId, Pattern, SearchResult, - WithPaneLines, + alloc_pane_id, CloseReason, ForEachPaneLogicalLine, LogicalLine, Pane, PaneId, Pattern, + SearchResult, WithPaneLines, }; use mux::renderable::{RenderableDimensions, StableCursorPosition}; use mux::tab::TabId; @@ -204,7 +204,7 @@ impl Pane for ClientPane { } fn with_lines_mut(&self, lines: Range, with_lines: &mut dyn WithPaneLines) { - todo!(); + mux::pane::impl_with_lines_via_get_lines(self, lines, with_lines); } fn for_each_logical_line_in_stable_range_mut( @@ -212,13 +212,17 @@ impl Pane for ClientPane { lines: Range, for_line: &mut dyn ForEachPaneLogicalLine, ) { - todo!(); + mux::pane::impl_for_each_logical_line_via_get_logical_lines(self, lines, for_line); } fn get_lines(&self, lines: Range) -> (StableRowIndex, Vec) { self.renderable.borrow().get_lines(lines) } + fn get_logical_lines(&self, lines: Range) -> Vec { + mux::pane::impl_get_logical_lines_via_get_lines(self, lines) + } + fn get_current_seqno(&self) -> SequenceNo { self.renderable.borrow().get_current_seqno() } diff --git a/wezterm-client/src/pane/renderable.rs b/wezterm-client/src/pane/renderable.rs index b9ee23a60..047dfaea8 100644 --- a/wezterm-client/src/pane/renderable.rs +++ b/wezterm-client/src/pane/renderable.rs @@ -716,13 +716,6 @@ impl RenderableState { self.inner.borrow().cursor_position } - pub fn with_lines(&self, lines: Range, func: F) - where - F: FnMut(StableRowIndex, &[&Line]), - { - todo!(); - } - pub fn get_lines(&self, lines: Range) -> (StableRowIndex, Vec) { let mut inner = self.inner.borrow_mut(); let mut result = vec![]; diff --git a/wezterm-gui/src/overlay/copy.rs b/wezterm-gui/src/overlay/copy.rs index 1634930e0..f4c070a82 100644 --- a/wezterm-gui/src/overlay/copy.rs +++ b/wezterm-gui/src/overlay/copy.rs @@ -5,7 +5,9 @@ use config::keyassignment::{ ScrollbackEraseMode, SelectionMode, }; use mux::domain::DomainId; -use mux::pane::{ForEachPaneLogicalLine, Pane, PaneId, Pattern, SearchResult, WithPaneLines}; +use mux::pane::{ + ForEachPaneLogicalLine, LogicalLine, Pane, PaneId, Pattern, SearchResult, WithPaneLines, +}; use mux::renderable::*; use mux::tab::TabId; use rangeset::RangeSet; @@ -1023,6 +1025,10 @@ impl Pane for CopyOverlay { self.delegate.get_changed_since(lines, seqno) } + fn get_logical_lines(&self, lines: Range) -> Vec { + self.delegate.get_logical_lines(lines) + } + fn for_each_logical_line_in_stable_range_mut( &self, lines: Range, diff --git a/wezterm-gui/src/overlay/quickselect.rs b/wezterm-gui/src/overlay/quickselect.rs index d869edd7a..291468dbd 100644 --- a/wezterm-gui/src/overlay/quickselect.rs +++ b/wezterm-gui/src/overlay/quickselect.rs @@ -3,7 +3,9 @@ use crate::termwindow::{TermWindow, TermWindowNotif}; use config::keyassignment::{ClipboardCopyDestination, QuickSelectArguments, ScrollbackEraseMode}; use config::ConfigHandle; use mux::domain::DomainId; -use mux::pane::{ForEachPaneLogicalLine, Pane, PaneId, Pattern, SearchResult, WithPaneLines}; +use mux::pane::{ + ForEachPaneLogicalLine, LogicalLine, Pane, PaneId, Pattern, SearchResult, WithPaneLines, +}; use mux::renderable::*; use rangeset::RangeSet; use std::cell::{RefCell, RefMut}; @@ -461,6 +463,10 @@ impl Pane for QuickSelectOverlay { .for_each_logical_line_in_stable_range_mut(lines, for_line); } + fn get_logical_lines(&self, lines: Range) -> Vec { + self.delegate.get_logical_lines(lines) + } + fn with_lines_mut(&self, lines: Range, with_lines: &mut dyn WithPaneLines) { let mut renderer = self.renderer.borrow_mut(); // Take care to access self.delegate methods here before we get into diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index 5492aba69..54a35f703 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -48,13 +48,13 @@ use smol::Timer; use std::any::Any; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; -use std::ops::{Add, Range}; +use std::ops::Add; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use termwiz::hyperlink::Hyperlink; -use termwiz::surface::{Line, SequenceNo}; +use termwiz::surface::SequenceNo; use wezterm_dynamic::Value; use wezterm_font::FontConfiguration; use wezterm_gui_subcommands::GuiPosition; @@ -190,19 +190,6 @@ pub struct PaneState { bell_start: Option, pub mouse_terminal_coords: Option<(ClickPosition, StableRowIndex)>, - - /// Cache to avoid calling pane.get_lines_with_hyperlinks_applied - /// if the pane hasn't changed since the last render - pub logical_line_cache: Option, -} - -pub struct CachedLogicalLines { - // Key fields - pub seqno: SequenceNo, - pub stable_range: Range, - // Value fields - pub top: StableRowIndex, - pub lines: Rc>, } /// Data used when synchronously formatting pane and window titles