From cb9fe1a676aee7d28c3ef8cbaa9ca85f3e545b43 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sat, 27 Aug 2022 07:58:17 -0700 Subject: [PATCH] flesh out some todos with new pane trait methods Tidy some things up to avoid some dead code and redundant impls. Make it easier to select whether you want to implement the new methods in terms of the old, or the old methods in terms of the new in a given pane impl. --- mux/src/localpane.rs | 56 +++-- mux/src/pane.rs | 316 ++++++++++++------------- mux/src/renderable.rs | 35 +-- mux/src/tab.rs | 12 +- mux/src/termwiztermtab.rs | 6 +- wezterm-client/src/pane/clientpane.rs | 12 +- wezterm-client/src/pane/renderable.rs | 7 - wezterm-gui/src/overlay/copy.rs | 8 +- wezterm-gui/src/overlay/quickselect.rs | 8 +- wezterm-gui/src/termwindow/mod.rs | 17 +- 10 files changed, 222 insertions(+), 255 deletions(-) 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