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:
parent
42b83626f2
commit
6a9056d77c
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user