summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Duerr <chrisduerr@users.noreply.github.com>2018-10-20 22:30:59 +0000
committerGitHub <noreply@github.com>2018-10-20 22:30:59 +0000
commit4380d0864b1098909bdcfec132b866c34924517e (patch)
treef3c97e5897d1d8007f7fb651bfa2bff814bbc910
parent34ada9295d0d3801f305a9818de38ebaeb784f63 (diff)
Fix rotation of selection below 0
Whenever the viewport is scrolled, the selection is rotated to make sure that it moves with the viewport. However this did not correctly handle the underflow that happens when the selection goes below 0. This resolves that problem for the selection by moving the internal line representation to an isize, thus correctly keeping track of the selection start/end points even when they have a negative index. Once the selection is converted to a span, the lines are clamped to the visible region. This fixes #1640 and fixes #1643.
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/index.rs12
-rw-r--r--src/selection.rs201
-rw-r--r--src/term/mod.rs6
4 files changed, 171 insertions, 49 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ac8c0c06..c0aa9fd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed erroneous results when using the `indexed_colors` config option
- Fixed rendering cursors other than rectangular with the RustType backend
+- Selection memory leak and glitches in the alternate screen buffer
## Version 0.2.1
diff --git a/src/index.rs b/src/index.rs
index ab8e7416..149fd458 100644
--- a/src/index.rs
+++ b/src/index.rs
@@ -52,6 +52,18 @@ impl Ord for Point {
}
}
+impl From<Point<usize>> for Point<isize> {
+ fn from(point: Point<usize>) -> Self {
+ Point::new(point.line as isize, point.col)
+ }
+}
+
+impl From<Point<isize>> for Point<usize> {
+ fn from(point: Point<isize>) -> Self {
+ Point::new(point.line as usize, point.col)
+ }
+}
+
/// A line
///
/// Newtype to avoid passing values incorrectly
diff --git a/src/selection.rs b/src/selection.rs
index 702599e3..932a61ee 100644
--- a/src/selection.rs
+++ b/src/selection.rs
@@ -46,27 +46,27 @@ pub enum Selection {
},
Semantic {
/// The region representing start and end of cursor movement
- region: Range<Point<usize>>,
+ region: Range<Point<isize>>,
},
Lines {
/// The region representing start and end of cursor movement
- region: Range<Point<usize>>,
+ region: Range<Point<isize>>,
/// The line under the initial point. This is always selected regardless
/// of which way the cursor is moved.
- initial_line: usize
+ initial_line: isize
}
}
/// A Point and side within that point.
#[derive(Debug, Clone, PartialEq)]
pub struct Anchor {
- point: Point<usize>,
+ point: Point<isize>,
side: Side,
}
impl Anchor {
- fn new(point: Point<usize>, side: Side) -> Anchor {
+ fn new(point: Point<isize>, side: Side) -> Anchor {
Anchor { point, side }
}
}
@@ -92,8 +92,8 @@ impl Selection {
pub fn simple(location: Point<usize>, side: Side) -> Selection {
Selection::Simple {
region: Range {
- start: Anchor::new(location, side),
- end: Anchor::new(location, side)
+ start: Anchor::new(location.into(), side),
+ end: Anchor::new(location.into(), side)
}
}
}
@@ -101,17 +101,17 @@ impl Selection {
pub fn rotate(&mut self, offset: isize) {
match *self {
Selection::Simple { ref mut region } => {
- region.start.point.line = (region.start.point.line as isize + offset) as usize;
- region.end.point.line = (region.end.point.line as isize + offset) as usize;
+ region.start.point.line += offset;
+ region.end.point.line += offset;
},
Selection::Semantic { ref mut region } => {
- region.start.line = (region.start.line as isize + offset) as usize;
- region.end.line = (region.end.line as isize + offset) as usize;
+ region.start.line += offset;
+ region.end.line += offset;
},
Selection::Lines { ref mut region, ref mut initial_line } => {
- region.start.line = (region.start.line as isize + offset) as usize;
- region.end.line = (region.end.line as isize + offset) as usize;
- *initial_line = (*initial_line as isize + offset) as usize;
+ region.start.line += offset;
+ region.end.line += offset;
+ *initial_line += offset;
}
}
}
@@ -119,8 +119,8 @@ impl Selection {
pub fn semantic(point: Point<usize>) -> Selection {
Selection::Semantic {
region: Range {
- start: point,
- end: point,
+ start: point.into(),
+ end: point.into(),
}
}
}
@@ -128,10 +128,10 @@ impl Selection {
pub fn lines(point: Point<usize>) -> Selection {
Selection::Lines {
region: Range {
- start: point,
- end: point
+ start: point.into(),
+ end: point.into(),
},
- initial_line: point.line
+ initial_line: point.line as isize,
}
}
@@ -139,46 +139,58 @@ impl Selection {
// Always update the `end`; can normalize later during span generation.
match *self {
Selection::Simple { ref mut region } => {
- region.end = Anchor::new(location, side);
+ region.end = Anchor::new(location.into(), side);
},
Selection::Semantic { ref mut region } |
Selection::Lines { ref mut region, .. } =>
{
- region.end = location;
+ region.end = location.into();
},
}
}
- pub fn to_span<G: SemanticSearch + Dimensions>(&self, grid: &G) -> Option<Span> {
+ pub fn to_span<G>(&self, grid: &G, alt_screen: bool) -> Option<Span>
+ where
+ G: SemanticSearch + Dimensions,
+ {
match *self {
Selection::Simple { ref region } => {
- Selection::span_simple(grid, region)
+ Selection::span_simple(grid, region, alt_screen)
},
Selection::Semantic { ref region } => {
- Selection::span_semantic(grid, region)
+ Selection::span_semantic(grid, region, alt_screen)
},
Selection::Lines { ref region, initial_line } => {
- Selection::span_lines(grid, region, initial_line)
+ Selection::span_lines(grid, region, initial_line, alt_screen)
}
}
}
+
fn span_semantic<G>(
grid: &G,
- region: &Range<Point<usize>>,
+ region: &Range<Point<isize>>,
+ alt_screen: bool,
) -> Option<Span>
where G: SemanticSearch + Dimensions
{
+ let cols = grid.dimensions().col;
+ let lines = grid.dimensions().line.0 as isize;
+
// Normalize ordering of selected cells
- let (front, tail) = if region.start < region.end {
+ let (mut front, mut tail) = if region.start < region.end {
(region.start, region.end)
} else {
(region.end, region.start)
};
+ if alt_screen {
+ Selection::alt_screen_clamp(&mut front, &mut tail, lines, cols)?;
+ }
+
let (mut start, mut end) = if front < tail && front.line == tail.line {
- (grid.semantic_search_left(front), grid.semantic_search_right(tail))
+ (grid.semantic_search_left(front.into()), grid.semantic_search_right(tail.into()))
} else {
- (grid.semantic_search_right(front), grid.semantic_search_left(tail))
+ (grid.semantic_search_right(front.into()), grid.semantic_search_left(tail.into()))
};
if start > end {
@@ -186,20 +198,29 @@ impl Selection {
}
Some(Span {
- cols: grid.dimensions().col,
+ cols,
front: start,
tail: end,
ty: SpanType::Inclusive,
})
}
- fn span_lines<G>(grid: &G, region: &Range<Point<usize>>, initial_line: usize) -> Option<Span>
- where G: Dimensions
+ fn span_lines<G>(
+ grid: &G,
+ region: &Range<Point<isize>>,
+ initial_line: isize,
+ alt_screen: bool,
+ ) -> Option<Span>
+ where
+ G: Dimensions
{
+ let cols = grid.dimensions().col;
+ let lines = grid.dimensions().line.0 as isize;
+
// First, create start and end points based on initial line and the grid
// dimensions.
let mut start = Point {
- col: grid.dimensions().col - 1,
+ col: cols - 1,
line: initial_line
};
let mut end = Point {
@@ -218,20 +239,28 @@ impl Selection {
end.line = max(end.line, region.start.line);
}
+ if alt_screen {
+ Selection::alt_screen_clamp(&mut start, &mut end, lines, cols)?;
+ }
+
Some(Span {
- cols: grid.dimensions().col,
- front: start,
- tail: end,
+ cols,
+ front: start.into(),
+ tail: end.into(),
ty: SpanType::Inclusive
})
}
- fn span_simple<G: Dimensions>(grid: &G, region: &Range<Anchor>) -> Option<Span> {
+ fn span_simple<G>(grid: &G, region: &Range<Anchor>, alt_screen: bool) -> Option<Span>
+ where
+ G: Dimensions
+ {
let start = region.start.point;
let start_side = region.start.side;
let end = region.end.point;
let end_side = region.end.side;
let cols = grid.dimensions().col;
+ let lines = grid.dimensions().line.0 as isize;
// Make sure front is always the "bottom" and tail is always the "top"
let (mut front, mut tail, front_side, tail_side) =
@@ -267,14 +296,50 @@ impl Selection {
tail.col += 1;
}
+ if alt_screen {
+ Selection::alt_screen_clamp(&mut front, &mut tail, lines, cols)?;
+ }
+
// Return the selection with all cells inclusive
Some(Span {
cols,
- front,
- tail,
+ front: front.into(),
+ tail: tail.into(),
ty: SpanType::Inclusive,
})
}
+
+ // Clamp selection in the alternate screen to the visible region
+ fn alt_screen_clamp(
+ front: &mut Point<isize>,
+ tail: &mut Point<isize>,
+ lines: isize,
+ cols: Column,
+ ) -> Option<()> {
+ if tail.line >= lines {
+ // Don't show selection above visible region
+ if front.line >= lines {
+ return None;
+ }
+
+ // Clamp selection above viewport to visible region
+ tail.line = lines - 1;
+ tail.col = Column(0);
+ }
+
+ if front.line < 0 {
+ // Don't show selection below visible region
+ if tail.line < 0 {
+ return None;
+ }
+
+ // Clamp selection below viewport to visible region
+ front.line = 0;
+ front.col = cols - 1;
+ }
+
+ Some(())
+ }
}
/// How to interpret the locations of a Span.
@@ -384,8 +449,8 @@ mod test {
}
impl super::SemanticSearch for Dimensions {
- fn semantic_search_left(&self, _: Point<usize>) -> Point<usize> { unimplemented!(); }
- fn semantic_search_right(&self, _: Point<usize>) -> Point<usize> { unimplemented!(); }
+ fn semantic_search_left(&self, point: Point<usize>) -> Point<usize> { point }
+ fn semantic_search_right(&self, point: Point<usize>) -> Point<usize> { point }
}
/// Test case of single cell selection
@@ -399,7 +464,7 @@ mod test {
let mut selection = Selection::simple(location, Side::Left);
selection.update(location, Side::Right);
- assert_eq!(selection.to_span(&Dimensions::new(1, 1)).unwrap(), Span {
+ assert_eq!(selection.to_span(&Dimensions::new(1, 1), false).unwrap(), Span {
cols: Column(1),
ty: SpanType::Inclusive,
front: location,
@@ -418,7 +483,7 @@ mod test {
let mut selection = Selection::simple(location, Side::Right);
selection.update(location, Side::Left);
- assert_eq!(selection.to_span(&Dimensions::new(1, 1)).unwrap(), Span {
+ assert_eq!(selection.to_span(&Dimensions::new(1, 1), false).unwrap(), Span {
cols: Column(1),
ty: SpanType::Inclusive,
front: location,
@@ -436,7 +501,7 @@ mod test {
let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Left);
- assert_eq!(selection.to_span(&Dimensions::new(1, 2)), None);
+ assert_eq!(selection.to_span(&Dimensions::new(1, 2), false), None);
}
/// Test adjacent cell selection from right to left
@@ -449,7 +514,7 @@ mod test {
let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(0, Column(0)), Side::Right);
- assert_eq!(selection.to_span(&Dimensions::new(1, 2)), None);
+ assert_eq!(selection.to_span(&Dimensions::new(1, 2), false), None);
}
/// Test selection across adjacent lines
@@ -466,7 +531,7 @@ mod test {
let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Right);
- assert_eq!(selection.to_span(&Dimensions::new(2, 5)).unwrap(), Span {
+ assert_eq!(selection.to_span(&Dimensions::new(2, 5), false).unwrap(), Span {
cols: Column(5),
front: Point::new(0, Column(1)),
tail: Point::new(1, Column(2)),
@@ -491,11 +556,53 @@ mod test {
selection.update(Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(1, Column(0)), Side::Right);
- assert_eq!(selection.to_span(&Dimensions::new(2, 5)).unwrap(), Span {
+ assert_eq!(selection.to_span(&Dimensions::new(2, 5), false).unwrap(), Span {
cols: Column(5),
front: Point::new(0, Column(1)),
tail: Point::new(1, Column(1)),
ty: SpanType::Inclusive,
});
}
+
+ #[test]
+ fn alt_scren_lines() {
+ let mut selection = Selection::lines(Point::new(0, Column(0)));
+ selection.update(Point::new(5, Column(3)), Side::Right);
+ selection.rotate(-3);
+
+ assert_eq!(selection.to_span(&Dimensions::new(10, 5), true).unwrap(), Span {
+ cols: Column(5),
+ front: Point::new(0, Column(4)),
+ tail: Point::new(2, Column(0)),
+ ty: SpanType::Inclusive,
+ });
+ }
+
+ #[test]
+ fn alt_screen_semantic() {
+ let mut selection = Selection::semantic(Point::new(0, Column(0)));
+ selection.update(Point::new(5, Column(3)), Side::Right);
+ selection.rotate(-3);
+
+ assert_eq!(selection.to_span(&Dimensions::new(10, 5), true).unwrap(), Span {
+ cols: Column(5),
+ front: Point::new(0, Column(4)),
+ tail: Point::new(2, Column(3)),
+ ty: SpanType::Inclusive,
+ });
+ }
+
+ #[test]
+ fn alt_screen_simple() {
+ let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right);
+ selection.update(Point::new(5, Column(3)), Side::Right);
+ selection.rotate(-3);
+
+ assert_eq!(selection.to_span(&Dimensions::new(10, 5), true).unwrap(), Span {
+ cols: Column(5),
+ front: Point::new(0, Column(4)),
+ tail: Point::new(2, Column(4)),
+ ty: SpanType::Inclusive,
+ });
+ }
}
diff --git a/src/term/mod.rs b/src/term/mod.rs
index 495a7567..f4563922 100644
--- a/src/term/mod.rs
+++ b/src/term/mod.rs
@@ -973,8 +973,9 @@ impl Term {
}
}
+ let alt_screen = self.mode.contains(TermMode::ALT_SCREEN);
let selection = self.grid.selection.clone()?;
- let span = selection.to_span(self)?;
+ let span = selection.to_span(self, alt_screen)?;
let mut res = String::new();
@@ -1058,8 +1059,9 @@ impl Term {
config: &'b Config,
window_focused: bool,
) -> RenderableCellsIter {
+ let alt_screen = self.mode.contains(TermMode::ALT_SCREEN);
let selection = self.grid.selection.as_ref()
- .and_then(|s| s.to_span(self))
+ .and_then(|s| s.to_span(self, alt_screen))
.map(|span| {
span.to_locations()
});