1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-23 23:21:08 +03:00

Fix mouse selection including the first character of the next line

ref: #2089

Co-authored-by: Wez Furlong <wez@wezfurlong.org>
This commit is contained in:
Funami580 2022-06-10 07:18:05 +02:00 committed by Wez Furlong
parent 42b83626f2
commit 6a9056d77c
5 changed files with 210 additions and 120 deletions

View File

@ -292,12 +292,11 @@ impl GlyphCache<SrgbTexture2d> {
// include this check, but it doesn't, and instead, the texture
// silently fails to bind when attempting to render into it later.
// So! We check and raise here for ourselves!
if size
> caps
.max_texture_size
.try_into()
.context("represent Capabilities.max_texture_size as usize")?
{
let max_texture_size: usize = caps
.max_texture_size
.try_into()
.context("represent Capabilities.max_texture_size as usize")?;
if size > max_texture_size {
anyhow::bail!(
"Cannot use a texture of size {} as it is larger \
than the max {} supported by your GPU",

View File

@ -1,4 +1,4 @@
use crate::selection::{SelectionCoordinate, SelectionRange};
use crate::selection::{SelectionCoordinate, SelectionRange, SelectionX};
use crate::termwindow::{TermWindow, TermWindowNotif};
use config::keyassignment::{
CopyModeAssignment, KeyAssignment, KeyTable, KeyTableEntry, ScrollbackEraseMode, SelectionMode,
@ -290,16 +290,8 @@ impl CopyRenderable {
self.cursor.y = result.end_y;
self.cursor.x = result.end_x.saturating_sub(1);
let start = SelectionCoordinate {
x: result.start_x,
y: result.start_y,
};
let end = SelectionCoordinate {
// inclusive range for selection, but the result
// range is exclusive
x: result.end_x.saturating_sub(1),
y: result.end_y,
};
let start = SelectionCoordinate::x_y(result.start_x, result.start_y);
let end = SelectionCoordinate::x_y(result.end_x.saturating_sub(1), result.end_y);
self.start.replace(start);
self.adjust_selection(start, SelectionRange { start, end });
}
@ -324,14 +316,14 @@ impl CopyRenderable {
if let Some(start) = self.start {
let cursor_is_above_start = self.cursor.y < start.y;
let start = if self.selection_mode == SelectionMode::Line {
SelectionCoordinate {
x: if cursor_is_above_start {
SelectionCoordinate::x_y(
if cursor_is_above_start {
usize::max_value()
} else {
0
},
y: start.y,
}
start.y,
)
} else {
SelectionCoordinate {
x: start.x,
@ -340,19 +332,16 @@ impl CopyRenderable {
};
let end = if self.selection_mode == SelectionMode::Line {
SelectionCoordinate {
x: if cursor_is_above_start {
SelectionCoordinate::x_y(
if cursor_is_above_start {
0
} else {
usize::max_value()
},
y: self.cursor.y,
}
self.cursor.y,
)
} else {
SelectionCoordinate {
x: self.cursor.x,
y: self.cursor.y,
}
SelectionCoordinate::x_y(self.cursor.x, self.cursor.y)
};
self.adjust_selection(start, SelectionRange { start, end });
@ -617,11 +606,12 @@ impl CopyRenderable {
fn move_to_selection_other_end(&mut self) {
if let Some(old_start) = self.start {
// Swap cursor & start of selection
self.start.replace(SelectionCoordinate {
x: self.cursor.x,
y: self.cursor.y,
});
self.cursor.x = old_start.x;
self.start
.replace(SelectionCoordinate::x_y(self.cursor.x, self.cursor.y));
self.cursor.x = match &old_start.x {
SelectionX::Cell(x) => *x,
SelectionX::BeforeZero => 0,
};
self.cursor.y = old_start.y;
self.select_to_cursor_pos();
}
@ -633,11 +623,12 @@ impl CopyRenderable {
}
if let Some(old_start) = self.start {
// Swap X coordinate of cursor & start of selection
self.start.replace(SelectionCoordinate {
x: self.cursor.x,
y: old_start.y,
});
self.cursor.x = old_start.x;
self.start
.replace(SelectionCoordinate::x_y(self.cursor.x, old_start.y));
self.cursor.x = match &old_start.x {
SelectionX::Cell(x) => *x,
SelectionX::BeforeZero => 0,
};
self.select_to_cursor_pos();
}
}
@ -742,10 +733,7 @@ impl CopyRenderable {
}
Some(mode) => {
if self.start.is_none() {
let coord = SelectionCoordinate {
x: self.cursor.x,
y: self.cursor.y,
};
let coord = SelectionCoordinate::x_y(self.cursor.x, self.cursor.y);
self.start.replace(coord);
}
self.selection_mode = *mode;

View File

@ -722,19 +722,16 @@ impl QuickSelectRenderable {
if let Some(pane) = mux.get_pane(pane_id) {
{
let mut selection = term_window.selection(pane_id);
let start = SelectionCoordinate {
x: result.start_x,
y: result.start_y,
};
let start = SelectionCoordinate::x_y(result.start_x, result.start_y);
selection.origin = Some(start);
selection.range = Some(SelectionRange {
start,
end: SelectionCoordinate {
// inclusive range for selection, but the result
// range is exclusive
x: result.end_x.saturating_sub(1),
y: result.end_y,
},
// inclusive range for selection, but the result
// range is exclusive
end: SelectionCoordinate::x_y(
result.end_x.saturating_sub(1),
result.end_y,
),
});
// Ensure that selection doesn't get invalidated when
// the overlay is closed

View File

@ -39,13 +39,123 @@ impl Selection {
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum SelectionX {
/// Zero-based cell index
Cell(usize),
/// Exactly before the 0th cell
BeforeZero,
}
impl SelectionX {
pub const fn saturating_add(self, rhs: usize) -> Self {
match self {
Self::Cell(x) => Self::Cell(x.saturating_add(rhs)),
Self::BeforeZero => {
if rhs == 0 {
Self::BeforeZero
} else {
Self::Cell(rhs - 1)
}
}
}
}
pub const fn saturating_sub(self, rhs: usize) -> Self {
match self {
Self::Cell(x) => match x.checked_sub(rhs) {
Some(x) => Self::Cell(x),
None => Self::BeforeZero,
},
Self::BeforeZero => Self::BeforeZero,
}
}
pub const fn range(self, rhs: Self) -> Range<usize> {
match self {
Self::Cell(left) => match rhs {
Self::Cell(right) => left..right,
Self::BeforeZero => 0..0,
},
Self::BeforeZero => match rhs {
Self::Cell(right) => 0..right,
Self::BeforeZero => 0..0,
},
}
}
}
impl Default for SelectionX {
// Default is 0th cell
fn default() -> Self {
Self::Cell(0)
}
}
impl PartialEq<usize> for SelectionX {
fn eq(&self, other: &usize) -> bool {
match self {
Self::Cell(x) => x == other,
_ => false,
}
}
}
impl PartialEq<SelectionX> for usize {
fn eq(&self, other: &SelectionX) -> bool {
other == self
}
}
impl Ord for SelectionX {
fn cmp(&self, other: &Self) -> Ordering {
match self {
Self::Cell(x1) => match other {
Self::Cell(x2) => x1.cmp(x2),
Self::BeforeZero => Ordering::Greater,
},
Self::BeforeZero => match other {
Self::Cell(_) => Ordering::Less,
Self::BeforeZero => Ordering::Equal,
},
}
}
}
impl PartialOrd for SelectionX {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<usize> for SelectionX {
fn partial_cmp(&self, other: &usize) -> Option<Ordering> {
self.partial_cmp(&Self::Cell(*other))
}
}
impl PartialOrd<SelectionX> for usize {
fn partial_cmp(&self, other: &SelectionX) -> Option<Ordering> {
SelectionX::Cell(*self).partial_cmp(other)
}
}
/// The x,y coordinates of either the start or end of a selection region
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct SelectionCoordinate {
pub x: usize,
pub x: SelectionX,
pub y: StableRowIndex,
}
impl SelectionCoordinate {
pub const fn x_y(x: usize, y: StableRowIndex) -> Self {
Self {
x: SelectionX::Cell(x),
y,
}
}
}
/// Represents the selected text range.
/// The end coordinates are inclusive.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
@ -74,14 +184,11 @@ impl SelectionRange {
for logical in pane.get_logical_lines(start.y..start.y + 1) {
if logical.contains_y(start.y) {
return Self {
start: SelectionCoordinate {
x: 0,
y: logical.first_row,
},
end: SelectionCoordinate {
x: usize::max_value(),
y: logical.first_row + (logical.physical_lines.len() - 1) as StableRowIndex,
},
start: SelectionCoordinate::x_y(0, logical.first_row),
end: SelectionCoordinate::x_y(
usize::max_value(),
logical.first_row + (logical.physical_lines.len() - 1) as StableRowIndex,
),
};
}
}
@ -100,7 +207,7 @@ impl SelectionRange {
Ordering::Greater => return Ordering::Greater,
// If the zone starts on the same line then check that the
// x position is within bounds
Ordering::Equal => match zone.start_x.cmp(&start.x) {
Ordering::Equal => match SelectionX::Cell(zone.start_x).cmp(&start.x) {
Ordering::Greater => return Ordering::Greater,
Ordering::Equal | Ordering::Less => {}
},
@ -110,7 +217,7 @@ impl SelectionRange {
Ordering::Less => Ordering::Less,
// If the zone ends on the same line then check that the
// x position is within bounds
Ordering::Equal => match zone.end_x.cmp(&start.x) {
Ordering::Equal => match SelectionX::Cell(zone.end_x).cmp(&start.x) {
Ordering::Less => Ordering::Less,
Ordering::Equal | Ordering::Greater => Ordering::Equal,
},
@ -121,14 +228,8 @@ impl SelectionRange {
if let Ok(idx) = zones.binary_search_by(|zone| find_zone(&start, zone)) {
let zone = &zones[idx];
Self {
start: SelectionCoordinate {
x: zone.start_x,
y: zone.start_y,
},
end: SelectionCoordinate {
x: zone.end_x,
y: zone.end_y,
},
start: SelectionCoordinate::x_y(zone.start_x, zone.start_y),
end: SelectionCoordinate::x_y(zone.end_x, zone.end_y),
}
} else {
Self { start, end: start }
@ -142,24 +243,25 @@ impl SelectionRange {
continue;
}
let start_idx = logical.xy_to_logical_x(start.x, start.y);
return match logical
.logical
.compute_double_click_range(start_idx, is_double_click_word)
{
DoubleClickRange::RangeWithWrap(click_range)
| DoubleClickRange::Range(click_range) => {
let (start_y, start_x) = logical.logical_x_to_physical_coord(click_range.start);
let (end_y, end_x) = logical.logical_x_to_physical_coord(click_range.end - 1);
Self {
start: SelectionCoordinate {
x: start_x,
y: start_y,
},
end: SelectionCoordinate { x: end_x, y: end_y },
if let SelectionX::Cell(start_x) = start.x {
let start_idx = logical.xy_to_logical_x(start_x, start.y);
return match logical
.logical
.compute_double_click_range(start_idx, is_double_click_word)
{
DoubleClickRange::RangeWithWrap(click_range)
| DoubleClickRange::Range(click_range) => {
let (start_y, start_x) =
logical.logical_x_to_physical_coord(click_range.start);
let (end_y, end_x) =
logical.logical_x_to_physical_coord(click_range.end - 1);
Self {
start: SelectionCoordinate::x_y(start_x, start_y),
end: SelectionCoordinate::x_y(end_x, end_y),
}
}
}
};
};
}
}
// Shouldn't happen, but return a reasonable fallback
@ -225,9 +327,9 @@ impl SelectionRange {
0..0
} else {
if norm.start.x <= norm.end.x {
norm.start.x..norm.end.x.saturating_add(1)
norm.start.x.range(norm.end.x.saturating_add(1))
} else {
norm.end.x..norm.start.x.saturating_add(1)
norm.end.x.range(norm.start.x.saturating_add(1))
}
}
} else {
@ -236,16 +338,16 @@ impl SelectionRange {
} else if norm.start.y == norm.end.y {
// A single line selection
if norm.start.x <= norm.end.x {
norm.start.x..norm.end.x.saturating_add(1)
norm.start.x.range(norm.end.x.saturating_add(1))
} else {
norm.end.x..norm.start.x.saturating_add(1)
norm.end.x.range(norm.start.x.saturating_add(1))
}
} else if row == norm.end.y {
// last line of multi-line
0..norm.end.x.saturating_add(1)
SelectionX::Cell(0).range(norm.end.x.saturating_add(1))
} else if row == norm.start.y {
// first line of multi-line
norm.start.x..usize::max_value()
norm.start.x.range(SelectionX::Cell(usize::max_value()))
} else {
// some "middle" line of multi-line
0..usize::max_value()

View File

@ -1,4 +1,4 @@
use crate::selection::{Selection, SelectionCoordinate, SelectionMode, SelectionRange};
use crate::selection::{Selection, SelectionCoordinate, SelectionMode, SelectionRange, SelectionX};
use ::window::WindowOps;
use mux::pane::{Pane, PaneId};
use std::cell::RefMut;
@ -83,7 +83,7 @@ impl super::TermWindow {
let origin = self
.selection(pane.pane_id())
.origin
.unwrap_or(SelectionCoordinate { x, y });
.unwrap_or(SelectionCoordinate::x_y(x, y));
self.selection(pane.pane_id()).origin = Some(origin);
self.selection(pane.pane_id()).rectangular = mode == SelectionMode::Block;
@ -94,11 +94,11 @@ impl super::TermWindow {
if x >= origin.x {
// If the selection is extending forwards from the origin,
// it includes the origin
(origin.x, x.saturating_sub(1))
(origin.x, SelectionX::Cell(x).saturating_sub(1))
} else {
// If the selection is extending backwards from the origin,
// it doesn't include the origin
(origin.x.saturating_sub(1), x)
(origin.x.saturating_sub(1), SelectionX::Cell(x))
}
} else {
if (x >= origin.x && y == origin.y) || y > origin.y {
@ -108,30 +108,34 @@ impl super::TermWindow {
// screen, so this causes a visual cell on the screen to be selected when
// the mouse moves over 50% of its width, which effectively means the next
// cell is being reported here, hence it's excluded
(origin.x, x.saturating_sub(1))
(origin.x, SelectionX::Cell(x).saturating_sub(1))
} else {
// If the selection is extending backwards from the origin, it doesn't
// include the origin and includes the cell under the cursor, which has
// the same effect as described above when going backwards
(origin.x.saturating_sub(1), x)
(origin.x.saturating_sub(1), SelectionX::Cell(x))
}
};
self.selection(pane.pane_id()).range = if origin.x != x || origin.y != y {
// Only considers a selection if the cursor moved from the origin point
Some(
SelectionRange::start(SelectionCoordinate {
x: start_x,
y: origin.y,
})
.extend(SelectionCoordinate { x: end_x, y }),
)
} else {
None
};
self.selection(pane.pane_id()).range =
if mode == SelectionMode::Block && origin.x == x {
// Ignore rectangle selections with a width of zero
None
} else if origin.x != x || origin.y != y {
// Only considers a selection if the cursor moved from the origin point
Some(
SelectionRange::start(SelectionCoordinate {
x: start_x,
y: origin.y,
})
.extend(SelectionCoordinate { x: end_x, y }),
)
} else {
None
};
}
SelectionMode::Word => {
let end_word = SelectionRange::word_around(SelectionCoordinate { x, y }, &**pane);
let end_word = SelectionRange::word_around(SelectionCoordinate::x_y(x, y), &**pane);
let start_coord = self
.selection(pane.pane_id())
@ -145,7 +149,7 @@ impl super::TermWindow {
self.selection(pane.pane_id()).rectangular = false;
}
SelectionMode::Line => {
let end_line = SelectionRange::line_around(SelectionCoordinate { x, y }, &**pane);
let end_line = SelectionRange::line_around(SelectionCoordinate::x_y(x, y), &**pane);
let start_coord = self
.selection(pane.pane_id())
@ -159,7 +163,7 @@ impl super::TermWindow {
self.selection(pane.pane_id()).rectangular = false;
}
SelectionMode::SemanticZone => {
let end_word = SelectionRange::zone_around(SelectionCoordinate { x, y }, &**pane);
let end_word = SelectionRange::zone_around(SelectionCoordinate::x_y(x, y), &**pane);
let start_coord = self
.selection(pane.pane_id())
@ -196,7 +200,7 @@ impl super::TermWindow {
};
match mode {
SelectionMode::Line => {
let start = SelectionCoordinate { x, y };
let start = SelectionCoordinate::x_y(x, y);
let selection_range = SelectionRange::line_around(start, &**pane);
self.selection(pane.pane_id()).origin = Some(start);
@ -205,7 +209,7 @@ impl super::TermWindow {
}
SelectionMode::Word => {
let selection_range =
SelectionRange::word_around(SelectionCoordinate { x, y }, &**pane);
SelectionRange::word_around(SelectionCoordinate::x_y(x, y), &**pane);
self.selection(pane.pane_id()).origin = Some(selection_range.start);
self.selection(pane.pane_id()).range = Some(selection_range);
@ -213,7 +217,7 @@ impl super::TermWindow {
}
SelectionMode::SemanticZone => {
let selection_range =
SelectionRange::zone_around(SelectionCoordinate { x, y }, &**pane);
SelectionRange::zone_around(SelectionCoordinate::x_y(x, y), &**pane);
self.selection(pane.pane_id()).origin = Some(selection_range.start);
self.selection(pane.pane_id()).range = Some(selection_range);
@ -221,7 +225,7 @@ impl super::TermWindow {
}
SelectionMode::Cell | SelectionMode::Block => {
self.selection(pane.pane_id())
.begin(SelectionCoordinate { x, y });
.begin(SelectionCoordinate::x_y(x, y));
self.selection(pane.pane_id()).rectangular = mode == SelectionMode::Block;
}
}