From ef4a95211e13e836eb1faea99bc5a2fd0716cb8f Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Thu, 5 May 2022 20:49:22 -0700 Subject: [PATCH] add rectangular selection Alt-dragging will use rectangular selection in the default mouse assignments. refs: https://github.com/wez/wezterm/issues/1361 --- config/src/keyassignment.rs | 1 + docs/changelog.md | 1 + .../ExtendSelectionToMouseCursor.md | 2 ++ docs/config/mouse.md | 3 +++ wezterm-gui/src/inputmap.rs | 24 +++++++++++++++++++ wezterm-gui/src/selection.rs | 12 ++++++++-- wezterm-gui/src/termwindow/render.rs | 7 ++++-- wezterm-gui/src/termwindow/selection.rs | 15 +++++++++--- 8 files changed, 58 insertions(+), 7 deletions(-) diff --git a/config/src/keyassignment.rs b/config/src/keyassignment.rs index cd9fabb00..d881eb634 100644 --- a/config/src/keyassignment.rs +++ b/config/src/keyassignment.rs @@ -95,6 +95,7 @@ pub enum SelectionMode { Word, Line, SemanticZone, + Block, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] diff --git a/docs/changelog.md b/docs/changelog.md index 4284cf986..eee1e8add 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -25,6 +25,7 @@ As features stabilize some brief notes about them will accumulate here. * `wezterm cli list --format json` and `wezterm cli list-clients --format json` allow retrieving data in json format. Thanks to [@ratmice](https://github.com/ratmice)! [#1911](https://github.com/wez/wezterm/pull/1911) * macOS: you may now drag and drop files from other programs and have their paths paste into the terminal. The new [quote_dropped_files](config/lua/config/quote_dropped_files.md) option controls how the file names are quoted. Thanks to [@junnplus](https://github.com/junnplus)! [#1868](https://github.com/wez/wezterm/pull/1868) * The mouse scroll wheel now cycles between tabs when hovering over the tab tab. Thanks to [@junnplus](https://github.com/junnplus)! [#1726](https://github.com/wez/wezterm/issues/1726) +* Holding down `ALT` while dragging the left button will select a rectangular block. [ExtendSelectionToMouseCursor](config/lua/keyassignment/ExtendSelectionToMouseCursor.md) now accepts `"Block"` as a selection mode. [#1361](https://github.com/wez/wezterm/issues/1361) #### Updated * Bundled harfbuzz to 4.2.1 diff --git a/docs/config/lua/keyassignment/ExtendSelectionToMouseCursor.md b/docs/config/lua/keyassignment/ExtendSelectionToMouseCursor.md index a77903b30..7aeb3f975 100644 --- a/docs/config/lua/keyassignment/ExtendSelectionToMouseCursor.md +++ b/docs/config/lua/keyassignment/ExtendSelectionToMouseCursor.md @@ -23,4 +23,6 @@ when unspecified, wezterm will use a default mode which at the time of writing is `Cell`, but in a future release may be context sensitive based on recent actions. +*Since: nightly builds only* +The mode argument can also be `"Block"` to enable a rectangular block selection. diff --git a/docs/config/mouse.md b/docs/config/mouse.md index c251bd881..f6f434138 100644 --- a/docs/config/mouse.md +++ b/docs/config/mouse.md @@ -32,11 +32,14 @@ that order. | Double Left Down | `NONE` | `SelectTextAtMouseCursor="Word"` | | Single Left Down | `NONE` | `SelectTextAtMouseCursor="Cell"` | | Single Left Down | `SHIFT` | `ExtendSelectionToMouseCursor={}` | +| Single Left Down | `ALT` | `ExtendSelectionToMouseCursor="Block"` (*since: nightly builds only*) | | Single Left Up | `SHIFT` | `CompleteSelectionOrOpenLinkAtMouseCursor="PrimarySelection"` | | Single Left Up | `NONE` | `CompleteSelectionOrOpenLinkAtMouseCursor="PrimarySelection"` | +| Single Left Up | `ALT` | `CompleteSelection="PrimarySelection"` (*since: nightly builds only*) | | Double Left Up | `NONE` | `CompleteSelection="PrimarySelection"` | | Triple Left Up | `NONE` | `CompleteSelection="PrimarySelection"` | | Single Left Drag | `NONE` | `ExtendSelectionToMouseCursor="Cell"` | +| Single Left Drag | `ALT` | `ExtendSelectionToMouseCursor="Block"` (*since: nightly builds only*) | | Double Left Drag | `NONE` | `ExtendSelectionToMouseCursor="Word"` | | Triple Left Drag | `NONE` | `ExtendSelectionToMouseCursor="Line"` | | Single Middle Down | `NONE` | `PasteFrom="PrimarySelection"` | diff --git a/wezterm-gui/src/inputmap.rs b/wezterm-gui/src/inputmap.rs index 74119d698..2019ec0a6 100644 --- a/wezterm-gui/src/inputmap.rs +++ b/wezterm-gui/src/inputmap.rs @@ -75,6 +75,14 @@ impl InputMap { }, SelectTextAtMouseCursor(SelectionMode::Cell) ], + [ + Modifiers::ALT, + MouseEventTrigger::Down { + streak: 1, + button: MouseButton::Left + }, + SelectTextAtMouseCursor(SelectionMode::Block) + ], [ Modifiers::SHIFT, MouseEventTrigger::Down { @@ -103,6 +111,14 @@ impl InputMap { ClipboardCopyDestination::PrimarySelection ) ], + [ + Modifiers::ALT, + MouseEventTrigger::Up { + streak: 1, + button: MouseButton::Left + }, + CompleteSelection(ClipboardCopyDestination::PrimarySelection) + ], [ Modifiers::NONE, MouseEventTrigger::Up { @@ -127,6 +143,14 @@ impl InputMap { }, ExtendSelectionToMouseCursor(Some(SelectionMode::Cell)) ], + [ + Modifiers::ALT, + MouseEventTrigger::Drag { + streak: 1, + button: MouseButton::Left + }, + ExtendSelectionToMouseCursor(Some(SelectionMode::Block)) + ], [ Modifiers::NONE, MouseEventTrigger::Drag { diff --git a/wezterm-gui/src/selection.rs b/wezterm-gui/src/selection.rs index 4414ad09a..81c7a907d 100644 --- a/wezterm-gui/src/selection.rs +++ b/wezterm-gui/src/selection.rs @@ -17,6 +17,8 @@ pub struct Selection { pub range: Option, /// When the selection was made wrt. the pane content pub seqno: SequenceNo, + /// Whether the selection is rectangular + pub rectangular: bool, } pub use config::keyassignment::SelectionMode; @@ -215,9 +217,9 @@ impl SelectionRange { /// Since this struct has no knowledge of line length, it cannot be /// more precise than that. /// Must be called on a normalized range! - pub fn cols_for_row(&self, row: StableRowIndex) -> Range { + pub fn cols_for_row(&self, row: StableRowIndex, rectangular: bool) -> Range { let norm = self.normalize(); - if row < norm.start.y || row > norm.end.y { + let range = if row < norm.start.y || row > norm.end.y { 0..0 } else if norm.start.y == norm.end.y { // A single line selection @@ -235,6 +237,12 @@ impl SelectionRange { } else { // some "middle" line of multi-line 0..usize::max_value() + }; + + if rectangular { + range.start.max(norm.start.x)..range.end.min(norm.end.x.saturating_add(1)) + } else { + range } } } diff --git a/wezterm-gui/src/termwindow/render.rs b/wezterm-gui/src/termwindow/render.rs index 5220b5572..d2184b551 100644 --- a/wezterm-gui/src/termwindow/render.rs +++ b/wezterm-gui/src/termwindow/render.rs @@ -1349,7 +1349,10 @@ impl super::TermWindow { )?; } - let selrange = self.selection(pos.pane.pane_id()).range.clone(); + let (selrange, rectangular) = { + let sel = self.selection(pos.pane.pane_id()); + (sel.range.clone(), sel.rectangular) + }; let start = Instant::now(); let selection_fg = palette.selection_fg.to_linear(); @@ -1362,7 +1365,7 @@ impl super::TermWindow { for (line_idx, line) in lines.iter().enumerate() { let stable_row = stable_top + line_idx as StableRowIndex; - let selrange = selrange.map_or(0..0, |sel| sel.cols_for_row(stable_row)); + let selrange = selrange.map_or(0..0, |sel| sel.cols_for_row(stable_row, rectangular)); // Constrain to the pane width! let selrange = selrange.start..selrange.end.min(dims.cols); diff --git a/wezterm-gui/src/termwindow/selection.rs b/wezterm-gui/src/termwindow/selection.rs index c5cd860fe..7f126d4e0 100644 --- a/wezterm-gui/src/termwindow/selection.rs +++ b/wezterm-gui/src/termwindow/selection.rs @@ -12,6 +12,7 @@ impl super::TermWindow { pub fn selection_text(&self, pane: &Rc) -> String { let mut s = String::new(); + let rectangular = self.selection(pane.pane_id()).rectangular; if let Some(sel) = self .selection(pane.pane_id()) .range @@ -31,7 +32,7 @@ impl super::TermWindow { let this_row = line.first_row + idx as StableRowIndex; if this_row >= first_row && this_row < last_row { let last_phys_idx = phys.cells().len().saturating_sub(1); - let cols = sel.cols_for_row(this_row); + let cols = sel.cols_for_row(this_row, rectangular); let last_col_idx = cols.end.saturating_sub(1).min(last_phys_idx); let col_span = phys.columns_as_str(cols); // Only trim trailing whitespace if we are the last line @@ -76,7 +77,7 @@ impl super::TermWindow { }; let x = position.column; match mode { - SelectionMode::Cell => { + SelectionMode::Cell | SelectionMode::Block => { // Origin is the cell in which the selection action started. E.g. the cell // that had the mouse over it when the left mouse button was pressed let origin = self @@ -84,6 +85,7 @@ impl super::TermWindow { .origin .unwrap_or(SelectionCoordinate { x, y }); self.selection(pane.pane_id()).origin = Some(origin); + self.selection(pane.pane_id()).rectangular = mode == SelectionMode::Block; // Compute the start and end horizontall cell of the selection. // The selection extent depends on the mouse cursor position in relation @@ -128,6 +130,7 @@ impl super::TermWindow { let selection_range = start_word.extend_with(end_word); self.selection(pane.pane_id()).range = Some(selection_range); + self.selection(pane.pane_id()).rectangular = false; } SelectionMode::Line => { let end_line = SelectionRange::line_around(SelectionCoordinate { x, y }, &**pane); @@ -141,6 +144,7 @@ impl super::TermWindow { let selection_range = start_line.extend_with(end_line); self.selection(pane.pane_id()).range = Some(selection_range); + self.selection(pane.pane_id()).rectangular = false; } SelectionMode::SemanticZone => { let end_word = SelectionRange::zone_around(SelectionCoordinate { x, y }, &**pane); @@ -154,6 +158,7 @@ impl super::TermWindow { let selection_range = start_word.extend_with(end_word); self.selection(pane.pane_id()).range = Some(selection_range); + self.selection(pane.pane_id()).rectangular = false; } } @@ -184,6 +189,7 @@ impl super::TermWindow { self.selection(pane.pane_id()).origin = Some(start); self.selection(pane.pane_id()).range = Some(selection_range); + self.selection(pane.pane_id()).rectangular = false; } SelectionMode::Word => { let selection_range = @@ -191,6 +197,7 @@ impl super::TermWindow { self.selection(pane.pane_id()).origin = Some(selection_range.start); self.selection(pane.pane_id()).range = Some(selection_range); + self.selection(pane.pane_id()).rectangular = false; } SelectionMode::SemanticZone => { let selection_range = @@ -198,10 +205,12 @@ impl super::TermWindow { self.selection(pane.pane_id()).origin = Some(selection_range.start); self.selection(pane.pane_id()).range = Some(selection_range); + self.selection(pane.pane_id()).rectangular = false; } - SelectionMode::Cell => { + SelectionMode::Cell | SelectionMode::Block => { self.selection(pane.pane_id()) .begin(SelectionCoordinate { x, y }); + self.selection(pane.pane_id()).rectangular = mode == SelectionMode::Block; } }