mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-25 14:05:41 -05:00
Add block selection
This implements a block selection mode which can be triggered by holding Control before starting a selection. If text is copied using this block selection, newlines will be automatically added to the end of the lines. This fixes #526.
This commit is contained in:
parent
a1c70b1d68
commit
e0a286515f
5 changed files with 270 additions and 166 deletions
|
@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Block selection mode when Control is held while starting a selection
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- GUI programs launched by Alacritty starting in the background on X11
|
- GUI programs launched by Alacritty starting in the background on X11
|
||||||
|
|
|
@ -102,6 +102,12 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> {
|
||||||
self.terminal.dirty = true;
|
self.terminal.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn block_selection(&mut self, point: Point, side: Side) {
|
||||||
|
let point = self.terminal.visible_to_buffer(point);
|
||||||
|
*self.terminal.selection_mut() = Some(Selection::block(point, side));
|
||||||
|
self.terminal.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
fn semantic_selection(&mut self, point: Point) {
|
fn semantic_selection(&mut self, point: Point) {
|
||||||
let point = self.terminal.visible_to_buffer(point);
|
let point = self.terminal.visible_to_buffer(point);
|
||||||
*self.terminal.selection_mut() = Some(Selection::semantic(point));
|
*self.terminal.selection_mut() = Some(Selection::semantic(point));
|
||||||
|
|
|
@ -66,6 +66,7 @@ pub trait ActionContext {
|
||||||
fn clear_selection(&mut self);
|
fn clear_selection(&mut self);
|
||||||
fn update_selection(&mut self, point: Point, side: Side);
|
fn update_selection(&mut self, point: Point, side: Side);
|
||||||
fn simple_selection(&mut self, point: Point, side: Side);
|
fn simple_selection(&mut self, point: Point, side: Side);
|
||||||
|
fn block_selection(&mut self, point: Point, side: Side);
|
||||||
fn semantic_selection(&mut self, point: Point);
|
fn semantic_selection(&mut self, point: Point);
|
||||||
fn line_selection(&mut self, point: Point);
|
fn line_selection(&mut self, point: Point);
|
||||||
fn selection_is_empty(&self) -> bool;
|
fn selection_is_empty(&self) -> bool;
|
||||||
|
@ -612,8 +613,12 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
|
||||||
// Start new empty selection
|
// Start new empty selection
|
||||||
let side = self.ctx.mouse().cell_side;
|
let side = self.ctx.mouse().cell_side;
|
||||||
if let Some(point) = point {
|
if let Some(point) = point {
|
||||||
|
if modifiers.ctrl {
|
||||||
|
self.ctx.block_selection(point, side);
|
||||||
|
} else {
|
||||||
self.ctx.simple_selection(point, side);
|
self.ctx.simple_selection(point, side);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let report_modes =
|
let report_modes =
|
||||||
TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
|
TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
|
||||||
|
@ -991,6 +996,8 @@ mod tests {
|
||||||
|
|
||||||
fn simple_selection(&mut self, _point: Point, _side: Side) {}
|
fn simple_selection(&mut self, _point: Point, _side: Side) {}
|
||||||
|
|
||||||
|
fn block_selection(&mut self, _point: Point, _side: Side) {}
|
||||||
|
|
||||||
fn copy_selection(&mut self, _: ClipboardType) {}
|
fn copy_selection(&mut self, _: ClipboardType) {}
|
||||||
|
|
||||||
fn clear_selection(&mut self) {}
|
fn clear_selection(&mut self) {}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
//! also be cleared if the user clicks off of the selection.
|
//! also be cleared if the user clicks off of the selection.
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::index::{Column, Point, Side};
|
use crate::index::{Column, Line, Point, Side};
|
||||||
use crate::term::cell::Flags;
|
use crate::term::cell::Flags;
|
||||||
use crate::term::{Search, Term};
|
use crate::term::{Search, Term};
|
||||||
|
|
||||||
|
@ -45,6 +45,10 @@ pub enum Selection {
|
||||||
/// The region representing start and end of cursor movement
|
/// The region representing start and end of cursor movement
|
||||||
region: Range<Anchor>,
|
region: Range<Anchor>,
|
||||||
},
|
},
|
||||||
|
Block {
|
||||||
|
/// The region representing start and end of cursor movement
|
||||||
|
region: Range<Anchor>,
|
||||||
|
},
|
||||||
Semantic {
|
Semantic {
|
||||||
/// The region representing start and end of cursor movement
|
/// The region representing start and end of cursor movement
|
||||||
region: Range<Point<isize>>,
|
region: Range<Point<isize>>,
|
||||||
|
@ -52,10 +56,6 @@ pub enum Selection {
|
||||||
Lines {
|
Lines {
|
||||||
/// The region representing start and end of cursor movement
|
/// The region representing start and end of cursor movement
|
||||||
region: Range<Point<isize>>,
|
region: Range<Point<isize>>,
|
||||||
|
|
||||||
/// The line under the initial point. This is always selected regardless
|
|
||||||
/// of which way the cursor is moved.
|
|
||||||
initial_line: isize,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +79,19 @@ pub trait Dimensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selection {
|
impl Selection {
|
||||||
|
pub fn rotate(&mut self, offset: isize) {
|
||||||
|
match *self {
|
||||||
|
Selection::Simple { ref mut region } | Selection::Block { ref mut region } => {
|
||||||
|
region.start.point.line += offset;
|
||||||
|
region.end.point.line += offset;
|
||||||
|
},
|
||||||
|
Selection::Semantic { ref mut region } | Selection::Lines { ref mut region } => {
|
||||||
|
region.start.line += offset;
|
||||||
|
region.end.line += offset;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn simple(location: Point<usize>, side: Side) -> Selection {
|
pub fn simple(location: Point<usize>, side: Side) -> Selection {
|
||||||
Selection::Simple {
|
Selection::Simple {
|
||||||
region: Range {
|
region: Range {
|
||||||
|
@ -88,20 +101,11 @@ impl Selection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rotate(&mut self, offset: isize) {
|
pub fn block(location: Point<usize>, side: Side) -> Selection {
|
||||||
match *self {
|
Selection::Block {
|
||||||
Selection::Simple { ref mut region } => {
|
region: Range {
|
||||||
region.start.point.line += offset;
|
start: Anchor::new(location.into(), side),
|
||||||
region.end.point.line += offset;
|
end: Anchor::new(location.into(), side),
|
||||||
},
|
|
||||||
Selection::Semantic { ref mut region } => {
|
|
||||||
region.start.line += offset;
|
|
||||||
region.end.line += offset;
|
|
||||||
},
|
|
||||||
Selection::Lines { ref mut region, ref mut initial_line } => {
|
|
||||||
region.start.line += offset;
|
|
||||||
region.end.line += offset;
|
|
||||||
*initial_line += offset;
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,29 +115,49 @@ impl Selection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lines(point: Point<usize>) -> Selection {
|
pub fn lines(point: Point<usize>) -> Selection {
|
||||||
Selection::Lines {
|
Selection::Lines { region: Range { start: point.into(), end: point.into() } }
|
||||||
region: Range { start: point.into(), end: point.into() },
|
|
||||||
initial_line: point.line as isize,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, location: Point<usize>, side: Side) {
|
pub fn update(&mut self, location: Point<usize>, side: Side) {
|
||||||
// Always update the `end`; can normalize later during span generation.
|
// Always update the `end`; can normalize later during span generation.
|
||||||
match *self {
|
match *self {
|
||||||
Selection::Simple { ref mut region } => {
|
Selection::Simple { ref mut region } | Selection::Block { ref mut region } => {
|
||||||
region.end = Anchor::new(location.into(), side);
|
region.end = Anchor::new(location.into(), side);
|
||||||
},
|
},
|
||||||
Selection::Semantic { ref mut region } | Selection::Lines { ref mut region, .. } => {
|
Selection::Semantic { ref mut region } | Selection::Lines { ref mut region } => {
|
||||||
region.end = location.into();
|
region.end = location.into();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Selection::Simple { ref region } | Selection::Block { ref region } => {
|
||||||
|
let (start, end) =
|
||||||
|
if Selection::points_need_swap(region.start.point, region.end.point) {
|
||||||
|
(®ion.end, ®ion.start)
|
||||||
|
} else {
|
||||||
|
(®ion.start, ®ion.end)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Empty when single cell with identical sides or two cell with right+left sides
|
||||||
|
start == end
|
||||||
|
|| (start.side == Side::Left
|
||||||
|
&& end.side == Side::Right
|
||||||
|
&& start.point.line == end.point.line
|
||||||
|
&& start.point.col == end.point.col + 1)
|
||||||
|
},
|
||||||
|
Selection::Semantic { .. } | Selection::Lines { .. } => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_span(&self, term: &Term) -> Option<Span> {
|
pub fn to_span(&self, term: &Term) -> Option<Span> {
|
||||||
// Get both sides of the selection
|
// Get both sides of the selection
|
||||||
let (mut start, mut end) = match *self {
|
let (mut start, mut end) = match *self {
|
||||||
Selection::Simple { ref region } => (region.start.point, region.end.point),
|
Selection::Simple { ref region } | Selection::Block { ref region } => {
|
||||||
Selection::Semantic { ref region } | Selection::Lines { ref region, .. } => {
|
(region.start.point, region.end.point)
|
||||||
|
},
|
||||||
|
Selection::Semantic { ref region } | Selection::Lines { ref region } => {
|
||||||
(region.start, region.end)
|
(region.start, region.end)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -150,11 +174,23 @@ impl Selection {
|
||||||
let (start, end) = Selection::grid_clamp(start, end, lines, cols)?;
|
let (start, end) = Selection::grid_clamp(start, end, lines, cols)?;
|
||||||
|
|
||||||
let span = match *self {
|
let span = match *self {
|
||||||
Selection::Simple { ref region } if needs_swap => {
|
|
||||||
Selection::span_simple(term, start, end, region.end.side, region.start.side)
|
|
||||||
},
|
|
||||||
Selection::Simple { ref region } => {
|
Selection::Simple { ref region } => {
|
||||||
Selection::span_simple(term, start, end, region.start.side, region.end.side)
|
let (start_side, end_side) = if needs_swap {
|
||||||
|
(region.end.side, region.start.side)
|
||||||
|
} else {
|
||||||
|
(region.start.side, region.end.side)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.span_simple(term, start, end, start_side, end_side)
|
||||||
|
},
|
||||||
|
Selection::Block { ref region } => {
|
||||||
|
let (start_side, end_side) = if needs_swap {
|
||||||
|
(region.end.side, region.start.side)
|
||||||
|
} else {
|
||||||
|
(region.start.side, region.end.side)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.span_block(start, end, start_side, end_side)
|
||||||
},
|
},
|
||||||
Selection::Semantic { .. } => Selection::span_semantic(term, start, end),
|
Selection::Semantic { .. } => Selection::span_semantic(term, start, end),
|
||||||
Selection::Lines { .. } => Selection::span_lines(term, start, end),
|
Selection::Lines { .. } => Selection::span_lines(term, start, end),
|
||||||
|
@ -180,85 +216,9 @@ impl Selection {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
match *self {
|
|
||||||
Selection::Simple { ref region } => {
|
|
||||||
region.start == region.end && region.start.side == region.end.side
|
|
||||||
},
|
|
||||||
Selection::Semantic { .. } | Selection::Lines { .. } => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn span_semantic<T>(term: &T, start: Point<isize>, end: Point<isize>) -> Option<Span>
|
|
||||||
where
|
|
||||||
T: Search + Dimensions,
|
|
||||||
{
|
|
||||||
let (start, end) = if start == end {
|
|
||||||
if let Some(end) = term.bracket_search(start.into()) {
|
|
||||||
(start.into(), end)
|
|
||||||
} else {
|
|
||||||
(term.semantic_search_right(start.into()), term.semantic_search_left(end.into()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(term.semantic_search_right(start.into()), term.semantic_search_left(end.into()))
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(Span { start, end })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn span_lines<T>(term: &T, mut start: Point<isize>, mut end: Point<isize>) -> Option<Span>
|
|
||||||
where
|
|
||||||
T: Dimensions,
|
|
||||||
{
|
|
||||||
start.col = term.dimensions().col - 1;
|
|
||||||
end.col = Column(0);
|
|
||||||
|
|
||||||
Some(Span { start: start.into(), end: end.into() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn span_simple<T>(
|
|
||||||
term: &T,
|
|
||||||
mut start: Point<isize>,
|
|
||||||
mut end: Point<isize>,
|
|
||||||
start_side: Side,
|
|
||||||
end_side: Side,
|
|
||||||
) -> Option<Span>
|
|
||||||
where
|
|
||||||
T: Dimensions,
|
|
||||||
{
|
|
||||||
// No selection for single cell with identical sides or two cell with right+left sides
|
|
||||||
if (start == end && start_side == end_side)
|
|
||||||
|| (end_side == Side::Right
|
|
||||||
&& start_side == Side::Left
|
|
||||||
&& start.line == end.line
|
|
||||||
&& start.col == end.col + 1)
|
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove last cell if selection ends to the left of a cell
|
|
||||||
if start_side == Side::Left && start != end {
|
|
||||||
// Special case when selection starts to left of first cell
|
|
||||||
if start.col == Column(0) {
|
|
||||||
start.col = term.dimensions().col - 1;
|
|
||||||
start.line += 1;
|
|
||||||
} else {
|
|
||||||
start.col -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove first cell if selection starts at the right of a cell
|
|
||||||
if end_side == Side::Right && start != end {
|
|
||||||
end.col += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the selection with all cells inclusive
|
|
||||||
Some(Span { start: start.into(), end: end.into() })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bring start and end points in the correct order
|
// Bring start and end points in the correct order
|
||||||
fn points_need_swap(start: Point<isize>, end: Point<isize>) -> bool {
|
fn points_need_swap(start: Point<isize>, end: Point<isize>) -> bool {
|
||||||
start.line > end.line || start.line == end.line && start.col <= end.col
|
start.line > end.line || start.line == end.line && start.col < end.col
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clamp selection inside the grid to prevent out of bounds errors
|
// Clamp selection inside the grid to prevent out of bounds errors
|
||||||
|
@ -292,15 +252,129 @@ impl Selection {
|
||||||
|
|
||||||
Some((start, end))
|
Some((start, end))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn span_semantic<T>(term: &T, start: Point<isize>, end: Point<isize>) -> Option<Span>
|
||||||
|
where
|
||||||
|
T: Search + Dimensions,
|
||||||
|
{
|
||||||
|
let (start, end) = if start == end {
|
||||||
|
if let Some(end) = term.bracket_search(start.into()) {
|
||||||
|
(start.into(), end)
|
||||||
|
} else {
|
||||||
|
(term.semantic_search_right(start.into()), term.semantic_search_left(end.into()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(term.semantic_search_right(start.into()), term.semantic_search_left(end.into()))
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Span { start, end, is_block: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span_lines<T>(term: &T, mut start: Point<isize>, mut end: Point<isize>) -> Option<Span>
|
||||||
|
where
|
||||||
|
T: Dimensions,
|
||||||
|
{
|
||||||
|
start.col = term.dimensions().col - 1;
|
||||||
|
end.col = Column(0);
|
||||||
|
|
||||||
|
Some(Span { start: start.into(), end: end.into(), is_block: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span_simple<T>(
|
||||||
|
&self,
|
||||||
|
term: &T,
|
||||||
|
mut start: Point<isize>,
|
||||||
|
mut end: Point<isize>,
|
||||||
|
start_side: Side,
|
||||||
|
end_side: Side,
|
||||||
|
) -> Option<Span>
|
||||||
|
where
|
||||||
|
T: Dimensions,
|
||||||
|
{
|
||||||
|
if self.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove last cell if selection ends to the left of a cell
|
||||||
|
if start_side == Side::Left && start != end {
|
||||||
|
// Special case when selection starts to left of first cell
|
||||||
|
if start.col == Column(0) {
|
||||||
|
start.col = term.dimensions().col - 1;
|
||||||
|
start.line += 1;
|
||||||
|
} else {
|
||||||
|
start.col -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove first cell if selection starts at the right of a cell
|
||||||
|
if end_side == Side::Right && start != end {
|
||||||
|
end.col += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the selection with all cells inclusive
|
||||||
|
Some(Span { start: start.into(), end: end.into(), is_block: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span_block(
|
||||||
|
&self,
|
||||||
|
mut start: Point<isize>,
|
||||||
|
mut end: Point<isize>,
|
||||||
|
mut start_side: Side,
|
||||||
|
mut end_side: Side,
|
||||||
|
) -> Option<Span> {
|
||||||
|
if self.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always go bottom-right -> top-left
|
||||||
|
if start.col < end.col {
|
||||||
|
std::mem::swap(&mut start_side, &mut end_side);
|
||||||
|
std::mem::swap(&mut start.col, &mut end.col);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove last cell if selection ends to the left of a cell
|
||||||
|
if start_side == Side::Left && start != end && start.col.0 > 0 {
|
||||||
|
start.col -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove first cell if selection starts at the right of a cell
|
||||||
|
if end_side == Side::Right && start != end {
|
||||||
|
end.col += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the selection with all cells inclusive
|
||||||
|
Some(Span { start: start.into(), end: end.into(), is_block: true })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a span of selected cells
|
/// Represents a span of selected cells
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct Span {
|
pub struct Span {
|
||||||
/// Start point from bottom of buffer
|
/// Start point from bottom of buffer
|
||||||
pub start: Point<usize>,
|
pub start: Point<usize>,
|
||||||
/// End point towards top of buffer
|
/// End point towards top of buffer
|
||||||
pub end: Point<usize>,
|
pub end: Point<usize>,
|
||||||
|
/// Whether this selection is a block selection
|
||||||
|
pub is_block: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SelectionRange {
|
||||||
|
start: Point,
|
||||||
|
end: Point,
|
||||||
|
is_block: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectionRange {
|
||||||
|
pub fn new(start: Point, end: Point, is_block: bool) -> Self {
|
||||||
|
Self { start, end, is_block }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, col: Column, line: Line) -> bool {
|
||||||
|
self.start.line <= line
|
||||||
|
&& self.end.line >= line
|
||||||
|
&& (self.start.col <= col || (self.start.line != line && !self.is_block))
|
||||||
|
&& (self.end.col >= col || (self.end.line != line && !self.is_block))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests for selection
|
/// Tests for selection
|
||||||
|
@ -350,7 +424,8 @@ mod test {
|
||||||
|
|
||||||
assert_eq!(selection.to_span(&term(1, 1)).unwrap(), Span {
|
assert_eq!(selection.to_span(&term(1, 1)).unwrap(), Span {
|
||||||
start: location,
|
start: location,
|
||||||
end: location
|
end: location,
|
||||||
|
is_block: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +442,8 @@ mod test {
|
||||||
|
|
||||||
assert_eq!(selection.to_span(&term(1, 1)).unwrap(), Span {
|
assert_eq!(selection.to_span(&term(1, 1)).unwrap(), Span {
|
||||||
start: location,
|
start: location,
|
||||||
end: location
|
end: location,
|
||||||
|
is_block: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,6 +490,7 @@ mod test {
|
||||||
assert_eq!(selection.to_span(&term(5, 2)).unwrap(), Span {
|
assert_eq!(selection.to_span(&term(5, 2)).unwrap(), Span {
|
||||||
start: Point::new(0, Column(1)),
|
start: Point::new(0, Column(1)),
|
||||||
end: Point::new(1, Column(2)),
|
end: Point::new(1, Column(2)),
|
||||||
|
is_block: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,11 +514,12 @@ mod test {
|
||||||
assert_eq!(selection.to_span(&term(5, 2)).unwrap(), Span {
|
assert_eq!(selection.to_span(&term(5, 2)).unwrap(), Span {
|
||||||
start: Point::new(0, Column(1)),
|
start: Point::new(0, Column(1)),
|
||||||
end: Point::new(1, Column(1)),
|
end: Point::new(1, Column(1)),
|
||||||
|
is_block: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alt_screen_lines() {
|
fn line_selection() {
|
||||||
let mut selection = Selection::lines(Point::new(0, Column(0)));
|
let mut selection = Selection::lines(Point::new(0, Column(0)));
|
||||||
selection.update(Point::new(5, Column(3)), Side::Right);
|
selection.update(Point::new(5, Column(3)), Side::Right);
|
||||||
selection.rotate(-3);
|
selection.rotate(-3);
|
||||||
|
@ -449,11 +527,12 @@ mod test {
|
||||||
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
|
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
|
||||||
start: Point::new(0, Column(4)),
|
start: Point::new(0, Column(4)),
|
||||||
end: Point::new(2, Column(0)),
|
end: Point::new(2, Column(0)),
|
||||||
|
is_block: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alt_screen_semantic() {
|
fn semantic_selection() {
|
||||||
let mut selection = Selection::semantic(Point::new(0, Column(0)));
|
let mut selection = Selection::semantic(Point::new(0, Column(0)));
|
||||||
selection.update(Point::new(5, Column(3)), Side::Right);
|
selection.update(Point::new(5, Column(3)), Side::Right);
|
||||||
selection.rotate(-3);
|
selection.rotate(-3);
|
||||||
|
@ -461,11 +540,12 @@ mod test {
|
||||||
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
|
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
|
||||||
start: Point::new(0, Column(4)),
|
start: Point::new(0, Column(4)),
|
||||||
end: Point::new(2, Column(3)),
|
end: Point::new(2, Column(3)),
|
||||||
|
is_block: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alt_screen_simple() {
|
fn simple_selection() {
|
||||||
let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
|
let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
|
||||||
selection.update(Point::new(5, Column(3)), Side::Right);
|
selection.update(Point::new(5, Column(3)), Side::Right);
|
||||||
selection.rotate(-3);
|
selection.rotate(-3);
|
||||||
|
@ -473,6 +553,20 @@ mod test {
|
||||||
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
|
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
|
||||||
start: Point::new(0, Column(4)),
|
start: Point::new(0, Column(4)),
|
||||||
end: Point::new(2, Column(4)),
|
end: Point::new(2, Column(4)),
|
||||||
|
is_block: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_selection() {
|
||||||
|
let mut selection = Selection::block(Point::new(0, Column(0)), Side::Right);
|
||||||
|
selection.update(Point::new(5, Column(3)), Side::Right);
|
||||||
|
selection.rotate(-3);
|
||||||
|
|
||||||
|
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
|
||||||
|
start: Point::new(0, Column(4)),
|
||||||
|
end: Point::new(2, Column(4)),
|
||||||
|
is_block: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,6 +586,7 @@ mod test {
|
||||||
assert_eq!(selection.to_span(&term).unwrap(), Span {
|
assert_eq!(selection.to_span(&term).unwrap(), Span {
|
||||||
start: Point::new(0, Column(9)),
|
start: Point::new(0, Column(9)),
|
||||||
end: Point::new(0, Column(0)),
|
end: Point::new(0, Column(0)),
|
||||||
|
is_block: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ use crate::grid::{
|
||||||
use crate::index::{self, Column, Contains, IndexRange, Line, Linear, Point};
|
use crate::index::{self, Column, Contains, IndexRange, Line, Linear, Point};
|
||||||
use crate::input::FONT_SIZE_STEP;
|
use crate::input::FONT_SIZE_STEP;
|
||||||
use crate::message_bar::MessageBuffer;
|
use crate::message_bar::MessageBuffer;
|
||||||
use crate::selection::{self, Selection, Span};
|
use crate::selection::{self, Selection, SelectionRange, Span};
|
||||||
use crate::term::cell::{Cell, Flags, LineLength};
|
use crate::term::cell::{Cell, Flags, LineLength};
|
||||||
use crate::term::color::Rgb;
|
use crate::term::color::Rgb;
|
||||||
use crate::url::{Url, UrlParser};
|
use crate::url::{Url, UrlParser};
|
||||||
|
@ -215,7 +215,7 @@ pub struct RenderableCellsIter<'a> {
|
||||||
cursor_style: CursorStyle,
|
cursor_style: CursorStyle,
|
||||||
config: &'a Config,
|
config: &'a Config,
|
||||||
colors: &'a color::List,
|
colors: &'a color::List,
|
||||||
selection: Option<RangeInclusive<index::Linear>>,
|
selection: Option<SelectionRange>,
|
||||||
url_highlight: &'a Option<RangeInclusive<index::Linear>>,
|
url_highlight: &'a Option<RangeInclusive<index::Linear>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,19 +240,23 @@ impl<'a> RenderableCellsIter<'a> {
|
||||||
let start_line = grid.buffer_line_to_visible(span.start.line);
|
let start_line = grid.buffer_line_to_visible(span.start.line);
|
||||||
let end_line = grid.buffer_line_to_visible(span.end.line);
|
let end_line = grid.buffer_line_to_visible(span.end.line);
|
||||||
|
|
||||||
|
// Limit block selection columns to within start/end points
|
||||||
|
let (limit_start, limit_end) =
|
||||||
|
if span.is_block { (span.start.col, span.end.col) } else { (Column(0), Column(0)) };
|
||||||
|
|
||||||
// Get start/end locations based on what part of selection is on screen
|
// Get start/end locations based on what part of selection is on screen
|
||||||
let locations = match (start_line, end_line) {
|
let locations = match (start_line, end_line) {
|
||||||
(ViewportPosition::Visible(start_line), ViewportPosition::Visible(end_line)) => {
|
(ViewportPosition::Visible(start_line), ViewportPosition::Visible(end_line)) => {
|
||||||
Some((start_line, span.start.col, end_line, span.end.col))
|
Some((start_line, span.start.col, end_line, span.end.col))
|
||||||
},
|
},
|
||||||
(ViewportPosition::Visible(start_line), ViewportPosition::Above) => {
|
(ViewportPosition::Visible(start_line), ViewportPosition::Above) => {
|
||||||
Some((start_line, span.start.col, Line(0), Column(0)))
|
Some((start_line, span.start.col, Line(0), limit_end))
|
||||||
},
|
},
|
||||||
(ViewportPosition::Below, ViewportPosition::Visible(end_line)) => {
|
(ViewportPosition::Below, ViewportPosition::Visible(end_line)) => {
|
||||||
Some((grid.num_lines(), Column(0), end_line, span.end.col))
|
Some((grid.num_lines(), limit_start, end_line, span.end.col))
|
||||||
},
|
},
|
||||||
(ViewportPosition::Below, ViewportPosition::Above) => {
|
(ViewportPosition::Below, ViewportPosition::Above) => {
|
||||||
Some((grid.num_lines(), Column(0), Line(0), Column(0)))
|
Some((grid.num_lines(), limit_start, Line(0), limit_end))
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
@ -267,11 +271,7 @@ impl<'a> RenderableCellsIter<'a> {
|
||||||
::std::mem::swap(&mut start, &mut end);
|
::std::mem::swap(&mut start, &mut end);
|
||||||
}
|
}
|
||||||
|
|
||||||
let cols = grid.num_cols();
|
SelectionRange::new(start, end, span.is_block)
|
||||||
let start = Linear::from_point(cols, start.into());
|
|
||||||
let end = Linear::from_point(cols, end.into());
|
|
||||||
|
|
||||||
RangeInclusive::new(start, end)
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -425,9 +425,11 @@ impl<'a> Iterator for RenderableCellsIter<'a> {
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
loop {
|
loop {
|
||||||
if self.cursor_offset == self.inner.offset() && self.inner.column() == self.cursor.col {
|
if self.cursor_offset == self.inner.offset() && self.inner.column() == self.cursor.col {
|
||||||
let index = Linear::new(self.grid.num_cols(), self.cursor.col, self.cursor.line);
|
let selected = self
|
||||||
let selected =
|
.selection
|
||||||
self.selection.as_ref().map(|range| range.contains_(index)).unwrap_or(false);
|
.as_ref()
|
||||||
|
.map(|range| range.contains(self.cursor.col, self.cursor.line))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
// Handle cursor
|
// Handle cursor
|
||||||
if let Some(cursor_key) = self.cursor_key.take() {
|
if let Some(cursor_key) = self.cursor_key.take() {
|
||||||
|
@ -464,12 +466,14 @@ impl<'a> Iterator for RenderableCellsIter<'a> {
|
||||||
} else {
|
} else {
|
||||||
let mut cell = self.inner.next()?;
|
let mut cell = self.inner.next()?;
|
||||||
|
|
||||||
let index = Linear::new(self.grid.num_cols(), cell.column, cell.line);
|
let selected = self
|
||||||
|
.selection
|
||||||
let selected =
|
.as_ref()
|
||||||
self.selection.as_ref().map(|range| range.contains_(index)).unwrap_or(false);
|
.map(|range| range.contains(cell.column, cell.line))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
// Underline URL highlights
|
// Underline URL highlights
|
||||||
|
let index = Linear::new(self.grid.num_cols(), cell.column, cell.line);
|
||||||
if self.url_highlight.as_ref().map(|range| range.contains_(index)).unwrap_or(false)
|
if self.url_highlight.as_ref().map(|range| range.contains_(index)).unwrap_or(false)
|
||||||
{
|
{
|
||||||
cell.inner.flags.insert(Flags::UNDERLINE);
|
cell.inner.flags.insert(Flags::UNDERLINE);
|
||||||
|
@ -987,28 +991,10 @@ impl Term {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selection_to_string(&self) -> Option<String> {
|
pub fn selection_to_string(&self) -> Option<String> {
|
||||||
/// Need a generic push() for the Append trait
|
trait Append {
|
||||||
trait PushChar {
|
|
||||||
fn push_char(&mut self, c: char);
|
|
||||||
fn maybe_newline(&mut self, grid: &Grid<Cell>, line: usize, ending: Column) {
|
|
||||||
if ending != Column(0)
|
|
||||||
&& !grid[line][ending - 1].flags.contains(cell::Flags::WRAPLINE)
|
|
||||||
{
|
|
||||||
self.push_char('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PushChar for String {
|
|
||||||
#[inline]
|
|
||||||
fn push_char(&mut self, c: char) {
|
|
||||||
self.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Append: PushChar {
|
|
||||||
fn append(
|
fn append(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
append_newline: bool,
|
||||||
grid: &Grid<Cell>,
|
grid: &Grid<Cell>,
|
||||||
tabs: &TabStops,
|
tabs: &TabStops,
|
||||||
line: usize,
|
line: usize,
|
||||||
|
@ -1019,6 +1005,7 @@ impl Term {
|
||||||
impl Append for String {
|
impl Append for String {
|
||||||
fn append(
|
fn append(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
append_newline: bool,
|
||||||
grid: &Grid<Cell>,
|
grid: &Grid<Cell>,
|
||||||
tabs: &TabStops,
|
tabs: &TabStops,
|
||||||
mut line: usize,
|
mut line: usize,
|
||||||
|
@ -1031,9 +1018,7 @@ impl Term {
|
||||||
let line_length = grid_line.line_length();
|
let line_length = grid_line.line_length();
|
||||||
let line_end = min(line_length, cols.end + 1);
|
let line_end = min(line_length, cols.end + 1);
|
||||||
|
|
||||||
if line_end.0 == 0 && cols.end >= grid.num_cols() - 1 {
|
if cols.start < line_end {
|
||||||
self.push('\n');
|
|
||||||
} else if cols.start < line_end {
|
|
||||||
let mut tab_mode = false;
|
let mut tab_mode = false;
|
||||||
|
|
||||||
for col in IndexRange::from(cols.start..line_end) {
|
for col in IndexRange::from(cols.start..line_end) {
|
||||||
|
@ -1059,16 +1044,20 @@ impl Term {
|
||||||
tab_mode = true;
|
tab_mode = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cols.end >= grid.num_cols() - 1 {
|
|
||||||
self.maybe_newline(grid, line, line_end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if append_newline
|
||||||
|
|| (cols.end >= grid.num_cols() - 1
|
||||||
|
&& (line_end == Column(0)
|
||||||
|
|| !grid[line][line_end - 1].flags.contains(cell::Flags::WRAPLINE)))
|
||||||
|
{
|
||||||
|
self.push('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let selection = self.grid.selection.clone()?;
|
let selection = self.grid.selection.clone()?;
|
||||||
let Span { mut start, mut end } = selection.to_span(self)?;
|
let Span { mut start, mut end, is_block } = selection.to_span(self)?;
|
||||||
|
|
||||||
let mut res = String::new();
|
let mut res = String::new();
|
||||||
|
|
||||||
|
@ -1077,35 +1066,38 @@ impl Term {
|
||||||
}
|
}
|
||||||
|
|
||||||
let line_count = end.line - start.line;
|
let line_count = end.line - start.line;
|
||||||
let max_col = Column(usize::max_value() - 1);
|
|
||||||
|
// Setup block selection start/end point limits
|
||||||
|
let (limit_start, limit_end) =
|
||||||
|
if is_block { (end.col, start.col) } else { (Column(0), self.grid.num_cols()) };
|
||||||
|
|
||||||
match line_count {
|
match line_count {
|
||||||
// Selection within single line
|
// Selection within single line
|
||||||
0 => {
|
0 => {
|
||||||
res.append(&self.grid, &self.tabs, start.line, start.col..end.col);
|
res.append(false, &self.grid, &self.tabs, start.line, start.col..end.col);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Selection ends on line following start
|
// Selection ends on line following start
|
||||||
1 => {
|
1 => {
|
||||||
// Ending line
|
// Ending line
|
||||||
res.append(&self.grid, &self.tabs, end.line, end.col..max_col);
|
res.append(is_block, &self.grid, &self.tabs, end.line, end.col..limit_end);
|
||||||
|
|
||||||
// Starting line
|
// Starting line
|
||||||
res.append(&self.grid, &self.tabs, start.line, Column(0)..start.col);
|
res.append(false, &self.grid, &self.tabs, start.line, limit_start..start.col);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Multi line selection
|
// Multi line selection
|
||||||
_ => {
|
_ => {
|
||||||
// Ending line
|
// Ending line
|
||||||
res.append(&self.grid, &self.tabs, end.line, end.col..max_col);
|
res.append(is_block, &self.grid, &self.tabs, end.line, end.col..limit_end);
|
||||||
|
|
||||||
let middle_range = (start.line + 1)..(end.line);
|
let middle_range = (start.line + 1)..(end.line);
|
||||||
for line in middle_range.rev() {
|
for line in middle_range.rev() {
|
||||||
res.append(&self.grid, &self.tabs, line, Column(0)..max_col);
|
res.append(is_block, &self.grid, &self.tabs, line, limit_start..limit_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starting line
|
// Starting line
|
||||||
res.append(&self.grid, &self.tabs, start.line, Column(0)..start.col);
|
res.append(false, &self.grid, &self.tabs, start.line, limit_start..start.col);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue