diff --git a/term/src/terminalstate.rs b/term/src/terminalstate.rs index a8233c024..4013f53ca 100644 --- a/term/src/terminalstate.rs +++ b/term/src/terminalstate.rs @@ -228,6 +228,20 @@ fn write_all(w: &mut std::io::Write, mut buf: &[u8]) -> std::io::Result<()> { Ok(()) } +fn is_double_click_word(s: &str) -> bool { + // TODO: add configuration for this + if s.len() > 1 { + true + } else if s.len() == 1 { + match s.chars().nth(0).unwrap() { + ' ' | '\t' | '\n' | '{' | '[' | '}' | ']' | '(' | ')' | '"' | '\'' => false, + _ => true, + } + } else { + false + } +} + impl TerminalState { pub fn new( physical_rows: usize, @@ -429,34 +443,65 @@ impl TerminalState { let y = event.y as ScrollbackOrVisibleRowIndex - self.viewport_offset as ScrollbackOrVisibleRowIndex; let idx = self.screen().scrollback_or_visible_row(y); - let click_range = self.screen().lines[idx].compute_double_click_range(event.x, |s| { - // TODO: add configuration for this - if s.len() > 1 { - true - } else if s.len() == 1 { - match s.chars().nth(0).unwrap() { - ' ' | '\t' | '\n' | '{' | '[' | '}' | ']' | '(' | ')' | '"' | '\'' => false, - _ => true, - } - } else { - false - } - }); + let selection_range = match self.screen().lines[idx] + .compute_double_click_range(event.x, is_double_click_word) + { + DoubleClickRange::Range(click_range) => SelectionRange { + start: SelectionCoordinate { + x: click_range.start, + y, + }, + end: SelectionCoordinate { + x: click_range.end, + y, + }, + }, + DoubleClickRange::RangeWithWrap(range_start) => { + let start_coord = SelectionCoordinate { + x: range_start.start, + y, + }; + + let mut end_coord = SelectionCoordinate { + x: range_start.end, + y, + }; + + for y_cont in idx + 1..self.screen().lines.len() { + match self.screen().lines[y_cont] + .compute_double_click_range(0, is_double_click_word) + { + DoubleClickRange::Range(range_end) => { + if range_end.end > range_end.start { + end_coord = SelectionCoordinate { + x: range_end.end, + y: y + (y_cont - idx) as i32, + }; + } + break; + } + DoubleClickRange::RangeWithWrap(range_end) => { + end_coord = SelectionCoordinate { + x: range_end.end, + y: y + (y_cont - idx) as i32, + }; + } + } + } + + SelectionRange { + start: start_coord, + end: end_coord, + } + } + }; + + // TODO: if selection_range.start.x == 0, search backwards for wrapping + // lines too. + + self.selection_start = Some(selection_range.start); + self.selection_range = Some(selection_range); - self.selection_start = Some(SelectionCoordinate { - x: click_range.start, - y, - }); - self.selection_range = Some(SelectionRange { - start: SelectionCoordinate { - x: click_range.start, - y, - }, - end: SelectionCoordinate { - x: click_range.end, - y, - }, - }); self.dirty_selection_lines(); let text = self.get_selection_text(); debug!( diff --git a/term/src/test/selection.rs b/term/src/test/selection.rs index 000cd9bd9..482d69744 100644 --- a/term/src/test/selection.rs +++ b/term/src/test/selection.rs @@ -63,16 +63,27 @@ fn triple_click_selection() { assert_eq!(term.get_clipboard().unwrap(), "hello worl"); } +/// Test double click on wrapped line selects across the line boundary +#[test] +fn double_click_wrapped_selection() { + let mut term = TestTerm::new(3, 10, 0); + term.print("hello world"); + assert_visible_contents(&term, &["hello worl", "d ", " "]); + term.click_n(7, 0, MouseButton::Left, 2); + + assert_eq!(term.get_clipboard().unwrap(), "worl\nd"); +} + /// Make sure that we adjust for the viewport offset when scrolling #[test] fn selection_in_scrollback() { - let mut term = TestTerm::new(2, 1, 4); - term.print("1234"); - assert_all_contents(&term, &["1", "2", "3", "4"]); + let mut term = TestTerm::new(2, 2, 4); + term.print("1 2 3 4"); + assert_all_contents(&term, &["1 ", "2 ", "3 ", "4 "]); // Scroll back one line term.scroll_viewport(-1); - term.assert_viewport_contents(&["2", "3"]); + term.assert_viewport_contents(&["2 ", "3 "]); term.click_n(0, 0, MouseButton::Left, 2); assert_eq!(term.get_clipboard().unwrap(), "2"); diff --git a/termwiz/src/surface/line.rs b/termwiz/src/surface/line.rs index a52dc6616..8b604d0f7 100644 --- a/termwiz/src/surface/line.rs +++ b/termwiz/src/surface/line.rs @@ -30,6 +30,11 @@ pub struct Line { cells: Vec, } +pub enum DoubleClickRange { + Range(Range), + RangeWithWrap(Range), +} + impl Line { pub fn with_width(width: usize) -> Self { let mut cells = Vec::with_capacity(width); @@ -186,7 +191,7 @@ impl Line { &self, click_col: usize, is_word: fn(s: &str) -> bool, - ) -> Range { + ) -> DoubleClickRange { let mut lower = click_col; let mut upper = click_col; @@ -196,7 +201,7 @@ impl Line { if !is_word(cell.str()) { break; } - upper = idx; + upper = idx + 1; } for (idx, cell) in self.cells.iter().enumerate().rev() { if idx > click_col { @@ -208,7 +213,11 @@ impl Line { lower = idx; } - lower..upper + if upper == self.cells().len() { + DoubleClickRange::RangeWithWrap(lower..upper) + } else { + DoubleClickRange::Range(lower..upper) + } } /// Returns a substring from the line.