From fd7c1bf6639afc2a141a215f6f64fa299c5a160e Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 26 Nov 2017 11:48:28 -0800 Subject: Cleanup style --- src/renderer/mod.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 0d474123..ed61f7f5 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1354,11 +1354,11 @@ impl Atlas { } /// Insert a RasterizedGlyph into the texture atlas - pub fn insert(&mut self, - glyph: &RasterizedGlyph, - active_tex: &mut u32) - -> Result - { + pub fn insert( + &mut self, + glyph: &RasterizedGlyph, + active_tex: &mut u32 + ) -> Result { if glyph.width > self.width || glyph.height > self.height { return Err(AtlasInsertError::GlyphTooLarge); } @@ -1382,11 +1382,7 @@ impl Atlas { /// Internal function for use once atlas has been checked for space. GL /// errors could still occur at this point if we were checking for them; /// hence, the Result. - fn insert_inner(&mut self, - glyph: &RasterizedGlyph, - active_tex: &mut u32) - -> Glyph - { + fn insert_inner(&mut self, glyph: &RasterizedGlyph, active_tex: &mut u32) -> Glyph { let offset_y = self.row_baseline; let offset_x = self.row_extent; let height = glyph.height as i32; -- cgit From bbe276e3a80571dd0d72a9b32fb7bed38c3be868 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 12 Oct 2017 19:07:49 -0700 Subject: Back Grid with VecDeque VecDeque offers improved performance beyond a plain Vec for common scrolling situations (full screen scroll). Additionally, VecDeque is necessary for performant scrollback since recycling old rows for a Vec would be expensive (push/pop front would shift entire vec). --- src/grid.rs | 282 ++++++++++++++++++++++++++++++++++++++------------------ src/term/mod.rs | 6 +- 2 files changed, 194 insertions(+), 94 deletions(-) (limited to 'src') diff --git a/src/grid.rs b/src/grid.rs index f40ceec9..1b4236f7 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -22,9 +22,10 @@ use std::borrow::ToOwned; use std::cmp::Ordering; +use std::collections::{VecDeque, vec_deque}; use std::iter::IntoIterator; use std::ops::{Deref, DerefMut, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut}; -use std::slice::{self, Iter, IterMut}; +use std::slice; use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; @@ -58,7 +59,7 @@ impl Deref for Indexed { pub struct Grid { /// Lines in the grid. Each row holds a list of cells corresponding to the /// columns in that row. - raw: Vec>, + raw: VecDeque>, /// Number of columns cols: index::Column, @@ -76,9 +77,9 @@ pub struct GridIterator<'a, T: 'a> { impl Grid { pub fn new(lines: index::Line, cols: index::Column, template: &T) -> Grid { - let mut raw = Vec::with_capacity(*lines); + let mut raw = VecDeque::with_capacity(*lines); for _ in IndexRange(index::Line(0)..lines) { - raw.push(Row::new(cols, template)); + raw.push_back(Row::new(cols, template)); } Grid { @@ -109,7 +110,7 @@ impl Grid { fn grow_lines(&mut self, lines: index::Line, template: &T) { for _ in IndexRange(self.num_lines()..lines) { - self.raw.push(Row::new(self.cols, template)); + self.raw.push_back(Row::new(self.cols, template)); } self.lines = lines; @@ -125,14 +126,168 @@ impl Grid { } + +/// A subset of lines in the grid +/// +/// May be constructed using Grid::region(..) +pub struct Region<'a, T: 'a> { + start: Line, + end: Line, + raw: &'a VecDeque>, +} + +/// A mutable subset of lines in the grid +/// +/// May be constructed using Grid::region_mut(..) +pub struct RegionMut<'a, T: 'a> { + start: Line, + end: Line, + raw: &'a mut VecDeque>, +} + +pub trait IndexRegion { + /// Get an immutable region of Self + fn region<'a>(&'a self, _: I) -> Region<'a, T>; + + /// Get a mutable region of Self + fn region_mut<'a>(&'a mut self, _: I) -> RegionMut<'a, T>; +} + +impl IndexRegion, T> for Grid { + fn region(&self, index: Range) -> Region { + assert!(index.start < self.num_lines()); + assert!(index.end <= self.num_lines()); + assert!(index.start <= index.end); + Region { + start: index.start, + end: index.end, + raw: &self.raw + } + } + fn region_mut(&mut self, index: Range) -> RegionMut { + assert!(index.start < self.num_lines()); + assert!(index.end <= self.num_lines()); + assert!(index.start <= index.end); + RegionMut { + start: index.start, + end: index.end, + raw: &mut self.raw + } + } +} + +impl IndexRegion, T> for Grid { + fn region(&self, index: RangeTo) -> Region { + assert!(index.end <= self.num_lines()); + Region { + start: Line(0), + end: index.end, + raw: &self.raw + } + } + fn region_mut(&mut self, index: RangeTo) -> RegionMut { + assert!(index.end <= self.num_lines()); + RegionMut { + start: Line(0), + end: index.end, + raw: &mut self.raw + } + } +} + +impl IndexRegion, T> for Grid { + fn region(&self, index: RangeFrom) -> Region { + assert!(index.start < self.num_lines()); + Region { + start: index.start, + end: self.num_lines(), + raw: &self.raw + } + } + fn region_mut(&mut self, index: RangeFrom) -> RegionMut { + assert!(index.start < self.num_lines()); + RegionMut { + start: index.start, + end: self.num_lines(), + raw: &mut self.raw + } + } +} + +pub struct RegionIter<'a, T: 'a> { + end: Line, + cur: Line, + raw: &'a VecDeque>, +} + +pub struct RegionIterMut<'a, T: 'a> { + end: Line, + cur: Line, + raw: &'a mut VecDeque>, +} + +impl<'a, T> IntoIterator for Region<'a, T> { + type Item = &'a Row; + type IntoIter = RegionIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + RegionIter { + end: self.end, + cur: self.start, + raw: self.raw + } + } +} + +impl<'a, T> IntoIterator for RegionMut<'a, T> { + type Item = &'a mut Row; + type IntoIter = RegionIterMut<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + RegionIterMut { + end: self.end, + cur: self.start, + raw: self.raw + } + } +} + +impl<'a, T> Iterator for RegionIter<'a, T> { + type Item = &'a Row; + fn next(&mut self) -> Option { + if self.cur < self.end { + let index = self.cur; + self.cur += 1; + Some(&self.raw[*index]) + } else { + None + } + } +} + +impl<'a, T> Iterator for RegionIterMut<'a, T> { + type Item = &'a mut Row; + fn next(&mut self) -> Option { + if self.cur < self.end { + let index = self.cur; + self.cur += 1; + unsafe { + Some(&mut *(&mut self.raw[index.0] as *mut _)) + } + } else { + None + } + } +} + impl Grid { #[inline] - pub fn lines(&self) -> Iter> { + pub fn lines(&self) -> vec_deque::Iter> { self.raw.iter() } #[inline] - pub fn lines_mut(&mut self) -> IterMut> { + pub fn lines_mut(&mut self) -> vec_deque::IterMut> { self.raw.iter_mut() } @@ -146,21 +301,35 @@ impl Grid { self.cols } - pub fn iter_rows(&self) -> slice::Iter> { - self.raw.iter() - } - #[inline] pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { - for line in IndexRange((region.start + positions)..region.end).rev() { - self.swap_lines(line, line - positions); + if region.start == Line(0) && region.end == self.num_lines() { + // Full rotation + for _ in 0..positions.0 { + let item = self.raw.pop_back().unwrap(); + self.raw.push_front(item); + } + } else { + // Subregion rotation + for line in IndexRange((region.start + positions)..region.end).rev() { + self.swap_lines(line, line - positions); + } } } #[inline] pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { - for line in IndexRange(region.start..(region.end - positions)) { - self.swap_lines(line, line + positions); + if region.start == Line(0) && region.end == self.num_lines() { + // Full rotation + for _ in 0..positions.0 { + let item = self.raw.pop_front().unwrap(); + self.raw.push_back(item); + } + } else { + // Subregion rotation + for line in IndexRange(region.start..(region.end - positions)) { + self.swap_lines(line, line + positions); + } } } @@ -182,24 +351,7 @@ impl Grid { /// better error messages by doing the bounds checking ourselves. #[inline] pub fn swap_lines(&mut self, src: index::Line, dst: index::Line) { - use util::unlikely; - - unsafe { - // check that src/dst are in bounds. Since index::Line newtypes usize, - // we can assume values are positive. - if unlikely(src >= self.lines) { - panic!("swap_lines src out of bounds; len={}, src={}", self.raw.len(), src); - } - - if unlikely(dst >= self.lines) { - panic!("swap_lines dst out of bounds; len={}, dst={}", self.raw.len(), dst); - } - - let src: *mut _ = self.raw.get_unchecked_mut(src.0); - let dst: *mut _ = self.raw.get_unchecked_mut(dst.0); - - ::std::ptr::swap(src, dst); - } + self.raw.swap(*src, *dst); } #[inline] @@ -210,7 +362,7 @@ impl Grid { fn shrink_lines(&mut self, lines: index::Line) { while index::Line(self.raw.len()) != lines { - self.raw.pop(); + self.raw.pop_back(); } self.lines = lines; @@ -324,22 +476,22 @@ impl Row { } #[inline] - pub fn cells(&self) -> Iter { + pub fn cells(&self) -> slice::Iter { self.0.iter() } #[inline] - pub fn cells_mut(&mut self) -> IterMut { + pub fn cells_mut(&mut self) -> slice::IterMut { self.0.iter_mut() } } impl<'a, T> IntoIterator for &'a Grid { type Item = &'a Row; - type IntoIter = slice::Iter<'a, Row>; + type IntoIter = vec_deque::Iter<'a, Row>; #[inline] - fn into_iter(self) -> slice::Iter<'a, Row> { + fn into_iter(self) -> vec_deque::Iter<'a, Row> { self.raw.iter() } } @@ -421,58 +573,6 @@ row_index_range!(RangeTo); row_index_range!(RangeFrom); row_index_range!(RangeFull); -// ----------------------------------------------------------------------------- -// Row ranges for Grid -// ----------------------------------------------------------------------------- - -impl Index> for Grid { - type Output = [Row]; - - #[inline] - fn index(&self, index: Range) -> &[Row] { - &self.raw[(index.start.0)..(index.end.0)] - } -} - -impl IndexMut> for Grid { - #[inline] - fn index_mut(&mut self, index: Range) -> &mut [Row] { - &mut self.raw[(index.start.0)..(index.end.0)] - } -} - -impl Index> for Grid { - type Output = [Row]; - - #[inline] - fn index(&self, index: RangeTo) -> &[Row] { - &self.raw[..(index.end.0)] - } -} - -impl IndexMut> for Grid { - #[inline] - fn index_mut(&mut self, index: RangeTo) -> &mut [Row] { - &mut self.raw[..(index.end.0)] - } -} - -impl Index> for Grid { - type Output = [Row]; - - #[inline] - fn index(&self, index: RangeFrom) -> &[Row] { - &self.raw[(index.start.0)..] - } -} - -impl IndexMut> for Grid { - #[inline] - fn index_mut(&mut self, index: RangeFrom) -> &mut [Row] { - &mut self.raw[(index.start.0)..] - } -} - // ----------------------------------------------------------------------------- // Column ranges for Row // ----------------------------------------------------------------------------- @@ -533,7 +633,7 @@ macro_rules! clear_region_impl { ($range:ty) => { impl ClearRegion<$range, T> for Grid { fn clear_region(&mut self, region: $range, func: F) { - for row in self[region].iter_mut() { + for row in self.region_mut(region) { for cell in row { func(cell); } diff --git a/src/term/mod.rs b/src/term/mod.rs index 4f56e7fc..dd853368 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -24,7 +24,7 @@ use unicode_width::UnicodeWidthChar; use font::{self, Size}; use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; -use grid::{BidirectionalIterator, Grid, ClearRegion, ToRange, Indexed}; +use grid::{BidirectionalIterator, Grid, ClearRegion, ToRange, Indexed, IndexRegion}; use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive}; use selection::{self, Span, Selection}; use config::{Config, VisualBellAnimation}; @@ -1708,7 +1708,7 @@ impl ansi::Handler for Term { cell.reset(&template); } if self.cursor.point.line < self.grid.num_lines() - 1 { - for row in &mut self.grid[(self.cursor.point.line + 1)..] { + for row in self.grid.region_mut((self.cursor.point.line + 1)..) { for cell in row { cell.reset(&template); } @@ -1722,7 +1722,7 @@ impl ansi::Handler for Term { // If clearing more than one line if self.cursor.point.line > Line(1) { // Fully clear all lines before the current line - for row in &mut self.grid[..self.cursor.point.line] { + for row in self.grid.region_mut(..self.cursor.point.line) { for cell in row { cell.reset(&template); } -- cgit From 277425956f361677deb1de92b25aeca9cbcd1cd1 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 12 Oct 2017 20:01:28 -0700 Subject: Move grid Row and tests into submodules This is part of some cleanup for the grid module as a whole. --- src/grid.rs | 824 ------------------------------------------------------ src/grid/mod.rs | 495 ++++++++++++++++++++++++++++++++ src/grid/row.rs | 186 ++++++++++++ src/grid/tests.rs | 188 +++++++++++++ 4 files changed, 869 insertions(+), 824 deletions(-) delete mode 100644 src/grid.rs create mode 100644 src/grid/mod.rs create mode 100644 src/grid/row.rs create mode 100644 src/grid/tests.rs (limited to 'src') diff --git a/src/grid.rs b/src/grid.rs deleted file mode 100644 index 1b4236f7..00000000 --- a/src/grid.rs +++ /dev/null @@ -1,824 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! A generic 2d grid implementation optimized for use in a terminal. -//! -//! The current implementation uses a vector of vectors to store cell data. -//! Reimplementing the store as a single contiguous vector may be desirable in -//! the future. Rotation and indexing would need to be reconsidered at that -//! time; rotation currently reorganize Vecs in the lines Vec, and indexing with -//! ranges is currently supported. - -use std::borrow::ToOwned; -use std::cmp::Ordering; -use std::collections::{VecDeque, vec_deque}; -use std::iter::IntoIterator; -use std::ops::{Deref, DerefMut, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut}; -use std::slice; - -use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; - -/// Convert a type to a linear index range. -pub trait ToRange { - fn to_range(&self) -> RangeInclusive; -} - -/// Bidirection iterator -pub trait BidirectionalIterator: Iterator { - fn prev(&mut self) -> Option; -} - -pub struct Indexed { - pub line: Line, - pub column: Column, - pub inner: T -} - -impl Deref for Indexed { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - &self.inner - } -} - -/// Represents the terminal display contents -#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] -pub struct Grid { - /// Lines in the grid. Each row holds a list of cells corresponding to the - /// columns in that row. - raw: VecDeque>, - - /// Number of columns - cols: index::Column, - - /// Number of lines. - /// - /// Invariant: lines is equivalent to raw.len() - lines: index::Line, -} - -pub struct GridIterator<'a, T: 'a> { - grid: &'a Grid, - pub cur: Point, -} - -impl Grid { - pub fn new(lines: index::Line, cols: index::Column, template: &T) -> Grid { - let mut raw = VecDeque::with_capacity(*lines); - for _ in IndexRange(index::Line(0)..lines) { - raw.push_back(Row::new(cols, template)); - } - - Grid { - raw, - cols, - lines, - } - } - - pub fn resize(&mut self, lines: index::Line, cols: index::Column, template: &T) { - // Check that there's actually work to do and return early if not - if lines == self.lines && cols == self.cols { - return; - } - - match self.lines.cmp(&lines) { - Ordering::Less => self.grow_lines(lines, template), - Ordering::Greater => self.shrink_lines(lines), - Ordering::Equal => (), - } - - match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(cols, template), - Ordering::Greater => self.shrink_cols(cols), - Ordering::Equal => (), - } - } - - fn grow_lines(&mut self, lines: index::Line, template: &T) { - for _ in IndexRange(self.num_lines()..lines) { - self.raw.push_back(Row::new(self.cols, template)); - } - - self.lines = lines; - } - - fn grow_cols(&mut self, cols: index::Column, template: &T) { - for row in self.lines_mut() { - row.grow(cols, template); - } - - self.cols = cols; - } - -} - - -/// A subset of lines in the grid -/// -/// May be constructed using Grid::region(..) -pub struct Region<'a, T: 'a> { - start: Line, - end: Line, - raw: &'a VecDeque>, -} - -/// A mutable subset of lines in the grid -/// -/// May be constructed using Grid::region_mut(..) -pub struct RegionMut<'a, T: 'a> { - start: Line, - end: Line, - raw: &'a mut VecDeque>, -} - -pub trait IndexRegion { - /// Get an immutable region of Self - fn region<'a>(&'a self, _: I) -> Region<'a, T>; - - /// Get a mutable region of Self - fn region_mut<'a>(&'a mut self, _: I) -> RegionMut<'a, T>; -} - -impl IndexRegion, T> for Grid { - fn region(&self, index: Range) -> Region { - assert!(index.start < self.num_lines()); - assert!(index.end <= self.num_lines()); - assert!(index.start <= index.end); - Region { - start: index.start, - end: index.end, - raw: &self.raw - } - } - fn region_mut(&mut self, index: Range) -> RegionMut { - assert!(index.start < self.num_lines()); - assert!(index.end <= self.num_lines()); - assert!(index.start <= index.end); - RegionMut { - start: index.start, - end: index.end, - raw: &mut self.raw - } - } -} - -impl IndexRegion, T> for Grid { - fn region(&self, index: RangeTo) -> Region { - assert!(index.end <= self.num_lines()); - Region { - start: Line(0), - end: index.end, - raw: &self.raw - } - } - fn region_mut(&mut self, index: RangeTo) -> RegionMut { - assert!(index.end <= self.num_lines()); - RegionMut { - start: Line(0), - end: index.end, - raw: &mut self.raw - } - } -} - -impl IndexRegion, T> for Grid { - fn region(&self, index: RangeFrom) -> Region { - assert!(index.start < self.num_lines()); - Region { - start: index.start, - end: self.num_lines(), - raw: &self.raw - } - } - fn region_mut(&mut self, index: RangeFrom) -> RegionMut { - assert!(index.start < self.num_lines()); - RegionMut { - start: index.start, - end: self.num_lines(), - raw: &mut self.raw - } - } -} - -pub struct RegionIter<'a, T: 'a> { - end: Line, - cur: Line, - raw: &'a VecDeque>, -} - -pub struct RegionIterMut<'a, T: 'a> { - end: Line, - cur: Line, - raw: &'a mut VecDeque>, -} - -impl<'a, T> IntoIterator for Region<'a, T> { - type Item = &'a Row; - type IntoIter = RegionIter<'a, T>; - - fn into_iter(self) -> Self::IntoIter { - RegionIter { - end: self.end, - cur: self.start, - raw: self.raw - } - } -} - -impl<'a, T> IntoIterator for RegionMut<'a, T> { - type Item = &'a mut Row; - type IntoIter = RegionIterMut<'a, T>; - - fn into_iter(self) -> Self::IntoIter { - RegionIterMut { - end: self.end, - cur: self.start, - raw: self.raw - } - } -} - -impl<'a, T> Iterator for RegionIter<'a, T> { - type Item = &'a Row; - fn next(&mut self) -> Option { - if self.cur < self.end { - let index = self.cur; - self.cur += 1; - Some(&self.raw[*index]) - } else { - None - } - } -} - -impl<'a, T> Iterator for RegionIterMut<'a, T> { - type Item = &'a mut Row; - fn next(&mut self) -> Option { - if self.cur < self.end { - let index = self.cur; - self.cur += 1; - unsafe { - Some(&mut *(&mut self.raw[index.0] as *mut _)) - } - } else { - None - } - } -} - -impl Grid { - #[inline] - pub fn lines(&self) -> vec_deque::Iter> { - self.raw.iter() - } - - #[inline] - pub fn lines_mut(&mut self) -> vec_deque::IterMut> { - self.raw.iter_mut() - } - - #[inline] - pub fn num_lines(&self) -> index::Line { - self.lines - } - - #[inline] - pub fn num_cols(&self) -> index::Column { - self.cols - } - - #[inline] - pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { - if region.start == Line(0) && region.end == self.num_lines() { - // Full rotation - for _ in 0..positions.0 { - let item = self.raw.pop_back().unwrap(); - self.raw.push_front(item); - } - } else { - // Subregion rotation - for line in IndexRange((region.start + positions)..region.end).rev() { - self.swap_lines(line, line - positions); - } - } - } - - #[inline] - pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { - if region.start == Line(0) && region.end == self.num_lines() { - // Full rotation - for _ in 0..positions.0 { - let item = self.raw.pop_front().unwrap(); - self.raw.push_back(item); - } - } else { - // Subregion rotation - for line in IndexRange(region.start..(region.end - positions)) { - self.swap_lines(line, line + positions); - } - } - } - - pub fn iter_from(&self, point: Point) -> GridIterator { - GridIterator { - grid: self, - cur: point, - } - } - - #[inline] - pub fn contains(&self, point: &Point) -> bool { - self.lines > point.line && self.cols > point.col - } - - /// Swap two lines in the grid - /// - /// This could have used slice::swap internally, but we are able to have - /// better error messages by doing the bounds checking ourselves. - #[inline] - pub fn swap_lines(&mut self, src: index::Line, dst: index::Line) { - self.raw.swap(*src, *dst); - } - - #[inline] - pub fn clear(&mut self, func: F) { - let region = index::Line(0)..self.num_lines(); - self.clear_region(region, func); - } - - fn shrink_lines(&mut self, lines: index::Line) { - while index::Line(self.raw.len()) != lines { - self.raw.pop_back(); - } - - self.lines = lines; - } - - fn shrink_cols(&mut self, cols: index::Column) { - for row in self.lines_mut() { - row.shrink(cols); - } - - self.cols = cols; - } -} - -impl<'a, T> Iterator for GridIterator<'a, T> { - type Item = &'a T; - - fn next(&mut self) -> Option { - let last_line = self.grid.num_lines() - Line(1); - let last_col = self.grid.num_cols() - Column(1); - match self.cur { - Point { line, col } if - (line == last_line) && - (col == last_col) => None, - Point { col, .. } if - (col == last_col) => { - self.cur.line += Line(1); - self.cur.col = Column(0); - Some(&self.grid[self.cur.line][self.cur.col]) - }, - _ => { - self.cur.col += Column(1); - Some(&self.grid[self.cur.line][self.cur.col]) - } - } - } -} - -impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { - fn prev(&mut self) -> Option { - let num_cols = self.grid.num_cols(); - - match self.cur { - Point { line: Line(0), col: Column(0) } => None, - Point { col: Column(0), .. } => { - self.cur.line -= Line(1); - self.cur.col = num_cols - Column(1); - Some(&self.grid[self.cur.line][self.cur.col]) - }, - _ => { - self.cur.col -= Column(1); - Some(&self.grid[self.cur.line][self.cur.col]) - } - } - } -} - -impl Index for Grid { - type Output = Row; - - #[inline] - fn index(&self, index: index::Line) -> &Row { - &self.raw[index.0] - } -} - -impl IndexMut for Grid { - #[inline] - fn index_mut(&mut self, index: index::Line) -> &mut Row { - &mut self.raw[index.0] - } -} - -impl<'point, T> Index<&'point Point> for Grid { - type Output = T; - - #[inline] - fn index<'a>(&'a self, point: &Point) -> &'a T { - &self.raw[point.line.0][point.col] - } -} - -impl<'point, T> IndexMut<&'point Point> for Grid { - #[inline] - fn index_mut<'a, 'b>(&'a mut self, point: &'b Point) -> &'a mut T { - &mut self.raw[point.line.0][point.col] - } -} - -/// A row in the grid -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct Row(Vec); - -impl Row { - pub fn new(columns: index::Column, template: &T) -> Row { - Row(vec![template.to_owned(); *columns]) - } - - pub fn grow(&mut self, cols: index::Column, template: &T) { - while self.len() != *cols { - self.push(template.to_owned()); - } - } -} - -impl Row { - pub fn shrink(&mut self, cols: index::Column) { - while self.len() != *cols { - self.pop(); - } - } - - #[inline] - pub fn cells(&self) -> slice::Iter { - self.0.iter() - } - - #[inline] - pub fn cells_mut(&mut self) -> slice::IterMut { - self.0.iter_mut() - } -} - -impl<'a, T> IntoIterator for &'a Grid { - type Item = &'a Row; - type IntoIter = vec_deque::Iter<'a, Row>; - - #[inline] - fn into_iter(self) -> vec_deque::Iter<'a, Row> { - self.raw.iter() - } -} - -impl<'a, T> IntoIterator for &'a Row { - type Item = &'a T; - type IntoIter = slice::Iter<'a, T>; - - #[inline] - fn into_iter(self) -> slice::Iter<'a, T> { - self.iter() - } -} - -impl<'a, T> IntoIterator for &'a mut Row { - type Item = &'a mut T; - type IntoIter = slice::IterMut<'a, T>; - - #[inline] - fn into_iter(self) -> slice::IterMut<'a, T> { - self.iter_mut() - } -} - -impl Deref for Row { - type Target = Vec; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Row { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Index for Row { - type Output = T; - - #[inline] - fn index(&self, index: index::Column) -> &T { - &self.0[index.0] - } -} - -impl IndexMut for Row { - #[inline] - fn index_mut(&mut self, index: index::Column) -> &mut T { - &mut self.0[index.0] - } -} - -macro_rules! row_index_range { - ($range:ty) => { - impl Index<$range> for Row { - type Output = [T]; - - #[inline] - fn index(&self, index: $range) -> &[T] { - &self.0[index] - } - } - - impl IndexMut<$range> for Row { - #[inline] - fn index_mut(&mut self, index: $range) -> &mut [T] { - &mut self.0[index] - } - } - } -} - -row_index_range!(Range); -row_index_range!(RangeTo); -row_index_range!(RangeFrom); -row_index_range!(RangeFull); - -// ----------------------------------------------------------------------------- -// Column ranges for Row -// ----------------------------------------------------------------------------- - -impl Index> for Row { - type Output = [T]; - - #[inline] - fn index(&self, index: Range) -> &[T] { - &self.0[(index.start.0)..(index.end.0)] - } -} - -impl IndexMut> for Row { - #[inline] - fn index_mut(&mut self, index: Range) -> &mut [T] { - &mut self.0[(index.start.0)..(index.end.0)] - } -} - -impl Index> for Row { - type Output = [T]; - - #[inline] - fn index(&self, index: RangeTo) -> &[T] { - &self.0[..(index.end.0)] - } -} - -impl IndexMut> for Row { - #[inline] - fn index_mut(&mut self, index: RangeTo) -> &mut [T] { - &mut self.0[..(index.end.0)] - } -} - -impl Index> for Row { - type Output = [T]; - - #[inline] - fn index(&self, index: RangeFrom) -> &[T] { - &self.0[(index.start.0)..] - } -} - -impl IndexMut> for Row { - #[inline] - fn index_mut(&mut self, index: RangeFrom) -> &mut [T] { - &mut self.0[(index.start.0)..] - } -} - -pub trait ClearRegion { - fn clear_region(&mut self, region: R, func: F); -} - -macro_rules! clear_region_impl { - ($range:ty) => { - impl ClearRegion<$range, T> for Grid { - fn clear_region(&mut self, region: $range, func: F) { - for row in self.region_mut(region) { - for cell in row { - func(cell); - } - } - } - } - } -} - -clear_region_impl!(Range); -clear_region_impl!(RangeTo); -clear_region_impl!(RangeFrom); - -#[cfg(test)] -mod tests { - use super::{Grid, BidirectionalIterator}; - use index::{Point, Line, Column}; - #[test] - fn grid_swap_lines_ok() { - let mut grid = Grid::new(Line(10), Column(1), &0); - info!(""); - - // swap test ends - grid[Line(0)][Column(0)] = 1; - grid[Line(9)][Column(0)] = 2; - - assert_eq!(grid[Line(0)][Column(0)], 1); - assert_eq!(grid[Line(9)][Column(0)], 2); - - grid.swap_lines(Line(0), Line(9)); - - assert_eq!(grid[Line(0)][Column(0)], 2); - assert_eq!(grid[Line(9)][Column(0)], 1); - - // swap test mid - grid[Line(4)][Column(0)] = 1; - grid[Line(5)][Column(0)] = 2; - - info!("grid: {:?}", grid); - - assert_eq!(grid[Line(4)][Column(0)], 1); - assert_eq!(grid[Line(5)][Column(0)], 2); - - grid.swap_lines(Line(4), Line(5)); - - info!("grid: {:?}", grid); - - assert_eq!(grid[Line(4)][Column(0)], 2); - assert_eq!(grid[Line(5)][Column(0)], 1); - } - - #[test] - #[should_panic] - fn grid_swap_lines_oob1() { - let mut grid = Grid::new(Line(10), Column(1), &0); - grid.swap_lines(Line(0), Line(10)); - } - - #[test] - #[should_panic] - fn grid_swap_lines_oob2() { - let mut grid = Grid::new(Line(10), Column(1), &0); - grid.swap_lines(Line(10), Line(0)); - } - - #[test] - #[should_panic] - fn grid_swap_lines_oob3() { - let mut grid = Grid::new(Line(10), Column(1), &0); - grid.swap_lines(Line(10), Line(10)); - } - - // Scroll up moves lines upwards - #[test] - fn scroll_up() { - info!(""); - - let mut grid = Grid::new(Line(10), Column(1), &0); - for i in 0..10 { - grid[Line(i)][Column(0)] = i; - } - - info!("grid: {:?}", grid); - - grid.scroll_up(&(Line(0)..Line(10)), Line(2)); - - info!("grid: {:?}", grid); - - let mut other = Grid::new(Line(10), Column(1), &9); - - other[Line(0)][Column(0)] = 2; - other[Line(1)][Column(0)] = 3; - other[Line(2)][Column(0)] = 4; - other[Line(3)][Column(0)] = 5; - other[Line(4)][Column(0)] = 6; - other[Line(5)][Column(0)] = 7; - other[Line(6)][Column(0)] = 8; - other[Line(7)][Column(0)] = 9; - other[Line(8)][Column(0)] = 0; - other[Line(9)][Column(0)] = 1; - - for i in 0..10 { - assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)]); - } - } - - // Scroll down moves lines downwards - #[test] - fn scroll_down() { - info!(""); - - let mut grid = Grid::new(Line(10), Column(1), &0); - for i in 0..10 { - grid[Line(i)][Column(0)] = i; - } - - info!("grid: {:?}", grid); - - grid.scroll_down(&(Line(0)..Line(10)), Line(2)); - - info!("grid: {:?}", grid); - - let mut other = Grid::new(Line(10), Column(1), &9); - - other[Line(0)][Column(0)] = 8; - other[Line(1)][Column(0)] = 9; - other[Line(2)][Column(0)] = 0; - other[Line(3)][Column(0)] = 1; - other[Line(4)][Column(0)] = 2; - other[Line(5)][Column(0)] = 3; - other[Line(6)][Column(0)] = 4; - other[Line(7)][Column(0)] = 5; - other[Line(8)][Column(0)] = 6; - other[Line(9)][Column(0)] = 7; - - for i in 0..10 { - assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)]); - } - } - - // Test that GridIterator works - #[test] - fn test_iter() { - info!(""); - - let mut grid = Grid::new(Line(5), Column(5), &0); - for i in 0..5 { - for j in 0..5 { - grid[Line(i)][Column(j)] = i*5 + j; - } - } - - info!("grid: {:?}", grid); - - let mut iter = grid.iter_from(Point { - line: Line(0), - col: Column(0), - }); - - assert_eq!(None, iter.prev()); - assert_eq!(Some(&1), iter.next()); - assert_eq!(Column(1), iter.cur.col); - assert_eq!(Line(0), iter.cur.line); - - assert_eq!(Some(&2), iter.next()); - assert_eq!(Some(&3), iter.next()); - assert_eq!(Some(&4), iter.next()); - - // test linewrapping - assert_eq!(Some(&5), iter.next()); - assert_eq!(Column(0), iter.cur.col); - assert_eq!(Line(1), iter.cur.line); - - assert_eq!(Some(&4), iter.prev()); - assert_eq!(Column(4), iter.cur.col); - assert_eq!(Line(0), iter.cur.line); - - - // test that iter ends at end of grid - let mut final_iter = grid.iter_from(Point { - line: Line(4), - col: Column(4), - }); - assert_eq!(None, final_iter.next()); - assert_eq!(Some(&23), final_iter.prev()); - } - -} diff --git a/src/grid/mod.rs b/src/grid/mod.rs new file mode 100644 index 00000000..123e13fa --- /dev/null +++ b/src/grid/mod.rs @@ -0,0 +1,495 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A generic 2d grid implementation optimized for use in a terminal. +//! +//! The current implementation uses a vector of vectors to store cell data. +//! Reimplementing the store as a single contiguous vector may be desirable in +//! the future. Rotation and indexing would need to be reconsidered at that +//! time; rotation currently reorganize Vecs in the lines Vec, and indexing with +//! ranges is currently supported. + +use std::cmp::Ordering; +use std::collections::{VecDeque, vec_deque}; +use std::iter::IntoIterator; +use std::ops::{Deref, Range, RangeTo, RangeFrom, Index, IndexMut}; + +use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; + +mod row; +pub use self::row::Row; + +#[cfg(test)] +mod tests; + +/// Convert a type to a linear index range. +pub trait ToRange { + fn to_range(&self) -> RangeInclusive; +} + +/// Bidirection iterator +pub trait BidirectionalIterator: Iterator { + fn prev(&mut self) -> Option; +} + +/// An item in the grid along with its Line and Column. +pub struct Indexed { + pub inner: T, + pub line: Line, + pub column: Column, +} + +impl Deref for Indexed { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.inner + } +} + +/// Represents the terminal display contents +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +pub struct Grid { + /// Lines in the grid. Each row holds a list of cells corresponding to the + /// columns in that row. + raw: VecDeque>, + + /// Number of columns + cols: index::Column, + + /// Number of lines. + /// + /// Invariant: lines is equivalent to raw.len() + lines: index::Line, +} + +pub struct GridIterator<'a, T: 'a> { + grid: &'a Grid, + pub cur: Point, +} + +impl Grid { + pub fn new(lines: index::Line, cols: index::Column, template: &T) -> Grid { + let mut raw = VecDeque::with_capacity(*lines); + for _ in IndexRange(index::Line(0)..lines) { + raw.push_back(Row::new(cols, template)); + } + + Grid { + raw, + cols, + lines, + } + } + + pub fn resize(&mut self, lines: index::Line, cols: index::Column, template: &T) { + // Check that there's actually work to do and return early if not + if lines == self.lines && cols == self.cols { + return; + } + + match self.lines.cmp(&lines) { + Ordering::Less => self.grow_lines(lines, template), + Ordering::Greater => self.shrink_lines(lines), + Ordering::Equal => (), + } + + match self.cols.cmp(&cols) { + Ordering::Less => self.grow_cols(cols, template), + Ordering::Greater => self.shrink_cols(cols), + Ordering::Equal => (), + } + } + + fn grow_lines(&mut self, lines: index::Line, template: &T) { + for _ in IndexRange(self.num_lines()..lines) { + self.raw.push_back(Row::new(self.cols, template)); + } + + self.lines = lines; + } + + fn grow_cols(&mut self, cols: index::Column, template: &T) { + for row in self.lines_mut() { + row.grow(cols, template); + } + + self.cols = cols; + } + +} + + + +impl Grid { + #[inline] + pub fn lines(&self) -> vec_deque::Iter> { + self.raw.iter() + } + + #[inline] + pub fn lines_mut(&mut self) -> vec_deque::IterMut> { + self.raw.iter_mut() + } + + #[inline] + pub fn num_lines(&self) -> index::Line { + self.lines + } + + #[inline] + pub fn num_cols(&self) -> index::Column { + self.cols + } + + #[inline] + pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { + if region.start == Line(0) && region.end == self.num_lines() { + // Full rotation + for _ in 0..positions.0 { + let item = self.raw.pop_back().unwrap(); + self.raw.push_front(item); + } + } else { + // Subregion rotation + for line in IndexRange((region.start + positions)..region.end).rev() { + self.swap_lines(line, line - positions); + } + } + } + + #[inline] + pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { + if region.start == Line(0) && region.end == self.num_lines() { + // Full rotation + for _ in 0..positions.0 { + let item = self.raw.pop_front().unwrap(); + self.raw.push_back(item); + } + } else { + // Subregion rotation + for line in IndexRange(region.start..(region.end - positions)) { + self.swap_lines(line, line + positions); + } + } + } + + pub fn iter_from(&self, point: Point) -> GridIterator { + GridIterator { + grid: self, + cur: point, + } + } + + #[inline] + pub fn contains(&self, point: &Point) -> bool { + self.lines > point.line && self.cols > point.col + } + + /// Swap two lines in the grid + /// + /// This could have used slice::swap internally, but we are able to have + /// better error messages by doing the bounds checking ourselves. + #[inline] + pub fn swap_lines(&mut self, src: index::Line, dst: index::Line) { + self.raw.swap(*src, *dst); + } + + #[inline] + pub fn clear(&mut self, func: F) { + let region = index::Line(0)..self.num_lines(); + self.clear_region(region, func); + } + + fn shrink_lines(&mut self, lines: index::Line) { + while index::Line(self.raw.len()) != lines { + self.raw.pop_back(); + } + + self.lines = lines; + } + + fn shrink_cols(&mut self, cols: index::Column) { + for row in self.lines_mut() { + row.shrink(cols); + } + + self.cols = cols; + } +} + +impl<'a, T> Iterator for GridIterator<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + let last_line = self.grid.num_lines() - Line(1); + let last_col = self.grid.num_cols() - Column(1); + match self.cur { + Point { line, col } if + (line == last_line) && + (col == last_col) => None, + Point { col, .. } if + (col == last_col) => { + self.cur.line += Line(1); + self.cur.col = Column(0); + Some(&self.grid[self.cur.line][self.cur.col]) + }, + _ => { + self.cur.col += Column(1); + Some(&self.grid[self.cur.line][self.cur.col]) + } + } + } +} + +impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { + fn prev(&mut self) -> Option { + let num_cols = self.grid.num_cols(); + + match self.cur { + Point { line: Line(0), col: Column(0) } => None, + Point { col: Column(0), .. } => { + self.cur.line -= Line(1); + self.cur.col = num_cols - Column(1); + Some(&self.grid[self.cur.line][self.cur.col]) + }, + _ => { + self.cur.col -= Column(1); + Some(&self.grid[self.cur.line][self.cur.col]) + } + } + } +} + +impl Index for Grid { + type Output = Row; + + #[inline] + fn index(&self, index: index::Line) -> &Row { + &self.raw[index.0] + } +} + +impl IndexMut for Grid { + #[inline] + fn index_mut(&mut self, index: index::Line) -> &mut Row { + &mut self.raw[index.0] + } +} + +impl<'point, T> Index<&'point Point> for Grid { + type Output = T; + + #[inline] + fn index<'a>(&'a self, point: &Point) -> &'a T { + &self.raw[point.line.0][point.col] + } +} + +impl<'point, T> IndexMut<&'point Point> for Grid { + #[inline] + fn index_mut<'a, 'b>(&'a mut self, point: &'b Point) -> &'a mut T { + &mut self.raw[point.line.0][point.col] + } +} + +impl<'a, T> IntoIterator for &'a Grid { + type Item = &'a Row; + type IntoIter = vec_deque::Iter<'a, Row>; + + #[inline] + fn into_iter(self) -> vec_deque::Iter<'a, Row> { + self.raw.iter() + } +} + +pub trait ClearRegion { + fn clear_region(&mut self, region: R, func: F); +} + +macro_rules! clear_region_impl { + ($range:ty) => { + impl ClearRegion<$range, T> for Grid { + fn clear_region(&mut self, region: $range, func: F) { + for row in self.region_mut(region) { + for cell in row { + func(cell); + } + } + } + } + } +} + +clear_region_impl!(Range); +clear_region_impl!(RangeTo); +clear_region_impl!(RangeFrom); + +// ================================================================================================= +// Regions ========================================================================================= +// ================================================================================================= + +/// A subset of lines in the grid +/// +/// May be constructed using Grid::region(..) +pub struct Region<'a, T: 'a> { + start: Line, + end: Line, + raw: &'a VecDeque>, +} + +/// A mutable subset of lines in the grid +/// +/// May be constructed using Grid::region_mut(..) +pub struct RegionMut<'a, T: 'a> { + start: Line, + end: Line, + raw: &'a mut VecDeque>, +} + +pub trait IndexRegion { + /// Get an immutable region of Self + fn region<'a>(&'a self, _: I) -> Region<'a, T>; + + /// Get a mutable region of Self + fn region_mut<'a>(&'a mut self, _: I) -> RegionMut<'a, T>; +} + +impl IndexRegion, T> for Grid { + fn region(&self, index: Range) -> Region { + assert!(index.start < self.num_lines()); + assert!(index.end <= self.num_lines()); + assert!(index.start <= index.end); + Region { + start: index.start, + end: index.end, + raw: &self.raw + } + } + fn region_mut(&mut self, index: Range) -> RegionMut { + assert!(index.start < self.num_lines()); + assert!(index.end <= self.num_lines()); + assert!(index.start <= index.end); + RegionMut { + start: index.start, + end: index.end, + raw: &mut self.raw + } + } +} + +impl IndexRegion, T> for Grid { + fn region(&self, index: RangeTo) -> Region { + assert!(index.end <= self.num_lines()); + Region { + start: Line(0), + end: index.end, + raw: &self.raw + } + } + fn region_mut(&mut self, index: RangeTo) -> RegionMut { + assert!(index.end <= self.num_lines()); + RegionMut { + start: Line(0), + end: index.end, + raw: &mut self.raw + } + } +} + +impl IndexRegion, T> for Grid { + fn region(&self, index: RangeFrom) -> Region { + assert!(index.start < self.num_lines()); + Region { + start: index.start, + end: self.num_lines(), + raw: &self.raw + } + } + fn region_mut(&mut self, index: RangeFrom) -> RegionMut { + assert!(index.start < self.num_lines()); + RegionMut { + start: index.start, + end: self.num_lines(), + raw: &mut self.raw + } + } +} + +pub struct RegionIter<'a, T: 'a> { + end: Line, + cur: Line, + raw: &'a VecDeque>, +} + +pub struct RegionIterMut<'a, T: 'a> { + end: Line, + cur: Line, + raw: &'a mut VecDeque>, +} + +impl<'a, T> IntoIterator for Region<'a, T> { + type Item = &'a Row; + type IntoIter = RegionIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + RegionIter { + end: self.end, + cur: self.start, + raw: self.raw + } + } +} + +impl<'a, T> IntoIterator for RegionMut<'a, T> { + type Item = &'a mut Row; + type IntoIter = RegionIterMut<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + RegionIterMut { + end: self.end, + cur: self.start, + raw: self.raw + } + } +} + +impl<'a, T> Iterator for RegionIter<'a, T> { + type Item = &'a Row; + fn next(&mut self) -> Option { + if self.cur < self.end { + let index = self.cur; + self.cur += 1; + Some(&self.raw[*index]) + } else { + None + } + } +} + +impl<'a, T> Iterator for RegionIterMut<'a, T> { + type Item = &'a mut Row; + fn next(&mut self) -> Option { + if self.cur < self.end { + let index = self.cur; + self.cur += 1; + unsafe { + Some(&mut *(&mut self.raw[index.0] as *mut _)) + } + } else { + None + } + } +} diff --git a/src/grid/row.rs b/src/grid/row.rs new file mode 100644 index 00000000..8711d04f --- /dev/null +++ b/src/grid/row.rs @@ -0,0 +1,186 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Defines the Row type which makes up lines in the grid + +use std::ops::{Deref, DerefMut, Index, IndexMut}; +use std::ops::{Range, RangeTo, RangeFrom, RangeFull}; +use std::slice; + +use index::Column; + +/// A row in the grid +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct Row(Vec); + +impl Row { + pub fn new(columns: Column, template: &T) -> Row { + Row(vec![template.to_owned(); *columns]) + } + + pub fn grow(&mut self, cols: Column, template: &T) { + while self.len() != *cols { + self.push(template.to_owned()); + } + } +} + +impl Row { + pub fn shrink(&mut self, cols: Column) { + while self.len() != *cols { + self.pop(); + } + } + + #[inline] + pub fn cells(&self) -> slice::Iter { + self.0.iter() + } + + #[inline] + pub fn cells_mut(&mut self) -> slice::IterMut { + self.0.iter_mut() + } +} + + +impl<'a, T> IntoIterator for &'a Row { + type Item = &'a T; + type IntoIter = slice::Iter<'a, T>; + + #[inline] + fn into_iter(self) -> slice::Iter<'a, T> { + self.iter() + } +} + +impl<'a, T> IntoIterator for &'a mut Row { + type Item = &'a mut T; + type IntoIter = slice::IterMut<'a, T>; + + #[inline] + fn into_iter(self) -> slice::IterMut<'a, T> { + self.iter_mut() + } +} + +impl Deref for Row { + type Target = Vec; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + + +impl DerefMut for Row { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Index for Row { + type Output = T; + + #[inline] + fn index(&self, index: Column) -> &T { + &self.0[index.0] + } +} + +impl IndexMut for Row { + #[inline] + fn index_mut(&mut self, index: Column) -> &mut T { + &mut self.0[index.0] + } +} + +macro_rules! row_index_range { + ($range:ty) => { + impl Index<$range> for Row { + type Output = [T]; + + #[inline] + fn index(&self, index: $range) -> &[T] { + &self.0[index] + } + } + + impl IndexMut<$range> for Row { + #[inline] + fn index_mut(&mut self, index: $range) -> &mut [T] { + &mut self.0[index] + } + } + } +} + +row_index_range!(Range); +row_index_range!(RangeTo); +row_index_range!(RangeFrom); +row_index_range!(RangeFull); + +// ----------------------------------------------------------------------------- +// Column ranges for Row +// ----------------------------------------------------------------------------- + +impl Index> for Row { + type Output = [T]; + + #[inline] + fn index(&self, index: Range) -> &[T] { + &self.0[(index.start.0)..(index.end.0)] + } +} + +impl IndexMut> for Row { + #[inline] + fn index_mut(&mut self, index: Range) -> &mut [T] { + &mut self.0[(index.start.0)..(index.end.0)] + } +} + +impl Index> for Row { + type Output = [T]; + + #[inline] + fn index(&self, index: RangeTo) -> &[T] { + &self.0[..(index.end.0)] + } +} + +impl IndexMut> for Row { + #[inline] + fn index_mut(&mut self, index: RangeTo) -> &mut [T] { + &mut self.0[..(index.end.0)] + } +} + +impl Index> for Row { + type Output = [T]; + + #[inline] + fn index(&self, index: RangeFrom) -> &[T] { + &self.0[(index.start.0)..] + } +} + +impl IndexMut> for Row { + #[inline] + fn index_mut(&mut self, index: RangeFrom) -> &mut [T] { + &mut self.0[(index.start.0)..] + } +} diff --git a/src/grid/tests.rs b/src/grid/tests.rs new file mode 100644 index 00000000..169cefa0 --- /dev/null +++ b/src/grid/tests.rs @@ -0,0 +1,188 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for the Gird + +use super::{Grid, BidirectionalIterator}; +use index::{Point, Line, Column}; + +#[test] +fn grid_swap_lines_ok() { + let mut grid = Grid::new(Line(10), Column(1), &0); + info!(""); + + // swap test ends + grid[Line(0)][Column(0)] = 1; + grid[Line(9)][Column(0)] = 2; + + assert_eq!(grid[Line(0)][Column(0)], 1); + assert_eq!(grid[Line(9)][Column(0)], 2); + + grid.swap_lines(Line(0), Line(9)); + + assert_eq!(grid[Line(0)][Column(0)], 2); + assert_eq!(grid[Line(9)][Column(0)], 1); + + // swap test mid + grid[Line(4)][Column(0)] = 1; + grid[Line(5)][Column(0)] = 2; + + info!("grid: {:?}", grid); + + assert_eq!(grid[Line(4)][Column(0)], 1); + assert_eq!(grid[Line(5)][Column(0)], 2); + + grid.swap_lines(Line(4), Line(5)); + + info!("grid: {:?}", grid); + + assert_eq!(grid[Line(4)][Column(0)], 2); + assert_eq!(grid[Line(5)][Column(0)], 1); +} + +#[test] +#[should_panic] +fn grid_swap_lines_oob1() { + let mut grid = Grid::new(Line(10), Column(1), &0); + grid.swap_lines(Line(0), Line(10)); +} + +#[test] +#[should_panic] +fn grid_swap_lines_oob2() { + let mut grid = Grid::new(Line(10), Column(1), &0); + grid.swap_lines(Line(10), Line(0)); +} + +#[test] +#[should_panic] +fn grid_swap_lines_oob3() { + let mut grid = Grid::new(Line(10), Column(1), &0); + grid.swap_lines(Line(10), Line(10)); +} + +// Scroll up moves lines upwards +#[test] +fn scroll_up() { + info!(""); + + let mut grid = Grid::new(Line(10), Column(1), &0); + for i in 0..10 { + grid[Line(i)][Column(0)] = i; + } + + info!("grid: {:?}", grid); + + grid.scroll_up(Line(0)..Line(10), Line(2)); + + info!("grid: {:?}", grid); + + let mut other = Grid::new(Line(10), Column(1), &9); + + other[Line(0)][Column(0)] = 2; + other[Line(1)][Column(0)] = 3; + other[Line(2)][Column(0)] = 4; + other[Line(3)][Column(0)] = 5; + other[Line(4)][Column(0)] = 6; + other[Line(5)][Column(0)] = 7; + other[Line(6)][Column(0)] = 8; + other[Line(7)][Column(0)] = 9; + other[Line(8)][Column(0)] = 0; + other[Line(9)][Column(0)] = 1; + + for i in 0..10 { + assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)]); + } +} + +// Scroll down moves lines downwards +#[test] +fn scroll_down() { + info!(""); + + let mut grid = Grid::new(Line(10), Column(1), &0); + for i in 0..10 { + grid[Line(i)][Column(0)] = i; + } + + info!("grid: {:?}", grid); + + grid.scroll_down(Line(0)..Line(10), Line(2)); + + info!("grid: {:?}", grid); + + let mut other = Grid::new(Line(10), Column(1), &9); + + other[Line(0)][Column(0)] = 8; + other[Line(1)][Column(0)] = 9; + other[Line(2)][Column(0)] = 0; + other[Line(3)][Column(0)] = 1; + other[Line(4)][Column(0)] = 2; + other[Line(5)][Column(0)] = 3; + other[Line(6)][Column(0)] = 4; + other[Line(7)][Column(0)] = 5; + other[Line(8)][Column(0)] = 6; + other[Line(9)][Column(0)] = 7; + + for i in 0..10 { + assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)]); + } +} + +// Test that GridIterator works +#[test] +fn test_iter() { + info!(""); + + let mut grid = Grid::new(Line(5), Column(5), &0); + for i in 0..5 { + for j in 0..5 { + grid[Line(i)][Column(j)] = i*5 + j; + } + } + + info!("grid: {:?}", grid); + + let mut iter = grid.iter_from(Point { + line: Line(0), + col: Column(0), + }); + + assert_eq!(None, iter.prev()); + assert_eq!(Some(&1), iter.next()); + assert_eq!(Column(1), iter.cur.col); + assert_eq!(Line(0), iter.cur.line); + + assert_eq!(Some(&2), iter.next()); + assert_eq!(Some(&3), iter.next()); + assert_eq!(Some(&4), iter.next()); + + // test linewrapping + assert_eq!(Some(&5), iter.next()); + assert_eq!(Column(0), iter.cur.col); + assert_eq!(Line(1), iter.cur.line); + + assert_eq!(Some(&4), iter.prev()); + assert_eq!(Column(4), iter.cur.col); + assert_eq!(Line(0), iter.cur.line); + + + // test that iter ends at end of grid + let mut final_iter = grid.iter_from(Point { + line: Line(4), + col: Column(4), + }); + assert_eq!(None, final_iter.next()); + assert_eq!(Some(&23), final_iter.prev()); +} -- cgit From 6fc0e1ec49561fd9783332b30632471336004aed Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 12 Oct 2017 20:12:29 -0700 Subject: Eliminate ClearRegion trait --- src/grid/mod.rs | 59 +++++++++++++++++++++++++++++---------------------------- src/term/mod.rs | 36 +++++++++++++++++------------------ 2 files changed, 48 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 123e13fa..bd994033 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -23,7 +23,7 @@ use std::cmp::Ordering; use std::collections::{VecDeque, vec_deque}; use std::iter::IntoIterator; -use std::ops::{Deref, Range, RangeTo, RangeFrom, Index, IndexMut}; +use std::ops::{Deref, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut}; use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; @@ -207,12 +207,6 @@ impl Grid { self.raw.swap(*src, *dst); } - #[inline] - pub fn clear(&mut self, func: F) { - let region = index::Line(0)..self.num_lines(); - self.clear_region(region, func); - } - fn shrink_lines(&mut self, lines: index::Line) { while index::Line(self.raw.len()) != lines { self.raw.pop_back(); @@ -315,28 +309,6 @@ impl<'a, T> IntoIterator for &'a Grid { } } -pub trait ClearRegion { - fn clear_region(&mut self, region: R, func: F); -} - -macro_rules! clear_region_impl { - ($range:ty) => { - impl ClearRegion<$range, T> for Grid { - fn clear_region(&mut self, region: $range, func: F) { - for row in self.region_mut(region) { - for cell in row { - func(cell); - } - } - } - } - } -} - -clear_region_impl!(Range); -clear_region_impl!(RangeTo); -clear_region_impl!(RangeFrom); - // ================================================================================================= // Regions ========================================================================================= // ================================================================================================= @@ -359,6 +331,17 @@ pub struct RegionMut<'a, T: 'a> { raw: &'a mut VecDeque>, } +impl<'a, T> RegionMut<'a, T> { + /// Call the provided function for every item in this region + pub fn each(self, func: F) { + for row in self { + for item in row { + func(item) + } + } + } +} + pub trait IndexRegion { /// Get an immutable region of Self fn region<'a>(&'a self, _: I) -> Region<'a, T>; @@ -428,6 +411,24 @@ impl IndexRegion, T> for Grid { } } +impl IndexRegion for Grid { + fn region(&self, _: RangeFull) -> Region { + Region { + start: Line(0), + end: self.num_lines(), + raw: &self.raw + } + } + + fn region_mut(&mut self, _: RangeFull) -> RegionMut { + RegionMut { + start: Line(0), + end: self.num_lines(), + raw: &mut self.raw + } + } +} + pub struct RegionIter<'a, T: 'a> { end: Line, cur: Line, diff --git a/src/term/mod.rs b/src/term/mod.rs index dd853368..fa6145f9 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -24,7 +24,7 @@ use unicode_width::UnicodeWidthChar; use font::{self, Size}; use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; -use grid::{BidirectionalIterator, Grid, ClearRegion, ToRange, Indexed, IndexRegion}; +use grid::{BidirectionalIterator, Grid, ToRange, Indexed, IndexRegion}; use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive}; use selection::{self, Span, Selection}; use config::{Config, VisualBellAnimation}; @@ -1089,8 +1089,12 @@ impl Term { if num_lines > old_lines { // Make sure bottom of terminal is clear let template = self.cursor.template; - self.grid.clear_region((self.cursor.point.line + 1).., |c| c.reset(&template)); - self.alt_grid.clear_region((self.cursor_save_alt.point.line + 1).., |c| c.reset(&template)); + self.grid + .region_mut((self.cursor.point.line + 1)..) + .each(|c| c.reset(&template)); + self.alt_grid + .region_mut((self.cursor_save_alt.point.line + 1)..) + .each(|c| c.reset(&template)); } } @@ -1113,7 +1117,7 @@ impl Term { pub fn swap_alt(&mut self) { if self.alt { let template = &self.cursor.template; - self.grid.clear(|c| c.reset(template)); + self.grid.region_mut(..).each(|c| c.reset(template)); } self.alt = !self.alt; @@ -1135,7 +1139,9 @@ impl Term { // Clear `lines` lines at bottom of area { let start = max(origin, Line(self.scroll_region.end.0.saturating_sub(lines.0))); - self.grid.clear_region(start..self.scroll_region.end, |c| c.reset(&template)); + self.grid + .region_mut(start..self.scroll_region.end) + .each(|c| c.reset(&template)); } // Scroll between origin and bottom @@ -1157,7 +1163,7 @@ impl Term { // Clear `lines` lines starting from origin to origin + lines { let end = min(origin + lines, self.scroll_region.end); - self.grid.clear_region(origin..end, |c| c.reset(&template)); + self.grid.region_mut(origin..end).each(|c| c.reset(&template)); } // Scroll from origin to bottom less number of lines @@ -1172,7 +1178,7 @@ impl Term { // Clear grid let template = self.cursor.template; - self.grid.clear(|c| c.reset(&template)); + self.grid.region_mut(..).each(|c| c.reset(&template)); } #[inline] @@ -1708,25 +1714,19 @@ impl ansi::Handler for Term { cell.reset(&template); } if self.cursor.point.line < self.grid.num_lines() - 1 { - for row in self.grid.region_mut((self.cursor.point.line + 1)..) { - for cell in row { - cell.reset(&template); - } - } + self.grid.region_mut((self.cursor.point.line + 1)..) + .each(|cell| cell.reset(&template)); } }, ansi::ClearMode::All => { - self.grid.clear(|c| c.reset(&template)); + self.grid.region_mut(..).each(|c| c.reset(&template)); }, ansi::ClearMode::Above => { // If clearing more than one line if self.cursor.point.line > Line(1) { // Fully clear all lines before the current line - for row in self.grid.region_mut(..self.cursor.point.line) { - for cell in row { - cell.reset(&template); - } - } + self.grid.region_mut(..self.cursor.point.line) + .each(|cell| cell.reset(&template)); } // Clear up to the current column in the current line let end = min(self.cursor.point.col + 1, self.grid.num_cols()); -- cgit From 4ed25009c49b5963d91f4e3c7ead0f4a5b678980 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 12 Oct 2017 20:29:35 -0700 Subject: Remove some unused methods and impls --- src/grid/mod.rs | 16 ++-------------- src/grid/row.rs | 43 +++++++++++++++++-------------------------- src/term/mod.rs | 7 ++----- 3 files changed, 21 insertions(+), 45 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index bd994033..9ca0011a 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -122,7 +122,7 @@ impl Grid { } fn grow_cols(&mut self, cols: index::Column, template: &T) { - for row in self.lines_mut() { + for row in self.raw.iter_mut() { row.grow(cols, template); } @@ -131,19 +131,7 @@ impl Grid { } - - impl Grid { - #[inline] - pub fn lines(&self) -> vec_deque::Iter> { - self.raw.iter() - } - - #[inline] - pub fn lines_mut(&mut self) -> vec_deque::IterMut> { - self.raw.iter_mut() - } - #[inline] pub fn num_lines(&self) -> index::Line { self.lines @@ -216,7 +204,7 @@ impl Grid { } fn shrink_cols(&mut self, cols: index::Column) { - for row in self.lines_mut() { + for row in self.raw.iter_mut() { row.shrink(cols); } diff --git a/src/grid/row.rs b/src/grid/row.rs index 8711d04f..4b355a56 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -108,33 +108,8 @@ impl IndexMut for Row { } } -macro_rules! row_index_range { - ($range:ty) => { - impl Index<$range> for Row { - type Output = [T]; - - #[inline] - fn index(&self, index: $range) -> &[T] { - &self.0[index] - } - } - - impl IndexMut<$range> for Row { - #[inline] - fn index_mut(&mut self, index: $range) -> &mut [T] { - &mut self.0[index] - } - } - } -} - -row_index_range!(Range); -row_index_range!(RangeTo); -row_index_range!(RangeFrom); -row_index_range!(RangeFull); - // ----------------------------------------------------------------------------- -// Column ranges for Row +// Index ranges of columns // ----------------------------------------------------------------------------- impl Index> for Row { @@ -184,3 +159,19 @@ impl IndexMut> for Row { &mut self.0[(index.start.0)..] } } + +impl Index for Row { + type Output = [T]; + + #[inline] + fn index(&self, _: RangeFull) -> &[T] { + &self.0[..] + } +} + +impl IndexMut for Row { + #[inline] + fn index_mut(&mut self, _: RangeFull) -> &mut [T] { + &mut self.0[..] + } +} diff --git a/src/term/mod.rs b/src/term/mod.rs index fa6145f9..c405490e 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1299,11 +1299,8 @@ impl ansi::Handler for Term { let mut template = self.cursor.template; template.c = 'E'; - for row in &mut self.grid.lines_mut() { - for cell in row { - cell.reset(&template); - } - } + self.grid.region_mut(..) + .each(|c| c.reset(&template)); } #[inline] -- cgit From a88961bbf01299cef3b675a30575a7d23c5c485a Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 12 Oct 2017 20:59:21 -0700 Subject: Remove redundant selection::Region type The type selection::Region was defined identially to std::ops::Range. Using something other than range just served to confuse. --- src/selection.rs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/selection.rs b/src/selection.rs index cd905164..64cee8c2 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -19,6 +19,7 @@ //! when text is added/removed/scrolled on the screen. The selection should //! also be cleared if the user clicks off of the selection. use std::cmp::{min, max}; +use std::ops::Range; use index::{Point, Column, RangeInclusive, Side, Linear, Line}; use grid::ToRange; @@ -41,20 +42,20 @@ use grid::ToRange; pub enum Selection { Simple { /// The region representing start and end of cursor movement - region: Region, + region: Range, }, Semantic { /// The region representing start and end of cursor movement - region: Region, + region: Range, /// When beginning a semantic selection, the grid is searched around the /// initial point to find semantic escapes, and this initial expansion /// marks those points. - initial_expansion: Region + initial_expansion: Range }, Lines { /// The region representing start and end of cursor movement - region: Region, + region: Range, /// The line under the initial point. This is always selected regardless /// of which way the cursor is moved. @@ -62,11 +63,6 @@ pub enum Selection { } } -pub struct Region { - start: T, - end: T -} - /// A Point and side within that point. pub struct Anchor { point: Point, @@ -99,7 +95,7 @@ pub trait Dimensions { impl Selection { pub fn simple(location: Point, side: Side) -> Selection { Selection::Simple { - region: Region { + region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) } @@ -109,20 +105,20 @@ impl Selection { pub fn semantic(point: Point, grid: &G) -> Selection { let (start, end) = (grid.semantic_search_left(point), grid.semantic_search_right(point)); Selection::Semantic { - region: Region { + region: Range { start: point, end: point, }, - initial_expansion: Region { - start, - end, + initial_expansion: Range { + start: start, + end: end } } } pub fn lines(point: Point) -> Selection { Selection::Lines { - region: Region { + region: Range { start: point, end: point }, @@ -159,8 +155,8 @@ impl Selection { } fn span_semantic( grid: &G, - region: &Region, - initial_expansion: &Region + region: &Range, + initial_expansion: &Range ) -> Option where G: SemanticSearch + Dimensions { @@ -192,7 +188,7 @@ impl Selection { }) } - fn span_lines(grid: &G, region: &Region, initial_line: &Line) -> Option + fn span_lines(grid: &G, region: &Range, initial_line: &Line) -> Option where G: Dimensions { // First, create start and end points based on initial line and the grid @@ -225,7 +221,7 @@ impl Selection { }) } - fn span_simple(grid: &G, region: &Region) -> Option { + fn span_simple(grid: &G, region: &Range) -> Option { let start = region.start.point; let start_side = region.start.side; let end = region.end.point; -- cgit From 350bb8c800232ce0b27512420e99645ec8b95ef1 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 14 Jan 2018 10:17:08 -0800 Subject: Use memcpy for resetting row contents In addition to a marginal performance improvement, this simplifies some logic in the Term implementation since now the Grid fully handles row recycling. --- src/grid/mod.rs | 81 ++++++++++++++++++++++++++++++++++++++++----------------- src/grid/row.rs | 12 ++++++--- src/term/mod.rs | 27 +++---------------- 3 files changed, 69 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 9ca0011a..e790dd41 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -73,6 +73,15 @@ pub struct Grid { /// /// Invariant: lines is equivalent to raw.len() lines: index::Line, + + /// Template row. + /// + /// This is used to quickly populate new lines and clear recycled lines + /// during scroll wrapping. + template_row: Row, + + /// Template cell for populating template_row + template: T, } pub struct GridIterator<'a, T: 'a> { @@ -80,66 +89,60 @@ pub struct GridIterator<'a, T: 'a> { pub cur: Point, } -impl Grid { - pub fn new(lines: index::Line, cols: index::Column, template: &T) -> Grid { +impl Grid { + pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { let mut raw = VecDeque::with_capacity(*lines); + let template_row = Row::new(cols, &template); for _ in IndexRange(index::Line(0)..lines) { - raw.push_back(Row::new(cols, template)); + raw.push_back(template_row.clone()); } Grid { raw, cols, lines, + template_row, + template, } } - pub fn resize(&mut self, lines: index::Line, cols: index::Column, template: &T) { + pub fn resize(&mut self, lines: index::Line, cols: index::Column) { // Check that there's actually work to do and return early if not if lines == self.lines && cols == self.cols { return; } match self.lines.cmp(&lines) { - Ordering::Less => self.grow_lines(lines, template), + Ordering::Less => self.grow_lines(lines), Ordering::Greater => self.shrink_lines(lines), Ordering::Equal => (), } match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(cols, template), + Ordering::Less => self.grow_cols(cols), Ordering::Greater => self.shrink_cols(cols), Ordering::Equal => (), } } - fn grow_lines(&mut self, lines: index::Line, template: &T) { + fn grow_lines(&mut self, lines: index::Line) { for _ in IndexRange(self.num_lines()..lines) { - self.raw.push_back(Row::new(self.cols, template)); + self.raw.push_back(self.template_row.clone()); } self.lines = lines; } - fn grow_cols(&mut self, cols: index::Column, template: &T) { + fn grow_cols(&mut self, cols: index::Column) { for row in self.raw.iter_mut() { - row.grow(cols, template); + row.grow(cols, &self.template); } + // Update self cols self.cols = cols; - } - -} - -impl Grid { - #[inline] - pub fn num_lines(&self) -> index::Line { - self.lines - } - #[inline] - pub fn num_cols(&self) -> index::Column { - self.cols + // Also update template_row to be the correct length + self.template_row.grow(cols, &self.template); } #[inline] @@ -147,7 +150,8 @@ impl Grid { if region.start == Line(0) && region.end == self.num_lines() { // Full rotation for _ in 0..positions.0 { - let item = self.raw.pop_back().unwrap(); + let mut item = self.raw.pop_back().unwrap(); + item.reset(&self.template_row); self.raw.push_front(item); } } else { @@ -155,6 +159,13 @@ impl Grid { for line in IndexRange((region.start + positions)..region.end).rev() { self.swap_lines(line, line - positions); } + + let template = &self.template_row; + for i in IndexRange(Line(0)..positions) { + self.raw + .get_mut(*(region.start + i)) + .map(|row| row.reset(template)); + } } } @@ -163,7 +174,8 @@ impl Grid { if region.start == Line(0) && region.end == self.num_lines() { // Full rotation for _ in 0..positions.0 { - let item = self.raw.pop_front().unwrap(); + let mut item = self.raw.pop_front().unwrap(); + item.reset(&self.template_row); self.raw.push_back(item); } } else { @@ -171,8 +183,28 @@ impl Grid { for line in IndexRange(region.start..(region.end - positions)) { self.swap_lines(line, line + positions); } + + // Clear reused lines + let template = &self.template_row; + for i in IndexRange(Line(0)..positions) { + self.raw + .get_mut(*(region.end - i - 1)) + .map(|row| row.reset(template)); + } } } +} + +impl Grid { + #[inline] + pub fn num_lines(&self) -> index::Line { + self.lines + } + + #[inline] + pub fn num_cols(&self) -> index::Column { + self.cols + } pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { @@ -209,6 +241,7 @@ impl Grid { } self.cols = cols; + self.template_row.shrink(cols); } } diff --git a/src/grid/row.rs b/src/grid/row.rs index 4b355a56..e9cf8dc9 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -24,16 +24,22 @@ use index::Column; #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct Row(Vec); -impl Row { +impl Row { pub fn new(columns: Column, template: &T) -> Row { - Row(vec![template.to_owned(); *columns]) + Row(vec![*template; *columns]) } pub fn grow(&mut self, cols: Column, template: &T) { while self.len() != *cols { - self.push(template.to_owned()); + self.push(*template); } } + + /// Resets contents to the contents of `other` + #[inline] + pub fn reset(&mut self, other: &Row) { + self.copy_from_slice(&**other); + } } impl Row { diff --git a/src/term/mod.rs b/src/term/mod.rs index c405490e..efcafc25 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -800,7 +800,7 @@ impl Term { let num_cols = size.cols(); let num_lines = size.lines(); - let grid = Grid::new(num_lines, num_cols, &template); + let grid = Grid::new(num_lines, num_cols, template); let tabspaces = config.tabspaces(); let tabs = IndexRange::from(Column(0)..grid.num_cols()) @@ -1066,9 +1066,8 @@ impl Term { debug!("num_cols, num_lines = {}, {}", num_cols, num_lines); // Resize grids to new size - let template = Cell::default(); - self.grid.resize(num_lines, num_cols, &template); - self.alt_grid.resize(num_lines, num_cols, &template); + self.grid.resize(num_lines, num_cols); + self.alt_grid.resize(num_lines, num_cols); // Reset scrolling region to new size self.scroll_region = Line(0)..self.grid.num_lines(); @@ -1133,17 +1132,6 @@ impl Term { trace!("scroll_down_relative: origin={}, lines={}", origin, lines); let lines = min(lines, self.scroll_region.end - self.scroll_region.start); - // Copy of cell template; can't have it borrowed when calling clear/scroll - let template = self.cursor.template; - - // Clear `lines` lines at bottom of area - { - let start = max(origin, Line(self.scroll_region.end.0.saturating_sub(lines.0))); - self.grid - .region_mut(start..self.scroll_region.end) - .each(|c| c.reset(&template)); - } - // Scroll between origin and bottom self.grid.scroll_down(&(origin..self.scroll_region.end), lines); } @@ -1157,15 +1145,6 @@ impl Term { trace!("scroll_up_relative: origin={}, lines={}", origin, lines); let lines = min(lines, self.scroll_region.end - self.scroll_region.start); - // Copy of cell template; can't have it borrowed when calling clear/scroll - let template = self.cursor.template; - - // Clear `lines` lines starting from origin to origin + lines - { - let end = min(origin + lines, self.scroll_region.end); - self.grid.region_mut(origin..end).each(|c| c.reset(&template)); - } - // Scroll from origin to bottom less number of lines self.grid.scroll_up(&(origin..self.scroll_region.end), lines); } -- cgit From f13685918ff42c3a919f8a84fea64a144979d13a Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 14 Jan 2018 21:51:56 -0800 Subject: WIP optimize scroll in region This intends to optimize the case where the top of the scrolling region is the top of the screen. In theory, scrolling in this case can be optimized to shifting the start/end of the visible region, and then rearranging any lines that were not supposed to be scrolled (at the bottom of the region). However, this didn't produce quite the speedup I expected. --- src/grid/mod.rs | 90 +++++++++++++++++++++++------------- src/grid/storage.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 32 deletions(-) create mode 100644 src/grid/storage.rs (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index e790dd41..8e30915c 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -21,7 +21,6 @@ //! ranges is currently supported. use std::cmp::Ordering; -use std::collections::{VecDeque, vec_deque}; use std::iter::IntoIterator; use std::ops::{Deref, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut}; @@ -33,6 +32,9 @@ pub use self::row::Row; #[cfg(test)] mod tests; +mod storage; +use self::storage::Storage; + /// Convert a type to a linear index range. pub trait ToRange { fn to_range(&self) -> RangeInclusive; @@ -64,7 +66,7 @@ impl Deref for Indexed { pub struct Grid { /// Lines in the grid. Each row holds a list of cells corresponding to the /// columns in that row. - raw: VecDeque>, + raw: Storage>, /// Number of columns cols: index::Column, @@ -82,6 +84,9 @@ pub struct Grid { /// Template cell for populating template_row template: T, + + /// Temporary row storage for scrolling with a region + temp: Vec>, } pub struct GridIterator<'a, T: 'a> { @@ -91,10 +96,10 @@ pub struct GridIterator<'a, T: 'a> { impl Grid { pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { - let mut raw = VecDeque::with_capacity(*lines); + let mut raw = Storage::with_capacity(*lines); let template_row = Row::new(cols, &template); for _ in IndexRange(index::Line(0)..lines) { - raw.push_back(template_row.clone()); + raw.push(template_row.clone()); } Grid { @@ -103,6 +108,7 @@ impl Grid { lines, template_row, template, + temp: Vec::new(), } } @@ -127,7 +133,7 @@ impl Grid { fn grow_lines(&mut self, lines: index::Line) { for _ in IndexRange(self.num_lines()..lines) { - self.raw.push_back(self.template_row.clone()); + self.raw.push(self.template_row.clone()); } self.lines = lines; @@ -147,12 +153,25 @@ impl Grid { #[inline] pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { - if region.start == Line(0) && region.end == self.num_lines() { - // Full rotation - for _ in 0..positions.0 { - let mut item = self.raw.pop_back().unwrap(); - item.reset(&self.template_row); - self.raw.push_front(item); + // Whether or not there is a scrolling region active, as long as it + // starts at the top, we can do a full rotation which just involves + // changing the start index. + // + // To accomodate scroll regions, rows are reordered at the end. + if region.start == Line(0) { + // Rotate the entire line buffer. If there's a scrolling region + // active, the bottom lines are restored in the next step. + self.raw.rotate(-(*positions as isize)); + + // Now, restore any scroll region lines + for i in IndexRange(region.end .. self.num_lines()) { + // First do the swap + self.raw.swap(*i, *i + *positions); + } + + // Finally, reset recycled lines + for i in 0..*positions { + self.raw[i].reset(&self.template_row); } } else { // Subregion rotation @@ -160,23 +179,33 @@ impl Grid { self.swap_lines(line, line - positions); } - let template = &self.template_row; for i in IndexRange(Line(0)..positions) { - self.raw - .get_mut(*(region.start + i)) - .map(|row| row.reset(template)); + self.raw[*(region.start - i - 1)].reset(&self.template_row); } } } #[inline] pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { - if region.start == Line(0) && region.end == self.num_lines() { - // Full rotation - for _ in 0..positions.0 { - let mut item = self.raw.pop_front().unwrap(); - item.reset(&self.template_row); - self.raw.push_back(item); + if region.start == Line(0) { + // Rotate the entire line buffer. If there's a scrolling region + // active, the bottom lines are restored in the next step. + self.raw.rotate(*positions as isize); + + // Now, restore any lines outside the scroll region + let mut i = 0; + for _ in IndexRange(region.end .. self.num_lines()) { + let idx = *self.num_lines() - i - 1; + // First do the swap + self.raw.swap(idx, idx - *positions); + i += 1; + } + + // Finally, reset recycled lines + // + // Recycled lines are just above the end of the scrolling region. + for i in 0..*positions { + self.raw[*region.end - i - 1].reset(&self.template_row); } } else { // Subregion rotation @@ -185,11 +214,8 @@ impl Grid { } // Clear reused lines - let template = &self.template_row; for i in IndexRange(Line(0)..positions) { - self.raw - .get_mut(*(region.end - i - 1)) - .map(|row| row.reset(template)); + self.raw[*(region.start - i - 1)].reset(&self.template_row); } } } @@ -229,7 +255,7 @@ impl Grid { fn shrink_lines(&mut self, lines: index::Line) { while index::Line(self.raw.len()) != lines { - self.raw.pop_back(); + self.raw.pop(); } self.lines = lines; @@ -322,10 +348,10 @@ impl<'point, T> IndexMut<&'point Point> for Grid { impl<'a, T> IntoIterator for &'a Grid { type Item = &'a Row; - type IntoIter = vec_deque::Iter<'a, Row>; + type IntoIter = self::storage::Iter<'a, Row>; #[inline] - fn into_iter(self) -> vec_deque::Iter<'a, Row> { + fn into_iter(self) -> self::storage::Iter<'a, Row> { self.raw.iter() } } @@ -340,7 +366,7 @@ impl<'a, T> IntoIterator for &'a Grid { pub struct Region<'a, T: 'a> { start: Line, end: Line, - raw: &'a VecDeque>, + raw: &'a Storage>, } /// A mutable subset of lines in the grid @@ -349,7 +375,7 @@ pub struct Region<'a, T: 'a> { pub struct RegionMut<'a, T: 'a> { start: Line, end: Line, - raw: &'a mut VecDeque>, + raw: &'a mut Storage>, } impl<'a, T> RegionMut<'a, T> { @@ -453,13 +479,13 @@ impl IndexRegion for Grid { pub struct RegionIter<'a, T: 'a> { end: Line, cur: Line, - raw: &'a VecDeque>, + raw: &'a Storage>, } pub struct RegionIterMut<'a, T: 'a> { end: Line, cur: Line, - raw: &'a mut VecDeque>, + raw: &'a mut Storage>, } impl<'a, T> IntoIterator for Region<'a, T> { diff --git a/src/grid/storage.rs b/src/grid/storage.rs new file mode 100644 index 00000000..f5c2d87e --- /dev/null +++ b/src/grid/storage.rs @@ -0,0 +1,129 @@ +/// Wrapper around Vec which supports fast indexing and rotation +/// +/// The rotation implemented by grid::Storage is a simple integer addition. +/// Compare with standard library rotation which requires rearranging items in +/// memory. +/// +/// As a consequence, the indexing operators need to be reimplemented for this +/// type to account for the 0th element not always being at the start of the +/// allocation. +/// +/// Because certain Vec operations are no longer valid on this type, no Deref +/// implementation is provided. Anything from Vec that should be exposed must be +/// done so manually. +use std::ops::{Index, IndexMut}; + +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +pub struct Storage { + inner: Vec, + zero: usize, +} + +impl Storage { + #[inline] + pub fn with_capacity(cap: usize) -> Storage { + Storage { + inner: Vec::with_capacity(cap), + zero: 0 + } + } + + #[inline] + pub fn push(&mut self, item: T) { + self.inner.push(item) + } + + #[inline] + pub fn pop(&mut self) -> Option { + self.inner.pop() + } + + #[inline] + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Compute actual index in underlying storage given the requested index. + #[inline] + fn compute_index(&self, requested: usize) -> usize { + (requested + self.zero) % self.len() + } + + pub fn swap(&mut self, a: usize, b: usize) { + let a = self.compute_index(a); + let b = self.compute_index(b); + self.inner.swap(a, b); + } + + pub fn iter(&self) -> Iter { + Iter { storage: self, index: 0 } + } + + pub fn iter_mut(&mut self) -> IterMut { + IterMut { storage: self, index: 0 } + } + + pub fn rotate(&mut self, count: isize) { + let len = self.len(); + assert!(count.abs() as usize <= len); + self.zero += (count + len as isize) as usize % len; + } +} + +impl Index for Storage { + type Output = T; + #[inline] + fn index(&self, index: usize) -> &T { + let index = self.compute_index(index); // borrowck + &self.inner[index] + } +} + +impl IndexMut for Storage { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut T { + let index = self.compute_index(index); // borrowck + &mut self.inner[index] + } +} + +pub struct Iter<'a, T: 'a> { + storage: &'a Storage, + index: usize, +} + +pub struct IterMut<'a, T: 'a> { + storage: &'a mut Storage, + index: usize, +} + +impl<'a, T: 'a> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.index == self.storage.len() { + None + } else { + let res = Some(&self.storage[self.index]); + self.index += 1; + res + } + } +} + +impl<'a, T: 'a> Iterator for IterMut<'a, T> { + type Item = &'a mut T; + + fn next(&mut self) -> Option { + if self.index == self.storage.len() { + None + } else { + let index = self.index; + self.index += 1; + + unsafe { + Some(&mut *(&mut self.storage[index] as *mut _)) + } + } + } +} -- cgit From 92b11cfa43c3a5d32b461e43975146b27eb71ccd Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 11 Feb 2018 10:07:33 -0800 Subject: Minor improvements --- src/grid/mod.rs | 18 +++++++++--------- src/grid/storage.rs | 5 +++++ 2 files changed, 14 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 8e30915c..0c84530a 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -179,26 +179,26 @@ impl Grid { self.swap_lines(line, line - positions); } - for i in IndexRange(Line(0)..positions) { - self.raw[*(region.start - i - 1)].reset(&self.template_row); + for line in IndexRange(region.start .. (region.start + positions)) { + self.raw[*line].reset(&self.template_row); } } } + /// scroll_up moves lines at the bottom towards the top + /// + /// This is the performance-sensitive part of scrolling. #[inline] pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { if region.start == Line(0) { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. - self.raw.rotate(*positions as isize); + self.raw.rotate_up(*positions); // Now, restore any lines outside the scroll region - let mut i = 0; - for _ in IndexRange(region.end .. self.num_lines()) { - let idx = *self.num_lines() - i - 1; + for idx in (*region.end .. *self.num_lines()).rev() { // First do the swap self.raw.swap(idx, idx - *positions); - i += 1; } // Finally, reset recycled lines @@ -214,8 +214,8 @@ impl Grid { } // Clear reused lines - for i in IndexRange(Line(0)..positions) { - self.raw[*(region.start - i - 1)].reset(&self.template_row); + for line in IndexRange((region.end - positions) .. region.end) { + self.raw[*line].reset(&self.template_row); } } } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index f5c2d87e..49f3d26c 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -68,6 +68,11 @@ impl Storage { assert!(count.abs() as usize <= len); self.zero += (count + len as isize) as usize % len; } + + // Fast path + pub fn rotate_up(&mut self, count: usize) { + self.zero = (self.zero + count) % self.len(); + } } impl Index for Storage { -- cgit From 84769ce170bd529989698bbbb4ee902e72569cbe Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 11 Feb 2018 12:27:23 -0800 Subject: Remove some unused impls --- src/grid/mod.rs | 10 ---------- src/grid/storage.rs | 23 ----------------------- 2 files changed, 33 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 0c84530a..12702ddf 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -346,16 +346,6 @@ impl<'point, T> IndexMut<&'point Point> for Grid { } } -impl<'a, T> IntoIterator for &'a Grid { - type Item = &'a Row; - type IntoIter = self::storage::Iter<'a, Row>; - - #[inline] - fn into_iter(self) -> self::storage::Iter<'a, Row> { - self.raw.iter() - } -} - // ================================================================================================= // Regions ========================================================================================= // ================================================================================================= diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 49f3d26c..3c64f700 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -55,10 +55,6 @@ impl Storage { self.inner.swap(a, b); } - pub fn iter(&self) -> Iter { - Iter { storage: self, index: 0 } - } - pub fn iter_mut(&mut self) -> IterMut { IterMut { storage: self, index: 0 } } @@ -92,30 +88,11 @@ impl IndexMut for Storage { } } -pub struct Iter<'a, T: 'a> { - storage: &'a Storage, - index: usize, -} - pub struct IterMut<'a, T: 'a> { storage: &'a mut Storage, index: usize, } -impl<'a, T: 'a> Iterator for Iter<'a, T> { - type Item = &'a T; - - fn next(&mut self) -> Option { - if self.index == self.storage.len() { - None - } else { - let res = Some(&self.storage[self.index]); - self.index += 1; - res - } - } -} - impl<'a, T: 'a> Iterator for IterMut<'a, T> { type Item = &'a mut T; -- cgit From 94796a70fcbc11df2dc642057fef242466822067 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 11 Feb 2018 21:25:33 -0800 Subject: wip scrollback --- src/grid/mod.rs | 35 +++++++++++++++++++++++++++++------ src/grid/row.rs | 8 ++++++-- 2 files changed, 35 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 12702ddf..f0bd2f6f 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -35,6 +35,9 @@ mod tests; mod storage; use self::storage::Storage; +/// Lines to keep in scrollback buffer +const SCROLLBACK_LINES: usize = 100_000; + /// Convert a type to a linear index range. pub trait ToRange { fn to_range(&self) -> RangeInclusive; @@ -87,6 +90,13 @@ pub struct Grid { /// Temporary row storage for scrolling with a region temp: Vec>, + + /// Offset of displayed area + /// + /// If the displayed region isn't at the bottom of the screen, it stays + /// stationary while more text is emitted. The scrolling implementation + /// updates this offset accordingly. + display_offset: usize, } pub struct GridIterator<'a, T: 'a> { @@ -96,9 +106,15 @@ pub struct GridIterator<'a, T: 'a> { impl Grid { pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { - let mut raw = Storage::with_capacity(*lines); + let mut raw = Storage::with_capacity(*lines + SCROLLBACK_LINES); let template_row = Row::new(cols, &template); - for _ in IndexRange(index::Line(0)..lines) { + + // Allocate all lines in the buffer, including scrollback history + // + // TODO (jwilm) Allocating each line at this point is expensive and + // delays startup. A nice solution might be having `Row` delay + // allocation until it's actually used. + for _ in 0..raw.capacity() { raw.push(template_row.clone()); } @@ -191,6 +207,11 @@ impl Grid { #[inline] pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { if region.start == Line(0) { + // Update display offset when not pinned to active area + if self.display_offset != 0 { + self.display_offset += *positions; + } + // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. self.raw.rotate_up(*positions); @@ -319,14 +340,16 @@ impl Index for Grid { #[inline] fn index(&self, index: index::Line) -> &Row { - &self.raw[index.0] + let index = self.lines.0 - index.0; + &self.raw[index] } } impl IndexMut for Grid { #[inline] fn index_mut(&mut self, index: index::Line) -> &mut Row { - &mut self.raw[index.0] + let index = self.lines.0 - index.0; + &mut self.raw[index] } } @@ -335,14 +358,14 @@ impl<'point, T> Index<&'point Point> for Grid { #[inline] fn index<'a>(&'a self, point: &Point) -> &'a T { - &self.raw[point.line.0][point.col] + &self[point.line][point.col] } } impl<'point, T> IndexMut<&'point Point> for Grid { #[inline] fn index_mut<'a, 'b>(&'a mut self, point: &'b Point) -> &'a mut T { - &mut self.raw[point.line.0][point.col] + &mut self[point.line][point.col] } } diff --git a/src/grid/row.rs b/src/grid/row.rs index e9cf8dc9..1c83d45d 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -22,7 +22,10 @@ use index::Column; /// A row in the grid #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct Row(Vec); +pub struct Row { + data: Vec, + id: u64 +} impl Row { pub fn new(columns: Column, template: &T) -> Row { @@ -37,8 +40,9 @@ impl Row { /// Resets contents to the contents of `other` #[inline] - pub fn reset(&mut self, other: &Row) { + pub fn reset(&mut self, other: &Row, id: u64) { self.copy_from_slice(&**other); + self.id = id; } } -- cgit From 45c2b3fbf72fa6dfd36bee590e64314c6da7c6c2 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 15 Feb 2018 18:35:49 -0800 Subject: checkpoint: very basic scrolling works Things that do not work - Limiting how far back in the buffer it's possible to scroll - Selections (need to transform to buffer offsets) --- src/event.rs | 4 ++ src/grid/mod.rs | 200 +++++++++++++++++++++++++++++++++++++++------------- src/grid/row.rs | 18 +---- src/grid/storage.rs | 40 ++++++++++- src/input.rs | 28 ++++---- src/term/mod.rs | 183 ++++++++++++++++++++++------------------------- 6 files changed, 295 insertions(+), 178 deletions(-) (limited to 'src') diff --git a/src/event.rs b/src/event.rs index 4ae25860..9c3e8edc 100644 --- a/src/event.rs +++ b/src/event.rs @@ -55,6 +55,10 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { *self.size_info } + fn scroll(&mut self, count: isize) { + self.terminal.scroll_display(count); + } + fn copy_selection(&self, buffer: ::copypasta::Buffer) { if let Some(ref selection) = *self.selection { if let Some(ref span) = selection.to_span(self.terminal) { diff --git a/src/grid/mod.rs b/src/grid/mod.rs index f0bd2f6f..820cd9e2 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -12,17 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! A generic 2d grid implementation optimized for use in a terminal. -//! -//! The current implementation uses a vector of vectors to store cell data. -//! Reimplementing the store as a single contiguous vector may be desirable in -//! the future. Rotation and indexing would need to be reconsidered at that -//! time; rotation currently reorganize Vecs in the lines Vec, and indexing with -//! ranges is currently supported. - -use std::cmp::Ordering; -use std::iter::IntoIterator; -use std::ops::{Deref, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut}; +//! A specialized 2d grid implementation optimized for use in a terminal. + +use std::cmp::{max, Ordering}; +use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull}; use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; @@ -36,7 +29,7 @@ mod storage; use self::storage::Storage; /// Lines to keep in scrollback buffer -const SCROLLBACK_LINES: usize = 100_000; +const SCROLLBACK_LINES: usize = 10_000; /// Convert a type to a linear index range. pub trait ToRange { @@ -105,8 +98,12 @@ pub struct GridIterator<'a, T: 'a> { } impl Grid { + pub fn scroll_display(&mut self, count: isize) { + self.display_offset = max((self.display_offset as isize) + count, 0isize) as usize; + } + pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { - let mut raw = Storage::with_capacity(*lines + SCROLLBACK_LINES); + let mut raw = Storage::with_capacity(*lines + SCROLLBACK_LINES, lines); let template_row = Row::new(cols, &template); // Allocate all lines in the buffer, including scrollback history @@ -125,6 +122,7 @@ impl Grid { template_row, template, temp: Vec::new(), + display_offset: 0, } } @@ -147,12 +145,23 @@ impl Grid { } } - fn grow_lines(&mut self, lines: index::Line) { - for _ in IndexRange(self.num_lines()..lines) { - self.raw.push(self.template_row.clone()); - } + /// Add lines to the visible area + /// + /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the + /// bottom of the screen as long as there is scrollback available. Once + /// scrollback is exhausted, new lines are simply added to the bottom of the + /// screen. + /// + /// Alacritty takes a different approach. Rather than trying to move with + /// the scrollback, we simply pull additional lines from the back of the + /// buffer in order to populate the new area. + fn grow_lines(&mut self, target: index::Line) { + let delta = target - self.lines; - self.lines = lines; + self.raw.set_visible_lines(target); + self.lines = target; + + self.scroll_up(&(Line(0)..target), delta); } fn grow_cols(&mut self, cols: index::Column) { @@ -167,6 +176,37 @@ impl Grid { self.template_row.grow(cols, &self.template); } + /// Remove lines from the visible area + /// + /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the + /// bottom of the screen. This is achieved by pushing history "out the top" + /// of the terminal window. + /// + /// Alacritty takes the same approach. + fn shrink_lines(&mut self, target: index::Line) { + // TODO handle disabled scrollback + // while index::Line(self.raw.len()) != lines { + // self.raw.pop(); + // } + + let prev = self.lines; + + self.raw.rotate(*prev as isize - *target as isize); + self.raw.set_visible_lines(target); + self.lines = target; + } + + /// Convert a Line index (active region) to a buffer offset + /// + /// # Panics + /// + /// This method will panic if `Line` is larger than the grid dimensions + pub fn line_to_offset(&self, line: index::Line) -> usize { + assert!(line < self.num_lines()); + + *(self.num_lines() - line - 1) + } + #[inline] pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { // Whether or not there is a scrolling region active, as long as it @@ -177,12 +217,12 @@ impl Grid { if region.start == Line(0) { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. - self.raw.rotate(-(*positions as isize)); + self.raw.rotate_up(*positions); // Now, restore any scroll region lines for i in IndexRange(region.end .. self.num_lines()) { // First do the swap - self.raw.swap(*i, *i + *positions); + self.raw.swap_lines(i, i + positions); } // Finally, reset recycled lines @@ -192,7 +232,7 @@ impl Grid { } else { // Subregion rotation for line in IndexRange((region.start + positions)..region.end).rev() { - self.swap_lines(line, line - positions); + self.raw.swap_lines(line, line - positions); } for line in IndexRange(region.start .. (region.start + positions)) { @@ -214,29 +254,29 @@ impl Grid { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. - self.raw.rotate_up(*positions); + self.raw.rotate(-(*positions as isize)); // Now, restore any lines outside the scroll region for idx in (*region.end .. *self.num_lines()).rev() { // First do the swap - self.raw.swap(idx, idx - *positions); + self.raw.swap_lines(Line(idx), Line(idx) - positions); } // Finally, reset recycled lines // // Recycled lines are just above the end of the scrolling region. for i in 0..*positions { - self.raw[*region.end - i - 1].reset(&self.template_row); + self.raw[region.end - i - 1].reset(&self.template_row); } } else { // Subregion rotation for line in IndexRange(region.start..(region.end - positions)) { - self.swap_lines(line, line + positions); + self.raw.swap_lines(line, line + positions); } // Clear reused lines for line in IndexRange((region.end - positions) .. region.end) { - self.raw[*line].reset(&self.template_row); + self.raw[line].reset(&self.template_row); } } } @@ -248,6 +288,10 @@ impl Grid { self.lines } + pub fn display_iter(&self) -> DisplayIter { + DisplayIter::new(self) + } + #[inline] pub fn num_cols(&self) -> index::Column { self.cols @@ -265,22 +309,14 @@ impl Grid { self.lines > point.line && self.cols > point.col } - /// Swap two lines in the grid - /// - /// This could have used slice::swap internally, but we are able to have - /// better error messages by doing the bounds checking ourselves. - #[inline] - pub fn swap_lines(&mut self, src: index::Line, dst: index::Line) { - self.raw.swap(*src, *dst); - } - - fn shrink_lines(&mut self, lines: index::Line) { - while index::Line(self.raw.len()) != lines { - self.raw.pop(); - } - - self.lines = lines; - } + // /// Swap two lines in the grid + // /// + // /// This could have used slice::swap internally, but we are able to have + // /// better error messages by doing the bounds checking ourselves. + // #[inline] + // pub fn swap_lines(&mut self, src: index::Line, dst: index::Line) { + // self.raw.swap(*src, *dst); + // } fn shrink_cols(&mut self, cols: index::Column) { for row in self.raw.iter_mut() { @@ -335,12 +371,22 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { } } +/// Index active region by line impl Index for Grid { type Output = Row; #[inline] fn index(&self, index: index::Line) -> &Row { - let index = self.lines.0 - index.0; + &self.raw[index] + } +} + +/// Index with buffer offset +impl Index for Grid { + type Output = Row; + + #[inline] + fn index(&self, index: usize) -> &Row { &self.raw[index] } } @@ -348,7 +394,6 @@ impl Index for Grid { impl IndexMut for Grid { #[inline] fn index_mut(&mut self, index: index::Line) -> &mut Row { - let index = self.lines.0 - index.0; &mut self.raw[index] } } @@ -369,9 +414,9 @@ impl<'point, T> IndexMut<&'point Point> for Grid { } } -// ================================================================================================= -// Regions ========================================================================================= -// ================================================================================================= +// ------------------------------------------------------------------------------------------------- +// REGIONS +// ------------------------------------------------------------------------------------------------- /// A subset of lines in the grid /// @@ -533,7 +578,7 @@ impl<'a, T> Iterator for RegionIter<'a, T> { if self.cur < self.end { let index = self.cur; self.cur += 1; - Some(&self.raw[*index]) + Some(&self.raw[index]) } else { None } @@ -547,10 +592,67 @@ impl<'a, T> Iterator for RegionIterMut<'a, T> { let index = self.cur; self.cur += 1; unsafe { - Some(&mut *(&mut self.raw[index.0] as *mut _)) + Some(&mut *(&mut self.raw[index] as *mut _)) } } else { None } } } + +// ------------------------------------------------------------------------------------------------- +// DISPLAY ITERATOR +// ------------------------------------------------------------------------------------------------- + +/// Iterates over the visible area accounting for buffer transform +pub struct DisplayIter<'a, T: 'a> { + grid: &'a Grid, + offset: usize, + limit: usize, + col: Column, +} + +impl<'a, T: 'a> DisplayIter<'a, T> { + pub fn new(grid: &'a Grid) -> DisplayIter<'a, T> { + let offset = grid.display_offset + *grid.num_lines() - 1; + let limit = grid.display_offset; + let col = Column(0); + + DisplayIter { grid, offset, col, limit } + } + + pub fn offset(&self) -> usize { + self.offset + } + + pub fn column(&self) -> Column { + self.col + } +} + +impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { + type Item = Indexed; + + #[inline] + fn next(&mut self) -> Option { + // Make sure indices are valid. Return None if we've reached the end. + if self.col == self.grid.num_cols() { + if self.offset == self.limit { + return None; + } + + self.col = Column(0); + self.offset -= 1; + } + + // Return the next item. + let item = Some(Indexed { + inner: self.grid.raw[self.offset][self.col], + line: Line( *self.grid.lines - 1 - (self.offset - self.limit)), + column: self.col + }); + + self.col += 1; + item + } +} diff --git a/src/grid/row.rs b/src/grid/row.rs index 1c83d45d..6b6af7c8 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -22,10 +22,7 @@ use index::Column; /// A row in the grid #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct Row { - data: Vec, - id: u64 -} +pub struct Row(Vec); impl Row { pub fn new(columns: Column, template: &T) -> Row { @@ -40,9 +37,8 @@ impl Row { /// Resets contents to the contents of `other` #[inline] - pub fn reset(&mut self, other: &Row, id: u64) { + pub fn reset(&mut self, other: &Row) { self.copy_from_slice(&**other); - self.id = id; } } @@ -52,16 +48,6 @@ impl Row { self.pop(); } } - - #[inline] - pub fn cells(&self) -> slice::Iter { - self.0.iter() - } - - #[inline] - pub fn cells_mut(&mut self) -> slice::IterMut { - self.0.iter_mut() - } } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 3c64f700..b4228687 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -13,21 +13,34 @@ /// done so manually. use std::ops::{Index, IndexMut}; +use index::Line; + #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct Storage { inner: Vec, zero: usize, + visible_lines: Line, } impl Storage { #[inline] - pub fn with_capacity(cap: usize) -> Storage { + pub fn with_capacity(cap: usize, lines: Line) -> Storage { Storage { inner: Vec::with_capacity(cap), - zero: 0 + zero: 0, + visible_lines: lines - 1, } } + #[inline] + pub fn capacity(&self) -> usize { + self.inner.capacity() + } + + pub fn set_visible_lines(&mut self, next: Line) { + self.visible_lines = next - 1; + } + #[inline] pub fn push(&mut self, item: T) { self.inner.push(item) @@ -55,6 +68,12 @@ impl Storage { self.inner.swap(a, b); } + pub fn swap_lines(&mut self, a: Line, b: Line) { + let a = self.visible_lines - a; + let b = self.visible_lines - b; + self.swap(*a, *b); + } + pub fn iter_mut(&mut self) -> IterMut { IterMut { storage: self, index: 0 } } @@ -88,6 +107,23 @@ impl IndexMut for Storage { } } +impl Index for Storage { + type Output = T; + #[inline] + fn index(&self, index: Line) -> &T { + let index = self.visible_lines - index; + &self[*index] + } +} + +impl IndexMut for Storage { + #[inline] + fn index_mut(&mut self, index: Line) -> &mut T { + let index = self.visible_lines - index; + &mut self[*index] + } +} + pub struct IterMut<'a, T: 'a> { storage: &'a mut Storage, index: usize, diff --git a/src/input.rs b/src/input.rs index 2d603783..2e345c2a 100644 --- a/src/input.rs +++ b/src/input.rs @@ -64,6 +64,7 @@ pub trait ActionContext { fn last_modifiers(&mut self) -> &mut ModifiersState; fn change_font_size(&mut self, delta: i8); fn reset_font_size(&mut self); + fn scroll(&mut self, count: isize) {} } /// Describes a state and action to take in that state @@ -428,10 +429,6 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { pub fn on_mouse_wheel(&mut self, delta: MouseScrollDelta, phase: TouchPhase, modifiers: ModifiersState) { let mouse_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - if !self.ctx.terminal_mode().intersects(mouse_modes | TermMode::ALT_SCREEN) { - return; - } - match delta { MouseScrollDelta::LineDelta(_columns, lines) => { let to_scroll = self.ctx.mouse_mut().lines_scrolled + lines; @@ -482,16 +479,19 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { let mouse_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; if self.ctx.terminal_mode().intersects(mouse_modes) { self.mouse_report(code, ElementState::Pressed, modifiers); - } else if faux_scrollback_lines > 0 { - // Faux scrolling - let cmd = code + 1; // 64 + 1 = A, 65 + 1 = B - let mut content = Vec::with_capacity(faux_scrollback_lines * 3); - for _ in 0..faux_scrollback_lines { - content.push(0x1b); - content.push(b'O'); - content.push(cmd); - } - self.ctx.write_to_pty(content); + // } else if faux_scrollback_lines > 0 { + // // Faux scrolling + // let cmd = code + 1; // 64 + 1 = A, 65 + 1 = B + // let mut content = Vec::with_capacity(faux_scrollback_lines * 3); + // for _ in 0..faux_scrollback_lines { + // content.push(0x1b); + // content.push(b'O'); + // content.push(cmd); + // } + // self.ctx.write_to_pty(content); + // } + } else { + self.ctx.scroll(-((code as isize) * 2 - 129)); } } diff --git a/src/term/mod.rs b/src/term/mod.rs index efcafc25..e2088413 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -24,8 +24,8 @@ use unicode_width::UnicodeWidthChar; use font::{self, Size}; use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; -use grid::{BidirectionalIterator, Grid, ToRange, Indexed, IndexRegion}; -use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive}; +use grid::{BidirectionalIterator, Grid, ToRange, Indexed, IndexRegion, DisplayIter}; +use index::{self, Point, Column, Line, IndexRange, Contains, RangeInclusive}; use selection::{self, Span, Selection}; use config::{Config, VisualBellAnimation}; use {MouseCursor, Rgb}; @@ -94,12 +94,11 @@ impl selection::Dimensions for Term { /// This manages the cursor during a render. The cursor location is inverted to /// draw it, and reverted after drawing to maintain state. pub struct RenderableCellsIter<'a> { + inner: DisplayIter<'a, Cell>, grid: &'a Grid, cursor: &'a Point, - cursor_index: index::Linear, + cursor_offset: usize, mode: TermMode, - line: Line, - column: Column, config: &'a Config, colors: &'a color::List, selection: Option>, @@ -120,18 +119,18 @@ impl<'a> RenderableCellsIter<'a> { selection: Option>, cursor_style: CursorStyle, ) -> RenderableCellsIter<'b> { - let cursor_index = Linear(cursor.line.0 * grid.num_cols().0 + cursor.col.0); + let cursor_offset = grid.line_to_offset(cursor.line); + let inner = grid.display_iter(); RenderableCellsIter { - grid, - cursor, - cursor_index, - mode, - line: Line(0), - column: Column(0), - selection, - config, - colors, + cursor: cursor, + cursor_offset: cursor_offset, + grid: grid, + inner: inner, + mode: mode, + selection: selection, + config: config, + colors: colors, cursor_cells: ArrayDeque::new(), }.initialize(cursor_style) } @@ -318,6 +317,7 @@ impl<'a> RenderableCellsIter<'a> { } pub struct RenderableCell { + /// A _Display_ line (not necessarily an _Active_ line) pub line: Line, pub column: Column, pub c: char, @@ -336,81 +336,70 @@ impl<'a> Iterator for RenderableCellsIter<'a> { /// (eg. invert fg and bg colors). #[inline] fn next(&mut self) -> Option { - while self.line < self.grid.num_lines() { - while self.column < self.grid.num_cols() { - // Grab current state for this iteration - let line = self.line; - let mut column = self.column; - let cell = &self.grid[line][column]; - - let index = Linear(line.0 * self.grid.num_cols().0 + column.0); - - let (cell, selected) = if index == self.cursor_index { - // Cursor cell - let cell = self.cursor_cells.pop_front().unwrap(); - column = cell.column; - - // Since there may be multiple cursor cells (for a wide - // char), only update iteration position after all cursor - // cells have been drawn. - if self.cursor_cells.is_empty() { - self.line = cell.line; - self.column = cell.column + 1; - } - (cell.inner, false) - } else { - // Normal cell - self.column += 1; + loop { + // Handle cursor + let (cell, selected) = if self.cursor_offset == self.inner.offset() && + self.inner.column() == self.cursor.col + { + // Cursor cell + let cell = self.cursor_cells.pop_front().unwrap(); + + // Since there may be multiple cursor cells (for a wide + // char), only update iteration position after all cursor + // cells have been drawn. + if self.cursor_cells.is_empty() { + self.inner.next(); + } + (cell, false) + } else { + let cell = self.inner.next()?; + + // XXX (jwilm) selection temp disabled + // + // let selected = self.selection.as_ref() + // .map(|range| range.contains_(index)) + // .unwrap_or(false); + let selected = false; + + // Skip empty cells + if cell.is_empty() && !selected { + continue; + } + (cell, selected) + }; - let selected = self.selection.as_ref() - .map(|range| range.contains_(index)) - .unwrap_or(false); + // Apply inversion and lookup RGB values + let mut bg_alpha = 1.0; + let fg_rgb; + let bg_rgb; - // Skip empty cells - if cell.is_empty() && !selected { - continue; - } - (*cell, selected) - }; + let invert = selected ^ cell.inverse(); - // Apply inversion and lookup RGB values - let mut bg_alpha = 1.0; - let fg_rgb; - let bg_rgb; - - let invert = selected ^ cell.inverse(); - - if invert { - if cell.fg == cell.bg { - bg_rgb = self.colors[NamedColor::Foreground]; - fg_rgb = self.colors[NamedColor::Background]; - bg_alpha = 1.0 - } else { - bg_rgb = self.compute_fg_rgb(&cell.fg, &cell); - fg_rgb = self.compute_bg_rgb(&cell.bg); - } + if invert { + if cell.fg == cell.bg { + bg_rgb = self.colors[NamedColor::Foreground]; + fg_rgb = self.colors[NamedColor::Background]; + bg_alpha = 1.0 } else { - fg_rgb = self.compute_fg_rgb(&cell.fg, &cell); - bg_rgb = self.compute_bg_rgb(&cell.bg); - bg_alpha = self.compute_bg_alpha(&cell.bg); + bg_rgb = self.compute_fg_rgb(&cell.fg, &cell); + fg_rgb = self.compute_bg_rgb(&cell.bg); } - - return Some(RenderableCell { - line, - column, - flags: cell.flags, - c: cell.c, - fg: fg_rgb, - bg: bg_rgb, - bg_alpha, - }) + } else { + fg_rgb = self.compute_fg_rgb(&cell.fg, &cell); + bg_rgb = self.compute_bg_rgb(&cell.bg); + bg_alpha = self.compute_bg_alpha(&cell.bg); } - self.column = Column(0); - self.line += 1; + return Some(RenderableCell { + line: cell.line, + column: cell.column, + flags: cell.flags, + c: cell.c, + fg: fg_rgb, + bg: bg_rgb, + bg_alpha: bg_alpha, + }) } - - None } } @@ -789,6 +778,11 @@ impl Term { self.next_title.take() } + pub fn scroll_display(&mut self, count: isize) { + self.grid.scroll_display(count); + self.dirty = true; + } + #[inline] pub fn get_next_mouse_cursor(&mut self) -> Option { self.next_mouse_cursor.take() @@ -1047,10 +1041,6 @@ impl Term { num_lines = Line(2); } - // Scroll up to keep cursor and as much context as possible in grid. - // This only runs when the lines decreases. - self.scroll_region = Line(0)..self.grid.num_lines(); - // Scroll up to keep cursor in terminal if self.cursor.point.line >= num_lines { let lines = self.cursor.point.line - num_lines + 1; @@ -1062,7 +1052,6 @@ impl Term { let lines = self.cursor_save_alt.point.line - num_lines + 1; self.alt_grid.scroll_up(&(Line(0)..old_lines), lines); } - debug!("num_cols, num_lines = {}, {}", num_cols, num_lines); // Resize grids to new size @@ -1085,16 +1074,16 @@ impl Term { .map(|i| (*i as usize) % self.tabspaces == 0) .collect::>(); - if num_lines > old_lines { - // Make sure bottom of terminal is clear - let template = self.cursor.template; - self.grid - .region_mut((self.cursor.point.line + 1)..) - .each(|c| c.reset(&template)); - self.alt_grid - .region_mut((self.cursor_save_alt.point.line + 1)..) - .each(|c| c.reset(&template)); - } + // if num_lines > old_lines { + // // Make sure bottom of terminal is clear + // let template = self.cursor.template; + // self.grid + // .region_mut((self.cursor.point.line + 1)..) + // .each(|c| c.reset(&template)); + // self.alt_grid + // .region_mut((self.cursor_save_alt.point.line + 1)..) + // .each(|c| c.reset(&template)); + // } } -- cgit From 5748066b8ac18140187de91fef0bdbddc9fdb338 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 15 Feb 2018 19:06:39 -0800 Subject: Add scrolling limit and update on grow lines --- src/grid/mod.rs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 820cd9e2..749012fa 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -14,7 +14,7 @@ //! A specialized 2d grid implementation optimized for use in a terminal. -use std::cmp::{max, Ordering}; +use std::cmp::{min, max, Ordering}; use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull}; use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; @@ -90,6 +90,9 @@ pub struct Grid { /// stationary while more text is emitted. The scrolling implementation /// updates this offset accordingly. display_offset: usize, + + /// An limit on how far back it's possible to scroll + scroll_limit: usize, } pub struct GridIterator<'a, T: 'a> { @@ -99,7 +102,10 @@ pub struct GridIterator<'a, T: 'a> { impl Grid { pub fn scroll_display(&mut self, count: isize) { - self.display_offset = max((self.display_offset as isize) + count, 0isize) as usize; + self.display_offset = min( + max((self.display_offset as isize) + count, 0isize) as usize, + self.scroll_limit + ); } pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { @@ -123,6 +129,7 @@ impl Grid { template, temp: Vec::new(), display_offset: 0, + scroll_limit: 0, } } @@ -145,6 +152,10 @@ impl Grid { } } + fn increase_scroll_limit(&mut self, count: usize) { + self.scroll_limit = min(self.scroll_limit + count, self.raw.len() - *self.lines); + } + /// Add lines to the visible area /// /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the @@ -155,13 +166,18 @@ impl Grid { /// Alacritty takes a different approach. Rather than trying to move with /// the scrollback, we simply pull additional lines from the back of the /// buffer in order to populate the new area. - fn grow_lines(&mut self, target: index::Line) { - let delta = target - self.lines; + fn grow_lines(&mut self, new_line_count: index::Line) { + let previous_scroll_limit = self.scroll_limit; + let lines_added = new_line_count - self.lines; - self.raw.set_visible_lines(target); - self.lines = target; + // Need to "resize" before updating buffer + self.raw.set_visible_lines(new_line_count); + self.lines = new_line_count; + + // Add new lines to bottom + self.scroll_up(&(Line(0)..new_line_count), lines_added); - self.scroll_up(&(Line(0)..target), delta); + self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added); } fn grow_cols(&mut self, cols: index::Column) { @@ -252,6 +268,8 @@ impl Grid { self.display_offset += *positions; } + self.increase_scroll_limit(*positions); + // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. self.raw.rotate(-(*positions as isize)); -- cgit From 9b9b138bac9353d2d95ce71ec155c3a9b2963491 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 15 Feb 2018 19:34:09 -0800 Subject: Fir cursor not scrolling --- src/grid/mod.rs | 12 ++++++++++-- src/term/mod.rs | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 749012fa..1889d59f 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -628,6 +628,7 @@ pub struct DisplayIter<'a, T: 'a> { offset: usize, limit: usize, col: Column, + line: Line, } impl<'a, T: 'a> DisplayIter<'a, T> { @@ -635,8 +636,9 @@ impl<'a, T: 'a> DisplayIter<'a, T> { let offset = grid.display_offset + *grid.num_lines() - 1; let limit = grid.display_offset; let col = Column(0); + let line = Line(0); - DisplayIter { grid, offset, col, limit } + DisplayIter { grid, offset, col, limit, line } } pub fn offset(&self) -> usize { @@ -646,6 +648,10 @@ impl<'a, T: 'a> DisplayIter<'a, T> { pub fn column(&self) -> Column { self.col } + + pub fn line(&self) -> Line { + self.line + } } impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { @@ -660,13 +666,15 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { } self.col = Column(0); + self.offset -= 1; + self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); } // Return the next item. let item = Some(Indexed { inner: self.grid.raw[self.offset][self.col], - line: Line( *self.grid.lines - 1 - (self.offset - self.limit)), + line: self.line, column: self.col }); diff --git a/src/term/mod.rs b/src/term/mod.rs index e2088413..824577f7 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -342,7 +342,8 @@ impl<'a> Iterator for RenderableCellsIter<'a> { self.inner.column() == self.cursor.col { // Cursor cell - let cell = self.cursor_cells.pop_front().unwrap(); + let mut cell = self.cursor_cells.pop_front().unwrap(); + cell.line = self.inner.line(); // Since there may be multiple cursor cells (for a wide // char), only update iteration position after all cursor -- cgit From f67b17ca7b2e0e47ad5eee24d8b86800c61139b4 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 15 Feb 2018 19:34:23 -0800 Subject: wip fix scroll_down --- src/grid/mod.rs | 5 ++++- src/grid/storage.rs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 1889d59f..23ee9cf4 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -236,8 +236,11 @@ impl Grid { self.raw.rotate_up(*positions); // Now, restore any scroll region lines - for i in IndexRange(region.end .. self.num_lines()) { + let lines = self.lines; + for i in IndexRange(region.end .. lines) { // First do the swap + // TODO there is a bug here causing a panic. + // TODO math self.raw.swap_lines(i, i + positions); } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index b4228687..0ca2f525 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -69,6 +69,7 @@ impl Storage { } pub fn swap_lines(&mut self, a: Line, b: Line) { + println!("visible: {}, a: {}, b: {}", self.visible_lines, a, b); let a = self.visible_lines - a; let b = self.visible_lines - b; self.swap(*a, *b); -- cgit From 7fe67743ebffd047532f6271bf28474f9d947f64 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 16 Feb 2018 17:33:32 -0800 Subject: Scroll to bottom on character received --- src/event.rs | 4 ++++ src/grid/mod.rs | 4 ++++ src/input.rs | 2 ++ src/term/mod.rs | 4 ++++ 4 files changed, 14 insertions(+) (limited to 'src') diff --git a/src/event.rs b/src/event.rs index 9c3e8edc..3b1df006 100644 --- a/src/event.rs +++ b/src/event.rs @@ -59,6 +59,10 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { self.terminal.scroll_display(count); } + fn reset_scroll(&mut self) { + self.terminal.reset_scroll(); + } + fn copy_selection(&self, buffer: ::copypasta::Buffer) { if let Some(ref selection) = *self.selection { if let Some(ref span) = selection.to_span(self.terminal) { diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 23ee9cf4..c6543270 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -108,6 +108,10 @@ impl Grid { ); } + pub fn reset_scroll(&mut self) { + self.display_offset = 0; + } + pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { let mut raw = Storage::with_capacity(*lines + SCROLLBACK_LINES, lines); let template_row = Row::new(cols, &template); diff --git a/src/input.rs b/src/input.rs index 2e345c2a..68787979 100644 --- a/src/input.rs +++ b/src/input.rs @@ -65,6 +65,7 @@ pub trait ActionContext { fn change_font_size(&mut self, delta: i8); fn reset_font_size(&mut self); fn scroll(&mut self, count: isize) {} + fn reset_scroll(&mut self) {} } /// Describes a state and action to take in that state @@ -560,6 +561,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { /// Process a received character pub fn received_char(&mut self, c: char) { if !*self.ctx.suppress_chars() { + self.ctx.reset_scroll(); self.ctx.clear_selection(); let utf8_len = c.len_utf8(); diff --git a/src/term/mod.rs b/src/term/mod.rs index 824577f7..3bce60cd 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -784,6 +784,10 @@ impl Term { self.dirty = true; } + pub fn reset_scroll(&mut self) { + self.grid.reset_scroll(); + } + #[inline] pub fn get_next_mouse_cursor(&mut self) -> Option { self.next_mouse_cursor.take() -- cgit From c49a7e88f64d1421474d492cc6f51bfd30e1e4d1 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 16 Feb 2018 17:54:32 -0800 Subject: Make number of scrollback lines configurable --- src/config.rs | 12 ++++++++++++ src/grid/mod.rs | 29 +++++++++++++---------------- src/term/mod.rs | 4 ++-- 3 files changed, 27 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 40c550b0..b0523c13 100644 --- a/src/config.rs +++ b/src/config.rs @@ -396,6 +396,14 @@ pub struct Config { /// Number of spaces in one tab #[serde(default="default_tabspaces", deserialize_with = "deserialize_tabspaces")] tabspaces: usize, + + /// How much scrolling history to keep + #[serde(default="default_scroll_history")] + scroll_history: u32, +} + +fn default_scroll_history() -> u32 { + 10_000 } fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result, D::Error> @@ -1244,6 +1252,10 @@ impl Config { .map(|path| path.into()) } + pub fn scroll_history(&self) -> usize { + self.scroll_history as _ + } + pub fn write_defaults() -> io::Result> { let path = ::xdg::BaseDirectories::with_prefix("alacritty") .map_err(|err| io::Error::new(io::ErrorKind::NotFound, ::std::error::Error::description(&err))) diff --git a/src/grid/mod.rs b/src/grid/mod.rs index c6543270..1cb0876e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -28,9 +28,6 @@ mod tests; mod storage; use self::storage::Storage; -/// Lines to keep in scrollback buffer -const SCROLLBACK_LINES: usize = 10_000; - /// Convert a type to a linear index range. pub trait ToRange { fn to_range(&self) -> RangeInclusive; @@ -101,19 +98,8 @@ pub struct GridIterator<'a, T: 'a> { } impl Grid { - pub fn scroll_display(&mut self, count: isize) { - self.display_offset = min( - max((self.display_offset as isize) + count, 0isize) as usize, - self.scroll_limit - ); - } - - pub fn reset_scroll(&mut self) { - self.display_offset = 0; - } - - pub fn new(lines: index::Line, cols: index::Column, template: T) -> Grid { - let mut raw = Storage::with_capacity(*lines + SCROLLBACK_LINES, lines); + pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid { + let mut raw = Storage::with_capacity(*lines + scrollback, lines); let template_row = Row::new(cols, &template); // Allocate all lines in the buffer, including scrollback history @@ -137,6 +123,17 @@ impl Grid { } } + pub fn scroll_display(&mut self, count: isize) { + self.display_offset = min( + max((self.display_offset as isize) + count, 0isize) as usize, + self.scroll_limit + ); + } + + pub fn reset_scroll_display(&mut self) { + self.display_offset = 0; + } + pub fn resize(&mut self, lines: index::Line, cols: index::Column) { // Check that there's actually work to do and return early if not if lines == self.lines && cols == self.cols { diff --git a/src/term/mod.rs b/src/term/mod.rs index 3bce60cd..220dd8e6 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -785,7 +785,7 @@ impl Term { } pub fn reset_scroll(&mut self) { - self.grid.reset_scroll(); + self.grid.reset_scroll_display(); } #[inline] @@ -799,7 +799,7 @@ impl Term { let num_cols = size.cols(); let num_lines = size.lines(); - let grid = Grid::new(num_lines, num_cols, template); + let grid = Grid::new(num_lines, num_cols, config.scroll_history(), template); let tabspaces = config.tabspaces(); let tabs = IndexRange::from(Column(0)..grid.num_cols()) -- cgit From 54d50ed3be810861d3c2fa500c3fcc8e802198d9 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 16 Feb 2018 18:35:54 -0800 Subject: Fix scrolling backwards in tmux --- src/grid/mod.rs | 11 +++++++---- src/grid/storage.rs | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 1cb0876e..ca43471d 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -157,6 +157,10 @@ impl Grid { self.scroll_limit = min(self.scroll_limit + count, self.raw.len() - *self.lines); } + fn decrease_scroll_limit(&mut self, count: usize) { + self.scroll_limit = self.scroll_limit.saturating_sub(count); + } + /// Add lines to the visible area /// /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the @@ -236,17 +240,16 @@ impl Grid { // active, the bottom lines are restored in the next step. self.raw.rotate_up(*positions); + self.decrease_scroll_limit(*positions); + // Now, restore any scroll region lines let lines = self.lines; for i in IndexRange(region.end .. lines) { - // First do the swap - // TODO there is a bug here causing a panic. - // TODO math self.raw.swap_lines(i, i + positions); } // Finally, reset recycled lines - for i in 0..*positions { + for i in IndexRange(Line(0)..positions) { self.raw[i].reset(&self.template_row); } } else { diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 0ca2f525..1588b006 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -62,6 +62,10 @@ impl Storage { (requested + self.zero) % self.len() } + fn compute_line_index(&self, requested: Line) -> usize { + ((self.len() + self.zero + *self.visible_lines) - *requested) % self.len() + } + pub fn swap(&mut self, a: usize, b: usize) { let a = self.compute_index(a); let b = self.compute_index(b); @@ -69,10 +73,9 @@ impl Storage { } pub fn swap_lines(&mut self, a: Line, b: Line) { - println!("visible: {}, a: {}, b: {}", self.visible_lines, a, b); - let a = self.visible_lines - a; - let b = self.visible_lines - b; - self.swap(*a, *b); + let a = self.compute_line_index(a); + let b = self.compute_line_index(b); + self.inner.swap(a, b); } pub fn iter_mut(&mut self) -> IterMut { -- cgit From ef3c384540b31004a423ada778ed5c02d90e68c6 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Mon, 5 Mar 2018 09:26:36 -0800 Subject: Style cleanup --- src/term/mod.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/term/mod.rs b/src/term/mod.rs index 220dd8e6..493086a7 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -135,33 +135,28 @@ impl<'a> RenderableCellsIter<'a> { }.initialize(cursor_style) } - fn push_cursor_cells( - &mut self, - original_cell: Cell, - cursor_cell: Cell, - wide_cell: Cell, - ) { + fn push_cursor_cells(&mut self, original: Cell, cursor: Cell, wide: Cell) { // Prints the char under the cell if cursor is situated on a non-empty cell self.cursor_cells.push_back(Indexed { line: self.cursor.line, column: self.cursor.col, - inner: original_cell, + inner: original, }).expect("won't exceed capacity"); // Prints the cursor self.cursor_cells.push_back(Indexed { line: self.cursor.line, column: self.cursor.col, - inner: cursor_cell, + inner: cursor, }).expect("won't exceed capacity"); // If cursor is over a wide (2 cell size) character, // print the second cursor cell - if self.is_wide_cursor(&cursor_cell) { + if self.is_wide_cursor(&cursor) { self.cursor_cells.push_back(Indexed { line: self.cursor.line, column: self.cursor.col + 1, - inner: wide_cell, + inner: wide, }).expect("won't exceed capacity"); } } -- cgit From 8ef062efd94975885776e61b6df936088b018da0 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Mon, 5 Mar 2018 09:57:34 -0800 Subject: Move selection into Grid Supporting selections with scrollback has two major components: 1. Grid needs access to Selection so that it may update the scroll position as the terminal text changes. 2. Selection needs to be implemented in terms of buffer offsets -- NOT lines -- and be updated when Storage is rotated. This commit implements the first part. --- src/display.rs | 5 ++--- src/event.rs | 56 +++++++++++++++++++++++--------------------------------- src/grid/mod.rs | 22 +++++++++++++++++----- src/grid/row.rs | 2 +- src/main.rs | 2 +- src/selection.rs | 2 ++ src/term/mod.rs | 21 +++++++++++++++++---- 7 files changed, 63 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/display.rs b/src/display.rs index 733c66e8..5e540a8b 100644 --- a/src/display.rs +++ b/src/display.rs @@ -24,7 +24,6 @@ use config::Config; use font::{self, Rasterize}; use meter::Meter; use renderer::{self, GlyphCache, QuadRenderer}; -use selection::Selection; use term::{Term, SizeInfo}; use window::{self, Size, Pixels, Window, SetInnerSize}; @@ -323,7 +322,7 @@ impl Display { /// A reference to Term whose state is being drawn must be provided. /// /// This call may block if vsync is enabled - pub fn draw(&mut self, mut terminal: MutexGuard, config: &Config, selection: Option<&Selection>) { + pub fn draw(&mut self, mut terminal: MutexGuard, config: &Config) { // Clear dirty flag terminal.dirty = !terminal.visual_bell.completed(); @@ -371,7 +370,7 @@ impl Display { // Draw the grid api.render_cells( - terminal.renderable_cells(config, selection, window_focused), + terminal.renderable_cells(config, window_focused), glyph_cache, ); }); diff --git a/src/event.rs b/src/event.rs index 3b1df006..d6348ba0 100644 --- a/src/event.rs +++ b/src/event.rs @@ -33,10 +33,8 @@ pub trait Notify { pub struct ActionContext<'a, N: 'a> { pub notifier: &'a mut N, pub terminal: &'a mut Term, - pub selection: &'a mut Option, pub size_info: &'a SizeInfo, pub mouse: &'a mut Mouse, - pub selection_modified: bool, pub received_count: &'a mut usize, pub suppress_chars: &'a mut bool, pub last_modifiers: &'a mut ModifiersState, @@ -64,30 +62,35 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { } fn copy_selection(&self, buffer: ::copypasta::Buffer) { - if let Some(ref selection) = *self.selection { - if let Some(ref span) = selection.to_span(self.terminal) { - let buf = self.terminal.string_from_selection(&span); - if !buf.is_empty() { + self.terminal + .selection_to_string() + .map(|selected| { + if !selected.is_empty() { Clipboard::new() - .and_then(|mut clipboard| clipboard.store(buf, buffer)) + .and_then(|mut clipboard| clipboard.store(selected, buffer)) .unwrap_or_else(|err| { warn!("Error storing selection to clipboard. {}", Red(err)); }); } - } - } + }); } fn clear_selection(&mut self) { - *self.selection = None; - self.selection_modified = true; + *self.terminal.selection_mut() = None; + self.terminal.dirty = true; } fn update_selection(&mut self, point: Point, side: Side) { - self.selection_modified = true; + // Update selection if one exists - if let Some(ref mut selection) = *self.selection { + let mut had_selection = false; // borrowck + if let Some(ref mut selection) = *self.terminal.selection_mut() { selection.update(point, side); + had_selection = true; + } + + if had_selection { // borrowck + self.terminal.dirty = true; return; } @@ -96,18 +99,18 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { } fn simple_selection(&mut self, point: Point, side: Side) { - *self.selection = Some(Selection::simple(point, side)); - self.selection_modified = true; + *self.terminal.selection_mut() = Some(Selection::simple(point, side)); + self.terminal.dirty = true; } fn semantic_selection(&mut self, point: Point) { - *self.selection = Some(Selection::semantic(point, self.terminal)); - self.selection_modified = true; + *self.terminal.selection_mut() = Some(Selection::semantic(point, &*self.terminal)); + self.terminal.dirty = true; } fn line_selection(&mut self, point: Point) { - *self.selection = Some(Selection::lines(point)); - self.selection_modified = true; + *self.terminal.selection_mut() = Some(Selection::lines(point)); + self.terminal.dirty = true; } fn mouse_coords(&self) -> Option { @@ -200,7 +203,6 @@ pub struct Processor { resize_tx: mpsc::Sender<(u32, u32)>, ref_test: bool, size_info: SizeInfo, - pub selection: Option, hide_cursor_when_typing: bool, hide_cursor: bool, received_count: usize, @@ -215,7 +217,6 @@ pub struct Processor { impl OnResize for Processor { fn on_resize(&mut self, size: &SizeInfo) { self.size_info = size.to_owned(); - self.selection = None; } } @@ -242,8 +243,7 @@ impl Processor { resize_tx, ref_test, mouse: Default::default(), - selection: None, - size_info, + size_info: size_info, hide_cursor_when_typing: config.hide_cursor_when_typing(), hide_cursor: false, received_count: 0, @@ -321,10 +321,6 @@ impl Processor { *hide_cursor = false; processor.mouse_moved(x as u32, y as u32, modifiers); - - if !processor.ctx.selection.is_none() { - processor.ctx.terminal.dirty = true; - } }, MouseWheel { delta, phase, modifiers, .. } => { *hide_cursor = false; @@ -400,10 +396,8 @@ impl Processor { context = ActionContext { terminal: &mut terminal, notifier: &mut self.notifier, - selection: &mut self.selection, mouse: &mut self.mouse, size_info: &self.size_info, - selection_modified: false, received_count: &mut self.received_count, suppress_chars: &mut self.suppress_chars, last_modifiers: &mut self.last_modifiers, @@ -448,10 +442,6 @@ impl Processor { } window.is_focused = window_is_focused; - - if processor.ctx.selection_modified { - processor.ctx.terminal.dirty = true; - } } self.wait_for_event = !terminal.dirty; diff --git a/src/grid/mod.rs b/src/grid/mod.rs index ca43471d..cf66a420 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -18,6 +18,7 @@ use std::cmp::{min, max, Ordering}; use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull}; use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; +use selection::Selection; mod row; pub use self::row::Row; @@ -54,8 +55,16 @@ impl Deref for Indexed { } } +impl ::std::cmp::PartialEq for Grid { + fn eq(&self, other: &Self) -> bool { + self.cols.eq(&other.cols) && + self.lines.eq(&other.lines) && + self.raw.eq(&other.raw) + } +} + /// Represents the terminal display contents -#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Grid { /// Lines in the grid. Each row holds a list of cells corresponding to the /// columns in that row. @@ -73,14 +82,13 @@ pub struct Grid { /// /// This is used to quickly populate new lines and clear recycled lines /// during scroll wrapping. + #[serde(skip)] template_row: Row, /// Template cell for populating template_row + #[serde(skip)] template: T, - /// Temporary row storage for scrolling with a region - temp: Vec>, - /// Offset of displayed area /// /// If the displayed region isn't at the bottom of the screen, it stays @@ -90,6 +98,10 @@ pub struct Grid { /// An limit on how far back it's possible to scroll scroll_limit: usize, + + /// Selected region + #[serde(skip)] + pub selection: Option, } pub struct GridIterator<'a, T: 'a> { @@ -117,9 +129,9 @@ impl Grid { lines, template_row, template, - temp: Vec::new(), display_offset: 0, scroll_limit: 0, + selection: None, } } diff --git a/src/grid/row.rs b/src/grid/row.rs index 6b6af7c8..f6b2a20e 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -21,7 +21,7 @@ use std::slice; use index::Column; /// A row in the grid -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct Row(Vec); impl Row { diff --git a/src/main.rs b/src/main.rs index c45dfda5..77cb4949 100644 --- a/src/main.rs +++ b/src/main.rs @@ -201,7 +201,7 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box> { display.handle_resize(&mut terminal, &config, &mut [&mut pty, &mut processor]); // Draw the current state of the terminal - display.draw(terminal, &config, processor.selection.as_ref()); + display.draw(terminal, &config); } // Begin shutdown if the flag was raised. diff --git a/src/selection.rs b/src/selection.rs index 64cee8c2..b5bf5493 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -39,6 +39,7 @@ use grid::ToRange; /// [`simple`]: enum.Selection.html#method.simple /// [`semantic`]: enum.Selection.html#method.semantic /// [`lines`]: enum.Selection.html#method.lines +#[derive(Debug, Clone)] pub enum Selection { Simple { /// The region representing start and end of cursor movement @@ -64,6 +65,7 @@ pub enum Selection { } /// A Point and side within that point. +#[derive(Debug, Clone)] pub struct Anchor { point: Point, side: Side, diff --git a/src/term/mod.rs b/src/term/mod.rs index 493086a7..9eeabc31 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -769,6 +769,14 @@ impl SizeInfo { impl Term { + pub fn selection(&self) -> &Option { + &self.grid.selection + } + + pub fn selection_mut(&mut self) -> &mut Option { + &mut self.grid.selection + } + #[inline] pub fn get_next_title(&mut self) -> Option { self.next_title.take() @@ -865,7 +873,7 @@ impl Term { self.dirty } - pub fn string_from_selection(&self, span: &Span) -> String { + pub fn selection_to_string(&self) -> Option { /// Need a generic push() for the Append trait trait PushChar { fn push_char(&mut self, c: char); @@ -921,6 +929,9 @@ impl Term { } } + let selection = self.grid.selection.clone()?; + let span = selection.to_span(self)?; + let mut res = String::new(); let (start, end) = span.to_locations(); @@ -957,7 +968,7 @@ impl Term { } } - res + Some(res) } /// Convert the given pixel values to a grid coordinate @@ -986,10 +997,9 @@ impl Term { pub fn renderable_cells<'b>( &'b self, config: &'b Config, - selection: Option<&'b Selection>, window_focused: bool, ) -> RenderableCellsIter { - let selection = selection.and_then(|s| s.to_span(self)) + let selection = self.grid.selection.as_ref().and_then(|s| s.to_span(self)) .map(|span| span.to_range()); let cursor = if window_focused { self.cursor_style.unwrap_or(self.default_cursor_style) @@ -1031,6 +1041,9 @@ impl Term { return; } + self.grid.selection = None; + self.alt_grid.selection = None; + // Should not allow less than 1 col, causes all sorts of checks to be required. if num_cols <= Column(1) { num_cols = Column(2); -- cgit From 8018dee1812ab88793c0f18e13335fa77c068000 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 6 Mar 2018 20:57:40 -0800 Subject: Support selections with scrolling buffer Selections now *mostly* work. They move as the buffer scrolls, copying works as it should, and it looks like the different selection modes behave properly as well. The new Selection implementation uses buffer coordinates instead of screen coordinates. This leads to doing a transform from mouse input to update the selection, and back to screen coordinates when displaying the selection. Scrolling the selection is fast because the grid is already operating in buffer coordinates. There are several bugs to address: * A _partially_ visible selection will lead to a crash since the drawing routine converts selection coordinates to screen coordinates. The solution will be to clip the coordinates at draw time. * A selection scrolling off the buffer in either direction leads to indexing out-of-bounds. The solution again is to clip, but this needs to be done within Selection::rotate by passing a max limit. It may also need a return type to indicate that the selection is no longer visible and should be discarded. * A selection scrolling out of a logical scrolling region is not clipped. A temporary and robust workaround is to simply discard the selection in the case of scrolling in a region. wip selections fix issue with line selection selection mostly working need to support selection not being on the screen at draw time Fix selection_to_string Uncomment tests --- src/event.rs | 13 ++--- src/grid/mod.rs | 68 +++++++++++++++++----- src/index.rs | 8 +-- src/selection.rs | 174 ++++++++++++++++++++++++++++--------------------------- src/term/mod.rs | 93 ++++++++++++++++++++--------- 5 files changed, 219 insertions(+), 137 deletions(-) (limited to 'src') diff --git a/src/event.rs b/src/event.rs index d6348ba0..8d2d80e4 100644 --- a/src/event.rs +++ b/src/event.rs @@ -81,34 +81,33 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { } fn update_selection(&mut self, point: Point, side: Side) { + self.terminal.dirty = true; + let point = self.terminal.visible_to_buffer(point); // Update selection if one exists - let mut had_selection = false; // borrowck if let Some(ref mut selection) = *self.terminal.selection_mut() { selection.update(point, side); - had_selection = true; - } - - if had_selection { // borrowck - self.terminal.dirty = true; return; } // Otherwise, start a regular selection - self.simple_selection(point, side); + *self.terminal.selection_mut() = Some(Selection::simple(point, side)); } fn simple_selection(&mut self, point: Point, side: Side) { + let point = self.terminal.visible_to_buffer(point); *self.terminal.selection_mut() = Some(Selection::simple(point, side)); self.terminal.dirty = true; } fn semantic_selection(&mut self, point: Point) { + let point = self.terminal.visible_to_buffer(point); *self.terminal.selection_mut() = Some(Selection::semantic(point, &*self.terminal)); self.terminal.dirty = true; } fn line_selection(&mut self, point: Point) { + let point = self.terminal.visible_to_buffer(point); *self.terminal.selection_mut() = Some(Selection::lines(point)); self.terminal.dirty = true; } diff --git a/src/grid/mod.rs b/src/grid/mod.rs index cf66a420..7f648f46 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -17,7 +17,7 @@ use std::cmp::{min, max, Ordering}; use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull}; -use index::{self, Point, Line, Column, IndexRange, RangeInclusive}; +use index::{self, Point, Line, Column, IndexRange}; use selection::Selection; mod row; @@ -29,11 +29,6 @@ mod tests; mod storage; use self::storage::Storage; -/// Convert a type to a linear index range. -pub trait ToRange { - fn to_range(&self) -> RangeInclusive; -} - /// Bidirection iterator pub trait BidirectionalIterator: Iterator { fn prev(&mut self) -> Option; @@ -105,8 +100,17 @@ pub struct Grid { } pub struct GridIterator<'a, T: 'a> { + /// Immutable grid reference grid: &'a Grid, - pub cur: Point, + + /// Current position of the iterator within the grid. + pub cur: Point, + + /// Bottom of screen (buffer) + bot: usize, + + /// Top of screen (buffer) + top: usize, } impl Grid { @@ -135,6 +139,28 @@ impl Grid { } } + pub fn visible_to_buffer(&self, point: Point) -> Point { + Point { + line: self.visible_line_to_buffer(point.line), + col: point.col + } + } + + pub fn buffer_to_visible(&self, point: Point) -> Point { + Point { + line: self.buffer_line_to_visible(point.line), + col: point.col + } + } + + pub fn buffer_line_to_visible(&self, line: usize) -> Line { + self.offset_to_line(line - self.display_offset) + } + + pub fn visible_line_to_buffer(&self, line: Line) -> usize { + self.line_to_offset(line) + self.display_offset + } + pub fn scroll_display(&mut self, count: isize) { self.display_offset = min( max((self.display_offset as isize) + count, 0isize) as usize, @@ -224,6 +250,7 @@ impl Grid { let prev = self.lines; + self.selection = None; self.raw.rotate(*prev as isize - *target as isize); self.raw.set_visible_lines(target); self.lines = target; @@ -240,6 +267,12 @@ impl Grid { *(self.num_lines() - line - 1) } + pub fn offset_to_line(&self, offset: usize) -> Line { + assert!(offset < *self.num_lines()); + + self.lines - offset - 1 + } + #[inline] pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { // Whether or not there is a scrolling region active, as long as it @@ -251,6 +284,9 @@ impl Grid { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. self.raw.rotate_up(*positions); + if let Some(ref mut selection) = self.selection { + selection.rotate(-(*positions as isize)); + } self.decrease_scroll_limit(*positions); @@ -292,6 +328,9 @@ impl Grid { // Rotate the entire line buffer. If there's a scrolling region // active, the bottom lines are restored in the next step. self.raw.rotate(-(*positions as isize)); + if let Some(ref mut selection) = self.selection { + selection.rotate(*positions as isize); + } // Now, restore any lines outside the scroll region for idx in (*region.end .. *self.num_lines()).rev() { @@ -334,10 +373,12 @@ impl Grid { self.cols } - pub fn iter_from(&self, point: Point) -> GridIterator { + pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { grid: self, cur: point, + bot: self.display_offset, + top: self.display_offset + *self.num_lines() - 1, } } @@ -369,15 +410,12 @@ impl<'a, T> Iterator for GridIterator<'a, T> { type Item = &'a T; fn next(&mut self) -> Option { - let last_line = self.grid.num_lines() - Line(1); let last_col = self.grid.num_cols() - Column(1); match self.cur { - Point { line, col } if - (line == last_line) && - (col == last_col) => None, + Point { line, col } if (line == self.bot) && (col == last_col) => None, Point { col, .. } if (col == last_col) => { - self.cur.line += Line(1); + self.cur.line -= 1; self.cur.col = Column(0); Some(&self.grid[self.cur.line][self.cur.col]) }, @@ -394,9 +432,9 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { let num_cols = self.grid.num_cols(); match self.cur { - Point { line: Line(0), col: Column(0) } => None, + Point { line, col: Column(0) } if line == self.top => None, Point { col: Column(0), .. } => { - self.cur.line -= Line(1); + self.cur.line += 1; self.cur.col = num_cols - Column(1); Some(&self.grid[self.cur.line][self.cur.col]) }, diff --git a/src/index.rs b/src/index.rs index 418faff2..ada5f5b1 100644 --- a/src/index.rs +++ b/src/index.rs @@ -28,13 +28,13 @@ pub enum Side { /// Index in the grid using row, column notation #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, PartialOrd)] -pub struct Point { - pub line: Line, +pub struct Point { + pub line: L, pub col: Column, } -impl Point { - pub fn new(line: Line, col: Column) -> Point { +impl Point { + pub fn new(line: L, col: Column) -> Point { Point { line, col } } } diff --git a/src/selection.rs b/src/selection.rs index b5bf5493..31787bbb 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -21,8 +21,7 @@ use std::cmp::{min, max}; use std::ops::Range; -use index::{Point, Column, RangeInclusive, Side, Linear, Line}; -use grid::ToRange; +use index::{Point, Column, Side}; /// Describes a region of a 2-dimensional area /// @@ -47,32 +46,32 @@ pub enum Selection { }, Semantic { /// The region representing start and end of cursor movement - region: Range, + region: Range>, /// When beginning a semantic selection, the grid is searched around the /// initial point to find semantic escapes, and this initial expansion /// marks those points. - initial_expansion: Range + initial_expansion: Range> }, Lines { /// The region representing start and end of cursor movement - region: Range, + region: Range>, /// The line under the initial point. This is always selected regardless /// of which way the cursor is moved. - initial_line: Line + initial_line: usize } } /// A Point and side within that point. #[derive(Debug, Clone)] pub struct Anchor { - point: Point, + point: Point, side: Side, } impl Anchor { - fn new(point: Point, side: Side) -> Anchor { + fn new(point: Point, side: Side) -> Anchor { Anchor { point, side } } } @@ -83,9 +82,9 @@ impl Anchor { /// points are two dimensional indices. pub trait SemanticSearch { /// Find the nearest semantic boundary _to the left_ of provided point. - fn semantic_search_left(&self, _: Point) -> Point; + fn semantic_search_left(&self, _: Point) -> Point; /// Find the nearest semantic boundary _to the point_ of provided point. - fn semantic_search_right(&self, _: Point) -> Point; + fn semantic_search_right(&self, _: Point) -> Point; } /// A type that has 2-dimensional boundaries @@ -95,7 +94,7 @@ pub trait Dimensions { } impl Selection { - pub fn simple(location: Point, side: Side) -> Selection { + pub fn simple(location: Point, side: Side) -> Selection { Selection::Simple { region: Range { start: Anchor::new(location, side), @@ -104,7 +103,27 @@ impl Selection { } } - pub fn semantic(point: Point, grid: &G) -> 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; + }, + Selection::Semantic { ref mut region, ref mut initial_expansion } => { + region.start.line = (region.start.line as isize + offset) as usize; + region.end.line = (region.end.line as isize + offset) as usize; + initial_expansion.start.line = (initial_expansion.start.line as isize + offset) as usize; + initial_expansion.end.line = (initial_expansion.end.line as isize + offset) as usize; + }, + 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; + } + } + } + + pub fn semantic(point: Point, grid: &G) -> Selection { let (start, end) = (grid.semantic_search_left(point), grid.semantic_search_right(point)); Selection::Semantic { region: Range { @@ -118,7 +137,7 @@ impl Selection { } } - pub fn lines(point: Point) -> Selection { + pub fn lines(point: Point) -> Selection { Selection::Lines { region: Range { start: point, @@ -128,7 +147,7 @@ impl Selection { } } - pub fn update(&mut self, location: Point, side: Side) { + pub fn update(&mut self, location: Point, side: Side) { // Always update the `end`; can normalize later during span generation. match *self { Selection::Simple { ref mut region } => { @@ -151,14 +170,14 @@ impl Selection { Selection::span_semantic(grid, region, initial_expansion) }, Selection::Lines { ref region, ref initial_line } => { - Selection::span_lines(grid, region, initial_line) + Selection::span_lines(grid, region, *initial_line) } } } fn span_semantic( grid: &G, - region: &Range, - initial_expansion: &Range + region: &Range>, + initial_expansion: &Range> ) -> Option where G: SemanticSearch + Dimensions { @@ -172,14 +191,20 @@ impl Selection { (region.end, region.start) }; - // Update start of selection *if* front has moved beyond initial start - if front < start { + println!("BEFORE front={:?}, start={:?}, tail={:?}, end={:?}", front, start, tail, end); + + if front < tail && front.line == tail.line { start = grid.semantic_search_left(front); + end = grid.semantic_search_right(tail); + } else { + start = grid.semantic_search_right(front); + end = grid.semantic_search_left(tail); } - // Update end of selection *if* tail has moved beyond initial end. - if tail > end { - end = grid.semantic_search_right(tail); + println!("AFTER front={:?}, start={:?}, tail={:?}, end={:?}", front, start, tail, end); + + if start > end { + ::std::mem::swap(&mut start, &mut end); } Some(Span { @@ -190,27 +215,27 @@ impl Selection { }) } - fn span_lines(grid: &G, region: &Range, initial_line: &Line) -> Option + fn span_lines(grid: &G, region: &Range>, initial_line: usize) -> Option where G: Dimensions { // First, create start and end points based on initial line and the grid // dimensions. let mut start = Point { - col: Column(0), - line: *initial_line + col: grid.dimensions().col - 1, + line: initial_line }; let mut end = Point { - col: grid.dimensions().col - 1, - line: *initial_line + col: Column(0), + line: initial_line }; // Now, expand lines based on where cursor started and ended. if region.start.line < region.end.line { - // Start is above end + // Start is below end start.line = min(start.line, region.start.line); end.line = max(end.line, region.end.line); } else { - // Start is below end + // Start is above end start.line = min(start.line, region.end.line); end.line = max(end.line, region.start.line); } @@ -313,27 +338,37 @@ pub enum SpanType { /// Represents a span of selected cells #[derive(Debug, Eq, PartialEq)] pub struct Span { - front: Point, - tail: Point, + front: Point, + tail: Point, cols: Column, /// The type says whether ends are included or not. ty: SpanType, } +#[derive(Debug)] +pub struct Locations { + /// Start point from bottom of buffer + pub start: Point, + /// End point towards top of buffer + pub end: Point, +} + impl Span { - pub fn to_locations(&self) -> (Point, Point) { - match self.ty { + pub fn to_locations(&self) -> Locations { + let (start, end) = match self.ty { SpanType::Inclusive => (self.front, self.tail), SpanType::Exclusive => { (Span::wrap_start(self.front, self.cols), Span::wrap_end(self.tail, self.cols)) }, SpanType::ExcludeFront => (Span::wrap_start(self.front, self.cols), self.tail), SpanType::ExcludeTail => (self.front, Span::wrap_end(self.tail, self.cols)) - } + }; + + Locations { start, end } } - fn wrap_start(mut start: Point, cols: Column) -> Point { + fn wrap_start(mut start: Point, cols: Column) -> Point { if start.col == cols - 1 { Point { line: start.line + 1, @@ -345,8 +380,8 @@ impl Span { } } - fn wrap_end(end: Point, cols: Column) -> Point { - if end.col == Column(0) && end.line != Line(0) { + fn wrap_end(end: Point, cols: Column) -> Point { + if end.col == Column(0) && end.line != 0 { Point { line: end.line - 1, col: cols @@ -358,37 +393,6 @@ impl Span { } } } - - #[inline] - fn exclude_start(start: Linear) -> Linear { - start + 1 - } - - #[inline] - fn exclude_end(end: Linear) -> Linear { - if end > Linear(0) { - end - 1 - } else { - end - } - } -} - -impl ToRange for Span { - fn to_range(&self) -> RangeInclusive { - let cols = self.cols; - let start = Linear(self.front.line.0 * cols.0 + self.front.col.0); - let end = Linear(self.tail.line.0 * cols.0 + self.tail.col.0); - - let (start, end) = match self.ty { - SpanType::Inclusive => (start, end), - SpanType::Exclusive => (Span::exclude_start(start), Span::exclude_end(end)), - SpanType::ExcludeFront => (Span::exclude_start(start), end), - SpanType::ExcludeTail => (start, Span::exclude_end(end)) - }; - - RangeInclusive::new(start, end) - } } /// Tests for selection @@ -422,8 +426,8 @@ mod test { } impl super::SemanticSearch for Dimensions { - fn semantic_search_left(&self, _: Point) -> Point { unimplemented!(); } - fn semantic_search_right(&self, _: Point) -> Point { unimplemented!(); } + fn semantic_search_left(&self, _: Point) -> Point { unimplemented!(); } + fn semantic_search_right(&self, _: Point) -> Point { unimplemented!(); } } /// Test case of single cell selection @@ -433,7 +437,7 @@ mod test { /// 3. [BE] #[test] fn single_cell_left_to_right() { - let location = Point { line: Line(0), col: Column(0) }; + let location = Point { line: 0, col: Column(0) }; let mut selection = Selection::simple(location, Side::Left); selection.update(location, Side::Right); @@ -452,7 +456,7 @@ mod test { /// 3. [EB] #[test] fn single_cell_right_to_left() { - let location = Point { line: Line(0), col: Column(0) }; + let location = Point { line: 0, col: Column(0) }; let mut selection = Selection::simple(location, Side::Right); selection.update(location, Side::Left); @@ -471,8 +475,8 @@ mod test { /// 3. [ B][E ] #[test] fn between_adjacent_cells_left_to_right() { - let mut selection = Selection::simple(Point::new(Line(0), Column(0)), Side::Right); - selection.update(Point::new(Line(0), Column(1)), Side::Left); + 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); } @@ -484,8 +488,8 @@ mod test { /// 3. [ E][B ] #[test] fn between_adjacent_cells_right_to_left() { - let mut selection = Selection::simple(Point::new(Line(0), Column(1)), Side::Left); - selection.update(Point::new(Line(0), Column(0)), Side::Right); + 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); } @@ -501,13 +505,13 @@ mod test { /// [XX][XB][ ][ ][ ] #[test] fn across_adjacent_lines_upward_final_cell_exclusive() { - let mut selection = Selection::simple(Point::new(Line(1), Column(1)), Side::Right); - selection.update(Point::new(Line(0), Column(1)), Side::Right); + 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 { cols: Column(5), - front: Point::new(Line(0), Column(1)), - tail: Point::new(Line(1), Column(1)), + front: Point::new(0, Column(1)), + tail: Point::new(1, Column(1)), ty: SpanType::ExcludeFront }); } @@ -525,14 +529,14 @@ mod test { /// [XE][ ][ ][ ][ ] #[test] fn selection_bigger_then_smaller() { - let mut selection = Selection::simple(Point::new(Line(0), Column(1)), Side::Right); - selection.update(Point::new(Line(1), Column(1)), Side::Right); - selection.update(Point::new(Line(1), Column(0)), Side::Right); + let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Right); + 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 { cols: Column(5), - front: Point::new(Line(0), Column(1)), - tail: Point::new(Line(1), Column(0)), + front: Point::new(0, Column(1)), + tail: Point::new(1, Column(0)), ty: SpanType::ExcludeFront }); } diff --git a/src/term/mod.rs b/src/term/mod.rs index 9eeabc31..d1ce1d58 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -24,9 +24,9 @@ use unicode_width::UnicodeWidthChar; use font::{self, Size}; use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; -use grid::{BidirectionalIterator, Grid, ToRange, Indexed, IndexRegion, DisplayIter}; -use index::{self, Point, Column, Line, IndexRange, Contains, RangeInclusive}; -use selection::{self, Span, Selection}; +use grid::{BidirectionalIterator, Grid, Indexed, IndexRegion, DisplayIter}; +use index::{self, Point, Column, Line, IndexRange, Contains, RangeInclusive, Linear}; +use selection::{self, Selection, Locations}; use config::{Config, VisualBellAnimation}; use {MouseCursor, Rgb}; use copypasta::{Clipboard, Load, Store}; @@ -37,7 +37,7 @@ pub use self::cell::Cell; use self::cell::LineLength; impl selection::SemanticSearch for Term { - fn semantic_search_left(&self, mut point: Point) -> Point { + fn semantic_search_left(&self, mut point: Point) -> Point { let mut iter = self.grid.iter_from(point); let last_col = self.grid.num_cols() - Column(1); @@ -56,7 +56,7 @@ impl selection::SemanticSearch for Term { point } - fn semantic_search_right(&self, mut point: Point) -> Point { + fn semantic_search_right(&self, mut point: Point) -> Point { let mut iter = self.grid.iter_from(point); let last_col = self.grid.num_cols() - Column(1); @@ -116,12 +116,37 @@ impl<'a> RenderableCellsIter<'a> { colors: &'b color::List, mode: TermMode, config: &'b Config, - selection: Option>, + selection: Option, cursor_style: CursorStyle, ) -> RenderableCellsIter<'b> { let cursor_offset = grid.line_to_offset(cursor.line); let inner = grid.display_iter(); + let selection = selection.map(|loc| { + // start and end *lines* are swapped as we switch from buffer to + // Line coordinates. + let mut end = Point { + line: grid.buffer_line_to_visible(loc.start.line), + col: loc.start.col + }; + let mut start = Point { + line: grid.buffer_line_to_visible(loc.end.line), + col: loc.end.col + }; + + if start > end { + ::std::mem::swap(&mut start, &mut end); + } + + println!("start={:?}, end={:?}", start, end); + + let cols = grid.num_cols(); + let start = Linear(start.line.0 * cols.0 + start.col.0); + let end = Linear(end.line.0 * cols.0 + end.col.0); + + RangeInclusive::new(start, end) + }); + RenderableCellsIter { cursor: cursor, cursor_offset: cursor_offset, @@ -350,12 +375,13 @@ impl<'a> Iterator for RenderableCellsIter<'a> { } else { let cell = self.inner.next()?; + let index = Linear(cell.line.0 * self.grid.num_cols().0 + cell.column.0); + // XXX (jwilm) selection temp disabled // - // let selected = self.selection.as_ref() - // .map(|range| range.contains_(index)) - // .unwrap_or(false); - let selected = false; + let selected = self.selection.as_ref() + .map(|range| range.contains_(index)) + .unwrap_or(false); // Skip empty cells if cell.is_empty() && !selected { @@ -877,7 +903,7 @@ impl Term { /// Need a generic push() for the Append trait trait PushChar { fn push_char(&mut self, c: char); - fn maybe_newline(&mut self, grid: &Grid, line: Line, ending: Column) { + fn maybe_newline(&mut self, grid: &Grid, line: usize, ending: Column) { if ending != Column(0) && !grid[line][ending - 1].flags.contains(cell::Flags::WRAPLINE) { self.push_char('\n'); } @@ -894,14 +920,14 @@ impl Term { use std::ops::Range; trait Append : PushChar { - fn append(&mut self, grid: &Grid, line: Line, cols: Range) -> Option>; + fn append(&mut self, grid: &Grid, line: usize, cols: Range) -> Option>; } impl Append for String { fn append( &mut self, grid: &Grid, - line: Line, + line: usize, cols: Range ) -> Option> { let grid_line = &grid[line]; @@ -934,43 +960,54 @@ impl Term { let mut res = String::new(); - let (start, end) = span.to_locations(); + let Locations { mut start, mut end } = span.to_locations(); + + if start > end { + ::std::mem::swap(&mut start, &mut end); + } + let line_count = end.line - start.line; let max_col = Column(usize::max_value() - 1); match line_count { // Selection within single line - Line(0) => { + 0 => { res.append(&self.grid, start.line, start.col..end.col); }, // Selection ends on line following start - Line(1) => { + 1 => { + // Ending line + res.append(&self.grid, end.line, end.col..max_col); + // Starting line - res.append(&self.grid, start.line, start.col..max_col); + res.append(&self.grid, start.line, Column(0)..start.col); - // Ending line - res.append(&self.grid, end.line, Column(0)..end.col); }, // Multi line selection _ => { - // Starting line - res.append(&self.grid, start.line, start.col..max_col); + // Ending line + res.append(&self.grid, end.line, end.col..max_col); - let middle_range = IndexRange::from((start.line + 1)..(end.line)); + let middle_range = (start.line + 1)..(end.line); for line in middle_range { res.append(&self.grid, line, Column(0)..max_col); } - // Ending line - res.append(&self.grid, end.line, Column(0)..end.col); + // Starting line + res.append(&self.grid, start.line, Column(0)..(start.col + 1)); + } } Some(res) } + pub(crate) fn visible_to_buffer(&self, point: Point) -> Point { + self.grid.visible_to_buffer(point) + } + /// Convert the given pixel values to a grid coordinate /// /// The mouse coordinates are expected to be relative to the top left. The @@ -999,8 +1036,12 @@ impl Term { config: &'b Config, window_focused: bool, ) -> RenderableCellsIter { - let selection = self.grid.selection.as_ref().and_then(|s| s.to_span(self)) - .map(|span| span.to_range()); + let selection = self.grid.selection.as_ref() + .and_then(|s| s.to_span(self)) + .map(|span| { + // println!("span={:?}, locations={:?}", span, span.to_locations()); + span.to_locations() + }); let cursor = if window_focused { self.cursor_style.unwrap_or(self.default_cursor_style) } else { -- cgit From 452126013e9fa898b801d8e8f4f26adcaaa67c69 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Wed, 7 Mar 2018 09:56:19 -0800 Subject: Add SCROLL_MULTIPLIER Scroll wheel needs some scaling so it feels like urxvt and friends. --- src/input.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/input.rs b/src/input.rs index 68787979..ad9fcd39 100644 --- a/src/input.rs +++ b/src/input.rs @@ -34,6 +34,8 @@ use term::SizeInfo; use term::mode::TermMode; use util::fmt::Red; +const SCROLL_MULTIPLIER: usize = 3; + /// Processes input from glutin. /// /// An escape sequence may be emitted in case specific keys or key combinations @@ -439,7 +441,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { 65 }; - for _ in 0..(to_scroll.abs() as usize) { + for _ in 0..(to_scroll.abs() as usize * SCROLL_MULTIPLIER) { self.scroll_terminal(code, modifiers) } -- cgit From 0484a07fe1bdedd658e74907a7f2bdeb068688db Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Thu, 8 Mar 2018 20:12:48 -0800 Subject: Fix 4+ line copying --- src/term/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/term/mod.rs b/src/term/mod.rs index d1ce1d58..c3c3a300 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -991,7 +991,7 @@ impl Term { res.append(&self.grid, end.line, end.col..max_col); let middle_range = (start.line + 1)..(end.line); - for line in middle_range { + for line in middle_range.rev() { res.append(&self.grid, line, Column(0)..max_col); } -- cgit From dd60ea563d1b3022e387d96e2b6cf8bbb682b947 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 9 Mar 2018 11:01:42 +0100 Subject: Add `failure_default` deserializer to `scroll_history` --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index b0523c13..2c2cfe46 100644 --- a/src/config.rs +++ b/src/config.rs @@ -398,7 +398,7 @@ pub struct Config { tabspaces: usize, /// How much scrolling history to keep - #[serde(default="default_scroll_history")] + #[serde(default="default_scroll_history", deserialize_with="failure_default")] scroll_history: u32, } -- cgit From 04086186a00d50f22a7e1914d89a8dd1beb695cc Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 9 Mar 2018 17:57:24 +0100 Subject: Provide correct default for scroll_history When implementing fallback to the default value with an u32 you will get 0 as the default value. However the default scrollback value is 10_000. A custom deserializer has been implemented which automatically falls back to the correct default value. --- src/config.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 2c2cfe46..d49dd3b4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -398,7 +398,7 @@ pub struct Config { tabspaces: usize, /// How much scrolling history to keep - #[serde(default="default_scroll_history", deserialize_with="failure_default")] + #[serde(default="default_scroll_history", deserialize_with="deserialize_scroll_history")] scroll_history: u32, } @@ -406,6 +406,18 @@ fn default_scroll_history() -> u32 { 10_000 } +fn deserialize_scroll_history<'a, D>(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> +{ + match u32::deserialize(deserializer) { + Ok(lines) => Ok(lines), + Err(err) => { + eprintln!("problem with config: {}; Using default value", err); + Ok(default_scroll_history()) + }, + } +} + fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result, D::Error> where D: de::Deserializer<'a>, T: Deserialize<'a> -- cgit From 9a5a0ec546f4cbb6206c4c7cc9900211a3bbefa5 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 9 Mar 2018 11:17:47 +0100 Subject: Add faux_scrolling back to scrollback --- src/input.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/input.rs b/src/input.rs index ad9fcd39..1974a348 100644 --- a/src/input.rs +++ b/src/input.rs @@ -441,8 +441,8 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { 65 }; - for _ in 0..(to_scroll.abs() as usize * SCROLL_MULTIPLIER) { - self.scroll_terminal(code, modifiers) + for _ in 0..(to_scroll.abs() as usize) { + self.scroll_terminal(code, modifiers, SCROLL_MULTIPLIER) } self.ctx.mouse_mut().lines_scrolled = to_scroll % 1.0; @@ -466,7 +466,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { 65 }; - self.scroll_terminal(code, modifiers) + self.scroll_terminal(code, modifiers, 1) } }, _ => (), @@ -475,26 +475,29 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } } - fn scroll_terminal(&mut self, code: u8, modifiers: ModifiersState) { + fn scroll_terminal(&mut self, code: u8, modifiers: ModifiersState, scroll_multiplier: usize) { debug_assert!(code == 64 || code == 65); let faux_scrollback_lines = self.mouse_config.faux_scrollback_lines; let mouse_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; if self.ctx.terminal_mode().intersects(mouse_modes) { self.mouse_report(code, ElementState::Pressed, modifiers); - // } else if faux_scrollback_lines > 0 { - // // Faux scrolling - // let cmd = code + 1; // 64 + 1 = A, 65 + 1 = B - // let mut content = Vec::with_capacity(faux_scrollback_lines * 3); - // for _ in 0..faux_scrollback_lines { - // content.push(0x1b); - // content.push(b'O'); - // content.push(cmd); - // } - // self.ctx.write_to_pty(content); - // } + } else if self.ctx.terminal_mode().contains(TermMode::ALT_SCREEN) + && faux_scrollback_lines > 0 + { + // Faux scrolling + let cmd = code + 1; // 64 + 1 = A, 65 + 1 = B + let mut content = Vec::with_capacity(faux_scrollback_lines * 3); + for _ in 0..faux_scrollback_lines { + content.push(0x1b); + content.push(b'O'); + content.push(cmd); + } + self.ctx.write_to_pty(content); } else { - self.ctx.scroll(-((code as isize) * 2 - 129)); + for _ in 0..scroll_multiplier { + self.ctx.scroll(-((code as isize) * 2 - 129)); + } } } -- cgit From 786e274dd1d6dce26404a2ad5672ee33e6115ad0 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 9 Mar 2018 23:09:42 +0100 Subject: Disable faux scrolling when shift is pressed To make it possible to access the native scrollback buffer in the alternate screen without having to disable faux scrolling, faux scrolling is now disabled when the `shift` key is held down. This should allow alacritty to have the best of both worlds, a native scrollback buffer in the alternate screen buffer and faux scrolling. --- src/input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/input.rs b/src/input.rs index 1974a348..35ae4a38 100644 --- a/src/input.rs +++ b/src/input.rs @@ -483,7 +483,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { if self.ctx.terminal_mode().intersects(mouse_modes) { self.mouse_report(code, ElementState::Pressed, modifiers); } else if self.ctx.terminal_mode().contains(TermMode::ALT_SCREEN) - && faux_scrollback_lines > 0 + && faux_scrollback_lines > 0 && !modifiers.shift { // Faux scrolling let cmd = code + 1; // 64 + 1 = A, 65 + 1 = B -- cgit From 231ef51365e3cb0de760d04a47a7d2b74809c41d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 10 Mar 2018 14:53:54 +0100 Subject: Fix crash when selection leaves viewport There was an issue where alacritty tries to convert the lines in a selection to the on-screen lines even when the selection is not on the screen. This results in a crash. To prevent this from happening the selection now is not shown if it is off the screen. There currently still is a bug that when the selection is at the top of the screen but still half visible, it will not show the top line as selected but start in the second line. This bug should be resolved with https://github.com/jwilm/alacritty/pull/1171. This fixes #1148. --- src/grid/mod.rs | 20 +++++++++++++------- src/term/mod.rs | 58 ++++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 52 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 7f648f46..a52c27c5 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -148,13 +148,17 @@ impl Grid { pub fn buffer_to_visible(&self, point: Point) -> Point { Point { - line: self.buffer_line_to_visible(point.line), + line: self.buffer_line_to_visible(point.line).expect("Line not visible"), col: point.col } } - pub fn buffer_line_to_visible(&self, line: usize) -> Line { - self.offset_to_line(line - self.display_offset) + pub fn buffer_line_to_visible(&self, line: usize) -> Option { + if line >= self.display_offset { + self.offset_to_line(line - self.display_offset) + } else { + None + } } pub fn visible_line_to_buffer(&self, line: Line) -> usize { @@ -267,10 +271,12 @@ impl Grid { *(self.num_lines() - line - 1) } - pub fn offset_to_line(&self, offset: usize) -> Line { - assert!(offset < *self.num_lines()); - - self.lines - offset - 1 + pub fn offset_to_line(&self, offset: usize) -> Option { + if offset < *self.num_lines() { + Some(self.lines - offset - 1) + } else { + None + } } #[inline] diff --git a/src/term/mod.rs b/src/term/mod.rs index c3c3a300..54c8a7e2 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -122,29 +122,49 @@ impl<'a> RenderableCellsIter<'a> { let cursor_offset = grid.line_to_offset(cursor.line); let inner = grid.display_iter(); - let selection = selection.map(|loc| { - // start and end *lines* are swapped as we switch from buffer to - // Line coordinates. - let mut end = Point { - line: grid.buffer_line_to_visible(loc.start.line), - col: loc.start.col - }; - let mut start = Point { - line: grid.buffer_line_to_visible(loc.end.line), - col: loc.end.col + let mut selection_range = None; + selection.map(|loc| { + // Get on-screen lines of the selection's locations + let start_line = grid.buffer_line_to_visible(loc.start.line); + let end_line = grid.buffer_line_to_visible(loc.end.line); + + // Get start/end locations based on what part of selection is on screen + let locations = match (start_line, end_line) { + (Some(start_line), Some(end_line)) => { + Some((start_line, loc.start.col, end_line, loc.end.col)) + }, + (Some(start_line), None) => { + Some((start_line, loc.start.col, Line(0), grid.num_cols())) + }, + (None, Some(end_line)) => { + Some((grid.num_lines(), Column(0), end_line, loc.end.col)) + }, + (None, None) => None, }; - if start > end { - ::std::mem::swap(&mut start, &mut end); - } + if let Some((start_line, start_col, end_line, end_col)) = locations { + // start and end *lines* are swapped as we switch from buffer to + // Line coordinates. + let mut end = Point { + line: start_line, + col: start_col, + }; + let mut start = Point { + line: end_line, + col: end_col, + }; - println!("start={:?}, end={:?}", start, end); + if start > end { + ::std::mem::swap(&mut start, &mut end); + } - let cols = grid.num_cols(); - let start = Linear(start.line.0 * cols.0 + start.col.0); - let end = Linear(end.line.0 * cols.0 + end.col.0); + let cols = grid.num_cols(); + let start = Linear(start.line.0 * cols.0 + start.col.0); + let end = Linear(end.line.0 * cols.0 + end.col.0); - RangeInclusive::new(start, end) + // Update the selection + selection_range = Some(RangeInclusive::new(start, end)); + } }); RenderableCellsIter { @@ -153,7 +173,7 @@ impl<'a> RenderableCellsIter<'a> { grid: grid, inner: inner, mode: mode, - selection: selection, + selection: selection_range, config: config, colors: colors, cursor_cells: ArrayDeque::new(), -- cgit From dab0eca4a7bbb94e728465ca0248fbbbf32e6674 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 9 Mar 2018 16:06:39 +0100 Subject: Make normal scrolling line amount configurable It is now possible to configure the amount of lines the viewport should scroll when using the normal scrolling mode. This fixes #1160. --- src/config.rs | 32 +++++++++++++++++++++++++++----- src/input.rs | 10 ++++------ 2 files changed, 31 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index d49dd3b4..6502cf74 100644 --- a/src/config.rs +++ b/src/config.rs @@ -85,17 +85,26 @@ pub struct Mouse { /// up/down arrows sent when scrolling in alt screen buffer #[serde(deserialize_with = "deserialize_faux_scrollback_lines")] #[serde(default="default_faux_scrollback_lines")] - pub faux_scrollback_lines: usize, + pub faux_scrollback_lines: u8, + + /// Number of lines scrolled in normal buffer with scrollback + #[serde(deserialize_with = "deserialize_normal_scrolling_lines")] + #[serde(default="default_normal_scrolling_lines")] + pub normal_scrolling_lines: u8, } -fn default_faux_scrollback_lines() -> usize { +fn default_faux_scrollback_lines() -> u8 { 1 } -fn deserialize_faux_scrollback_lines<'a, D>(deserializer: D) -> ::std::result::Result +fn default_normal_scrolling_lines() -> u8 { + 3 +} + +fn deserialize_faux_scrollback_lines<'a, D>(deserializer: D) -> ::std::result::Result where D: de::Deserializer<'a> { - match usize::deserialize(deserializer) { + match u8::deserialize(deserializer) { Ok(lines) => Ok(lines), Err(err) => { eprintln!("problem with config: {}; Using default value", err); @@ -104,6 +113,18 @@ fn deserialize_faux_scrollback_lines<'a, D>(deserializer: D) -> ::std::result::R } } +fn deserialize_normal_scrolling_lines<'a, D>(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> +{ + match u8::deserialize(deserializer) { + Ok(lines) => Ok(lines), + Err(err) => { + eprintln!("problem with config: {}; Using default value", err); + Ok(default_normal_scrolling_lines()) + }, + } +} + impl Default for Mouse { fn default() -> Mouse { Mouse { @@ -113,7 +134,8 @@ impl Default for Mouse { triple_click: ClickHandler { threshold: Duration::from_millis(300), }, - faux_scrollback_lines: 1, + faux_scrollback_lines: default_faux_scrollback_lines(), + normal_scrolling_lines: default_normal_scrolling_lines(), } } } diff --git a/src/input.rs b/src/input.rs index 35ae4a38..20a0b25b 100644 --- a/src/input.rs +++ b/src/input.rs @@ -34,8 +34,6 @@ use term::SizeInfo; use term::mode::TermMode; use util::fmt::Red; -const SCROLL_MULTIPLIER: usize = 3; - /// Processes input from glutin. /// /// An escape sequence may be emitted in case specific keys or key combinations @@ -431,7 +429,6 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } pub fn on_mouse_wheel(&mut self, delta: MouseScrollDelta, phase: TouchPhase, modifiers: ModifiersState) { - let mouse_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; match delta { MouseScrollDelta::LineDelta(_columns, lines) => { let to_scroll = self.ctx.mouse_mut().lines_scrolled + lines; @@ -441,8 +438,9 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { 65 }; + let scrolling_multiplier = self.mouse_config.normal_scrolling_lines; for _ in 0..(to_scroll.abs() as usize) { - self.scroll_terminal(code, modifiers, SCROLL_MULTIPLIER) + self.scroll_terminal(code, modifiers, scrolling_multiplier) } self.ctx.mouse_mut().lines_scrolled = to_scroll % 1.0; @@ -475,7 +473,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } } - fn scroll_terminal(&mut self, code: u8, modifiers: ModifiersState, scroll_multiplier: usize) { + fn scroll_terminal(&mut self, code: u8, modifiers: ModifiersState, scroll_multiplier: u8) { debug_assert!(code == 64 || code == 65); let faux_scrollback_lines = self.mouse_config.faux_scrollback_lines; @@ -487,7 +485,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { { // Faux scrolling let cmd = code + 1; // 64 + 1 = A, 65 + 1 = B - let mut content = Vec::with_capacity(faux_scrollback_lines * 3); + let mut content = Vec::with_capacity(faux_scrollback_lines as usize * 3); for _ in 0..faux_scrollback_lines { content.push(0x1b); content.push(b'O'); -- cgit From d3f64072f3fe4d31f7a60eb0cb17a98096617b4b Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 9 Mar 2018 19:45:40 +0100 Subject: Merge branch #1095 Because there was some overlap with branch #1095, these two PRs have been added together and the config has been restructured to make use of a `scrolling` section. The default faux scrolling amount has also been changed to `3` because this simplifies the code and falls in line with what most other terminal emulators do. There should be no additional test failures due to this. --- src/config.rs | 127 +++++++++++++++++++++++++++++++++----------------------- src/event.rs | 3 ++ src/input.rs | 4 +- src/term/mod.rs | 3 +- 4 files changed, 82 insertions(+), 55 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 6502cf74..8e531a9c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -82,47 +82,9 @@ pub struct Mouse { #[serde(default, deserialize_with = "failure_default")] pub triple_click: ClickHandler, - /// up/down arrows sent when scrolling in alt screen buffer - #[serde(deserialize_with = "deserialize_faux_scrollback_lines")] - #[serde(default="default_faux_scrollback_lines")] - pub faux_scrollback_lines: u8, - - /// Number of lines scrolled in normal buffer with scrollback - #[serde(deserialize_with = "deserialize_normal_scrolling_lines")] - #[serde(default="default_normal_scrolling_lines")] - pub normal_scrolling_lines: u8, -} - -fn default_faux_scrollback_lines() -> u8 { - 1 -} - -fn default_normal_scrolling_lines() -> u8 { - 3 -} - -fn deserialize_faux_scrollback_lines<'a, D>(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> -{ - match u8::deserialize(deserializer) { - Ok(lines) => Ok(lines), - Err(err) => { - eprintln!("problem with config: {}; Using default value", err); - Ok(default_faux_scrollback_lines()) - }, - } -} - -fn deserialize_normal_scrolling_lines<'a, D>(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> -{ - match u8::deserialize(deserializer) { - Ok(lines) => Ok(lines), - Err(err) => { - eprintln!("problem with config: {}; Using default value", err); - Ok(default_normal_scrolling_lines()) - }, - } + // TODO: DEPRECATED + #[serde(default)] + pub faux_scrollback_lines: Option, } impl Default for Mouse { @@ -134,8 +96,7 @@ impl Default for Mouse { triple_click: ClickHandler { threshold: Duration::from_millis(300), }, - faux_scrollback_lines: default_faux_scrollback_lines(), - normal_scrolling_lines: default_normal_scrolling_lines(), + faux_scrollback_lines: None, } } } @@ -420,12 +381,8 @@ pub struct Config { tabspaces: usize, /// How much scrolling history to keep - #[serde(default="default_scroll_history", deserialize_with="deserialize_scroll_history")] - scroll_history: u32, -} - -fn default_scroll_history() -> u32 { - 10_000 + #[serde(default, deserialize_with="failure_default")] + scrolling: Scrolling, } fn deserialize_scroll_history<'a, D>(deserializer: D) -> ::std::result::Result @@ -521,6 +478,63 @@ impl Default for Config { } } +/// Struct for scrolling related settings +#[derive(Copy, Clone, Debug, Deserialize)] +pub struct Scrolling { + #[serde(deserialize_with="deserialize_scrolling_history")] + #[serde(default="default_scrolling_history")] + pub history: u32, + #[serde(deserialize_with="deserialize_scrolling_multiplier")] + #[serde(default="default_scrolling_multiplier")] + pub multiplier: u8, + #[serde(deserialize_with="deserialize_scrolling_multiplier")] + #[serde(default="default_scrolling_multiplier")] + pub faux_multiplier: u8, +} + +fn default_scrolling_history() -> u32 { + 10_000 +} + +// Default for normal and faux scrolling +fn default_scrolling_multiplier() -> u8 { + 3 +} + +impl Default for Scrolling { + fn default() -> Self { + Self { + history: default_scrolling_history(), + multiplier: default_scrolling_multiplier(), + faux_multiplier: default_scrolling_multiplier(), + } + } +} + +fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> +{ + match u32::deserialize(deserializer) { + Ok(lines) => Ok(lines), + Err(err) => { + eprintln!("problem with config: {}; Using default value", err); + Ok(default_scrolling_history()) + }, + } +} + +fn deserialize_scrolling_multiplier<'a, D>(deserializer: D) -> ::std::result::Result + where D: de::Deserializer<'a> +{ + match u8::deserialize(deserializer) { + Ok(lines) => Ok(lines), + Err(err) => { + eprintln!("problem with config: {}; Using default value", err); + Ok(default_scrolling_multiplier()) + }, + } +} + /// Newtype for implementing deserialize on glutin Mods /// /// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the @@ -1286,10 +1300,6 @@ impl Config { .map(|path| path.into()) } - pub fn scroll_history(&self) -> usize { - self.scroll_history as _ - } - pub fn write_defaults() -> io::Result> { let path = ::xdg::BaseDirectories::with_prefix("alacritty") .map_err(|err| io::Error::new(io::ErrorKind::NotFound, ::std::error::Error::description(&err))) @@ -1419,6 +1429,12 @@ impl Config { self.dynamic_title } + /// Scrolling settings + #[inline] + pub fn scrolling(&self) -> Scrolling { + self.scrolling + } + pub fn load_from>(path: P) -> Result { let path = path.into(); let raw = Config::read_file(path.as_path())?; @@ -1451,6 +1467,11 @@ impl Config { eprintln!("{}", fmt::Yellow("Config `padding` is deprecated. \ Please use `window.padding` instead.")); } + + if self.mouse.faux_scrollback_lines.is_some() { + println!("{}", fmt::Yellow("Config `mouse.faux_scrollback_lines` is deprecated. \ + Please use `mouse.faux_scrolling_lines` instead.")); + } } } diff --git a/src/event.rs b/src/event.rs index 8d2d80e4..14ec0b0e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -195,6 +195,7 @@ pub struct Processor { key_bindings: Vec, mouse_bindings: Vec, mouse_config: config::Mouse, + scrolling_config: config::Scrolling, print_events: bool, wait_for_event: bool, notifier: N, @@ -236,6 +237,7 @@ impl Processor { key_bindings: config.key_bindings().to_vec(), mouse_bindings: config.mouse_bindings().to_vec(), mouse_config: config.mouse().to_owned(), + scrolling_config: config.scrolling(), print_events: options.print_events, wait_for_event: true, notifier, @@ -404,6 +406,7 @@ impl Processor { processor = input::Processor { ctx: context, + scrolling_config: &self.scrolling_config, mouse_config: &self.mouse_config, key_bindings: &self.key_bindings[..], mouse_bindings: &self.mouse_bindings[..], diff --git a/src/input.rs b/src/input.rs index 20a0b25b..3ff631e0 100644 --- a/src/input.rs +++ b/src/input.rs @@ -44,6 +44,7 @@ pub struct Processor<'a, A: 'a> { pub key_bindings: &'a [KeyBinding], pub mouse_bindings: &'a [MouseBinding], pub mouse_config: &'a config::Mouse, + pub scrolling_config: &'a config::Scrolling, pub ctx: A, } @@ -756,8 +757,9 @@ mod tests { triple_click: ClickHandler { threshold: Duration::from_millis(1000), }, - faux_scrollback_lines: 1, + faux_scrollback_lines: None, }, + scrolling_config: &config::Scrolling::default(), key_bindings: &config.key_bindings()[..], mouse_bindings: &config.mouse_bindings()[..], }; diff --git a/src/term/mod.rs b/src/term/mod.rs index 54c8a7e2..fd321fc6 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -848,7 +848,8 @@ impl Term { let num_cols = size.cols(); let num_lines = size.lines(); - let grid = Grid::new(num_lines, num_cols, config.scroll_history(), template); + let history_size = config.scrolling().history as usize; + let grid = Grid::new(num_lines, num_cols, history_size, template); let tabspaces = config.tabspaces(); let tabs = IndexRange::from(Column(0)..grid.num_cols()) -- cgit From a238e9ac5832cb9a40f886a3b873728cd7890d01 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 9 Mar 2018 23:02:45 +0100 Subject: Fix linux config default value --- src/config.rs | 12 ------------ src/input.rs | 9 +++++++-- 2 files changed, 7 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 8e531a9c..e7f1b587 100644 --- a/src/config.rs +++ b/src/config.rs @@ -385,18 +385,6 @@ pub struct Config { scrolling: Scrolling, } -fn deserialize_scroll_history<'a, D>(deserializer: D) -> ::std::result::Result - where D: de::Deserializer<'a> -{ - match u32::deserialize(deserializer) { - Ok(lines) => Ok(lines), - Err(err) => { - eprintln!("problem with config: {}; Using default value", err); - Ok(default_scroll_history()) - }, - } -} - fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result, D::Error> where D: de::Deserializer<'a>, T: Deserialize<'a> diff --git a/src/input.rs b/src/input.rs index 3ff631e0..16fddc2c 100644 --- a/src/input.rs +++ b/src/input.rs @@ -439,7 +439,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { 65 }; - let scrolling_multiplier = self.mouse_config.normal_scrolling_lines; + let scrolling_multiplier = self.scrolling_config.multiplier; for _ in 0..(to_scroll.abs() as usize) { self.scroll_terminal(code, modifiers, scrolling_multiplier) } @@ -477,8 +477,13 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { fn scroll_terminal(&mut self, code: u8, modifiers: ModifiersState, scroll_multiplier: u8) { debug_assert!(code == 64 || code == 65); - let faux_scrollback_lines = self.mouse_config.faux_scrollback_lines; let mouse_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; + + // Make sure the new and deprecated setting are both allowed + let faux_scrollback_lines = self.mouse_config + .faux_scrollback_lines + .unwrap_or(self.scrolling_config.faux_multiplier as usize); + if self.ctx.terminal_mode().intersects(mouse_modes) { self.mouse_report(code, ElementState::Pressed, modifiers); } else if self.ctx.terminal_mode().contains(TermMode::ALT_SCREEN) -- cgit From d9bd21d33f7f35d1362a581cefb1c897a821fcad Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 10 Mar 2018 12:14:58 +0100 Subject: Fix selection in scrollback There were a few issues with selection in scrollback that were mainly off-by-one errors. This aims at fixing these issues. This also fixes a bug that currently exists in master where the last cell is not selected when the mouse leaves the window to the right. --- src/input.rs | 5 ++++- src/selection.rs | 17 +++++++++++------ src/term/mod.rs | 1 - 3 files changed, 15 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/input.rs b/src/input.rs index 16fddc2c..7b9eda56 100644 --- a/src/input.rs +++ b/src/input.rs @@ -278,7 +278,10 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { let cell_x = (x as usize - size_info.padding_x as usize) % size_info.cell_width as usize; let half_cell_width = (size_info.cell_width / 2.0) as usize; - let cell_side = if cell_x > half_cell_width { + let cell_side = if cell_x > half_cell_width + // Edge case when mouse leaves the window + || x as f32 >= size_info.width - size_info.padding_x + { Side::Right } else { Side::Left diff --git a/src/selection.rs b/src/selection.rs index 31787bbb..8e7fa29b 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -191,8 +191,6 @@ impl Selection { (region.end, region.start) }; - println!("BEFORE front={:?}, start={:?}, tail={:?}, end={:?}", front, start, tail, end); - if front < tail && front.line == tail.line { start = grid.semantic_search_left(front); end = grid.semantic_search_right(tail); @@ -201,8 +199,6 @@ impl Selection { end = grid.semantic_search_left(tail); } - println!("AFTER front={:?}, start={:?}, tail={:?}, end={:?}", front, start, tail, end); - if start > end { ::std::mem::swap(&mut start, &mut end); } @@ -249,12 +245,21 @@ impl Selection { } fn span_simple(grid: &G, region: &Range) -> Option { - let start = region.start.point; + let mut start = region.start.point; let start_side = region.start.side; - let end = region.end.point; + let mut end = region.end.point; let end_side = region.end.side; let cols = grid.dimensions().col; + // Handle some edge cases + if start.line > end.line { + start.col += 1; + end.col -= 1; + } else if start.line < end.line { + start.col -= 1; + end.col += 1; + } + let (front, tail, front_side, tail_side) = if start > end { // Selected upward; start/end are swapped (end, start, end_side, start_side) diff --git a/src/term/mod.rs b/src/term/mod.rs index fd321fc6..6318c680 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1060,7 +1060,6 @@ impl Term { let selection = self.grid.selection.as_ref() .and_then(|s| s.to_span(self)) .map(|span| { - // println!("span={:?}, locations={:?}", span, span.to_locations()); span.to_locations() }); let cursor = if window_focused { -- cgit From 58c69cafad3b1dafa3631d911c6bfc21f5e5dec5 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 13 Mar 2018 19:00:14 +0100 Subject: Fix multi-line selection with single cell end When the user selected multiple lines, dragging the selection downwards, and then leaves the cursor to the left side of the first cell, the first cell was still incorrectly selected. This has been fixed. The selection also did not update if the mouse was outside of the window, now all movement events are accpeted even when the mouse is outside of the window. This allows updating the selection when the user is dragging the cursor too far. Mouse movement and click events outside of the window are not propagated, these are only used for updating the selection. --- src/event.rs | 12 ++++---- src/input.rs | 87 +++++++++++++++++++++++++++----------------------------- src/selection.rs | 27 ++++++++++++++++-- src/term/mod.rs | 22 +++++++------- 4 files changed, 83 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/event.rs b/src/event.rs index 14ec0b0e..b0987d58 100644 --- a/src/event.rs +++ b/src/event.rs @@ -154,8 +154,8 @@ pub enum ClickState { /// State of the mouse pub struct Mouse { - pub x: u32, - pub y: u32, + pub x: usize, + pub y: usize, pub left_button_state: ElementState, pub middle_button_state: ElementState, pub right_button_state: ElementState, @@ -315,13 +315,11 @@ impl Processor { processor.ctx.terminal.dirty = true; }, CursorMoved { position: (x, y), modifiers, .. } => { - let x = x as i32; - let y = y as i32; - let x = limit(x, 0, processor.ctx.size_info.width as i32); - let y = limit(y, 0, processor.ctx.size_info.height as i32); + let x = limit(x as i32, 0, processor.ctx.size_info.width as i32); + let y = limit(y as i32, 0, processor.ctx.size_info.height as i32); *hide_cursor = false; - processor.mouse_moved(x as u32, y as u32, modifiers); + processor.mouse_moved(x as usize, y as usize, modifiers); }, MouseWheel { delta, phase, modifiers, .. } => { *hide_cursor = false; diff --git a/src/input.rs b/src/input.rs index 7b9eda56..98e0d241 100644 --- a/src/input.rs +++ b/src/input.rs @@ -266,55 +266,52 @@ impl From<&'static str> for Action { impl<'a, A: ActionContext + 'a> Processor<'a, A> { #[inline] - pub fn mouse_moved(&mut self, x: u32, y: u32, modifiers: ModifiersState) { + pub fn mouse_moved(&mut self, x: usize, y: usize, modifiers: ModifiersState) { self.ctx.mouse_mut().x = x; self.ctx.mouse_mut().y = y; let size_info = self.ctx.size_info(); - if let Some(point) = size_info.pixels_to_coords(x as usize, y as usize) { - let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line); - let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col); - - let cell_x = (x as usize - size_info.padding_x as usize) % size_info.cell_width as usize; - let half_cell_width = (size_info.cell_width / 2.0) as usize; - - let cell_side = if cell_x > half_cell_width - // Edge case when mouse leaves the window - || x as f32 >= size_info.width - size_info.padding_x - { - Side::Right - } else { - Side::Left - }; - self.ctx.mouse_mut().cell_side = cell_side; - - let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG; - if self.ctx.mouse_mut().left_button_state == ElementState::Pressed - && ( - modifiers.shift - || !self.ctx.terminal_mode().intersects(TermMode::MOUSE_REPORT_CLICK | motion_mode) - ) - { - self.ctx.update_selection(Point { - line: point.line, - col: point.col - }, cell_side); - } else if self.ctx.terminal_mode().intersects(motion_mode) - // Only report motion when changing cells - && ( - prev_line != self.ctx.mouse_mut().line - || prev_col != self.ctx.mouse_mut().column - ) - { - if self.ctx.mouse_mut().left_button_state == ElementState::Pressed { - self.mouse_report(32, ElementState::Pressed, modifiers); - } else if self.ctx.mouse_mut().middle_button_state == ElementState::Pressed { - self.mouse_report(33, ElementState::Pressed, modifiers); - } else if self.ctx.mouse_mut().right_button_state == ElementState::Pressed { - self.mouse_report(34, ElementState::Pressed, modifiers); - } else if self.ctx.terminal_mode().contains(TermMode::MOUSE_MOTION) { - self.mouse_report(35, ElementState::Pressed, modifiers); - } + let point = size_info.pixels_to_coords(x, y); + + let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line); + let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col); + + let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize; + let half_cell_width = (size_info.cell_width / 2.0) as usize; + + let cell_side = if cell_x > half_cell_width + // Edge case when mouse leaves the window + || x as f32 >= size_info.width - size_info.padding_x + { + Side::Right + } else { + Side::Left + }; + self.ctx.mouse_mut().cell_side = cell_side; + + let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG; + let report_mode = TermMode::MOUSE_REPORT_CLICK | motion_mode; + + if self.ctx.mouse_mut().left_button_state == ElementState::Pressed && + ( modifiers.shift || !self.ctx.terminal_mode().intersects(report_mode)) + { + self.ctx.update_selection(Point { + line: point.line, + col: point.col + }, cell_side); + } else if self.ctx.terminal_mode().intersects(motion_mode) + // Only report motion when changing cells + && (prev_line != self.ctx.mouse_mut().line || prev_col != self.ctx.mouse_mut().column) + && size_info.contains_point(x, y) + { + if self.ctx.mouse_mut().left_button_state == ElementState::Pressed { + self.mouse_report(32, ElementState::Pressed, modifiers); + } else if self.ctx.mouse_mut().middle_button_state == ElementState::Pressed { + self.mouse_report(33, ElementState::Pressed, modifiers); + } else if self.ctx.mouse_mut().right_button_state == ElementState::Pressed { + self.mouse_report(34, ElementState::Pressed, modifiers); + } else if self.ctx.terminal_mode().contains(TermMode::MOUSE_MOTION) { + self.mouse_report(35, ElementState::Pressed, modifiers); } } } diff --git a/src/selection.rs b/src/selection.rs index 8e7fa29b..d49236a4 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -253,8 +253,31 @@ impl Selection { // Handle some edge cases if start.line > end.line { - start.col += 1; - end.col -= 1; + if end.col > Column(0) { + start.col += 1; + end.col -= 1; + } + // Special case for when a multi-line selection to the + // bottom ends on a new line with just one cell selected + // and the first cell should not be selected + else { + if start_side == Side::Right { + start.col += 1; + } + + // Remove the single selected cell if mouse left window + if end_side == Side::Left { + end.line += 1; + end.col = cols - 1; + } + + return Some(Span { + cols, + front: end, + tail: start, + ty: SpanType::Inclusive, + }); + } } else if start.line < end.line { start.col -= 1; end.col += 1; diff --git a/src/term/mod.rs b/src/term/mod.rs index 6318c680..8db21402 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -791,25 +791,21 @@ impl SizeInfo { Column(((self.width - 2. * self.padding_x) / self.cell_width) as usize) } - fn contains_point(&self, x: usize, y:usize) -> bool { + pub fn contains_point(&self, x: usize, y:usize) -> bool { x <= (self.width - self.padding_x) as usize && x >= self.padding_x as usize && y <= (self.height - self.padding_y) as usize && y >= self.padding_y as usize } - pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option { - if !self.contains_point(x, y) { - return None; - } + pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point { + let col = Column(x.saturating_sub(self.padding_x as usize) / (self.cell_width as usize)); + let line = Line(y.saturating_sub(self.padding_y as usize) / (self.cell_height as usize)); - let col = Column((x - self.padding_x as usize) / (self.cell_width as usize)); - let line = Line((y - self.padding_y as usize) / (self.cell_height as usize)); - - Some(Point { + Point { line: min(line, self.lines() - 1), col: min(col, self.cols() - 1) - }) + } } } @@ -1036,7 +1032,11 @@ impl Term { /// /// Returns None if the coordinates are outside the screen pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option { - self.size_info().pixels_to_coords(x, y) + if self.size_info.contains_point(x, y) { + Some(self.size_info.pixels_to_coords(x, y)) + } else { + None + } } /// Access to the raw grid data structure -- cgit From b0f272f41950558cfec36414d2430d524d7b8b55 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 13 Mar 2018 22:07:23 +0100 Subject: Fix buggy selection when scrolling down When scrolling down with a selection on screen the first line was not properly selected. This has been fixed by making sure the selection always starts in the first cell when it is only partially visible. --- src/term/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/term/mod.rs b/src/term/mod.rs index 8db21402..c8017262 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -134,7 +134,7 @@ impl<'a> RenderableCellsIter<'a> { Some((start_line, loc.start.col, end_line, loc.end.col)) }, (Some(start_line), None) => { - Some((start_line, loc.start.col, Line(0), grid.num_cols())) + Some((start_line, loc.start.col, Line(0), Column(0))) }, (None, Some(end_line)) => { Some((grid.num_lines(), Column(0), end_line, loc.end.col)) -- cgit From e20aa550cbcf7b3f7be6757c007960b3f9b1ac08 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 13 Mar 2018 22:48:08 +0100 Subject: Fix selection starting in first cell When selecting to the top and starting in the first cell, alacritty would crash. These cases have been fixed and now selection should be completely working. --- src/selection.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/selection.rs b/src/selection.rs index d49236a4..f775f1f6 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -279,8 +279,31 @@ impl Selection { }); } } else if start.line < end.line { - start.col -= 1; end.col += 1; + if start.col > Column(0) { + start.col -= 1; + } + // Special case for when a selection is started + // in the first cell of a line + else { + let ty = match end_side { + Side::Left => SpanType::ExcludeTail, + Side::Right => SpanType::Inclusive, + }; + + // Switch start cell to last cell of previous line + if start_side == Side::Left { + start.line += 1; + start.col = cols - 1; + } + + return Some(Span { + ty, + cols, + front: start, + tail: end, + }); + } } let (front, tail, front_side, tail_side) = if start > end { -- cgit From 2c7bb9a4d3ce3ead6de4ca6485ca67c44c0bd1c1 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 10 Mar 2018 20:24:10 +0100 Subject: Add scrollback hotkeys This offers a few additional hotkeys that can be used in combination with scrollback. None of these are used by default yet. This implements the following bindings: - ScrollPageUp: Scroll exactly one screen height up - ScrollPageDown: Scroll exactly one screen height down - ScrollToTop: Scroll as far up as possible - ScrollToBottom: Scroll as far down as possible This fixes #1151. --- src/config.rs | 7 ++++++- src/event.rs | 12 ++++++++++++ src/grid/mod.rs | 20 ++++++++++++++++++++ src/input.rs | 33 ++++++++++++++++++++++++++++++--- src/term/mod.rs | 16 ++++++++++++++++ 5 files changed, 84 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index e7f1b587..f8dad1f2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -588,7 +588,8 @@ impl<'a> de::Deserialize<'a> for ActionWrapper { type Value = ActionWrapper; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, ResetFontSize, or Quit") + f.write_str("Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, \ + ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollToTop, ScrollToBottom or Quit") } fn visit_str(self, value: &str) -> ::std::result::Result @@ -601,6 +602,10 @@ impl<'a> de::Deserialize<'a> for ActionWrapper { "IncreaseFontSize" => Action::IncreaseFontSize, "DecreaseFontSize" => Action::DecreaseFontSize, "ResetFontSize" => Action::ResetFontSize, + "ScrollPageUp" => Action::ScrollPageUp, + "ScrollPageDown" => Action::ScrollPageDown, + "ScrollToTop" => Action::ScrollToTop, + "ScrollToBottom" => Action::ScrollToBottom, "Quit" => Action::Quit, _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), })) diff --git a/src/event.rs b/src/event.rs index b0987d58..4823d824 100644 --- a/src/event.rs +++ b/src/event.rs @@ -61,6 +61,18 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { self.terminal.reset_scroll(); } + fn scroll_to_top(&mut self) { + self.terminal.scroll_to_top(); + } + + fn scroll_page_up(&mut self) { + self.terminal.scroll_page_up(); + } + + fn scroll_page_down(&mut self) { + self.terminal.scroll_page_down(); + } + fn copy_selection(&self, buffer: ::copypasta::Buffer) { self.terminal .selection_to_string() diff --git a/src/grid/mod.rs b/src/grid/mod.rs index a52c27c5..ac761adc 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -176,6 +176,26 @@ impl Grid { self.display_offset = 0; } + pub fn scroll_to_top(&mut self) { + self.display_offset = self.scroll_limit; + } + + pub fn scroll_page_up(&mut self) { + if self.display_offset + self.lines.0 >= self.scroll_limit { + self.display_offset = self.scroll_limit; + } else { + self.display_offset += self.lines.0; + } + } + + pub fn scroll_page_down(&mut self) { + if self.display_offset <= self.lines.0 { + self.display_offset = 0; + } else { + self.display_offset -= self.lines.0; + } + } + pub fn resize(&mut self, lines: index::Line, cols: index::Column) { // Check that there's actually work to do and return early if not if lines == self.lines && cols == self.cols { diff --git a/src/input.rs b/src/input.rs index 98e0d241..adce3448 100644 --- a/src/input.rs +++ b/src/input.rs @@ -65,8 +65,11 @@ pub trait ActionContext { fn last_modifiers(&mut self) -> &mut ModifiersState; fn change_font_size(&mut self, delta: i8); fn reset_font_size(&mut self); - fn scroll(&mut self, count: isize) {} - fn reset_scroll(&mut self) {} + fn scroll(&mut self, count: isize); + fn reset_scroll(&mut self); + fn scroll_to_top(&mut self); + fn scroll_page_up(&mut self); + fn scroll_page_down(&mut self); } /// Describes a state and action to take in that state @@ -168,6 +171,18 @@ pub enum Action { /// Reset font size to the config value ResetFontSize, + /// Scroll exactly one page up + ScrollPageUp, + + /// Scroll exactly one page down + ScrollPageDown, + + /// Scroll all the way to the top + ScrollToTop, + + /// Scroll all the way to the bottom + ScrollToBottom, + /// Run given command Command(String, Vec), @@ -237,7 +252,19 @@ impl Action { } Action::ResetFontSize => { ctx.reset_font_size(); - } + }, + Action::ScrollPageUp => { + ctx.scroll_page_up(); + }, + Action::ScrollPageDown => { + ctx.scroll_page_down(); + }, + Action::ScrollToTop => { + ctx.scroll_to_top(); + }, + Action::ScrollToBottom => { + ctx.reset_scroll(); + }, } } diff --git a/src/term/mod.rs b/src/term/mod.rs index c8017262..8e973b0b 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -831,6 +831,22 @@ impl Term { pub fn reset_scroll(&mut self) { self.grid.reset_scroll_display(); + self.dirty = true; + } + + pub fn scroll_to_top(&mut self) { + self.grid.scroll_to_top(); + self.dirty = true; + } + + pub fn scroll_page_up(&mut self) { + self.grid.scroll_page_up(); + self.dirty = true; + } + + pub fn scroll_page_down(&mut self) { + self.grid.scroll_page_down(); + self.dirty = true; } #[inline] -- cgit From 0dcb9ca6a871fd6d28787142a134c9190001ac43 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 11 Mar 2018 13:01:06 +0100 Subject: Replace scrolling methods with enum The different scrolling methods added a bunch of boilerplate where the call was just forwarded to the next struct, this has been removed by making the scroll amount into a struct. Now everything is called through one method and the parameter decides how far the viewport should be scrolled. --- src/event.rs | 21 +++------------------ src/grid/mod.rs | 58 +++++++++++++++++++++++++++++---------------------------- src/input.rs | 19 ++++++++----------- src/term/mod.rs | 26 +++----------------------- 4 files changed, 44 insertions(+), 80 deletions(-) (limited to 'src') diff --git a/src/event.rs b/src/event.rs index 4823d824..cf7d1030 100644 --- a/src/event.rs +++ b/src/event.rs @@ -10,6 +10,7 @@ use parking_lot::MutexGuard; use glutin::{self, ModifiersState, Event, ElementState}; use copypasta::{Clipboard, Load, Store}; +use grid::Scroll; use config::{self, Config}; use cli::Options; use display::OnResize; @@ -53,24 +54,8 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { *self.size_info } - fn scroll(&mut self, count: isize) { - self.terminal.scroll_display(count); - } - - fn reset_scroll(&mut self) { - self.terminal.reset_scroll(); - } - - fn scroll_to_top(&mut self) { - self.terminal.scroll_to_top(); - } - - fn scroll_page_up(&mut self) { - self.terminal.scroll_page_up(); - } - - fn scroll_page_down(&mut self) { - self.terminal.scroll_page_down(); + fn scroll(&mut self, scroll: Scroll) { + self.terminal.scroll_display(scroll); } fn copy_selection(&self, buffer: ::copypasta::Buffer) { diff --git a/src/grid/mod.rs b/src/grid/mod.rs index ac761adc..65bc8382 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -113,6 +113,14 @@ pub struct GridIterator<'a, T: 'a> { top: usize, } +pub enum Scroll { + Lines(isize), + PageUp, + PageDown, + Top, + Bottom, +} + impl Grid { pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid { let mut raw = Storage::with_capacity(*lines + scrollback, lines); @@ -165,34 +173,28 @@ impl Grid { self.line_to_offset(line) + self.display_offset } - pub fn scroll_display(&mut self, count: isize) { - self.display_offset = min( - max((self.display_offset as isize) + count, 0isize) as usize, - self.scroll_limit - ); - } - - pub fn reset_scroll_display(&mut self) { - self.display_offset = 0; - } - - pub fn scroll_to_top(&mut self) { - self.display_offset = self.scroll_limit; - } - - pub fn scroll_page_up(&mut self) { - if self.display_offset + self.lines.0 >= self.scroll_limit { - self.display_offset = self.scroll_limit; - } else { - self.display_offset += self.lines.0; - } - } - - pub fn scroll_page_down(&mut self) { - if self.display_offset <= self.lines.0 { - self.display_offset = 0; - } else { - self.display_offset -= self.lines.0; + pub fn scroll_display(&mut self, scroll: Scroll) { + match scroll { + Scroll::Lines(count) => { + self.display_offset = min( + max((self.display_offset as isize) + count, 0isize) as usize, + self.scroll_limit + ); + }, + Scroll::PageUp => { + self.display_offset = min( + self.display_offset + self.lines.0, + self.scroll_limit + ); + }, + Scroll::PageDown => { + self.display_offset -= min( + self.display_offset, + self.lines.0 + ); + }, + Scroll::Top => self.display_offset = self.scroll_limit, + Scroll::Bottom => self.display_offset = 0, } } diff --git a/src/input.rs b/src/input.rs index adce3448..8cf11811 100644 --- a/src/input.rs +++ b/src/input.rs @@ -28,6 +28,7 @@ use copypasta::{Clipboard, Load, Buffer}; use glutin::{ElementState, VirtualKeyCode, MouseButton, TouchPhase, MouseScrollDelta, ModifiersState}; use config; +use grid::Scroll; use event::{ClickState, Mouse}; use index::{Line, Column, Side, Point}; use term::SizeInfo; @@ -65,11 +66,7 @@ pub trait ActionContext { fn last_modifiers(&mut self) -> &mut ModifiersState; fn change_font_size(&mut self, delta: i8); fn reset_font_size(&mut self); - fn scroll(&mut self, count: isize); - fn reset_scroll(&mut self); - fn scroll_to_top(&mut self); - fn scroll_page_up(&mut self); - fn scroll_page_down(&mut self); + fn scroll(&mut self, scroll: Scroll); } /// Describes a state and action to take in that state @@ -254,16 +251,16 @@ impl Action { ctx.reset_font_size(); }, Action::ScrollPageUp => { - ctx.scroll_page_up(); + ctx.scroll(Scroll::PageUp); }, Action::ScrollPageDown => { - ctx.scroll_page_down(); + ctx.scroll(Scroll::PageDown); }, Action::ScrollToTop => { - ctx.scroll_to_top(); + ctx.scroll(Scroll::Top); }, Action::ScrollToBottom => { - ctx.reset_scroll(); + ctx.scroll(Scroll::Bottom); }, } } @@ -527,7 +524,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { self.ctx.write_to_pty(content); } else { for _ in 0..scroll_multiplier { - self.ctx.scroll(-((code as isize) * 2 - 129)); + self.ctx.scroll(Scroll::Lines(-((code as isize) * 2 - 129))); } } } @@ -597,7 +594,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { /// Process a received character pub fn received_char(&mut self, c: char) { if !*self.ctx.suppress_chars() { - self.ctx.reset_scroll(); + self.ctx.scroll(Scroll::Bottom); self.ctx.clear_selection(); let utf8_len = c.len_utf8(); diff --git a/src/term/mod.rs b/src/term/mod.rs index 8e973b0b..3c7ef87c 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -24,7 +24,7 @@ use unicode_width::UnicodeWidthChar; use font::{self, Size}; use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; -use grid::{BidirectionalIterator, Grid, Indexed, IndexRegion, DisplayIter}; +use grid::{BidirectionalIterator, Grid, Indexed, IndexRegion, DisplayIter, Scroll}; use index::{self, Point, Column, Line, IndexRange, Contains, RangeInclusive, Linear}; use selection::{self, Selection, Locations}; use config::{Config, VisualBellAnimation}; @@ -824,28 +824,8 @@ impl Term { self.next_title.take() } - pub fn scroll_display(&mut self, count: isize) { - self.grid.scroll_display(count); - self.dirty = true; - } - - pub fn reset_scroll(&mut self) { - self.grid.reset_scroll_display(); - self.dirty = true; - } - - pub fn scroll_to_top(&mut self) { - self.grid.scroll_to_top(); - self.dirty = true; - } - - pub fn scroll_page_up(&mut self) { - self.grid.scroll_page_up(); - self.dirty = true; - } - - pub fn scroll_page_down(&mut self) { - self.grid.scroll_page_down(); + pub fn scroll_display(&mut self, scroll: Scroll) { + self.grid.scroll_display(scroll); self.dirty = true; } -- cgit From f46381616272ba18a1a8a3f441b836c79bd1a029 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Thu, 15 Mar 2018 20:13:00 +0100 Subject: Refactor `span_simple` selection The current `span_simple` selection is everything but simple. This version should have the same functionality as the current `span_simple` with the difference that a lot of complexity has been removed. Not only is this code shorter, it should also be significantly easier to understand with no "magic" to it. This will hopefully prevent us from having an unmaintainable blob of off-by-one guessing in the repo. Also removed the `out` file which I used in the original PR because scrollback is not implemented yet. :) --- src/selection.rs | 143 +++++++++++++------------------------------------------ 1 file changed, 34 insertions(+), 109 deletions(-) (limited to 'src') diff --git a/src/selection.rs b/src/selection.rs index f775f1f6..20b1d601 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -245,127 +245,52 @@ impl Selection { } fn span_simple(grid: &G, region: &Range) -> Option { - let mut start = region.start.point; + let start = region.start.point; let start_side = region.start.side; - let mut end = region.end.point; + let end = region.end.point; let end_side = region.end.side; let cols = grid.dimensions().col; - // Handle some edge cases - if start.line > end.line { - if end.col > Column(0) { - start.col += 1; - end.col -= 1; - } - // Special case for when a multi-line selection to the - // bottom ends on a new line with just one cell selected - // and the first cell should not be selected - else { - if start_side == Side::Right { - start.col += 1; - } - - // Remove the single selected cell if mouse left window - if end_side == Side::Left { - end.line += 1; - end.col = cols - 1; - } - - return Some(Span { - cols, - front: end, - tail: start, - ty: SpanType::Inclusive, - }); - } - } else if start.line < end.line { - end.col += 1; - if start.col > Column(0) { - start.col -= 1; - } - // Special case for when a selection is started - // in the first cell of a line - else { - let ty = match end_side { - Side::Left => SpanType::ExcludeTail, - Side::Right => SpanType::Inclusive, - }; - - // Switch start cell to last cell of previous line - if start_side == Side::Left { - start.line += 1; - start.col = cols - 1; - } - - return Some(Span { - ty, - cols, - front: start, - tail: end, - }); - } + // No selection for single cell with identical sides or two cell with right+left sides + if (start == end && start_side == end_side) + || (start_side == Side::Right && end_side == Side::Left && end.col == start.col + 1) + { + return None; } - let (front, tail, front_side, tail_side) = if start > end { - // Selected upward; start/end are swapped - (end, start, end_side, start_side) - } else { - // Selected downward; no swapping - (start, end, start_side, end_side) - }; - - debug_assert!(!(tail < front)); - - // Single-cell selections are a special case - if start == end { - if start_side == end_side { - return None; + // Make sure front is always the "bottom" and tail is always the "top" + let (mut front, mut tail, front_side, tail_side) = + if start.line > end.line || start.line == end.line && start.col <= end.col { + // Selected upward; start/end are swapped + (end, start, end_side, start_side) } else { - return Some(Span { - cols, - ty: SpanType::Inclusive, - front, - tail, - }); + // Selected downward; no swapping + (start, end, start_side, end_side) + }; + + // Remove last cell if selection ends to the left of a cell + if front_side == Side::Left && start != end { + if front.col != Column(0) { + front.col -= 1; + } + // Special case when selection starts to left of first cell + else { + front.col = cols - 1; + front.line += 1; } } - // The other special case is two adjacent cells with no - // selection: [ B][E ] or [ E][B ] - let adjacent = tail.line == front.line && tail.col - front.col == Column(1); - if adjacent && front_side == Side::Right && tail_side == Side::Left { - return None; + // Remove first cell if selection starts at the right of a cell + if tail_side == Side::Right && front != tail { + tail.col += 1; } - Some(match (front_side, tail_side) { - // [FX][XX][XT] - (Side::Left, Side::Right) => Span { - cols, - front, - tail, - ty: SpanType::Inclusive - }, - // [ F][XX][T ] - (Side::Right, Side::Left) => Span { - cols, - front, - tail, - ty: SpanType::Exclusive - }, - // [FX][XX][T ] - (Side::Left, Side::Left) => Span { - cols, - front, - tail, - ty: SpanType::ExcludeTail - }, - // [ F][XX][XT] - (Side::Right, Side::Right) => Span { - cols, - front, - tail, - ty: SpanType::ExcludeFront - }, + // Return the selection with all cells inclusive + Some(Span { + cols, + front, + tail, + ty: SpanType::Inclusive, }) } } -- cgit From 4333b2fb1992b8185dc7041d9d3df8eee86717bc Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 20 Mar 2018 10:04:22 -0700 Subject: Fix regression with scrolling regions Resolves #1154 --- src/grid/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 65bc8382..f1cec329 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -335,7 +335,7 @@ impl Grid { } for line in IndexRange(region.start .. (region.start + positions)) { - self.raw[*line].reset(&self.template_row); + self.raw[line].reset(&self.template_row); } } } -- cgit From 688cabefc0bfc3224189c10235668eae5e5b0101 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 23 Mar 2018 01:01:55 +0100 Subject: Rework auto-scrolling options This changes two things, the first thing it does is that now whenever a keybinding sends an escape sequence, the viewport is automatically scrolled to the bottom. This is enabled by default and fixes #1187. The second thing is automatic scrolling when a command writes to the terminal. So when running a command like `sleep 3; ls -lah`, alacritty will scroll to the bottom once the output is sent, even if the viewport is currently not at the bottom of the scrollback. Because this can have an impact on performance, and is not enabled by default in terminals like iTerm or Termite (VTE), it is an opt-in setting in the config. --- src/config.rs | 3 +++ src/event_loop.rs | 15 +++++++++++---- src/input.rs | 1 + src/term/mod.rs | 10 ++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index f8dad1f2..0eab1bfb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -478,6 +478,8 @@ pub struct Scrolling { #[serde(deserialize_with="deserialize_scrolling_multiplier")] #[serde(default="default_scrolling_multiplier")] pub faux_multiplier: u8, + #[serde(default, deserialize_with="failure_default")] + pub auto_scroll: bool, } fn default_scrolling_history() -> u32 { @@ -495,6 +497,7 @@ impl Default for Scrolling { history: default_scrolling_history(), multiplier: default_scrolling_multiplier(), faux_multiplier: default_scrolling_multiplier(), + auto_scroll: false, } } } diff --git a/src/event_loop.rs b/src/event_loop.rs index 18d48a52..d7d27243 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -255,6 +255,9 @@ impl EventLoop let mut processed = 0; let mut terminal = None; + // Flag to keep track if wakeup has already been sent + let mut send_wakeup = false; + loop { match self.pty.read(&mut buf[..]) { Ok(0) => break, @@ -272,10 +275,14 @@ impl EventLoop // Get reference to terminal. Lock is acquired on initial // iteration and held until there's no bytes left to parse // or we've reached MAX_READ. - if terminal.is_none() { + let terminal = if terminal.is_none() { terminal = Some(self.terminal.lock()); - } - let terminal = terminal.as_mut().unwrap(); + let terminal = terminal.as_mut().unwrap(); + send_wakeup = !terminal.dirty; + terminal + } else { + terminal.as_mut().unwrap() + }; // Run the parser for byte in &buf[..got] { @@ -301,7 +308,7 @@ impl EventLoop // Only request a draw if one hasn't already been requested. if let Some(mut terminal) = terminal { - if !terminal.dirty { + if send_wakeup { self.display.notify(); terminal.dirty = true; } diff --git a/src/input.rs b/src/input.rs index 8cf11811..047c81aa 100644 --- a/src/input.rs +++ b/src/input.rs @@ -192,6 +192,7 @@ impl Action { fn execute(&self, ctx: &mut A) { match *self { Action::Esc(ref s) => { + ctx.scroll(Scroll::Bottom); ctx.write_to_pty(s.clone().into_bytes()) }, Action::Copy => { diff --git a/src/term/mod.rs b/src/term/mod.rs index 3c7ef87c..198f8cea 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -756,6 +756,9 @@ pub struct Term { /// Number of spaces in one tab tabspaces: usize, + + /// Automatically scroll to bottom when new lines are added + auto_scroll: bool, } /// Terminal size info @@ -879,6 +882,7 @@ impl Term { default_cursor_style: config.cursor_style(), dynamic_title: config.dynamic_title(), tabspaces, + auto_scroll: config.scrolling().auto_scroll, } } @@ -905,6 +909,7 @@ impl Term { self.visual_bell.update_config(config); self.default_cursor_style = config.cursor_style(); self.dynamic_title = config.dynamic_title(); + self.auto_scroll = config.scrolling().auto_scroll; } #[inline] @@ -1255,6 +1260,11 @@ impl ansi::Handler for Term { /// A character to be displayed #[inline] fn input(&mut self, c: char) { + // If enabled, scroll to bottom when character is received + if self.auto_scroll { + self.scroll_display(Scroll::Bottom); + } + if self.input_needs_wrap { if !self.mode.contains(mode::TermMode::LINE_WRAP) { return; -- cgit From b0f655ac85ab6d86e9e482cbb9035200c6f08d40 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 9 Mar 2018 13:49:47 -0800 Subject: Make tests compile again Some tests are still not passing, though. A migration script was added to migrate serialized grids from pre-scrollback to the current format. The script is included with this commit for completeness, posterity, and as an example to be used in the future. A few tests in grid/tests.rs were removed due to becoming irrelevant. --- src/grid/mod.rs | 2 ++ src/grid/storage.rs | 14 ++++++++-- src/grid/tests.rs | 79 ++++++++--------------------------------------------- src/term/mod.rs | 24 ++++++++-------- 4 files changed, 37 insertions(+), 82 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index f1cec329..3495be1d 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -89,9 +89,11 @@ pub struct Grid { /// If the displayed region isn't at the bottom of the screen, it stays /// stationary while more text is emitted. The scrolling implementation /// updates this offset accordingly. + #[serde(default)] display_offset: usize, /// An limit on how far back it's possible to scroll + #[serde(default)] scroll_limit: usize, /// Selected region diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 1588b006..0f1ba9c5 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -13,15 +13,25 @@ /// done so manually. use std::ops::{Index, IndexMut}; -use index::Line; +use index::{IndexRange, Line}; -#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Storage { inner: Vec, zero: usize, visible_lines: Line, } +impl ::std::cmp::PartialEq for Storage { + fn eq(&self, other: &Self) -> bool { + let mut equal = true; + for i in IndexRange(Line(0) .. self.visible_lines) { + equal = equal && (self[i] == other[i]) + } + equal + } +} + impl Storage { #[inline] pub fn with_capacity(cap: usize, lines: Line) -> Storage { diff --git a/src/grid/tests.rs b/src/grid/tests.rs index 169cefa0..547fbcf9 100644 --- a/src/grid/tests.rs +++ b/src/grid/tests.rs @@ -17,78 +17,23 @@ use super::{Grid, BidirectionalIterator}; use index::{Point, Line, Column}; -#[test] -fn grid_swap_lines_ok() { - let mut grid = Grid::new(Line(10), Column(1), &0); - info!(""); - - // swap test ends - grid[Line(0)][Column(0)] = 1; - grid[Line(9)][Column(0)] = 2; - - assert_eq!(grid[Line(0)][Column(0)], 1); - assert_eq!(grid[Line(9)][Column(0)], 2); - - grid.swap_lines(Line(0), Line(9)); - - assert_eq!(grid[Line(0)][Column(0)], 2); - assert_eq!(grid[Line(9)][Column(0)], 1); - - // swap test mid - grid[Line(4)][Column(0)] = 1; - grid[Line(5)][Column(0)] = 2; - - info!("grid: {:?}", grid); - - assert_eq!(grid[Line(4)][Column(0)], 1); - assert_eq!(grid[Line(5)][Column(0)], 2); - - grid.swap_lines(Line(4), Line(5)); - - info!("grid: {:?}", grid); - - assert_eq!(grid[Line(4)][Column(0)], 2); - assert_eq!(grid[Line(5)][Column(0)], 1); -} - -#[test] -#[should_panic] -fn grid_swap_lines_oob1() { - let mut grid = Grid::new(Line(10), Column(1), &0); - grid.swap_lines(Line(0), Line(10)); -} - -#[test] -#[should_panic] -fn grid_swap_lines_oob2() { - let mut grid = Grid::new(Line(10), Column(1), &0); - grid.swap_lines(Line(10), Line(0)); -} - -#[test] -#[should_panic] -fn grid_swap_lines_oob3() { - let mut grid = Grid::new(Line(10), Column(1), &0); - grid.swap_lines(Line(10), Line(10)); -} - // Scroll up moves lines upwards #[test] fn scroll_up() { info!(""); - let mut grid = Grid::new(Line(10), Column(1), &0); + let mut grid = Grid::new(Line(10), Column(1), 0, 0); for i in 0..10 { grid[Line(i)][Column(0)] = i; } info!("grid: {:?}", grid); - grid.scroll_up(Line(0)..Line(10), Line(2)); + grid.scroll_up(&(Line(0)..Line(10)), Line(2)); info!("grid: {:?}", grid); - let mut other = Grid::new(Line(10), Column(1), &9); + let mut other = Grid::new(Line(10), Column(1), 0, 9); other[Line(0)][Column(0)] = 2; other[Line(1)][Column(0)] = 3; @@ -111,18 +56,18 @@ fn scroll_up() { fn scroll_down() { info!(""); - let mut grid = Grid::new(Line(10), Column(1), &0); + let mut grid = Grid::new(Line(10), Column(1), 0, 0); for i in 0..10 { grid[Line(i)][Column(0)] = i; } info!("grid: {:?}", grid); - grid.scroll_down(Line(0)..Line(10), Line(2)); + grid.scroll_down(&(Line(0)..Line(10)), Line(2)); info!("grid: {:?}", grid); - let mut other = Grid::new(Line(10), Column(1), &9); + let mut other = Grid::new(Line(10), Column(1), 0, 9); other[Line(0)][Column(0)] = 8; other[Line(1)][Column(0)] = 9; @@ -145,7 +90,7 @@ fn scroll_down() { fn test_iter() { info!(""); - let mut grid = Grid::new(Line(5), Column(5), &0); + let mut grid = Grid::new(Line(5), Column(5), 0, 0); for i in 0..5 { for j in 0..5 { grid[Line(i)][Column(j)] = i*5 + j; @@ -155,14 +100,14 @@ fn test_iter() { info!("grid: {:?}", grid); let mut iter = grid.iter_from(Point { - line: Line(0), + line: 4, col: Column(0), }); assert_eq!(None, iter.prev()); assert_eq!(Some(&1), iter.next()); assert_eq!(Column(1), iter.cur.col); - assert_eq!(Line(0), iter.cur.line); + assert_eq!(4, iter.cur.line); assert_eq!(Some(&2), iter.next()); assert_eq!(Some(&3), iter.next()); @@ -171,16 +116,16 @@ fn test_iter() { // test linewrapping assert_eq!(Some(&5), iter.next()); assert_eq!(Column(0), iter.cur.col); - assert_eq!(Line(1), iter.cur.line); + assert_eq!(3, iter.cur.line); assert_eq!(Some(&4), iter.prev()); assert_eq!(Column(4), iter.cur.col); - assert_eq!(Line(0), iter.cur.line); + assert_eq!(4, iter.cur.line); // test that iter ends at end of grid let mut final_iter = grid.iter_from(Point { - line: Line(4), + line: 0, col: Column(4), }); assert_eq!(None, final_iter.next()); diff --git a/src/term/mod.rs b/src/term/mod.rs index 198f8cea..7a50810e 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -2004,7 +2004,7 @@ mod tests { padding_y: 0.0, }; let mut term = Term::new(&Default::default(), size); - let mut grid: Grid = Grid::new(Line(3), Column(5), &Cell::default()); + let mut grid: Grid = Grid::new(Line(3), Column(5), 0, Cell::default()); for i in 0..5 { for j in 0..2 { grid[Line(j)][Column(i)].c = 'a'; @@ -2021,18 +2021,18 @@ mod tests { mem::swap(&mut term.semantic_escape_chars, &mut escape_chars); { - let selection = Selection::semantic(Point { line: Line(0), col: Column(1) }, &term); - assert_eq!(term.string_from_selection(&selection.to_span(&term).unwrap()), "aa"); + *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(1) }, &term)); + assert_eq!(term.selection_to_string(), Some(String::from("aa"))); } { - let selection = Selection::semantic(Point { line: Line(0), col: Column(4) }, &term); - assert_eq!(term.string_from_selection(&selection.to_span(&term).unwrap()), "aaa"); + *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(4) }, &term)); + assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); } { - let selection = Selection::semantic(Point { line: Line(1), col: Column(1) }, &term); - assert_eq!(term.string_from_selection(&selection.to_span(&term).unwrap()), "aaa"); + *term.selection_mut() = Some(Selection::semantic(Point { line: 1, col: Column(1) }, &term)); + assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); } } @@ -2047,7 +2047,7 @@ mod tests { padding_y: 0.0, }; let mut term = Term::new(&Default::default(), size); - let mut grid: Grid = Grid::new(Line(1), Column(5), &Cell::default()); + let mut grid: Grid = Grid::new(Line(1), Column(5), 0, Cell::default()); for i in 0..5 { grid[Line(0)][Column(i)].c = 'a'; } @@ -2057,10 +2057,8 @@ mod tests { mem::swap(&mut term.grid, &mut grid); - let selection = Selection::lines(Point { line: Line(0), col: Column(3) }); - if let Some(span) = selection.to_span(&term) { - assert_eq!(term.string_from_selection(&span), "\"aa\"a\n"); - } + *term.selection_mut() = Some(Selection::lines(Point { line: 0, col: Column(3) })); + assert_eq!(term.selection_to_string(), Some(String::from("\"aa\"a\n"))); } /// Check that the grid can be serialized back and forth losslessly @@ -2071,7 +2069,7 @@ mod tests { fn grid_serde() { let template = Cell::default(); - let grid = Grid::new(Line(24), Column(80), &template); + let grid: Grid = Grid::new(Line(24), Column(80), 0, template); let serialized = serde_json::to_string(&grid).expect("ser"); let deserialized = serde_json::from_str::>(&serialized) .expect("de"); -- cgit From 54314380f07f25b22591775931994196c30240ed Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 9 Mar 2018 14:00:36 -0800 Subject: Fix grid scroll tests --- src/grid/tests.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/grid/tests.rs b/src/grid/tests.rs index 547fbcf9..107e7103 100644 --- a/src/grid/tests.rs +++ b/src/grid/tests.rs @@ -43,11 +43,13 @@ fn scroll_up() { other[Line(5)][Column(0)] = 7; other[Line(6)][Column(0)] = 8; other[Line(7)][Column(0)] = 9; - other[Line(8)][Column(0)] = 0; - other[Line(9)][Column(0)] = 1; + other[Line(8)][Column(0)] = 0; // should be cleared on scroll; was 0 + other[Line(9)][Column(0)] = 0; // should be cleared on scroll; was 1 for i in 0..10 { - assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)]); + assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)], + "index={}; actual: {:?}, expected: {:?}", + Line(i), grid[Line(i)][Column(0)], other[Line(i)][Column(0)]); } } @@ -69,8 +71,8 @@ fn scroll_down() { let mut other = Grid::new(Line(10), Column(1), 0, 9); - other[Line(0)][Column(0)] = 8; - other[Line(1)][Column(0)] = 9; + other[Line(0)][Column(0)] = 0; // Should be cleared upon recycle; was 8 + other[Line(1)][Column(0)] = 0; // Should be cleared upon recycle; was 9 other[Line(2)][Column(0)] = 0; other[Line(3)][Column(0)] = 1; other[Line(4)][Column(0)] = 2; @@ -81,7 +83,9 @@ fn scroll_down() { other[Line(9)][Column(0)] = 7; for i in 0..10 { - assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)]); + assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)], + "index={}; actual: {:?}, expected: {:?}", + Line(i), grid[Line(i)][Column(0)], other[Line(i)][Column(0)]); } } -- cgit From f66e3e457bdda808cc3f994a02fd6f7ce5ba381e Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 25 Mar 2018 21:09:18 +0200 Subject: Fix selection tests The latest selection changes broke a few tests, these have been corrected. Two of these tests were broken because they assumed different span types, the test have been changed here because the result was correct. One test did actually catch a bug where selection of two cells from right to left would incorrectly mark the cells as selected even though they should not have been, this has been fixed in the `simple_span` method. --- src/input.rs | 5 +++++ src/selection.rs | 43 ++++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/input.rs b/src/input.rs index 047c81aa..90384198 100644 --- a/src/input.rs +++ b/src/input.rs @@ -664,6 +664,7 @@ mod tests { use config::{self, Config, ClickHandler}; use index::{Point, Side}; use selection::Selection; + use grid::Scroll; use super::{Action, Binding, Processor}; @@ -717,6 +718,10 @@ mod tests { self.last_action = MultiClick::TripleClick; } + fn scroll(&mut self, scroll: Scroll) { + self.terminal.scroll_display(scroll); + } + fn mouse_coords(&self) -> Option { self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize) } diff --git a/src/selection.rs b/src/selection.rs index 20b1d601..188df348 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -251,13 +251,6 @@ impl Selection { let end_side = region.end.side; let cols = grid.dimensions().col; - // No selection for single cell with identical sides or two cell with right+left sides - if (start == end && start_side == end_side) - || (start_side == Side::Right && end_side == Side::Left && end.col == start.col + 1) - { - return None; - } - // Make sure front is always the "bottom" and tail is always the "top" let (mut front, mut tail, front_side, tail_side) = if start.line > end.line || start.line == end.line && start.col <= end.col { @@ -268,6 +261,14 @@ impl Selection { (start, end, start_side, end_side) }; + // No selection for single cell with identical sides or two cell with right+left sides + if (front == tail && front_side == tail_side) + || (tail_side == Side::Right && front_side == Side::Left && front.line == tail.line + && front.col == tail.col + 1) + { + return None; + } + // Remove last cell if selection ends to the left of a cell if front_side == Side::Left && start != end { if front.col != Column(0) { @@ -475,10 +476,10 @@ mod test { /// /// 1. [ ][ ][ ][ ][ ] /// [ ][ ][ ][ ][ ] - /// 2. [ ][ ][ ][ ][ ] - /// [ ][ B][ ][ ][ ] - /// 3. [ ][ E][XX][XX][XX] - /// [XX][XB][ ][ ][ ] + /// 2. [ ][ B][ ][ ][ ] + /// [ ][ ][ ][ ][ ] + /// 3. [ ][ B][XX][XX][XX] + /// [XX][XE][ ][ ][ ] #[test] fn across_adjacent_lines_upward_final_cell_exclusive() { let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right); @@ -487,8 +488,8 @@ mod test { assert_eq!(selection.to_span(&Dimensions::new(2, 5)).unwrap(), Span { cols: Column(5), front: Point::new(0, Column(1)), - tail: Point::new(1, Column(1)), - ty: SpanType::ExcludeFront + tail: Point::new(1, Column(2)), + ty: SpanType::Inclusive, }); } @@ -497,12 +498,12 @@ mod test { /// /// 1. [ ][ ][ ][ ][ ] /// [ ][ ][ ][ ][ ] - /// 2. [ ][ B][ ][ ][ ] - /// [ ][ ][ ][ ][ ] - /// 3. [ ][ B][XX][XX][XX] - /// [XX][XE][ ][ ][ ] - /// 4. [ ][ B][XX][XX][XX] - /// [XE][ ][ ][ ][ ] + /// 2. [ ][ ][ ][ ][ ] + /// [ ][ B][ ][ ][ ] + /// 3. [ ][ E][XX][XX][XX] + /// [XX][XB][ ][ ][ ] + /// 4. [ E][XX][XX][XX][XX] + /// [XX][XB][ ][ ][ ] #[test] fn selection_bigger_then_smaller() { let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Right); @@ -512,8 +513,8 @@ mod test { assert_eq!(selection.to_span(&Dimensions::new(2, 5)).unwrap(), Span { cols: Column(5), front: Point::new(0, Column(1)), - tail: Point::new(1, Column(0)), - ty: SpanType::ExcludeFront + tail: Point::new(1, Column(1)), + ty: SpanType::Inclusive, }); } } -- cgit From b19045da66899999856c6b2cc6707b60c607660a Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Mon, 2 Apr 2018 08:44:54 -0700 Subject: Fix BCE ref tests BCE was broken in attempt to optimize row clearing. The fix is to revert to passing in the current cursor state when clearing. --- src/grid/mod.rs | 65 +++++++++++++++++++++++++++-------------------------- src/grid/row.rs | 6 +++-- src/grid/storage.rs | 11 --------- src/grid/tests.rs | 4 ++-- src/selection.rs | 13 ++++------- src/term/mod.rs | 12 +++++----- 6 files changed, 49 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 3495be1d..faf67772 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -73,17 +73,6 @@ pub struct Grid { /// Invariant: lines is equivalent to raw.len() lines: index::Line, - /// Template row. - /// - /// This is used to quickly populate new lines and clear recycled lines - /// during scroll wrapping. - #[serde(skip)] - template_row: Row, - - /// Template cell for populating template_row - #[serde(skip)] - template: T, - /// Offset of displayed area /// /// If the displayed region isn't at the bottom of the screen, it stays @@ -126,7 +115,6 @@ pub enum Scroll { impl Grid { pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid { let mut raw = Storage::with_capacity(*lines + scrollback, lines); - let template_row = Row::new(cols, &template); // Allocate all lines in the buffer, including scrollback history // @@ -134,15 +122,13 @@ impl Grid { // delays startup. A nice solution might be having `Row` delay // allocation until it's actually used. for _ in 0..raw.capacity() { - raw.push(template_row.clone()); + raw.push(Row::new(cols, &template)); } Grid { raw, cols, lines, - template_row, - template, display_offset: 0, scroll_limit: 0, selection: None, @@ -200,20 +186,25 @@ impl Grid { } } - pub fn resize(&mut self, lines: index::Line, cols: index::Column) { + pub fn resize( + &mut self, + lines: index::Line, + cols: index::Column, + template: &T, + ) { // Check that there's actually work to do and return early if not if lines == self.lines && cols == self.cols { return; } match self.lines.cmp(&lines) { - Ordering::Less => self.grow_lines(lines), + Ordering::Less => self.grow_lines(lines, template), Ordering::Greater => self.shrink_lines(lines), Ordering::Equal => (), } match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(cols), + Ordering::Less => self.grow_cols(cols, template), Ordering::Greater => self.shrink_cols(cols), Ordering::Equal => (), } @@ -237,7 +228,11 @@ impl Grid { /// Alacritty takes a different approach. Rather than trying to move with /// the scrollback, we simply pull additional lines from the back of the /// buffer in order to populate the new area. - fn grow_lines(&mut self, new_line_count: index::Line) { + fn grow_lines( + &mut self, + new_line_count: index::Line, + template: &T, + ) { let previous_scroll_limit = self.scroll_limit; let lines_added = new_line_count - self.lines; @@ -246,21 +241,18 @@ impl Grid { self.lines = new_line_count; // Add new lines to bottom - self.scroll_up(&(Line(0)..new_line_count), lines_added); + self.scroll_up(&(Line(0)..new_line_count), lines_added, template); self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added); } - fn grow_cols(&mut self, cols: index::Column) { + fn grow_cols(&mut self, cols: index::Column, template: &T) { for row in self.raw.iter_mut() { - row.grow(cols, &self.template); + row.grow(cols, template); } // Update self cols self.cols = cols; - - // Also update template_row to be the correct length - self.template_row.grow(cols, &self.template); } /// Remove lines from the visible area @@ -304,7 +296,12 @@ impl Grid { } #[inline] - pub fn scroll_down(&mut self, region: &Range, positions: index::Line) { + pub fn scroll_down( + &mut self, + region: &Range, + positions: index::Line, + template: &T, + ) { // Whether or not there is a scrolling region active, as long as it // starts at the top, we can do a full rotation which just involves // changing the start index. @@ -328,7 +325,7 @@ impl Grid { // Finally, reset recycled lines for i in IndexRange(Line(0)..positions) { - self.raw[i].reset(&self.template_row); + self.raw[i].reset(&template); } } else { // Subregion rotation @@ -337,7 +334,7 @@ impl Grid { } for line in IndexRange(region.start .. (region.start + positions)) { - self.raw[line].reset(&self.template_row); + self.raw[line].reset(&template); } } } @@ -346,7 +343,12 @@ impl Grid { /// /// This is the performance-sensitive part of scrolling. #[inline] - pub fn scroll_up(&mut self, region: &Range, positions: index::Line) { + pub fn scroll_up( + &mut self, + region: &Range, + positions: index::Line, + template: &T + ) { if region.start == Line(0) { // Update display offset when not pinned to active area if self.display_offset != 0 { @@ -372,7 +374,7 @@ impl Grid { // // Recycled lines are just above the end of the scrolling region. for i in 0..*positions { - self.raw[region.end - i - 1].reset(&self.template_row); + self.raw[region.end - i - 1].reset(&template); } } else { // Subregion rotation @@ -382,7 +384,7 @@ impl Grid { // Clear reused lines for line in IndexRange((region.end - positions) .. region.end) { - self.raw[line].reset(&self.template_row); + self.raw[line].reset(&template); } } } @@ -432,7 +434,6 @@ impl Grid { } self.cols = cols; - self.template_row.shrink(cols); } } diff --git a/src/grid/row.rs b/src/grid/row.rs index f6b2a20e..f7e4a98a 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -37,8 +37,10 @@ impl Row { /// Resets contents to the contents of `other` #[inline] - pub fn reset(&mut self, other: &Row) { - self.copy_from_slice(&**other); + pub fn reset(&mut self, other: &T) { + for item in &mut self.0 { + *item = *other; + } } } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 0f1ba9c5..b620b9c0 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -56,11 +56,6 @@ impl Storage { self.inner.push(item) } - #[inline] - pub fn pop(&mut self) -> Option { - self.inner.pop() - } - #[inline] pub fn len(&self) -> usize { self.inner.len() @@ -76,12 +71,6 @@ impl Storage { ((self.len() + self.zero + *self.visible_lines) - *requested) % self.len() } - pub fn swap(&mut self, a: usize, b: usize) { - let a = self.compute_index(a); - let b = self.compute_index(b); - self.inner.swap(a, b); - } - pub fn swap_lines(&mut self, a: Line, b: Line) { let a = self.compute_line_index(a); let b = self.compute_line_index(b); diff --git a/src/grid/tests.rs b/src/grid/tests.rs index 107e7103..3e229fb6 100644 --- a/src/grid/tests.rs +++ b/src/grid/tests.rs @@ -29,7 +29,7 @@ fn scroll_up() { info!("grid: {:?}", grid); - grid.scroll_up(&(Line(0)..Line(10)), Line(2)); + grid.scroll_up(&(Line(0)..Line(10)), Line(2), &0); info!("grid: {:?}", grid); @@ -65,7 +65,7 @@ fn scroll_down() { info!("grid: {:?}", grid); - grid.scroll_down(&(Line(0)..Line(10)), Line(2)); + grid.scroll_down(&(Line(0)..Line(10)), Line(2), &0); info!("grid: {:?}", grid); diff --git a/src/selection.rs b/src/selection.rs index 188df348..6f910bce 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -181,9 +181,6 @@ impl Selection { ) -> Option where G: SemanticSearch + Dimensions { - let mut start = initial_expansion.start; - let mut end = initial_expansion.end; - // Normalize ordering of selected cells let (front, tail) = if region.start < region.end { (region.start, region.end) @@ -191,13 +188,11 @@ impl Selection { (region.end, region.start) }; - if front < tail && front.line == tail.line { - start = grid.semantic_search_left(front); - end = grid.semantic_search_right(tail); + let (mut start, mut end) = if front < tail && front.line == tail.line { + (grid.semantic_search_left(front), grid.semantic_search_right(tail)) } else { - start = grid.semantic_search_right(front); - end = grid.semantic_search_left(tail); - } + (grid.semantic_search_right(front), grid.semantic_search_left(tail)) + }; if start > end { ::std::mem::swap(&mut start, &mut end); diff --git a/src/term/mod.rs b/src/term/mod.rs index 7a50810e..f66753d9 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1119,19 +1119,19 @@ impl Term { // Scroll up to keep cursor in terminal if self.cursor.point.line >= num_lines { let lines = self.cursor.point.line - num_lines + 1; - self.grid.scroll_up(&(Line(0)..old_lines), lines); + self.grid.scroll_up(&(Line(0)..old_lines), lines, &self.cursor.template); } // Scroll up alt grid as well if self.cursor_save_alt.point.line >= num_lines { let lines = self.cursor_save_alt.point.line - num_lines + 1; - self.alt_grid.scroll_up(&(Line(0)..old_lines), lines); + self.alt_grid.scroll_up(&(Line(0)..old_lines), lines, &self.cursor_save_alt.template); } debug!("num_cols, num_lines = {}, {}", num_cols, num_lines); // Resize grids to new size - self.grid.resize(num_lines, num_cols); - self.alt_grid.resize(num_lines, num_cols); + self.grid.resize(num_lines, num_cols, &self.cursor.template); + self.alt_grid.resize(num_lines, num_cols, &self.cursor_save_alt.template); // Reset scrolling region to new size self.scroll_region = Line(0)..self.grid.num_lines(); @@ -1197,7 +1197,7 @@ impl Term { let lines = min(lines, self.scroll_region.end - self.scroll_region.start); // Scroll between origin and bottom - self.grid.scroll_down(&(origin..self.scroll_region.end), lines); + self.grid.scroll_down(&(origin..self.scroll_region.end), lines, &self.cursor.template); } /// Scroll screen up @@ -1210,7 +1210,7 @@ impl Term { let lines = min(lines, self.scroll_region.end - self.scroll_region.start); // Scroll from origin to bottom less number of lines - self.grid.scroll_up(&(origin..self.scroll_region.end), lines); + self.grid.scroll_up(&(origin..self.scroll_region.end), lines, &self.cursor.template); } fn deccolm(&mut self) { -- cgit From 334d50e98c5f579e6f5b240ef2be971472ee27fb Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 24 Mar 2018 14:42:05 +0100 Subject: Fix cursor not showing in first column There was a bug in the display iterator where the first column was never reached after the top line because it was instantly incremented to 1 after it was reset when iterator column reached the end of the terminal width. This has been fixed by making sure that the column is never incremented when the column is reset due to a change in terminal line. This fixes #1198. --- src/grid/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index faf67772..0564cdf5 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -748,13 +748,13 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { #[inline] fn next(&mut self) -> Option { // Make sure indices are valid. Return None if we've reached the end. - if self.col == self.grid.num_cols() { + let next_line = self.col == self.grid.num_cols(); + if next_line { if self.offset == self.limit { return None; } self.col = Column(0); - self.offset -= 1; self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); } @@ -766,7 +766,9 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { column: self.col }); - self.col += 1; + if !next_line { + self.col += 1; + } item } } -- cgit From 7f2fe5acb288d3533af5b95ac50162ed60c14042 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Mon, 2 Apr 2018 21:12:04 +0200 Subject: Add documentation to explain the process --- src/grid/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 0564cdf5..15ddf30e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -747,25 +747,28 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { #[inline] fn next(&mut self) -> Option { - // Make sure indices are valid. Return None if we've reached the end. + // Check if the end of the line was reached let next_line = self.col == self.grid.num_cols(); if next_line { + // Return `None` if we've reached the end of the last line if self.offset == self.limit { return None; } + // Switch to the next line self.col = Column(0); self.offset -= 1; self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); } - // Return the next item. + // Return the next item let item = Some(Indexed { inner: self.grid.raw[self.offset][self.col], line: self.line, column: self.col }); + // Only increment column if the line hasn't changed if !next_line { self.col += 1; } -- cgit From e615d112fb9fffe46121bd9068498b07c4733fa8 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 7 Apr 2018 01:50:14 +0200 Subject: Fix scrollback history size 0 bug There was an issue where alacritty would panic whenever the scrollback history size is set to 0, this fixes that issue. The panic was caused by a substraction with unsigned integers which was underflowing, this has been fixed to use `saturating_sub`. After that was fixed there was still a bug where scrollback would not behave correctly because the number of lines in the grid was decided at startup. This has been adapted so whenever the size of the terminal changes, the scrollback history and grid adapts to make sure the number of lines in the terminal is always the number of visible lines plus the amount of scrollback lines configured in the config file. This fixes #1150. --- src/grid/mod.rs | 17 +++++++++++------ src/grid/storage.rs | 11 +++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 15ddf30e..e42e5cfa 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -211,7 +211,10 @@ impl Grid { } fn increase_scroll_limit(&mut self, count: usize) { - self.scroll_limit = min(self.scroll_limit + count, self.raw.len() - *self.lines); + self.scroll_limit = min( + self.scroll_limit + count, + self.raw.len().saturating_sub(*self.lines), + ); } fn decrease_scroll_limit(&mut self, count: usize) { @@ -240,6 +243,13 @@ impl Grid { self.raw.set_visible_lines(new_line_count); self.lines = new_line_count; + // Fill up the history with empty lines + if self.raw.len() < self.raw.capacity() { + for _ in self.raw.len()..self.raw.capacity() { + self.raw.push(Row::new(self.cols, &template)); + } + } + // Add new lines to bottom self.scroll_up(&(Line(0)..new_line_count), lines_added, template); @@ -263,11 +273,6 @@ impl Grid { /// /// Alacritty takes the same approach. fn shrink_lines(&mut self, target: index::Line) { - // TODO handle disabled scrollback - // while index::Line(self.raw.len()) != lines { - // self.raw.pop(); - // } - let prev = self.lines; self.selection = None; diff --git a/src/grid/storage.rs b/src/grid/storage.rs index b620b9c0..cc32d6d1 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -48,6 +48,17 @@ impl Storage { } pub fn set_visible_lines(&mut self, next: Line) { + // Change capacity to fit scrollback + screen size + if next > self.visible_lines + 1 { + self.inner.reserve_exact((next - (self.visible_lines + 1)).0); + } else if next < self.visible_lines + 1 { + let shrinkage = (self.visible_lines + 1 - next).0; + let new_size = self.inner.capacity() - shrinkage; + self.inner.truncate(new_size); + self.inner.shrink_to_fit(); + } + + // Update visible lines self.visible_lines = next - 1; } -- cgit From fe749cf0adee1be93fd3473f6d3533fbe892e3dc Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 22 Apr 2018 14:39:55 +0200 Subject: Fix order of lines after resize There was an issue where the lines would be messed up when the terminal was resized, this was because lines were just added/removed at the end of the buffer instead of the actual end of the terminal (since the end of the terminal might be in the middle of the buffer). This has been fixed by relying on `self.zero` to determine the position of the start of the terminal and then calculating where lines have to be inserted/removed. Some tests have also been added with documentation that should make it a little easier to understand how the process works and how the raw buffer is layed out. This should all work no matter how big the scrollback history and even when the currenty viewport is not at the bottom of the terminal output. --- src/grid/mod.rs | 11 +-- src/grid/storage.rs | 214 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 207 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index e42e5cfa..bc288e3b 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -240,16 +240,9 @@ impl Grid { let lines_added = new_line_count - self.lines; // Need to "resize" before updating buffer - self.raw.set_visible_lines(new_line_count); + self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, template)); self.lines = new_line_count; - // Fill up the history with empty lines - if self.raw.len() < self.raw.capacity() { - for _ in self.raw.len()..self.raw.capacity() { - self.raw.push(Row::new(self.cols, &template)); - } - } - // Add new lines to bottom self.scroll_up(&(Line(0)..new_line_count), lines_added, template); @@ -277,7 +270,7 @@ impl Grid { self.selection = None; self.raw.rotate(*prev as isize - *target as isize); - self.raw.set_visible_lines(target); + self.raw.shrink_visible_lines(target); self.lines = target; } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index cc32d6d1..f6fcc8f3 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -47,15 +47,49 @@ impl Storage { self.inner.capacity() } - pub fn set_visible_lines(&mut self, next: Line) { - // Change capacity to fit scrollback + screen size - if next > self.visible_lines + 1 { - self.inner.reserve_exact((next - (self.visible_lines + 1)).0); - } else if next < self.visible_lines + 1 { - let shrinkage = (self.visible_lines + 1 - next).0; - let new_size = self.inner.capacity() - shrinkage; - self.inner.truncate(new_size); - self.inner.shrink_to_fit(); + /// Increase the number of lines in the buffer + pub fn grow_visible_lines(&mut self, next: Line, template_row: T) + where + T: Clone, + { + // Calculate insert position (before the first line) + let offset = self.zero % self.inner.len(); + + // Insert new template row for every line grown + let lines_to_grow = (next - (self.visible_lines + 1)).0; + for _ in 0..lines_to_grow { + self.inner.insert(offset, template_row.clone()); + } + + // Set zero to old zero + lines grown + self.zero = offset + lines_to_grow; + + // Update visible lines + self.visible_lines = next - 1; + } + + /// Decrease the number of lines in the buffer + pub fn shrink_visible_lines(&mut self, next: Line) { + // Calculate shrinkage and last line of buffer + let shrinkage = (self.visible_lines + 1 - next).0; + let offset = (self.zero + self.inner.len() - 1) % self.inner.len(); + + // Generate range of lines that have to be deleted before the zero line + let start = offset.saturating_sub(shrinkage - 1); + let shrink_before = start..=offset; + + // Generate range of lines that have to be deleted after the zero line + let shrink_after = (self.inner.len() + offset + 1 - shrinkage)..self.inner.len(); + + // Delete all lines in reverse order + for i in shrink_before.chain(shrink_after).rev() { + self.inner.remove(i); + } + + // Check if zero has moved (not the first line in the buffer) + if self.zero % (self.inner.len() + shrinkage) != 0 { + // Set zero to the first deleted line in the buffer + self.zero = start; } // Update visible lines @@ -159,3 +193,165 @@ impl<'a, T: 'a> Iterator for IterMut<'a, T> { } } } + +/// Grow the buffer one line at the end of the buffer +/// +/// Before: +/// 0: 0 <- Zero +/// 1: 1 +/// 2: - +/// After: +/// 0: - +/// 1: 0 <- Zero +/// 2: 1 +/// 3: - +#[test] +fn grow_after_zero() { + // Setup storage area + let mut storage = Storage { + inner: vec!["0", "1", "-"], + zero: 0, + visible_lines: Line(2), + }; + + // Grow buffer + storage.grow_visible_lines(Line(4), "-"); + + // Make sure the result is correct + let expected = Storage { + inner: vec!["-", "0", "1", "-"], + zero: 1, + visible_lines: Line(0), + }; + assert_eq!(storage.inner, expected.inner); + assert_eq!(storage.zero, expected.zero); +} + +/// Grow the buffer one line at the start of the buffer +/// +/// Before: +/// 0: - +/// 1: 0 <- Zero +/// 2: 1 +/// After: +/// 0: - +/// 1: - +/// 2: 0 <- Zero +/// 3: 1 +#[test] +fn grow_before_zero() { + // Setup storage area + let mut storage = Storage { + inner: vec!["-", "0", "1"], + zero: 1, + visible_lines: Line(2), + }; + + // Grow buffer + storage.grow_visible_lines(Line(4), "-"); + + // Make sure the result is correct + let expected = Storage { + inner: vec!["-", "-", "0", "1"], + zero: 2, + visible_lines: Line(0), + }; + assert_eq!(storage.inner, expected.inner); + assert_eq!(storage.zero, expected.zero); +} + +/// Shrink the buffer one line at the start of the buffer +/// +/// Before: +/// 0: 2 +/// 1: 0 <- Zero +/// 2: 1 +/// After: +/// 0: 0 <- Zero +/// 1: 1 +#[test] +fn shrink_before_zero() { + // Setup storage area + let mut storage = Storage { + inner: vec!["2", "0", "1"], + zero: 1, + visible_lines: Line(2), + }; + + // Shrink buffer + storage.shrink_visible_lines(Line(2)); + + // Make sure the result is correct + let expected = Storage { + inner: vec!["0", "1"], + zero: 0, + visible_lines: Line(0), + }; + assert_eq!(storage.inner, expected.inner); + assert_eq!(storage.zero, expected.zero); +} + +/// Shrink the buffer one line at the end of the buffer +/// +/// Before: +/// 0: 0 <- Zero +/// 1: 1 +/// 2: 2 +/// After: +/// 0: 0 <- Zero +/// 1: 1 +#[test] +fn shrink_after_zero() { + // Setup storage area + let mut storage = Storage { + inner: vec!["0", "1", "2"], + zero: 0, + visible_lines: Line(2), + }; + + // Shrink buffer + storage.shrink_visible_lines(Line(2)); + + // Make sure the result is correct + let expected = Storage { + inner: vec!["0", "1"], + zero: 0, + visible_lines: Line(0), + }; + assert_eq!(storage.inner, expected.inner); + assert_eq!(storage.zero, expected.zero); +} + +/// Shrink the buffer at the start and end of the buffer +/// +/// Before: +/// 0: 4 +/// 1: 5 +/// 2: 0 <- Zero +/// 3: 1 +/// 4: 2 +/// 5: 3 +/// After: +/// 0: 0 <- Zero +/// 1: 1 +#[test] +fn shrink_before_and_after_zero() { + // Setup storage area + let mut storage = Storage { + inner: vec!["4", "5", "0", "1", "2", "3"], + zero: 2, + visible_lines: Line(5), + }; + + // Shrink buffer + storage.shrink_visible_lines(Line(2)); + + // Make sure the result is correct + let expected = Storage { + inner: vec!["0", "1"], + zero: 0, + visible_lines: Line(0), + }; + assert_eq!(storage.inner, expected.inner); + assert_eq!(storage.zero, expected.zero); +} -- cgit From 0d900cdf69d0d53b52c44b302907380ad4d3735d Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 24 Apr 2018 09:39:46 -0700 Subject: Compile on stable --- src/grid/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/grid/storage.rs b/src/grid/storage.rs index f6fcc8f3..fb66b0c3 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -76,7 +76,7 @@ impl Storage { // Generate range of lines that have to be deleted before the zero line let start = offset.saturating_sub(shrinkage - 1); - let shrink_before = start..=offset; + let shrink_before = start..(offset + 1); // Generate range of lines that have to be deleted after the zero line let shrink_after = (self.inner.len() + offset + 1 - shrinkage)..self.inner.len(); -- cgit From 4df09128ced0db31e26f3d26c67ffc658475bffb Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 21 Apr 2018 13:37:16 +0200 Subject: Revert "Fix cursor not showing in first column" This reverts commit 54b21b66ecc6f8f149d1425567e0e3d766a3ac54. --- src/grid/mod.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index bc288e3b..c05584c4 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -745,16 +745,15 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { #[inline] fn next(&mut self) -> Option { - // Check if the end of the line was reached - let next_line = self.col == self.grid.num_cols(); - if next_line { - // Return `None` if we've reached the end of the last line + // Make sure indices are valid. Return None if we've reached the end. + if self.col == self.grid.num_cols() { if self.offset == self.limit { return None; } // Switch to the next line self.col = Column(0); + self.offset -= 1; self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); } @@ -766,10 +765,7 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { column: self.col }); - // Only increment column if the line hasn't changed - if !next_line { - self.col += 1; - } + self.col += 1; item } } -- cgit From bfa926555145f9f578430b87bc1c1b72ef80589f Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 21 Apr 2018 13:45:43 +0200 Subject: Fix bright characters in first column This bug was introduced by the commit which fixed the invisible cursor in the first column (54b21b66ecc6f8f149d1425567e0e3d766a3ac54). To resolve this the alternative implementation by @jwilm has been applied which seems to work out. This fixes #1259. --- src/grid/mod.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index c05584c4..313973a3 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -745,27 +745,29 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { #[inline] fn next(&mut self) -> Option { - // Make sure indices are valid. Return None if we've reached the end. - if self.col == self.grid.num_cols() { - if self.offset == self.limit { - return None; - } - - // Switch to the next line - self.col = Column(0); - - self.offset -= 1; - self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); + // Return None if we've reached the end. + if self.offset == self.limit && self.grid.num_cols() == self.col { + return None; } - // Return the next item + // Get the next item. let item = Some(Indexed { inner: self.grid.raw[self.offset][self.col], line: self.line, column: self.col }); + // Update line/col to point to next item self.col += 1; + if self.col == self.grid.num_cols() { + if self.offset != self.limit { + self.offset -= 1; + + self.col = Column(0); + self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); + } + } + item } } -- cgit From d8bda60c3d8f906f1018012f4a55c7b894afb4b7 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 28 Apr 2018 14:14:45 +0000 Subject: Reset grid when running `reset` In the current scrollback PR the `reset` command does not affect the scrollback history. To make sure the terminal is properly reset, it should clear the scrollback history. To make resetting efficient, instead of resetting the history, the scrollback history is hidden by setting `grid.scroll_limit` to `0`. This will not clear the history but instead just make it inaccessible, which should have the same effect. The visible area is reset by the shell itself, so in combination this clears the complete terminal grid from a user perspective. This fixes #1242. --- src/grid/mod.rs | 4 ++++ src/term/mod.rs | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 313973a3..ac54f580 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -403,6 +403,10 @@ impl Grid { self.cols } + pub fn reset(&mut self) { + self.scroll_limit = 0; + } + pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { grid: self, diff --git a/src/term/mod.rs b/src/term/mod.rs index f66753d9..fa204a55 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -838,13 +838,11 @@ impl Term { } pub fn new(config: &Config, size: SizeInfo) -> Term { - let template = Cell::default(); - let num_cols = size.cols(); let num_lines = size.lines(); let history_size = config.scrolling().history as usize; - let grid = Grid::new(num_lines, num_cols, history_size, template); + let grid = Grid::new(num_lines, num_cols, history_size, Cell::default()); let tabspaces = config.tabspaces(); let tabs = IndexRange::from(Column(0)..grid.num_cols()) @@ -1820,6 +1818,7 @@ impl ansi::Handler for Term { self.colors = self.original_colors; self.color_modified = [false; color::COUNT]; self.cursor_style = None; + self.grid.reset(); } #[inline] -- cgit From 2234234ca9a2ab0d7eccd46893cbe6799b051aba Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 14 Apr 2018 17:21:48 +0200 Subject: Enable history comparison in ref-tests Previously ref-tests just ignored the scrollback history to keep the old tests working, this would lead to new tests which rely on scrollback history to succeeed even though they should not. This has been fixed and it is now possible to create ref-tests with and without scrollback history. When available the scrollback history is compared, but the old tests still work without having to adjust them. This fixes #1244. --- src/config.rs | 5 +++++ src/grid/mod.rs | 21 ++++++++++++++++++++- src/grid/storage.rs | 10 ---------- 3 files changed, 25 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 0eab1bfb..b9c943ee 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1431,6 +1431,11 @@ impl Config { self.scrolling } + // Update the history size, used in ref tests + pub fn set_history(&mut self, history: u32) { + self.scrolling.history = history; + } + pub fn load_from>(path: P) -> Result { let path = path.into(); let raw = Config::read_file(path.as_path())?; diff --git a/src/grid/mod.rs b/src/grid/mod.rs index ac54f580..b6cff604 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -52,9 +52,23 @@ impl Deref for Indexed { impl ::std::cmp::PartialEq for Grid { fn eq(&self, other: &Self) -> bool { + // Compare raw grid content + // TODO: This is pretty inefficient but only used in tests + let mut raw_match = true; + for i in 0..(self.lines.0 + self.history_size) { + for j in 0..self.cols.0 { + if self[i][Column(j)] != other[i][Column(j)] { + raw_match = false; + break; + } + } + } + + // Compare struct fields and check result of grid comparison self.cols.eq(&other.cols) && self.lines.eq(&other.lines) && - self.raw.eq(&other.raw) + self.history_size.eq(&other.history_size) && + raw_match } } @@ -88,6 +102,10 @@ pub struct Grid { /// Selected region #[serde(skip)] pub selection: Option, + + /// Maximum number of lines in the scrollback history + #[serde(default)] + history_size: usize, } pub struct GridIterator<'a, T: 'a> { @@ -132,6 +150,7 @@ impl Grid { display_offset: 0, scroll_limit: 0, selection: None, + history_size: scrollback, } } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index fb66b0c3..50ce6aa5 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -22,16 +22,6 @@ pub struct Storage { visible_lines: Line, } -impl ::std::cmp::PartialEq for Storage { - fn eq(&self, other: &Self) -> bool { - let mut equal = true; - for i in IndexRange(Line(0) .. self.visible_lines) { - equal = equal && (self[i] == other[i]) - } - equal - } -} - impl Storage { #[inline] pub fn with_capacity(cap: usize, lines: Line) -> Storage { -- cgit From d39370514a127faa05832701bb4a56fc6811de9d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 14 Apr 2018 17:37:57 +0200 Subject: Reset grid content when running `reset` In the current scrollback PR the `reset` command does not affect the scrollback history. To make sure the terminal is properly reset, it should clear the scrollback history. This commit fixes this by creating a new and empty grid whenever `reset` is executed. It takes the current dimensions and history size from the old grid. Right now there's an empty ref-test called `grid_reset` without any content, this should be implemented once #1244 is resolved. This fixes #1242. --- src/grid/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 50ce6aa5..66e0ccc8 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -13,7 +13,7 @@ /// done so manually. use std::ops::{Index, IndexMut}; -use index::{IndexRange, Line}; +use index::Line; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Storage { -- cgit From 8168d85a21b1a67b9cf25808c4e3e01f7437b50d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 28 Apr 2018 16:15:21 +0200 Subject: Improve storage comparison algorithm Instead of iterating over the raw storage vector because the offsets don't allow direct comparison, the comparison is now done in chunks. Based on benchmarking this is a lot more efficient than using split_off + append or iterating over the elements of the buffer. The `history_size` field has also been removed from the storage structure because it can be easily calculated by substracting the number of visible lines from the length of the raw storage vector. --- src/grid/mod.rs | 31 +++++++++++-------------------- src/grid/storage.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/selection.rs | 4 ++-- 3 files changed, 54 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index b6cff604..f3c8ea79 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -52,23 +52,13 @@ impl Deref for Indexed { impl ::std::cmp::PartialEq for Grid { fn eq(&self, other: &Self) -> bool { - // Compare raw grid content - // TODO: This is pretty inefficient but only used in tests - let mut raw_match = true; - for i in 0..(self.lines.0 + self.history_size) { - for j in 0..self.cols.0 { - if self[i][Column(j)] != other[i][Column(j)] { - raw_match = false; - break; - } - } - } - // Compare struct fields and check result of grid comparison - self.cols.eq(&other.cols) && + self.raw.eq(&other.raw) && + self.cols.eq(&other.cols) && self.lines.eq(&other.lines) && - self.history_size.eq(&other.history_size) && - raw_match + self.display_offset.eq(&other.display_offset) && + self.scroll_limit.eq(&other.scroll_limit) && + self.selection.eq(&other.selection) } } @@ -102,10 +92,6 @@ pub struct Grid { /// Selected region #[serde(skip)] pub selection: Option, - - /// Maximum number of lines in the scrollback history - #[serde(default)] - history_size: usize, } pub struct GridIterator<'a, T: 'a> { @@ -150,7 +136,6 @@ impl Grid { display_offset: 0, scroll_limit: 0, selection: None, - history_size: scrollback, } } @@ -426,6 +411,12 @@ impl Grid { self.scroll_limit = 0; } + /// Total number of lines in the buffer, this includes scrollback + visible lines + #[inline] + pub fn len(&self) -> usize { + self.raw.len() + } + pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { grid: self, diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 66e0ccc8..323d8ec4 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -22,6 +22,47 @@ pub struct Storage { visible_lines: Line, } +impl ::std::cmp::PartialEq for Storage { + fn eq(&self, other: &Self) -> bool { + // Make sure length is equal + if self.inner.len() != other.inner.len() { + return false; + } + + // Check which vec has the bigger zero + let (ref bigger, ref smaller) = if self.zero >= other.zero { + (self, other) + } else { + (other, self) + }; + + // Calculate the actual zero offset + let len = self.inner.len(); + let bigger_zero = bigger.zero % len; + let smaller_zero = smaller.zero % len; + + // Compare the slices in chunks + // Chunks: + // - Bigger zero to the end + // - Remaining lines in smaller zero vec + // - Beginning of smaller zero vec + // + // Example: + // Bigger Zero (6): + // 4 5 6 | 7 8 9 | 0 1 2 3 + // C2 C2 C2 | C3 C3 C3 | C1 C1 C1 C1 + // Smaller Zero (3): + // 7 8 9 | 0 1 2 3 | 4 5 6 + // C3 C3 C3 | C1 C1 C1 C1 | C2 C2 C2 + &bigger.inner[bigger_zero..] + == &smaller.inner[smaller_zero..smaller_zero + (len - bigger_zero)] + && &bigger.inner[..bigger_zero - smaller_zero] + == &smaller.inner[smaller_zero + (len - bigger_zero)..] + && &bigger.inner[bigger_zero - smaller_zero..bigger_zero] + == &smaller.inner[..smaller_zero] + } +} + impl Storage { #[inline] pub fn with_capacity(cap: usize, lines: Line) -> Storage { diff --git a/src/selection.rs b/src/selection.rs index 6f910bce..a54bd49d 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -38,7 +38,7 @@ use index::{Point, Column, Side}; /// [`simple`]: enum.Selection.html#method.simple /// [`semantic`]: enum.Selection.html#method.semantic /// [`lines`]: enum.Selection.html#method.lines -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Selection { Simple { /// The region representing start and end of cursor movement @@ -64,7 +64,7 @@ pub enum Selection { } /// A Point and side within that point. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Anchor { point: Point, side: Side, -- cgit From c3b7b98d6b19bbebf0b5a29689feb9b7162c9864 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 29 May 2018 11:13:49 -0700 Subject: Add assert to Row::grow This enforces the invariant that Row::Grow is only called when the row actually needs to be grown. --- src/grid/row.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/grid/row.rs b/src/grid/row.rs index f7e4a98a..e907b61d 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -30,8 +30,10 @@ impl Row { } pub fn grow(&mut self, cols: Column, template: &T) { + assert!(self.len() < * cols); + while self.len() != *cols { - self.push(*template); + self.inner.push(*template); } } -- cgit From c01ce1e6ff3d36d0ec55707421e9f31fe000559f Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 29 May 2018 16:33:00 -0700 Subject: fixup! Add assert to Row::grow Resolves #1337 --- src/grid/row.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/grid/row.rs b/src/grid/row.rs index e907b61d..55c82a0d 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -33,7 +33,7 @@ impl Row { assert!(self.len() < * cols); while self.len() != *cols { - self.inner.push(*template); + self.0.push(*template); } } -- cgit From a3c6c1db63af8dea62146739dc183c05b26b56d6 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Mon, 30 Apr 2018 00:22:15 +0200 Subject: Improve the resizing implementation Until now the resizing implementation with scrollback has been really inefficient because it made use of APIs like `Vec::insert`. This has been rewored with this commit. A `len` property has been added to the `Storage` struct which keeps track of the actual length of the raw buffer. This has changed both shrinking and growing implementations. With shrinking, no more lines are removed, only the `len` property is updated to set all lines shrunk to an "invisible" state which cannot be accessed from the outside, this effectively changes it to a O(1) operation. The only issue with this would be memory consumption, but since the maximum shrinkage is the number of lines on one screen, it should be a rather small impacte (probabl <100 lines usually). If desired it would be possible to change this to shrink the raw inner buffer whenever there are more than X lines hidden. Growing now works in a similar way to shrinking, if the "invisible" lines are enough, no new lines are inserted but rather the invisible buffer is made visible again. Otherwise the amount of lines that still needs to be inserted is added to the raw buffer, but instead of the inefficient `Vec::insert`, the `Vec::push` API is used now. This fixes #1271. --- src/grid/mod.rs | 2 +- src/grid/storage.rs | 108 +++++++++++++++++++++++++++++----------------------- 2 files changed, 62 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index f3c8ea79..87ac3d05 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -125,7 +125,7 @@ impl Grid { // TODO (jwilm) Allocating each line at this point is expensive and // delays startup. A nice solution might be having `Row` delay // allocation until it's actually used. - for _ in 0..raw.capacity() { + for _ in 0..raw.len() { raw.push(Row::new(cols, &template)); } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 323d8ec4..a50e8076 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -20,6 +20,8 @@ pub struct Storage { inner: Vec, zero: usize, visible_lines: Line, + #[serde(skip)] + len: usize, } impl ::std::cmp::PartialEq for Storage { @@ -70,58 +72,49 @@ impl Storage { inner: Vec::with_capacity(cap), zero: 0, visible_lines: lines - 1, + len: cap, } } - #[inline] - pub fn capacity(&self) -> usize { - self.inner.capacity() - } - /// Increase the number of lines in the buffer pub fn grow_visible_lines(&mut self, next: Line, template_row: T) where T: Clone, { - // Calculate insert position (before the first line) - let offset = self.zero % self.inner.len(); - - // Insert new template row for every line grown + // Number of lines the buffer needs to grow let lines_to_grow = (next - (self.visible_lines + 1)).0; - for _ in 0..lines_to_grow { - self.inner.insert(offset, template_row.clone()); - } - // Set zero to old zero + lines grown - self.zero = offset + lines_to_grow; + // Only grow if there are not enough lines still hidden + if lines_to_grow > (self.inner.len() - self.len) { + // Lines to grow additionally to invisible lines + let new_lines_to_grow = lines_to_grow - (self.inner.len() - self.len); - // Update visible lines - self.visible_lines = next - 1; - } + // Get the position of the start of the buffer + let offset = self.zero % self.inner.len(); - /// Decrease the number of lines in the buffer - pub fn shrink_visible_lines(&mut self, next: Line) { - // Calculate shrinkage and last line of buffer - let shrinkage = (self.visible_lines + 1 - next).0; - let offset = (self.zero + self.inner.len() - 1) % self.inner.len(); + // Split off the beginning of the raw inner buffer + let mut start_buffer = self.inner.split_off(offset); - // Generate range of lines that have to be deleted before the zero line - let start = offset.saturating_sub(shrinkage - 1); - let shrink_before = start..(offset + 1); + // Insert new template rows at the end of the raw inner buffer + let mut new_lines = vec![template_row; new_lines_to_grow]; + self.inner.append(&mut new_lines); - // Generate range of lines that have to be deleted after the zero line - let shrink_after = (self.inner.len() + offset + 1 - shrinkage)..self.inner.len(); + // Add the start to the raw inner buffer again + self.inner.append(&mut start_buffer); - // Delete all lines in reverse order - for i in shrink_before.chain(shrink_after).rev() { - self.inner.remove(i); + // Update the zero to after the lines we just inserted + self.zero = offset + lines_to_grow; } - // Check if zero has moved (not the first line in the buffer) - if self.zero % (self.inner.len() + shrinkage) != 0 { - // Set zero to the first deleted line in the buffer - self.zero = start; - } + // Update visible lines and raw buffer length + self.len += lines_to_grow; + self.visible_lines = next - 1; + } + + /// Decrease the number of lines in the buffer + pub fn shrink_visible_lines(&mut self, next: Line) { + // Shrink the size without removing any lines + self.len -= (self.visible_lines - (next - 1)).0; // Update visible lines self.visible_lines = next - 1; @@ -134,17 +127,17 @@ impl Storage { #[inline] pub fn len(&self) -> usize { - self.inner.len() + self.len } /// Compute actual index in underlying storage given the requested index. #[inline] fn compute_index(&self, requested: usize) -> usize { - (requested + self.zero) % self.len() + (requested + self.zero) % self.inner.len() } fn compute_line_index(&self, requested: Line) -> usize { - ((self.len() + self.zero + *self.visible_lines) - *requested) % self.len() + ((self.inner.len() + self.zero + *self.visible_lines) - *requested) % self.inner.len() } pub fn swap_lines(&mut self, a: Line, b: Line) { @@ -158,14 +151,14 @@ impl Storage { } pub fn rotate(&mut self, count: isize) { - let len = self.len(); + let len = self.inner.len(); assert!(count.abs() as usize <= len); self.zero += (count + len as isize) as usize % len; } // Fast path pub fn rotate_up(&mut self, count: usize) { - self.zero = (self.zero + count) % self.len(); + self.zero = (self.zero + count) % self.inner.len(); } } @@ -243,6 +236,7 @@ fn grow_after_zero() { inner: vec!["0", "1", "-"], zero: 0, visible_lines: Line(2), + len: 3, }; // Grow buffer @@ -253,9 +247,11 @@ fn grow_after_zero() { inner: vec!["-", "0", "1", "-"], zero: 1, visible_lines: Line(0), + len: 4, }; assert_eq!(storage.inner, expected.inner); assert_eq!(storage.zero, expected.zero); + assert_eq!(storage.len, expected.len); } /// Grow the buffer one line at the start of the buffer @@ -276,6 +272,7 @@ fn grow_before_zero() { inner: vec!["-", "0", "1"], zero: 1, visible_lines: Line(2), + len: 3, }; // Grow buffer @@ -286,9 +283,11 @@ fn grow_before_zero() { inner: vec!["-", "-", "0", "1"], zero: 2, visible_lines: Line(0), + len: 4, }; assert_eq!(storage.inner, expected.inner); assert_eq!(storage.zero, expected.zero); + assert_eq!(storage.len, expected.len); } /// Shrink the buffer one line at the start of the buffer @@ -298,6 +297,7 @@ fn grow_before_zero() { /// 1: 0 <- Zero /// 2: 1 /// After: +/// 0: 2 <- Hidden /// 0: 0 <- Zero /// 1: 1 #[test] @@ -307,6 +307,7 @@ fn shrink_before_zero() { inner: vec!["2", "0", "1"], zero: 1, visible_lines: Line(2), + len: 3, }; // Shrink buffer @@ -314,12 +315,14 @@ fn shrink_before_zero() { // Make sure the result is correct let expected = Storage { - inner: vec!["0", "1"], - zero: 0, + inner: vec!["2", "0", "1"], + zero: 1, visible_lines: Line(0), + len: 2, }; assert_eq!(storage.inner, expected.inner); assert_eq!(storage.zero, expected.zero); + assert_eq!(storage.len, expected.len); } /// Shrink the buffer one line at the end of the buffer @@ -331,6 +334,7 @@ fn shrink_before_zero() { /// After: /// 0: 0 <- Zero /// 1: 1 +/// 2: 2 <- Hidden #[test] fn shrink_after_zero() { // Setup storage area @@ -338,6 +342,7 @@ fn shrink_after_zero() { inner: vec!["0", "1", "2"], zero: 0, visible_lines: Line(2), + len: 3, }; // Shrink buffer @@ -345,12 +350,14 @@ fn shrink_after_zero() { // Make sure the result is correct let expected = Storage { - inner: vec!["0", "1"], + inner: vec!["0", "1", "2"], zero: 0, visible_lines: Line(0), + len: 2, }; assert_eq!(storage.inner, expected.inner); assert_eq!(storage.zero, expected.zero); + assert_eq!(storage.len, expected.len); } /// Shrink the buffer at the start and end of the buffer @@ -363,8 +370,12 @@ fn shrink_after_zero() { /// 4: 2 /// 5: 3 /// After: -/// 0: 0 <- Zero -/// 1: 1 +/// 0: 4 <- Hidden +/// 1: 5 <- Hidden +/// 2: 0 <- Zero +/// 3: 1 +/// 4: 2 <- Hidden +/// 5: 3 <- Hidden #[test] fn shrink_before_and_after_zero() { // Setup storage area @@ -372,6 +383,7 @@ fn shrink_before_and_after_zero() { inner: vec!["4", "5", "0", "1", "2", "3"], zero: 2, visible_lines: Line(5), + len: 6, }; // Shrink buffer @@ -379,10 +391,12 @@ fn shrink_before_and_after_zero() { // Make sure the result is correct let expected = Storage { - inner: vec!["0", "1"], - zero: 0, + inner: vec!["4", "5", "0", "1", "2", "3"], + zero: 2, visible_lines: Line(0), + len: 2, }; assert_eq!(storage.inner, expected.inner); assert_eq!(storage.zero, expected.zero); + assert_eq!(storage.len, expected.len); } -- cgit From eabd6bb95b1ab883bdec16f8c307432c1e7c73d5 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 1 May 2018 20:07:59 +0200 Subject: Remove `push` from `Storage` Since every line is allocated at startup anyways, the `push` method on `Storage` has been removed and instead of pushing to the vector the initialization has been moved to the `with_capacity` method. This has the advantage that we don't need to keep track of the `len` in push (like adding one), but we just need to worry about growing/shrinking the visible area. --- src/grid/mod.rs | 12 +----------- src/grid/storage.rs | 18 +++++++++++------- 2 files changed, 12 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 87ac3d05..803f1e6e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -118,17 +118,7 @@ pub enum Scroll { impl Grid { pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid { - let mut raw = Storage::with_capacity(*lines + scrollback, lines); - - // Allocate all lines in the buffer, including scrollback history - // - // TODO (jwilm) Allocating each line at this point is expensive and - // delays startup. A nice solution might be having `Row` delay - // allocation until it's actually used. - for _ in 0..raw.len() { - raw.push(Row::new(cols, &template)); - } - + let raw = Storage::with_capacity(*lines + scrollback, lines, Row::new(cols, &template)); Grid { raw, cols, diff --git a/src/grid/storage.rs b/src/grid/storage.rs index a50e8076..1f71b5b5 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -67,9 +67,18 @@ impl ::std::cmp::PartialEq for Storage { impl Storage { #[inline] - pub fn with_capacity(cap: usize, lines: Line) -> Storage { + pub fn with_capacity(cap: usize, lines: Line, template: T) -> Storage + where + T: Clone, + { + // Allocate all lines in the buffer, including scrollback history + // + // TODO (jwilm) Allocating each line at this point is expensive and + // delays startup. A nice solution might be having `Row` delay + // allocation until it's actually used. + let inner = vec![template; cap]; Storage { - inner: Vec::with_capacity(cap), + inner, zero: 0, visible_lines: lines - 1, len: cap, @@ -120,11 +129,6 @@ impl Storage { self.visible_lines = next - 1; } - #[inline] - pub fn push(&mut self, item: T) { - self.inner.push(item) - } - #[inline] pub fn len(&self) -> usize { self.len -- cgit From 6cddceb6cde032a79253f0596850c3e3c1c66db7 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 2 May 2018 20:49:32 +0200 Subject: Add documentation for `len` field on `Storage` Because the purpose of the `len` field wasn't obvious and collided with other uses (like Vec::len()), some additional documentation has added to make things a little easier to understand. --- src/grid/storage.rs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 1f71b5b5..f59b01b7 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -20,6 +20,13 @@ pub struct Storage { inner: Vec, zero: usize, visible_lines: Line, + + /// Total number of lines currently active in the terminal (scrollback + visible) + /// + /// Shrinking this length allows reducing the number of lines in the scrollback buffer without + /// having to truncate the raw `inner` buffer. + /// As long as `len` is bigger than `inner`, it is also possible to grow the scrollback buffer + /// without any additional insertions. #[serde(skip)] len: usize, } -- cgit From ac93f6d03198c1826dbed60fa8283aeb8d1a577d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 15 May 2018 22:36:14 +0200 Subject: Truncate invisible lines before storing ref-tests Because there is no good way to store invisible lines in a backwards- and forwards-compatible way, they buffer now gets truncated before dumping the state of a grid when creating a ref-test. This involved a few workaround of which a few required adding additional methods which are only used in ref-tests, these should be minimal though. Since this required the creation of a truncation method anyways, some logic has been added which automatically truncates the invisible buffer when there are more than X (set to 100) invisible lines. This should not impact performance because it rarely occurs, but it could save a bit of memory when the history size is shrunk during runtime (see #1293). This also adds an optional `config.json` file to the ref-test output where it is possible to manually specify variables which should override config defaults, this has been used only for history_size so far. Creating a new ref-test does also still work, so there was no regression here, if history size is altered, the config.json just has to be created manually with the content `{"history_size":HIST_SIZE}`, where `HIST_SIZE` is the desired history size. --- src/event.rs | 3 +- src/grid/mod.rs | 5 +++ src/grid/storage.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/event.rs b/src/event.rs index cf7d1030..37e4eccf 100644 --- a/src/event.rs +++ b/src/event.rs @@ -271,7 +271,8 @@ impl Processor { CloseRequested => { if ref_test { // dump grid state - let grid = processor.ctx.terminal.grid(); + let mut grid = processor.ctx.terminal.grid().clone(); + grid.truncate(); let serialized_grid = json::to_string(&grid) .expect("serialize grid"); diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 803f1e6e..0eea4201 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -407,6 +407,11 @@ impl Grid { self.raw.len() } + /// This is used only for truncating before saving ref-tests + pub fn truncate(&mut self) { + self.raw.truncate(); + } + pub fn iter_from(&self, point: Point) -> GridIterator { GridIterator { grid: self, diff --git a/src/grid/storage.rs b/src/grid/storage.rs index f59b01b7..885cb94c 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -15,6 +15,9 @@ use std::ops::{Index, IndexMut}; use index::Line; +/// Maximum number of invisible lines before buffer is resized +const TRUNCATE_STEP: usize = 100; + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Storage { inner: Vec, @@ -134,6 +137,32 @@ impl Storage { // Update visible lines self.visible_lines = next - 1; + + // Free memory + if self.inner.len() > self.len() + TRUNCATE_STEP { + self.truncate(); + } + } + + /// Truncate the invisible elements from the raw buffer + pub fn truncate(&mut self) { + // Calculate shrinkage/offset for indexing + let offset = self.zero % self.inner.len(); + let shrinkage = self.inner.len() - self.len; + let shrinkage_start = ::std::cmp::min(offset, shrinkage); + + // Create two vectors with correct ordering + let mut split = self.inner.split_off(offset); + + // Truncate the buffers + let len = self.inner.len(); + let split_len = split.len(); + self.inner.truncate(len - shrinkage_start); + split.truncate(split_len - (shrinkage - shrinkage_start)); + + // Merge buffers again and reset zero + self.zero = self.inner.len(); + self.inner.append(&mut split); } #[inline] @@ -411,3 +440,76 @@ fn shrink_before_and_after_zero() { assert_eq!(storage.zero, expected.zero); assert_eq!(storage.len, expected.len); } + +/// Check that when truncating all hidden lines are removed from the raw buffer +/// +/// Before: +/// 0: 4 <- Hidden +/// 1: 5 <- Hidden +/// 2: 0 <- Zero +/// 3: 1 +/// 4: 2 <- Hidden +/// 5: 3 <- Hidden +/// After: +/// 0: 0 <- Zero +/// 1: 1 +#[test] +fn truncate_invisible_lines() { + // Setup storage area + let mut storage = Storage { + inner: vec!["4", "5", "0", "1", "2", "3"], + zero: 2, + visible_lines: Line(1), + len: 2, + }; + + // Truncate buffer + storage.truncate(); + + // Make sure the result is correct + let expected = Storage { + inner: vec!["0", "1"], + zero: 0, + visible_lines: Line(1), + len: 2, + }; + assert_eq!(storage.visible_lines, expected.visible_lines); + assert_eq!(storage.inner, expected.inner); + assert_eq!(storage.zero, expected.zero); + assert_eq!(storage.len, expected.len); +} + +/// Truncate buffer only at the beginning +/// +/// Before: +/// 0: 1 +/// 1: 2 <- Hidden +/// 2: 0 <- Zero +/// After: +/// 0: 1 +/// 0: 0 <- Zero +#[test] +fn truncate_invisible_lines_beginning() { + // Setup storage area + let mut storage = Storage { + inner: vec!["1", "2", "0"], + zero: 2, + visible_lines: Line(1), + len: 2, + }; + + // Truncate buffer + storage.truncate(); + + // Make sure the result is correct + let expected = Storage { + inner: vec!["1", "0"], + zero: 1, + visible_lines: Line(1), + len: 2, + }; + assert_eq!(storage.visible_lines, expected.visible_lines); + assert_eq!(storage.inner, expected.inner); + assert_eq!(storage.zero, expected.zero); + assert_eq!(storage.len, expected.len); +} -- cgit From 4b1a3b1e929bbf580de7106a688043bb44523a7f Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 18 May 2018 20:44:54 -0700 Subject: Optimize Storage::swap_lines Saves a few cycles in a *very* hot function. --- src/grid/storage.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 885cb94c..cadd4e29 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -176,13 +176,10 @@ impl Storage { (requested + self.zero) % self.inner.len() } - fn compute_line_index(&self, requested: Line) -> usize { - ((self.inner.len() + self.zero + *self.visible_lines) - *requested) % self.inner.len() - } - pub fn swap_lines(&mut self, a: Line, b: Line) { - let a = self.compute_line_index(a); - let b = self.compute_line_index(b); + let offset = self.inner.len() + self.zero + *self.visible_lines; + let a = (offset - *a) % self.inner.len(); + let b = (offset - *b) % self.inner.len(); self.inner.swap(a, b); } @@ -191,9 +188,10 @@ impl Storage { } pub fn rotate(&mut self, count: isize) { + debug_assert!(count.abs() as usize <= self.inner.len()); + let len = self.inner.len(); - assert!(count.abs() as usize <= len); - self.zero += (count + len as isize) as usize % len; + self.zero = (self.zero as isize + count + len as isize) as usize % len; } // Fast path -- cgit From c61a912f6221a320ec4787cd883527b0e7f26a8e Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sat, 19 May 2018 11:18:37 -0700 Subject: Optimize Row::reset Now, only cells that have been used are cleared. This is achieved by using a "occupied" memo on the Row itself. The value, `occ`, is updated wherever the Row is accessed mutably, and it's cleared to zero in Row::reset. The tests for grid scroll_up and scroll_down were updated to include a test on the value `occ` and slightly refactored, but are otherwise equivalent to the previous implementation of those tests. Because of the change to the `Row` struct, the ref tests were updated so Deserialization keeps working as expected. --- src/grid/row.rs | 96 ++++++++++++++++++++++++++++++++++--------------------- src/grid/tests.rs | 92 +++++++++++++++++++++++++++------------------------- 2 files changed, 108 insertions(+), 80 deletions(-) (limited to 'src') diff --git a/src/grid/row.rs b/src/grid/row.rs index 55c82a0d..ea8804a7 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -14,43 +14,78 @@ //! Defines the Row type which makes up lines in the grid -use std::ops::{Deref, DerefMut, Index, IndexMut}; +use std::ops::{Index, IndexMut}; use std::ops::{Range, RangeTo, RangeFrom, RangeFull}; +use std::cmp::{max, min}; use std::slice; use index::Column; /// A row in the grid -#[derive(Default, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct Row(Vec); +#[derive(Default, Clone, Debug, Serialize, Deserialize)] +pub struct Row { + inner: Vec, + + /// occupied entries + /// + /// Semantically, this value can be understood as the **end** of an + /// Exclusive Range. Thus, + /// + /// - Zero means there are no occupied entries + /// - 1 means there is a value at index zero, but nowhere else + /// - `occ == inner.len` means every value is occupied + pub(crate) occ: usize, +} + +impl PartialEq for Row { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} impl Row { pub fn new(columns: Column, template: &T) -> Row { - Row(vec![*template; *columns]) + Row { + inner: vec![*template; *columns], + occ: 0, + } } pub fn grow(&mut self, cols: Column, template: &T) { assert!(self.len() < * cols); while self.len() != *cols { - self.0.push(*template); + self.inner.push(*template); } } /// Resets contents to the contents of `other` - #[inline] + #[inline(never)] pub fn reset(&mut self, other: &T) { - for item in &mut self.0 { + let occ = self.occ; + for item in &mut self.inner[..occ] { *item = *other; } + + self.occ = 0; } } impl Row { pub fn shrink(&mut self, cols: Column) { while self.len() != *cols { - self.pop(); + self.inner.pop(); } + + self.occ = min(self.occ, *cols); + } + + pub fn len(&self) -> usize { + self.inner.len() + } + + pub fn iter<'a>(&'a self) -> slice::Iter<'a, T> { + self.inner.iter() } } @@ -71,24 +106,8 @@ impl<'a, T> IntoIterator for &'a mut Row { #[inline] fn into_iter(self) -> slice::IterMut<'a, T> { - self.iter_mut() - } -} - -impl Deref for Row { - type Target = Vec; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - - -impl DerefMut for Row { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + self.occ = self.len(); + self.inner.iter_mut() } } @@ -97,14 +116,15 @@ impl Index for Row { #[inline] fn index(&self, index: Column) -> &T { - &self.0[index.0] + &self.inner[index.0] } } impl IndexMut for Row { #[inline] fn index_mut(&mut self, index: Column) -> &mut T { - &mut self.0[index.0] + self.occ = max(self.occ, *index + 1); + &mut self.inner[index.0] } } @@ -117,14 +137,15 @@ impl Index> for Row { #[inline] fn index(&self, index: Range) -> &[T] { - &self.0[(index.start.0)..(index.end.0)] + &self.inner[(index.start.0)..(index.end.0)] } } impl IndexMut> for Row { #[inline] fn index_mut(&mut self, index: Range) -> &mut [T] { - &mut self.0[(index.start.0)..(index.end.0)] + self.occ = max(self.occ, *index.end); + &mut self.inner[(index.start.0)..(index.end.0)] } } @@ -133,14 +154,15 @@ impl Index> for Row { #[inline] fn index(&self, index: RangeTo) -> &[T] { - &self.0[..(index.end.0)] + &self.inner[..(index.end.0)] } } impl IndexMut> for Row { #[inline] fn index_mut(&mut self, index: RangeTo) -> &mut [T] { - &mut self.0[..(index.end.0)] + self.occ = max(self.occ, *index.end); + &mut self.inner[..(index.end.0)] } } @@ -149,14 +171,15 @@ impl Index> for Row { #[inline] fn index(&self, index: RangeFrom) -> &[T] { - &self.0[(index.start.0)..] + &self.inner[(index.start.0)..] } } impl IndexMut> for Row { #[inline] fn index_mut(&mut self, index: RangeFrom) -> &mut [T] { - &mut self.0[(index.start.0)..] + self.occ = self.len(); + &mut self.inner[(index.start.0)..] } } @@ -165,13 +188,14 @@ impl Index for Row { #[inline] fn index(&self, _: RangeFull) -> &[T] { - &self.0[..] + &self.inner[..] } } impl IndexMut for Row { #[inline] fn index_mut(&mut self, _: RangeFull) -> &mut [T] { - &mut self.0[..] + self.occ = self.len(); + &mut self.inner[..] } } diff --git a/src/grid/tests.rs b/src/grid/tests.rs index 3e229fb6..435f0c3d 100644 --- a/src/grid/tests.rs +++ b/src/grid/tests.rs @@ -20,73 +20,77 @@ use index::{Point, Line, Column}; // Scroll up moves lines upwards #[test] fn scroll_up() { - info!(""); + println!(""); let mut grid = Grid::new(Line(10), Column(1), 0, 0); for i in 0..10 { grid[Line(i)][Column(0)] = i; } - info!("grid: {:?}", grid); + println!("grid: {:?}", grid); grid.scroll_up(&(Line(0)..Line(10)), Line(2), &0); - info!("grid: {:?}", grid); - - let mut other = Grid::new(Line(10), Column(1), 0, 9); - - other[Line(0)][Column(0)] = 2; - other[Line(1)][Column(0)] = 3; - other[Line(2)][Column(0)] = 4; - other[Line(3)][Column(0)] = 5; - other[Line(4)][Column(0)] = 6; - other[Line(5)][Column(0)] = 7; - other[Line(6)][Column(0)] = 8; - other[Line(7)][Column(0)] = 9; - other[Line(8)][Column(0)] = 0; // should be cleared on scroll; was 0 - other[Line(9)][Column(0)] = 0; // should be cleared on scroll; was 1 - - for i in 0..10 { - assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)], - "index={}; actual: {:?}, expected: {:?}", - Line(i), grid[Line(i)][Column(0)], other[Line(i)][Column(0)]); - } + println!("grid: {:?}", grid); + + assert_eq!(grid[Line(0)][Column(0)], 2); + assert_eq!(grid[Line(0)].occ, 1); + assert_eq!(grid[Line(1)][Column(0)], 3); + assert_eq!(grid[Line(1)].occ, 1); + assert_eq!(grid[Line(2)][Column(0)], 4); + assert_eq!(grid[Line(2)].occ, 1); + assert_eq!(grid[Line(3)][Column(0)], 5); + assert_eq!(grid[Line(3)].occ, 1); + assert_eq!(grid[Line(4)][Column(0)], 6); + assert_eq!(grid[Line(4)].occ, 1); + assert_eq!(grid[Line(5)][Column(0)], 7); + assert_eq!(grid[Line(5)].occ, 1); + assert_eq!(grid[Line(6)][Column(0)], 8); + assert_eq!(grid[Line(6)].occ, 1); + assert_eq!(grid[Line(7)][Column(0)], 9); + assert_eq!(grid[Line(7)].occ, 1); + assert_eq!(grid[Line(8)][Column(0)], 0); // was 0 + assert_eq!(grid[Line(8)].occ, 0); + assert_eq!(grid[Line(9)][Column(0)], 0); // was 1 + assert_eq!(grid[Line(9)].occ, 0); } // Scroll down moves lines downwards #[test] fn scroll_down() { - info!(""); + println!(""); let mut grid = Grid::new(Line(10), Column(1), 0, 0); for i in 0..10 { grid[Line(i)][Column(0)] = i; } - info!("grid: {:?}", grid); + println!("grid: {:?}", grid); grid.scroll_down(&(Line(0)..Line(10)), Line(2), &0); - info!("grid: {:?}", grid); - - let mut other = Grid::new(Line(10), Column(1), 0, 9); - - other[Line(0)][Column(0)] = 0; // Should be cleared upon recycle; was 8 - other[Line(1)][Column(0)] = 0; // Should be cleared upon recycle; was 9 - other[Line(2)][Column(0)] = 0; - other[Line(3)][Column(0)] = 1; - other[Line(4)][Column(0)] = 2; - other[Line(5)][Column(0)] = 3; - other[Line(6)][Column(0)] = 4; - other[Line(7)][Column(0)] = 5; - other[Line(8)][Column(0)] = 6; - other[Line(9)][Column(0)] = 7; - - for i in 0..10 { - assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)], - "index={}; actual: {:?}, expected: {:?}", - Line(i), grid[Line(i)][Column(0)], other[Line(i)][Column(0)]); - } + println!("grid: {:?}", grid); + + assert_eq!(grid[Line(0)][Column(0)], 0); // was 8 + assert_eq!(grid[Line(0)].occ, 0); + assert_eq!(grid[Line(1)][Column(0)], 0); // was 9 + assert_eq!(grid[Line(1)].occ, 0); + assert_eq!(grid[Line(2)][Column(0)], 0); + assert_eq!(grid[Line(2)].occ, 1); + assert_eq!(grid[Line(3)][Column(0)], 1); + assert_eq!(grid[Line(3)].occ, 1); + assert_eq!(grid[Line(4)][Column(0)], 2); + assert_eq!(grid[Line(4)].occ, 1); + assert_eq!(grid[Line(5)][Column(0)], 3); + assert_eq!(grid[Line(5)].occ, 1); + assert_eq!(grid[Line(6)][Column(0)], 4); + assert_eq!(grid[Line(6)].occ, 1); + assert_eq!(grid[Line(7)][Column(0)], 5); + assert_eq!(grid[Line(7)].occ, 1); + assert_eq!(grid[Line(8)][Column(0)], 6); + assert_eq!(grid[Line(8)].occ, 1); + assert_eq!(grid[Line(9)][Column(0)], 7); + assert_eq!(grid[Line(9)].occ, 1); } // Test that GridIterator works -- cgit From 5a11f8584317b6a949a1ff3a5b3a446766db0665 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sat, 19 May 2018 12:06:21 -0700 Subject: Fix OOB index in grid::DisplayIter When resizing prior to this patch, hidden rows in Storage were not having columns added along with everything else. This feels like a bit of tech debt, but the patch is simple enough that it won't be much extra to back out later when the underlying cause is addressed (see comments in code). --- src/grid/mod.rs | 2 +- src/grid/storage.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 0eea4201..9b36fcdf 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -244,7 +244,7 @@ impl Grid { } fn grow_cols(&mut self, cols: index::Column, template: &T) { - for row in self.raw.iter_mut() { + for row in self.raw.iter_mut_raw() { row.grow(cols, template); } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index cadd4e29..e7c1a307 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -12,6 +12,7 @@ /// implementation is provided. Anything from Vec that should be exposed must be /// done so manually. use std::ops::{Index, IndexMut}; +use std::slice; use index::Line; @@ -183,10 +184,25 @@ impl Storage { self.inner.swap(a, b); } + /// Iterator over *logical* entries in the storage + /// + /// This *does not* iterate over hidden entries. pub fn iter_mut(&mut self) -> IterMut { IterMut { storage: self, index: 0 } } + /// Iterate over *all* entries in the underlying buffer + /// + /// This includes hidden entries. + /// + /// XXX This suggests that Storage is a leaky abstraction. Ultimately, this + /// is needed because of the grow lines functionality implemented on + /// this type, and maybe that's where the leak is necessitating this + /// accessor. + pub fn iter_mut_raw<'a>(&'a mut self) -> slice::IterMut<'a, T> { + self.inner.iter_mut() + } + pub fn rotate(&mut self, count: isize) { debug_assert!(count.abs() as usize <= self.inner.len()); -- cgit From 169b87e4ce19f4fd22e5d06ff27f9d3d0ee19b44 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sat, 19 May 2018 14:47:16 -0700 Subject: Shave a few cycles off Grid::scroll_up This implementation avoids a few extra transformations by operating in buffer-space. However, there is still a transformation from the logical buffer-space to the underlying vector. --- src/grid/mod.rs | 15 +++++++++------ src/grid/storage.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 9b36fcdf..0998d640 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -334,7 +334,6 @@ impl Grid { /// scroll_up moves lines at the bottom towards the top /// /// This is the performance-sensitive part of scrolling. - #[inline] pub fn scroll_up( &mut self, region: &Range, @@ -356,17 +355,21 @@ impl Grid { selection.rotate(*positions as isize); } - // Now, restore any lines outside the scroll region - for idx in (*region.end .. *self.num_lines()).rev() { - // First do the swap - self.raw.swap_lines(Line(idx), Line(idx) - positions); + // // This next loop swaps "fixed" lines outside of a scroll region + // // back into place after the rotation. The work is done in buffer- + // // space rather than terminal-space to avoid redundant + // // transformations. + let fixed_lines = *self.num_lines() - *region.end; + + for i in 0..fixed_lines { + self.raw.swap(i, i + *positions); } // Finally, reset recycled lines // // Recycled lines are just above the end of the scrolling region. for i in 0..*positions { - self.raw[region.end - i - 1].reset(&template); + self.raw[i + fixed_lines].reset(&template); } } else { // Subregion rotation diff --git a/src/grid/storage.rs b/src/grid/storage.rs index e7c1a307..eb87c622 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -171,12 +171,25 @@ impl Storage { self.len } - /// Compute actual index in underlying storage given the requested index. #[inline] + pub fn raw_len(&self) -> usize { + self.inner.len() + } + + /// Compute actual index in underlying storage given the requested index. fn compute_index(&self, requested: usize) -> usize { (requested + self.zero) % self.inner.len() } + #[inline] + pub fn line_offset(&self) -> usize { + self.inner.len() + self.zero + *self.visible_lines + } + + pub fn compute_line_index(&self, a: Line) -> usize { + (self.line_offset() - *a) % self.inner.len() + } + pub fn swap_lines(&mut self, a: Line, b: Line) { let offset = self.inner.len() + self.zero + *self.visible_lines; let a = (offset - *a) % self.inner.len(); @@ -184,6 +197,19 @@ impl Storage { self.inner.swap(a, b); } + /// Swap two lines in raw buffer + /// + /// # Panics + /// + /// `swap` will panic if either `a` or `b` are out-of-bounds of the + /// underlying storage. + pub fn swap(&mut self, a: usize, b: usize) { + let a = self.compute_index(a); + let b = self.compute_index(b); + + self.inner.swap(a, b); + } + /// Iterator over *logical* entries in the storage /// /// This *does not* iterate over hidden entries. -- cgit From efc568be4e7da9481f5b6d35ff2854c1ff4cd2be Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 29 May 2018 11:15:03 -0700 Subject: Fix issue with endless allocation loop Shrinking columns wasn't updating hidden rows in the storage buffer on resizing. When growing the columns again, this resulted in an endless allocation loop. Only one real change was made here, and that was `raw.iter_mut()` to `raw.iter_mut_raw()`. The method `shrink_cols` was relocated to be adjacent to `grow_cols` in the code. --- src/grid/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 0998d640..c9829c2e 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -252,6 +252,14 @@ impl Grid { self.cols = cols; } + fn shrink_cols(&mut self, cols: index::Column) { + for row in self.raw.iter_mut_raw() { + row.shrink(cols); + } + + self.cols = cols; + } + /// Remove lines from the visible area /// /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the @@ -437,14 +445,6 @@ impl Grid { // pub fn swap_lines(&mut self, src: index::Line, dst: index::Line) { // self.raw.swap(*src, *dst); // } - - fn shrink_cols(&mut self, cols: index::Column) { - for row in self.raw.iter_mut() { - row.shrink(cols); - } - - self.cols = cols; - } } impl<'a, T> Iterator for GridIterator<'a, T> { -- cgit From 9a98d5e0ee9139d5a2988d125352c5d70a39ad20 Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 29 May 2018 21:33:13 -0700 Subject: Refactor Storage to be a Vec> internally This will allow certain optimizations around swap which are currently only possible in a generalized way with specialization. --- src/grid/mod.rs | 10 +++++----- src/grid/storage.rs | 57 +++++++++++++++++++++++++++-------------------------- 2 files changed, 34 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index c9829c2e..b03ae54f 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -67,7 +67,7 @@ impl ::std::cmp::PartialEq for Grid { pub struct Grid { /// Lines in the grid. Each row holds a list of cells corresponding to the /// columns in that row. - raw: Storage>, + raw: Storage, /// Number of columns cols: index::Column, @@ -540,7 +540,7 @@ impl<'point, T> IndexMut<&'point Point> for Grid { pub struct Region<'a, T: 'a> { start: Line, end: Line, - raw: &'a Storage>, + raw: &'a Storage, } /// A mutable subset of lines in the grid @@ -549,7 +549,7 @@ pub struct Region<'a, T: 'a> { pub struct RegionMut<'a, T: 'a> { start: Line, end: Line, - raw: &'a mut Storage>, + raw: &'a mut Storage, } impl<'a, T> RegionMut<'a, T> { @@ -653,13 +653,13 @@ impl IndexRegion for Grid { pub struct RegionIter<'a, T: 'a> { end: Line, cur: Line, - raw: &'a Storage>, + raw: &'a Storage, } pub struct RegionIterMut<'a, T: 'a> { end: Line, cur: Line, - raw: &'a mut Storage>, + raw: &'a mut Storage, } impl<'a, T> IntoIterator for Region<'a, T> { diff --git a/src/grid/storage.rs b/src/grid/storage.rs index eb87c622..57afde82 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -14,14 +14,15 @@ use std::ops::{Index, IndexMut}; use std::slice; -use index::Line; +use index::{Column, Line}; +use super::Row; /// Maximum number of invisible lines before buffer is resized const TRUNCATE_STEP: usize = 100; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Storage { - inner: Vec, + inner: Vec>, zero: usize, visible_lines: Line, @@ -78,7 +79,7 @@ impl ::std::cmp::PartialEq for Storage { impl Storage { #[inline] - pub fn with_capacity(cap: usize, lines: Line, template: T) -> Storage + pub fn with_capacity(cap: usize, lines: Line, template: Row) -> Storage where T: Clone, { @@ -97,7 +98,7 @@ impl Storage { } /// Increase the number of lines in the buffer - pub fn grow_visible_lines(&mut self, next: Line, template_row: T) + pub fn grow_visible_lines(&mut self, next: Line, template_row: Row) where T: Clone, { @@ -225,7 +226,7 @@ impl Storage { /// is needed because of the grow lines functionality implemented on /// this type, and maybe that's where the leak is necessitating this /// accessor. - pub fn iter_mut_raw<'a>(&'a mut self) -> slice::IterMut<'a, T> { + pub fn iter_mut_raw<'a>(&'a mut self) -> slice::IterMut<'a, Row> { self.inner.iter_mut() } @@ -243,9 +244,9 @@ impl Storage { } impl Index for Storage { - type Output = T; + type Output = Row; #[inline] - fn index(&self, index: usize) -> &T { + fn index(&self, index: usize) -> &Self::Output { let index = self.compute_index(index); // borrowck &self.inner[index] } @@ -253,16 +254,16 @@ impl Index for Storage { impl IndexMut for Storage { #[inline] - fn index_mut(&mut self, index: usize) -> &mut T { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { let index = self.compute_index(index); // borrowck &mut self.inner[index] } } impl Index for Storage { - type Output = T; + type Output = Row; #[inline] - fn index(&self, index: Line) -> &T { + fn index(&self, index: Line) -> &Self::Output { let index = self.visible_lines - index; &self[*index] } @@ -270,7 +271,7 @@ impl Index for Storage { impl IndexMut for Storage { #[inline] - fn index_mut(&mut self, index: Line) -> &mut T { + fn index_mut(&mut self, index: Line) -> &mut Self::Output { let index = self.visible_lines - index; &mut self[*index] } @@ -282,7 +283,7 @@ pub struct IterMut<'a, T: 'a> { } impl<'a, T: 'a> Iterator for IterMut<'a, T> { - type Item = &'a mut T; + type Item = &'a mut Row; fn next(&mut self) -> Option { if self.index == self.storage.len() { @@ -313,18 +314,18 @@ impl<'a, T: 'a> Iterator for IterMut<'a, T> { fn grow_after_zero() { // Setup storage area let mut storage = Storage { - inner: vec!["0", "1", "-"], + inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1'), Row::new(Column(1), &'-')], zero: 0, visible_lines: Line(2), len: 3, }; // Grow buffer - storage.grow_visible_lines(Line(4), "-"); + storage.grow_visible_lines(Line(4), Row::new(Column(1), &'-')); // Make sure the result is correct let expected = Storage { - inner: vec!["-", "0", "1", "-"], + inner: vec![Row::new(Column(1), &'-'), Row::new(Column(1), &'0'), Row::new(Column(1), &'1'), Row::new(Column(1), &'-')], zero: 1, visible_lines: Line(0), len: 4, @@ -349,18 +350,18 @@ fn grow_after_zero() { fn grow_before_zero() { // Setup storage area let mut storage = Storage { - inner: vec!["-", "0", "1"], + inner: vec![Row::new(Column(1), &'-'), Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], zero: 1, visible_lines: Line(2), len: 3, }; // Grow buffer - storage.grow_visible_lines(Line(4), "-"); + storage.grow_visible_lines(Line(4), Row::new(Column(1), &'-')); // Make sure the result is correct let expected = Storage { - inner: vec!["-", "-", "0", "1"], + inner: vec![Row::new(Column(1), &'-'), Row::new(Column(1), &'-'), Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], zero: 2, visible_lines: Line(0), len: 4, @@ -384,7 +385,7 @@ fn grow_before_zero() { fn shrink_before_zero() { // Setup storage area let mut storage = Storage { - inner: vec!["2", "0", "1"], + inner: vec![Row::new(Column(1), &'2'), Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], zero: 1, visible_lines: Line(2), len: 3, @@ -395,7 +396,7 @@ fn shrink_before_zero() { // Make sure the result is correct let expected = Storage { - inner: vec!["2", "0", "1"], + inner: vec![Row::new(Column(1), &'2'), Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], zero: 1, visible_lines: Line(0), len: 2, @@ -419,7 +420,7 @@ fn shrink_before_zero() { fn shrink_after_zero() { // Setup storage area let mut storage = Storage { - inner: vec!["0", "1", "2"], + inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1'), Row::new(Column(1), &'2')], zero: 0, visible_lines: Line(2), len: 3, @@ -430,7 +431,7 @@ fn shrink_after_zero() { // Make sure the result is correct let expected = Storage { - inner: vec!["0", "1", "2"], + inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1'), Row::new(Column(1), &'2')], zero: 0, visible_lines: Line(0), len: 2, @@ -460,7 +461,7 @@ fn shrink_after_zero() { fn shrink_before_and_after_zero() { // Setup storage area let mut storage = Storage { - inner: vec!["4", "5", "0", "1", "2", "3"], + inner: vec![Row::new(Column(1), &'4'), Row::new(Column(1), &'5'), Row::new(Column(1), &'0'), Row::new(Column(1), &'1'), Row::new(Column(1), &'2'), Row::new(Column(1), &'3')], zero: 2, visible_lines: Line(5), len: 6, @@ -471,7 +472,7 @@ fn shrink_before_and_after_zero() { // Make sure the result is correct let expected = Storage { - inner: vec!["4", "5", "0", "1", "2", "3"], + inner: vec![Row::new(Column(1), &'4'), Row::new(Column(1), &'5'), Row::new(Column(1), &'0'), Row::new(Column(1), &'1'), Row::new(Column(1), &'2'), Row::new(Column(1), &'3')], zero: 2, visible_lines: Line(0), len: 2, @@ -497,7 +498,7 @@ fn shrink_before_and_after_zero() { fn truncate_invisible_lines() { // Setup storage area let mut storage = Storage { - inner: vec!["4", "5", "0", "1", "2", "3"], + inner: vec![Row::new(Column(1), &'4'), Row::new(Column(1), &'5'), Row::new(Column(1), &'0'), Row::new(Column(1), &'1'), Row::new(Column(1), &'2'), Row::new(Column(1), &'3')], zero: 2, visible_lines: Line(1), len: 2, @@ -508,7 +509,7 @@ fn truncate_invisible_lines() { // Make sure the result is correct let expected = Storage { - inner: vec!["0", "1"], + inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], zero: 0, visible_lines: Line(1), len: 2, @@ -532,7 +533,7 @@ fn truncate_invisible_lines() { fn truncate_invisible_lines_beginning() { // Setup storage area let mut storage = Storage { - inner: vec!["1", "2", "0"], + inner: vec![Row::new(Column(1), &'1'), Row::new(Column(1), &'2'), Row::new(Column(1), &'0')], zero: 2, visible_lines: Line(1), len: 2, @@ -543,7 +544,7 @@ fn truncate_invisible_lines_beginning() { // Make sure the result is correct let expected = Storage { - inner: vec!["1", "0"], + inner: vec![Row::new(Column(1), &'1'), Row::new(Column(1), &'0')], zero: 1, visible_lines: Line(1), len: 2, -- cgit From a2f99883773676a9dcc537afff4bce54e04e412b Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 29 May 2018 21:37:56 -0700 Subject: Optimize Storage::swap Removes 4 movaps instructions from generated assembly. --- src/grid/storage.rs | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 57afde82..0f0f611b 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -14,7 +14,7 @@ use std::ops::{Index, IndexMut}; use std::slice; -use index::{Column, Line}; +use index::Line; use super::Row; /// Maximum number of invisible lines before buffer is resized @@ -198,17 +198,40 @@ impl Storage { self.inner.swap(a, b); } - /// Swap two lines in raw buffer + /// Swap implementation for Row. /// - /// # Panics + /// Exploits the known size of Row to produce a slightly more efficient + /// swap than going through slice::swap. /// - /// `swap` will panic if either `a` or `b` are out-of-bounds of the - /// underlying storage. + /// The default implementation from swap generates 8 movups and 4 movaps + /// instructions. This implementation achieves the swap in only 8 movups + /// instructions. + /// + // TODO Once specialization is available, Storage can be fully generic + // again instead of enforcing inner: Vec>. pub fn swap(&mut self, a: usize, b: usize) { + debug_assert!(::std::mem::size_of::>() == 32); + let a = self.compute_index(a); let b = self.compute_index(b); - self.inner.swap(a, b); + unsafe { + // Cast to a qword array to opt out of copy restrictions and avoid + // drop hazards. Byte array is no good here since for whatever + // reason LLVM won't optimized it. + let a_ptr = self.inner.as_mut_ptr().offset(a as isize) as *mut u64; + let b_ptr = self.inner.as_mut_ptr().offset(b as isize) as *mut u64; + + // Copy 1 qword at a time + // + // The optimizer unrolls this loop and vectorizes it. + let mut tmp: u64; + for i in 0..4 { + tmp = *a_ptr.offset(i); + *a_ptr.offset(i) = *b_ptr.offset(i); + *b_ptr.offset(i) = tmp; + } + } } /// Iterator over *logical* entries in the storage @@ -299,6 +322,9 @@ impl<'a, T: 'a> Iterator for IterMut<'a, T> { } } +#[cfg(test)] +use index::Column; + /// Grow the buffer one line at the end of the buffer /// /// Before: -- cgit From 24c50fa0a793cd8c6127b5cf8ba3ea59f47cc1ca Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Tue, 29 May 2018 21:45:28 -0700 Subject: Remove dead code --- src/event.rs | 2 +- src/grid/mod.rs | 1 - src/grid/storage.rs | 43 ------------------------------------------- src/selection.rs | 23 +++++------------------ 4 files changed, 6 insertions(+), 63 deletions(-) (limited to 'src') diff --git a/src/event.rs b/src/event.rs index 37e4eccf..ff95c880 100644 --- a/src/event.rs +++ b/src/event.rs @@ -99,7 +99,7 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { fn semantic_selection(&mut self, point: Point) { let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::semantic(point, &*self.terminal)); + *self.terminal.selection_mut() = Some(Selection::semantic(point)); self.terminal.dirty = true; } diff --git a/src/grid/mod.rs b/src/grid/mod.rs index b03ae54f..97614d71 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -230,7 +230,6 @@ impl Grid { new_line_count: index::Line, template: &T, ) { - let previous_scroll_limit = self.scroll_limit; let lines_added = new_line_count - self.lines; // Need to "resize" before updating buffer diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 0f0f611b..d517fa01 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -172,25 +172,11 @@ impl Storage { self.len } - #[inline] - pub fn raw_len(&self) -> usize { - self.inner.len() - } - /// Compute actual index in underlying storage given the requested index. fn compute_index(&self, requested: usize) -> usize { (requested + self.zero) % self.inner.len() } - #[inline] - pub fn line_offset(&self) -> usize { - self.inner.len() + self.zero + *self.visible_lines - } - - pub fn compute_line_index(&self, a: Line) -> usize { - (self.line_offset() - *a) % self.inner.len() - } - pub fn swap_lines(&mut self, a: Line, b: Line) { let offset = self.inner.len() + self.zero + *self.visible_lines; let a = (offset - *a) % self.inner.len(); @@ -234,13 +220,6 @@ impl Storage { } } - /// Iterator over *logical* entries in the storage - /// - /// This *does not* iterate over hidden entries. - pub fn iter_mut(&mut self) -> IterMut { - IterMut { storage: self, index: 0 } - } - /// Iterate over *all* entries in the underlying buffer /// /// This includes hidden entries. @@ -300,28 +279,6 @@ impl IndexMut for Storage { } } -pub struct IterMut<'a, T: 'a> { - storage: &'a mut Storage, - index: usize, -} - -impl<'a, T: 'a> Iterator for IterMut<'a, T> { - type Item = &'a mut Row; - - fn next(&mut self) -> Option { - if self.index == self.storage.len() { - None - } else { - let index = self.index; - self.index += 1; - - unsafe { - Some(&mut *(&mut self.storage[index] as *mut _)) - } - } - } -} - #[cfg(test)] use index::Column; diff --git a/src/selection.rs b/src/selection.rs index a54bd49d..dca16e26 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -47,11 +47,6 @@ pub enum Selection { Semantic { /// The region representing start and end of cursor movement region: Range>, - - /// When beginning a semantic selection, the grid is searched around the - /// initial point to find semantic escapes, and this initial expansion - /// marks those points. - initial_expansion: Range> }, Lines { /// The region representing start and end of cursor movement @@ -109,11 +104,9 @@ impl Selection { 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; }, - Selection::Semantic { ref mut region, ref mut initial_expansion } => { + 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; - initial_expansion.start.line = (initial_expansion.start.line as isize + offset) as usize; - initial_expansion.end.line = (initial_expansion.end.line as isize + offset) as usize; }, Selection::Lines { ref mut region, ref mut initial_line } => { region.start.line = (region.start.line as isize + offset) as usize; @@ -123,16 +116,11 @@ impl Selection { } } - pub fn semantic(point: Point, grid: &G) -> Selection { - let (start, end) = (grid.semantic_search_left(point), grid.semantic_search_right(point)); + pub fn semantic(point: Point) -> Selection { Selection::Semantic { region: Range { start: point, end: point, - }, - initial_expansion: Range { - start: start, - end: end } } } @@ -153,7 +141,7 @@ impl Selection { Selection::Simple { ref mut region } => { region.end = Anchor::new(location, side); }, - Selection::Semantic { ref mut region, .. } | + Selection::Semantic { ref mut region } | Selection::Lines { ref mut region, .. } => { region.end = location; @@ -166,8 +154,8 @@ impl Selection { Selection::Simple { ref region } => { Selection::span_simple(grid, region) }, - Selection::Semantic { ref region, ref initial_expansion } => { - Selection::span_semantic(grid, region, initial_expansion) + Selection::Semantic { ref region } => { + Selection::span_semantic(grid, region) }, Selection::Lines { ref region, ref initial_line } => { Selection::span_lines(grid, region, *initial_line) @@ -177,7 +165,6 @@ impl Selection { fn span_semantic( grid: &G, region: &Range>, - initial_expansion: &Range> ) -> Option where G: SemanticSearch + Dimensions { -- cgit From 4631ca4db5faf10eb0276e3968814a68c86c81ee Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 30 May 2018 10:20:47 +0200 Subject: Allow changing scrollback history size at runtime Making use of the changes that have been introduced in #1234 and #1284, this allows changing the size of the scrollback buffer at runtime. This simply changes the size of the raw inner buffer making use of the optimized mutation algorithms introduced in #1284. As a result, shrinking the scrollback history size at runtime should be basically free and growing will only introduce a performance cost when there are no more buffered lines. However, as a result there will not be any memory freed when shrinking the scrollback history size at runtime. As discussed in #1234 a potential solution for this could be to truncate the raw buffer whenever more than X lines are deleted, however this issue should not be very significant PR and if a solution is desired a separate issue/PR should be opened. This fixes #1235. --- src/grid/mod.rs | 7 +++ src/grid/storage.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++++------ src/term/mod.rs | 8 +-- 3 files changed, 138 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 97614d71..535f6cc6 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -155,6 +155,13 @@ impl Grid { self.line_to_offset(line) + self.display_offset } + /// Update the size of the scrollback history + pub fn update_history(&mut self, history_size: usize, template: &T) + { + self.raw.update_history(history_size, Row::new(self.cols, &template)); + self.scroll_limit = min(self.scroll_limit, history_size); + } + pub fn scroll_display(&mut self, scroll: Scroll) { match scroll { Scroll::Lines(count) => { diff --git a/src/grid/storage.rs b/src/grid/storage.rs index d517fa01..6ed4a06a 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -97,48 +97,75 @@ impl Storage { } } + /// Update the size of the scrollback history + pub fn update_history(&mut self, history_size: usize, template_row: Row) + where + T: Clone, + { + let current_history = self.len - (self.visible_lines.0 + 1); + if history_size > current_history { + self.grow_lines(history_size - current_history, template_row); + } else if history_size < current_history { + self.shrink_lines(current_history - history_size); + } + } + /// Increase the number of lines in the buffer pub fn grow_visible_lines(&mut self, next: Line, template_row: Row) where T: Clone, { // Number of lines the buffer needs to grow - let lines_to_grow = (next - (self.visible_lines + 1)).0; + let growage = (next - (self.visible_lines + 1)).0; + self.grow_lines(growage, template_row); + + // Update visible lines + self.visible_lines = next - 1; + } + + /// Grow the number of lines in the buffer, filling new lines with the template + pub fn grow_lines(&mut self, growage: usize, template_row: Row) + where + T: Clone, + { + // Get the position of the start of the buffer + let offset = self.zero % self.inner.len(); // Only grow if there are not enough lines still hidden - if lines_to_grow > (self.inner.len() - self.len) { + let mut new_growage = 0; + if growage > (self.inner.len() - self.len) { // Lines to grow additionally to invisible lines - let new_lines_to_grow = lines_to_grow - (self.inner.len() - self.len); - - // Get the position of the start of the buffer - let offset = self.zero % self.inner.len(); + new_growage = growage - (self.inner.len() - self.len); // Split off the beginning of the raw inner buffer let mut start_buffer = self.inner.split_off(offset); // Insert new template rows at the end of the raw inner buffer - let mut new_lines = vec![template_row; new_lines_to_grow]; + let mut new_lines = vec![template_row; new_growage]; self.inner.append(&mut new_lines); // Add the start to the raw inner buffer again self.inner.append(&mut start_buffer); - - // Update the zero to after the lines we just inserted - self.zero = offset + lines_to_grow; } - // Update visible lines and raw buffer length - self.len += lines_to_grow; - self.visible_lines = next - 1; + // Update raw buffer length and zero offset + self.zero = offset + new_growage; + self.len += growage; } /// Decrease the number of lines in the buffer pub fn shrink_visible_lines(&mut self, next: Line) { // Shrink the size without removing any lines - self.len -= (self.visible_lines - (next - 1)).0; + let shrinkage = (self.visible_lines - (next - 1)).0; + self.shrink_lines(shrinkage); // Update visible lines self.visible_lines = next - 1; + } + + // Shrink the number of lines in the buffer + fn shrink_lines(&mut self, shrinkage: usize) { + self.len -= shrinkage; // Free memory if self.inner.len() > self.len() + TRUNCATE_STEP { @@ -537,3 +564,88 @@ fn truncate_invisible_lines_beginning() { assert_eq!(storage.zero, expected.zero); assert_eq!(storage.len, expected.len); } + +/// First shrink the buffer and then grow it again +/// +/// Before: +/// 0: 4 +/// 1: 5 +/// 2: 0 <- Zero +/// 3: 1 +/// 4: 2 +/// 5: 3 +/// After Shrinking: +/// 0: 4 <- Hidden +/// 1: 5 <- Hidden +/// 2: 0 <- Zero +/// 3: 1 +/// 4: 2 +/// 5: 3 <- Hidden +/// After Growing: +/// 0: 4 +/// 1: 5 +/// 2: - +/// 3: 0 <- Zero +/// 4: 1 +/// 5: 2 +/// 6: 3 +#[test] +fn shrink_then_grow() { + // Setup storage area + let mut storage = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 6, + }; + + // Shrink buffer + storage.shrink_lines(3); + + // Make sure the result after shrinking is correct + let shrinking_expected = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 2, + visible_lines: Line(0), + len: 3, + }; + assert_eq!(storage.inner, shrinking_expected.inner); + assert_eq!(storage.zero, shrinking_expected.zero); + assert_eq!(storage.len, shrinking_expected.len); + + // Grow buffer + storage.grow_lines(4, Row::new(Column(1), &'-')); + + // Make sure the result after shrinking is correct + let growing_expected = Storage { + inner: vec![ + Row::new(Column(1), &'4'), + Row::new(Column(1), &'5'), + Row::new(Column(1), &'-'), + Row::new(Column(1), &'0'), + Row::new(Column(1), &'1'), + Row::new(Column(1), &'2'), + Row::new(Column(1), &'3'), + ], + zero: 3, + visible_lines: Line(0), + len: 7, + }; + assert_eq!(storage.inner, growing_expected.inner); + assert_eq!(storage.zero, growing_expected.zero); + assert_eq!(storage.len, growing_expected.len); +} diff --git a/src/term/mod.rs b/src/term/mod.rs index fa204a55..8635c818 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -908,6 +908,8 @@ impl Term { self.default_cursor_style = config.cursor_style(); self.dynamic_title = config.dynamic_title(); self.auto_scroll = config.scrolling().auto_scroll; + self.grid + .update_history(config.scrolling().history as usize, &self.cursor.template); } #[inline] @@ -2020,17 +2022,17 @@ mod tests { mem::swap(&mut term.semantic_escape_chars, &mut escape_chars); { - *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(1) }, &term)); + *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(1) })); assert_eq!(term.selection_to_string(), Some(String::from("aa"))); } { - *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(4) }, &term)); + *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(4) })); assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); } { - *term.selection_mut() = Some(Selection::semantic(Point { line: 1, col: Column(1) }, &term)); + *term.selection_mut() = Some(Selection::semantic(Point { line: 1, col: Column(1) })); assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); } } -- cgit From bfd62ef45bedf02a4f61c08bba134a2eb06c0b47 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 16 Jun 2018 17:50:44 +0000 Subject: Optimize indexing of the grid's raw buffer The `compute_index` method in the `Storage` struct used to normalize indices was responsible for a significant amount of the CPU time spent while running the `alt-screen-random-write` benchmark (~50%). The issue with this relatively simple method was that due to how often the method is executed, the modulo operation was too expensive. Instead of the modulo, a more conservative branch has been put in place which has a very efficient best-case (which is hit most of the time). Until now the methods for growing/shrinking the storage buffer and compute_index have been written with the assumption that `self.zero` might be bigger than `self.inner.len()`. However there is no reason why `self.zero` wouldn't be constrained to always be within the size of the raw buffer, so this has been changed to make things a little simpler and more explicit. Instead of clamping the selection to be within the buffer inside the storage, this is now checked in the selection logic to remove all selection-specific logic from `storage.rs`. --- src/grid/storage.rs | 32 +++++++++++++++++++------------- src/term/mod.rs | 5 ++++- 2 files changed, 23 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 6ed4a06a..2f34b3f0 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -128,9 +128,6 @@ impl Storage { where T: Clone, { - // Get the position of the start of the buffer - let offset = self.zero % self.inner.len(); - // Only grow if there are not enough lines still hidden let mut new_growage = 0; if growage > (self.inner.len() - self.len) { @@ -138,7 +135,7 @@ impl Storage { new_growage = growage - (self.inner.len() - self.len); // Split off the beginning of the raw inner buffer - let mut start_buffer = self.inner.split_off(offset); + let mut start_buffer = self.inner.split_off(self.zero); // Insert new template rows at the end of the raw inner buffer let mut new_lines = vec![template_row; new_growage]; @@ -149,7 +146,7 @@ impl Storage { } // Update raw buffer length and zero offset - self.zero = offset + new_growage; + self.zero = (self.zero + new_growage) % self.inner.len(); self.len += growage; } @@ -176,12 +173,11 @@ impl Storage { /// Truncate the invisible elements from the raw buffer pub fn truncate(&mut self) { // Calculate shrinkage/offset for indexing - let offset = self.zero % self.inner.len(); let shrinkage = self.inner.len() - self.len; - let shrinkage_start = ::std::cmp::min(offset, shrinkage); + let shrinkage_start = ::std::cmp::min(self.zero, shrinkage); // Create two vectors with correct ordering - let mut split = self.inner.split_off(offset); + let mut split = self.inner.split_off(self.zero); // Truncate the buffers let len = self.inner.len(); @@ -190,8 +186,9 @@ impl Storage { split.truncate(split_len - (shrinkage - shrinkage_start)); // Merge buffers again and reset zero - self.zero = self.inner.len(); - self.inner.append(&mut split); + split.append(&mut self.inner); + self.inner = split; + self.zero = 0; } #[inline] @@ -201,7 +198,16 @@ impl Storage { /// Compute actual index in underlying storage given the requested index. fn compute_index(&self, requested: usize) -> usize { - (requested + self.zero) % self.inner.len() + debug_assert!(requested < self.inner.len()); + let zeroed = requested + self.zero; + + // This part is critical for performance, + // so an if/else is used here instead of a moludo operation + if zeroed >= self.inner.len() { + zeroed - self.inner.len() + } else { + zeroed + } } pub fn swap_lines(&mut self, a: Line, b: Line) { @@ -554,8 +560,8 @@ fn truncate_invisible_lines_beginning() { // Make sure the result is correct let expected = Storage { - inner: vec![Row::new(Column(1), &'1'), Row::new(Column(1), &'0')], - zero: 1, + inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], + zero: 0, visible_lines: Line(1), len: 2, }; diff --git a/src/term/mod.rs b/src/term/mod.rs index 8635c818..6de8afac 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -945,9 +945,12 @@ impl Term { fn append( &mut self, grid: &Grid, - line: usize, + mut line: usize, cols: Range ) -> Option> { + // Select until last line still within the buffer + line = min(line, grid.len() - 1); + let grid_line = &grid[line]; let line_length = grid_line.line_length(); let line_end = min(line_length, cols.end + 1); -- cgit From 07aaf05f7463a971e56de87e3b6b24e4153e5a98 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Mon, 2 Jul 2018 22:03:04 +0000 Subject: Fix scrollback accessing indices out of bounds There have been two instances of the scrollback trying to access indices which were moved out of bounds due to new lines (`yes` command). These have both been fixed. The first instance was during semantic selection, since the logic of limiting the selection start point was moved outside of `compute_index`, it was necessary to add this to semantic selection too. Now semantic selection, line selection and normal selection should all work without crashing when new lines are shoving the selection out of bounds. The other error was with the viewport being outside of the scrollback history. Since the default is to keep the scrollback buffer at its current position when new lines are added, it is possible that the position the scrollback buffer is at is suddenly shoved out of the visible area. To fix this the `display_offset` is now limited to always be an allowed value. If a single line of the viewport is moved out of the history now, the viewport should move down a single line now, so only valid content is displayed, with multiple lines this process is repeated. This fixes #1400. There was another error where the iterator would attempt to iterate before the first line in the history buffer, this was because the bounds of the `prev` iterator weren't setup correctly. The iterator should now properly iterate from the first cell in the terminal until the last one. This also fixes #1406, since these semantic selection errors were partiall related to indexing. --- src/grid/mod.rs | 17 ++++++----------- src/grid/storage.rs | 2 +- src/term/mod.rs | 8 +++++++- 3 files changed, 14 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 535f6cc6..949a5ed5 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -100,12 +100,6 @@ pub struct GridIterator<'a, T: 'a> { /// Current position of the iterator within the grid. pub cur: Point, - - /// Bottom of screen (buffer) - bot: usize, - - /// Top of screen (buffer) - top: usize, } pub enum Scroll { @@ -357,7 +351,10 @@ impl Grid { if region.start == Line(0) { // Update display offset when not pinned to active area if self.display_offset != 0 { - self.display_offset += *positions; + self.display_offset = min( + self.display_offset + *positions, + self.len() - self.num_lines().0, + ); } self.increase_scroll_limit(*positions); @@ -433,8 +430,6 @@ impl Grid { GridIterator { grid: self, cur: point, - bot: self.display_offset, - top: self.display_offset + *self.num_lines() - 1, } } @@ -459,7 +454,7 @@ impl<'a, T> Iterator for GridIterator<'a, T> { fn next(&mut self) -> Option { let last_col = self.grid.num_cols() - Column(1); match self.cur { - Point { line, col } if (line == self.bot) && (col == last_col) => None, + Point { line, col } if line == 0 && col == last_col => None, Point { col, .. } if (col == last_col) => { self.cur.line -= 1; @@ -479,7 +474,7 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { let num_cols = self.grid.num_cols(); match self.cur { - Point { line, col: Column(0) } if line == self.top => None, + Point { line, col: Column(0) } if line == self.grid.len() - 1 => None, Point { col: Column(0), .. } => { self.cur.line += 1; self.cur.col = num_cols - Column(1); diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 2f34b3f0..5d6cb936 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -198,7 +198,7 @@ impl Storage { /// Compute actual index in underlying storage given the requested index. fn compute_index(&self, requested: usize) -> usize { - debug_assert!(requested < self.inner.len()); + debug_assert!(requested < self.len); let zeroed = requested + self.zero; // This part is critical for performance, diff --git a/src/term/mod.rs b/src/term/mod.rs index 6de8afac..67d34be1 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -38,6 +38,9 @@ use self::cell::LineLength; impl selection::SemanticSearch for Term { fn semantic_search_left(&self, mut point: Point) -> Point { + // Limit the starting point to the last line in the history + point.line = min(point.line, self.grid.len() - 1); + let mut iter = self.grid.iter_from(point); let last_col = self.grid.num_cols() - Column(1); @@ -57,6 +60,9 @@ impl selection::SemanticSearch for Term { } fn semantic_search_right(&self, mut point: Point) -> Point { + // Limit the starting point to the last line in the history + point.line = min(point.line, self.grid.len() - 1); + let mut iter = self.grid.iter_from(point); let last_col = self.grid.num_cols() - Column(1); @@ -1017,7 +1023,7 @@ impl Term { } // Starting line - res.append(&self.grid, start.line, Column(0)..(start.col + 1)); + res.append(&self.grid, start.line, Column(0)..start.col); } } -- cgit From cde1d8d1edb0c8001a28a2a674c46b5a581439db Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 4 Jul 2018 21:40:16 +0200 Subject: Fix incorrect cell side in selection Previously the cell side of a selection with the mouse outside of the grid has been calculated by setting the `Side` to `Right` whenever the `X` of the mouse is bigger or equal to `window_width - padding_x`. However since the grid doesn't perfectly fit the window in most cases, there was an additional few pixels where the `Side` would be `Left`, resulting in the selection jumping around. To fix this the additional padding due to not perfectly fitting window size has been included in the calculation. The `X` position is now checked to be bigger or equal to `width - padding_x - extra_padding_x`. An important note is that this will need changing when the grid is centered inside the window, so extra padding is split up evenly. Once that change is merged the calculation required will be `width - padding_x - extra_padding_x / 2.`. This fixes #1412. --- src/input.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/input.rs b/src/input.rs index 90384198..2d647114 100644 --- a/src/input.rs +++ b/src/input.rs @@ -304,9 +304,11 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize; let half_cell_width = (size_info.cell_width / 2.0) as usize; + let additional_padding = (size_info.width - size_info.padding_x * 2.) % size_info.cell_width; + let end_of_grid = size_info.width - size_info.padding_x - additional_padding; let cell_side = if cell_x > half_cell_width // Edge case when mouse leaves the window - || x as f32 >= size_info.width - size_info.padding_x + || x as f32 >= end_of_grid { Side::Right } else { -- cgit From b05ad74fe6d42ce0f913e02ef633ca119fc0b43e Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 6 Jul 2018 07:45:10 -0700 Subject: Disable scroll buffer for "alt" Grid The scroll history size for the alternative grid (used by fullscreen apps such as vim and tmux) is now forced to zero. There are two motivations for this change: 1. According to the literature, the alt screen should not have scroll history. 2. Reduce memory consumption by only allocating the single scroll history. In the future, it may be desirable to support a configuration option to enable a scroll buffer for the alt screen. By launching without this feature, we can delay a decision about whether to officially support this or not. --- src/term/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/term/mod.rs b/src/term/mod.rs index 67d34be1..9f8506d1 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -849,13 +849,13 @@ impl Term { let history_size = config.scrolling().history as usize; let grid = Grid::new(num_lines, num_cols, history_size, Cell::default()); + let alt = Grid::new(num_lines, num_cols, 0 /* scroll history */, Cell::default()); let tabspaces = config.tabspaces(); let tabs = IndexRange::from(Column(0)..grid.num_cols()) .map(|i| (*i as usize) % tabspaces == 0) .collect::>(); - let alt = grid.clone(); let scroll_region = Line(0)..grid.num_lines(); Term { -- cgit From f50ca1a54c94fe324d22d985c1acae1ff7c16a80 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 21 Jul 2018 17:17:41 +0000 Subject: Scrollback cleanup There were some unneeded codeblocks and TODO/XXX comments in the code that have been removed. All issues marked with TODO/XXX have either been already resolved or tracking issues exist. --- src/ansi.rs | 8 ++-- src/cli.rs | 22 +++++---- src/config.rs | 60 ++++++++++++++++++++--- src/display.rs | 13 +++-- src/event.rs | 34 ++++++------- src/grid/mod.rs | 25 ++++------ src/grid/row.rs | 3 +- src/grid/storage.rs | 22 ++++----- src/grid/tests.rs | 4 +- src/index.rs | 22 ++++----- src/input.rs | 51 ++++++++++---------- src/lib.rs | 12 ++--- src/locale.rs | 1 + src/logging.rs | 2 +- src/main.rs | 11 ++--- src/renderer/mod.rs | 14 +++--- src/selection.rs | 11 ++--- src/term/mod.rs | 136 ++++++++++++++++++++++++++++++++++++---------------- src/tty.rs | 2 +- src/util.rs | 19 ++------ src/window.rs | 30 ++++++++++-- 21 files changed, 298 insertions(+), 204 deletions(-) (limited to 'src') diff --git a/src/ansi.rs b/src/ansi.rs index dbc361a8..e37e25f1 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -561,8 +561,8 @@ pub enum NamedColor { } impl NamedColor { - pub fn to_bright(&self) -> Self { - match *self { + pub fn to_bright(self) -> Self { + match self { NamedColor::Black => NamedColor::BrightBlack, NamedColor::Red => NamedColor::BrightRed, NamedColor::Green => NamedColor::BrightGreen, @@ -583,8 +583,8 @@ impl NamedColor { } } - pub fn to_dim(&self) -> Self { - match *self { + pub fn to_dim(self) -> Self { + match self { NamedColor::Black => NamedColor::DimBlack, NamedColor::Red => NamedColor::DimRed, NamedColor::Green => NamedColor::DimGreen, diff --git a/src/cli.rs b/src/cli.rs index 532c46cd..0a36179a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -15,18 +15,18 @@ extern crate log; use clap::{Arg, App}; use index::{Line, Column}; use config::{Dimensions, Shell}; +use window::{DEFAULT_TITLE, DEFAULT_CLASS}; use std::path::{Path, PathBuf}; use std::borrow::Cow; -const DEFAULT_TITLE: &str = "Alacritty"; - /// Options specified on the command line pub struct Options { pub live_config_reload: Option, pub print_events: bool, pub ref_test: bool, pub dimensions: Option, - pub title: String, + pub title: Option, + pub class: Option, pub log_level: log::LevelFilter, pub command: Option>, pub working_dir: Option, @@ -40,7 +40,8 @@ impl Default for Options { print_events: false, ref_test: false, dimensions: None, - title: DEFAULT_TITLE.to_owned(), + title: None, + class: None, log_level: log::LevelFilter::Warn, command: None, working_dir: None, @@ -79,8 +80,12 @@ impl Options { .arg(Arg::with_name("title") .long("title") .short("t") - .default_value(DEFAULT_TITLE) - .help("Defines the window title")) + .takes_value(true) + .help(&format!("Defines the window title [default: {}]", DEFAULT_TITLE))) + .arg(Arg::with_name("class") + .long("class") + .takes_value(true) + .help(&format!("Defines window class on X11 [default: {}]", DEFAULT_CLASS))) .arg(Arg::with_name("q") .short("q") .multiple(true) @@ -132,9 +137,8 @@ impl Options { } } - if let Some(title) = matches.value_of("title") { - options.title = title.to_owned(); - } + options.class = matches.value_of("class").map(|c| c.to_owned()); + options.title = matches.value_of("title").map(|t| t.to_owned()); match matches.occurrences_of("q") { 0 => {}, diff --git a/src/config.rs b/src/config.rs index b9c943ee..7d8600f4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,6 +23,7 @@ use notify::{Watcher, watcher, DebouncedEvent, RecursiveMode}; use glutin::ModifiersState; +use cli::Options; use input::{Action, Binding, MouseBinding, KeyBinding}; use index::{Line, Column}; use ansi::CursorStyle; @@ -224,7 +225,7 @@ impl Alpha { } #[inline] - pub fn get(&self) -> f32 { + pub fn get(self) -> f32 { self.0 } @@ -1446,6 +1447,14 @@ impl Config { Ok(config) } + /// Overrides the `dynamic_title` configuration based on `--title`. + pub fn update_dynamic_title(mut self, options: &Options) -> Self { + if options.title.is_some() { + self.dynamic_title = false; + } + self + } + fn read_file>(path: P) -> Result { let mut f = fs::File::open(path)?; let mut contents = String::new(); @@ -1609,7 +1618,10 @@ pub struct Font { glyph_offset: Delta, #[serde(default="true_bool", deserialize_with = "default_true_bool")] - use_thin_strokes: bool + use_thin_strokes: bool, + + #[serde(default="true_bool", deserialize_with = "default_true_bool")] + scale_with_dpi: bool, } fn default_bold_desc() -> FontDescription { @@ -1662,6 +1674,11 @@ impl Font { .. self } } + + /// Check whether dpi should be applied + pub fn scale_with_dpi(&self) -> bool { + self.scale_with_dpi + } } #[cfg(target_os = "macos")] @@ -1673,8 +1690,9 @@ impl Default for Font { italic: FontDescription::new_with_family("Menlo"), size: Size::new(11.0), use_thin_strokes: true, + scale_with_dpi: true, + glyph_offset: Default::default(), offset: Default::default(), - glyph_offset: Default::default() } } } @@ -1688,8 +1706,9 @@ impl Default for Font { italic: FontDescription::new_with_family("monospace"), size: Size::new(11.0), use_thin_strokes: false, + scale_with_dpi: true, + glyph_offset: Default::default(), offset: Default::default(), - glyph_offset: Default::default() } } } @@ -1771,6 +1790,7 @@ impl Monitor { #[cfg(test)] mod tests { + use cli::Options; use super::Config; #[cfg(target_os="macos")] @@ -1791,9 +1811,29 @@ mod tests { // Sanity check that key bindings are being parsed assert!(!config.key_bindings.is_empty()); } + + #[test] + fn dynamic_title_ignoring_options_by_default() { + let config: Config = ::serde_yaml::from_str(ALACRITTY_YML) + .expect("deserialize config"); + let old_dynamic_title = config.dynamic_title; + let options = Options::default(); + let config = config.update_dynamic_title(&options); + assert_eq!(old_dynamic_title, config.dynamic_title); + } + + #[test] + fn dynamic_title_overridden_by_options() { + let config: Config = ::serde_yaml::from_str(ALACRITTY_YML) + .expect("deserialize config"); + let mut options = Options::default(); + options.title = Some("foo".to_owned()); + let config = config.update_dynamic_title(&options); + assert!(!config.dynamic_title); + } } -#[cfg_attr(feature = "clippy", allow(enum_variant_names))] +#[cfg_attr(feature = "cargo-clippy", allow(enum_variant_names))] #[derive(Deserialize, Copy, Clone)] enum Key { Key1, @@ -1947,13 +1987,16 @@ enum Key { WebStop, Yen, Caret, + Copy, + Paste, + Cut, } impl Key { - fn to_glutin_key(&self) -> ::glutin::VirtualKeyCode { + fn to_glutin_key(self) -> ::glutin::VirtualKeyCode { use ::glutin::VirtualKeyCode::*; // Thank you, vim macros! - match *self { + match self { Key::Key1 => Key1, Key::Key2 => Key2, Key::Key3 => Key3, @@ -2105,6 +2148,9 @@ impl Key { Key::WebStop => WebStop, Key::Yen => Yen, Key::Caret => Caret, + Key::Copy => Copy, + Key::Paste => Paste, + Key::Cut => Cut, } } } diff --git a/src/display.rs b/src/display.rs index 5e540a8b..4c3ffed1 100644 --- a/src/display.rs +++ b/src/display.rs @@ -136,12 +136,16 @@ impl Display { let render_timer = config.render_timer(); // Create the window where Alacritty will be displayed - let mut window = Window::new(&options.title, config.window())?; + let mut window = Window::new(&options, config.window())?; // get window properties for initializing the other subsystems let mut viewport_size = window.inner_size_pixels() .expect("glutin returns window size"); - let dpr = window.hidpi_factor(); + let dpr = if config.font().scale_with_dpi() { + window.hidpi_factor() + } else { + 1.0 + }; info!("device_pixel_ratio: {}", dpr); @@ -149,7 +153,7 @@ impl Display { let mut renderer = QuadRenderer::new(config, viewport_size)?; let (glyph_cache, cell_width, cell_height) = - Self::new_glyph_cache(&window, &mut renderer, config)?; + Self::new_glyph_cache(dpr, &mut renderer, config)?; let dimensions = options.dimensions() @@ -210,11 +214,10 @@ impl Display { }) } - fn new_glyph_cache(window : &Window, renderer : &mut QuadRenderer, config: &Config) + fn new_glyph_cache(dpr: f32, renderer: &mut QuadRenderer, config: &Config) -> Result<(GlyphCache, f32, f32), Error> { let font = config.font().clone(); - let dpr = window.hidpi_factor(); let rasterizer = font::Rasterizer::new(dpr, config.use_thin_strokes())?; // Initialize glyph cache diff --git a/src/event.rs b/src/event.rs index ff95c880..3af417bd 100644 --- a/src/event.rs +++ b/src/event.rs @@ -59,17 +59,15 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { } fn copy_selection(&self, buffer: ::copypasta::Buffer) { - self.terminal - .selection_to_string() - .map(|selected| { - if !selected.is_empty() { - Clipboard::new() - .and_then(|mut clipboard| clipboard.store(selected, buffer)) - .unwrap_or_else(|err| { - warn!("Error storing selection to clipboard. {}", Red(err)); - }); - } - }); + if let Some(selected) = self.terminal.selection_to_string() { + if !selected.is_empty() { + Clipboard::new() + .and_then(|mut clipboard| clipboard.store(selected, buffer)) + .unwrap_or_else(|err| { + warn!("Error storing selection to clipboard. {}", Red(err)); + }); + } + } } fn clear_selection(&mut self) { @@ -113,7 +111,7 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize) } - fn change_font_size(&mut self, delta: i8) { + fn change_font_size(&mut self, delta: f32) { self.terminal.change_font_size(delta); } @@ -241,7 +239,7 @@ impl Processor { resize_tx, ref_test, mouse: Default::default(), - size_info: size_info, + size_info, hide_cursor_when_typing: config.hide_cursor_when_typing(), hide_cursor: false, received_count: 0, @@ -298,7 +296,7 @@ impl Processor { }, KeyboardInput { input, .. } => { let glutin::KeyboardInput { state, virtual_keycode, modifiers, .. } = input; - processor.process_key(state, virtual_keycode, &modifiers); + processor.process_key(state, virtual_keycode, modifiers); if state == ElementState::Pressed { // Hide cursor while typing *hide_cursor = true; @@ -308,9 +306,11 @@ impl Processor { processor.received_char(c); }, MouseInput { state, button, modifiers, .. } => { - *hide_cursor = false; - processor.mouse_input(state, button, modifiers); - processor.ctx.terminal.dirty = true; + if *window_is_focused { + *hide_cursor = false; + processor.mouse_input(state, button, modifiers); + processor.ctx.terminal.dirty = true; + } }, CursorMoved { position: (x, y), modifiers, .. } => { let x = limit(x as i32, 0, processor.ctx.size_info.width as i32); diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 949a5ed5..ef587ffd 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -102,6 +102,7 @@ pub struct GridIterator<'a, T: 'a> { pub cur: Point, } +#[derive(Copy, Clone)] pub enum Scroll { Lines(isize), PageUp, @@ -396,6 +397,7 @@ impl Grid { } } +#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))] impl Grid { #[inline] pub fn num_lines(&self) -> index::Line { @@ -437,15 +439,6 @@ impl Grid { pub fn contains(&self, point: &Point) -> bool { self.lines > point.line && self.cols > point.col } - - // /// Swap two lines in the grid - // /// - // /// This could have used slice::swap internally, but we are able to have - // /// better error messages by doing the bounds checking ourselves. - // #[inline] - // pub fn swap_lines(&mut self, src: index::Line, dst: index::Line) { - // self.raw.swap(*src, *dst); - // } } impl<'a, T> Iterator for GridIterator<'a, T> { @@ -566,10 +559,10 @@ impl<'a, T> RegionMut<'a, T> { pub trait IndexRegion { /// Get an immutable region of Self - fn region<'a>(&'a self, _: I) -> Region<'a, T>; + fn region(&self, _: I) -> Region; /// Get a mutable region of Self - fn region_mut<'a>(&'a mut self, _: I) -> RegionMut<'a, T>; + fn region_mut(&mut self, _: I) -> RegionMut; } impl IndexRegion, T> for Grid { @@ -772,13 +765,11 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { // Update line/col to point to next item self.col += 1; - if self.col == self.grid.num_cols() { - if self.offset != self.limit { - self.offset -= 1; + if self.col == self.grid.num_cols() && self.offset != self.limit { + self.offset -= 1; - self.col = Column(0); - self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); - } + self.col = Column(0); + self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); } item diff --git a/src/grid/row.rs b/src/grid/row.rs index ea8804a7..69a4f2b2 100644 --- a/src/grid/row.rs +++ b/src/grid/row.rs @@ -71,6 +71,7 @@ impl Row { } } +#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))] impl Row { pub fn shrink(&mut self, cols: Column) { while self.len() != *cols { @@ -84,7 +85,7 @@ impl Row { self.inner.len() } - pub fn iter<'a>(&'a self) -> slice::Iter<'a, T> { + pub fn iter(&self) -> slice::Iter { self.inner.iter() } } diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 5d6cb936..6a453da6 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -68,12 +68,12 @@ impl ::std::cmp::PartialEq for Storage { // Smaller Zero (3): // 7 8 9 | 0 1 2 3 | 4 5 6 // C3 C3 C3 | C1 C1 C1 C1 | C2 C2 C2 - &bigger.inner[bigger_zero..] - == &smaller.inner[smaller_zero..smaller_zero + (len - bigger_zero)] - && &bigger.inner[..bigger_zero - smaller_zero] - == &smaller.inner[smaller_zero + (len - bigger_zero)..] - && &bigger.inner[bigger_zero - smaller_zero..bigger_zero] - == &smaller.inner[..smaller_zero] + bigger.inner[bigger_zero..] + == smaller.inner[smaller_zero..smaller_zero + (len - bigger_zero)] + && bigger.inner[..bigger_zero - smaller_zero] + == smaller.inner[smaller_zero + (len - bigger_zero)..] + && bigger.inner[bigger_zero - smaller_zero..bigger_zero] + == smaller.inner[..smaller_zero] } } @@ -84,11 +84,8 @@ impl Storage { T: Clone, { // Allocate all lines in the buffer, including scrollback history - // - // TODO (jwilm) Allocating each line at this point is expensive and - // delays startup. A nice solution might be having `Row` delay - // allocation until it's actually used. let inner = vec![template; cap]; + Storage { inner, zero: 0, @@ -124,7 +121,7 @@ impl Storage { } /// Grow the number of lines in the buffer, filling new lines with the template - pub fn grow_lines(&mut self, growage: usize, template_row: Row) + fn grow_lines(&mut self, growage: usize, template_row: Row) where T: Clone, { @@ -225,9 +222,6 @@ impl Storage { /// The default implementation from swap generates 8 movups and 4 movaps /// instructions. This implementation achieves the swap in only 8 movups /// instructions. - /// - // TODO Once specialization is available, Storage can be fully generic - // again instead of enforcing inner: Vec>. pub fn swap(&mut self, a: usize, b: usize) { debug_assert!(::std::mem::size_of::>() == 32); diff --git a/src/grid/tests.rs b/src/grid/tests.rs index 435f0c3d..e136e3b3 100644 --- a/src/grid/tests.rs +++ b/src/grid/tests.rs @@ -20,7 +20,7 @@ use index::{Point, Line, Column}; // Scroll up moves lines upwards #[test] fn scroll_up() { - println!(""); + println!(); let mut grid = Grid::new(Line(10), Column(1), 0, 0); for i in 0..10 { @@ -58,7 +58,7 @@ fn scroll_up() { // Scroll down moves lines downwards #[test] fn scroll_down() { - println!(""); + println!(); let mut grid = Grid::new(Line(10), Column(1), 0, 0); for i in 0..10 { diff --git a/src/index.rs b/src/index.rs index ada5f5b1..ab8e7416 100644 --- a/src/index.rs +++ b/src/index.rs @@ -270,7 +270,7 @@ macro_rules! inclusive { match *self { Empty { .. } => (0, Some(0)), - NonEmpty { ref start, ref end } => { + NonEmpty { start, end } => { let added = $steps_add_one(start, end); match added { Some(hint) => (hint.saturating_add(1), hint.checked_add(1)), @@ -283,9 +283,9 @@ macro_rules! inclusive { } } -fn steps_add_one_u8(start: &u8, end: &u8) -> Option { - if *start < *end { - Some((*end - *start) as usize) +fn steps_add_one_u8(start: u8, end: u8) -> Option { + if start < end { + Some((end - start) as usize) } else { None } @@ -330,11 +330,11 @@ macro_rules! ops { impl $ty { #[inline] #[allow(trivial_numeric_casts)] - fn steps_between(start: &$ty, end: &$ty, by: &$ty) -> Option { - if *by == $construct(0) { return None; } - if *start < *end { + fn steps_between(start: $ty, end: $ty, by: $ty) -> Option { + if by == $construct(0) { return None; } + if start < end { // Note: We assume $t <= usize here - let diff = (*end - *start).0; + let diff = (end - start).0; let by = by.0; if diff % by > 0 { Some(diff / by + 1) @@ -347,8 +347,8 @@ macro_rules! ops { } #[inline] - fn steps_between_by_one(start: &$ty, end: &$ty) -> Option { - Self::steps_between(start, end, &$construct(1)) + fn steps_between_by_one(start: $ty, end: $ty) -> Option { + Self::steps_between(start, end, $construct(1)) } } @@ -366,7 +366,7 @@ macro_rules! ops { } #[inline] fn size_hint(&self) -> (usize, Option) { - match Self::Item::steps_between_by_one(&self.0.start, &self.0.end) { + match Self::Item::steps_between_by_one(self.0.start, self.0.end) { Some(hint) => (hint, Some(hint)), None => (0, None) } diff --git a/src/input.rs b/src/input.rs index 2d647114..f2337d6a 100644 --- a/src/input.rs +++ b/src/input.rs @@ -35,6 +35,8 @@ use term::SizeInfo; use term::mode::TermMode; use util::fmt::Red; +pub const FONT_SIZE_STEP: f32 = 0.5; + /// Processes input from glutin. /// /// An escape sequence may be emitted in case specific keys or key combinations @@ -64,7 +66,7 @@ pub trait ActionContext { fn received_count(&mut self) -> &mut usize; fn suppress_chars(&mut self) -> &mut bool; fn last_modifiers(&mut self) -> &mut ModifiersState; - fn change_font_size(&mut self, delta: i8); + fn change_font_size(&mut self, delta: f32); fn reset_font_size(&mut self); fn scroll(&mut self, scroll: Scroll); } @@ -103,15 +105,15 @@ impl Binding { fn is_triggered_by( &self, mode: TermMode, - mods: &ModifiersState, + mods: ModifiersState, input: &T ) -> bool { // Check input first since bindings are stored in one big list. This is // the most likely item to fail so prioritizing it here allows more // checks to be short circuited. self.trigger == *input && - self.mode_matches(&mode) && - self.not_mode_matches(&mode) && + self.mode_matches(mode) && + self.not_mode_matches(mode) && self.mods_match(mods) } } @@ -124,12 +126,12 @@ impl Binding { } #[inline] - fn mode_matches(&self, mode: &TermMode) -> bool { + fn mode_matches(&self, mode: TermMode) -> bool { self.mode.is_empty() || mode.intersects(self.mode) } #[inline] - fn not_mode_matches(&self, mode: &TermMode) -> bool { + fn not_mode_matches(&self, mode: TermMode) -> bool { self.notmode.is_empty() || !mode.intersects(self.notmode) } @@ -137,10 +139,10 @@ impl Binding { /// /// Optimized to use single check instead of four (one per modifier) #[inline] - fn mods_match(&self, mods: &ModifiersState) -> bool { - debug_assert!(4 == mem::size_of::()); + fn mods_match(&self, mods: ModifiersState) -> bool { + assert_eq_size!(ModifiersState, u32); unsafe { - mem::transmute_copy::<_, u32>(&self.mods) == mem::transmute_copy::<_, u32>(mods) + mem::transmute_copy::<_, u32>(&self.mods) == mem::transmute_copy::<_, u32>(&mods) } } } @@ -243,10 +245,10 @@ impl Action { ::std::process::exit(0); }, Action::IncreaseFontSize => { - ctx.change_font_size(1); + ctx.change_font_size(FONT_SIZE_STEP); }, Action::DecreaseFontSize => { - ctx.change_font_size(-1); + ctx.change_font_size(-FONT_SIZE_STEP); } Action::ResetFontSize => { ctx.reset_font_size(); @@ -507,19 +509,19 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { let mouse_modes = TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; // Make sure the new and deprecated setting are both allowed - let faux_scrollback_lines = self.mouse_config + let faux_scrolling_lines = self.mouse_config .faux_scrollback_lines .unwrap_or(self.scrolling_config.faux_multiplier as usize); if self.ctx.terminal_mode().intersects(mouse_modes) { self.mouse_report(code, ElementState::Pressed, modifiers); } else if self.ctx.terminal_mode().contains(TermMode::ALT_SCREEN) - && faux_scrollback_lines > 0 && !modifiers.shift + && faux_scrolling_lines > 0 && !modifiers.shift { // Faux scrolling let cmd = code + 1; // 64 + 1 = A, 65 + 1 = B - let mut content = Vec::with_capacity(faux_scrollback_lines as usize * 3); - for _ in 0..faux_scrollback_lines { + let mut content = Vec::with_capacity(faux_scrolling_lines as usize * 3); + for _ in 0..faux_scrolling_lines { content.push(0x1b); content.push(b'O'); content.push(cmd); @@ -527,7 +529,8 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { self.ctx.write_to_pty(content); } else { for _ in 0..scroll_multiplier { - self.ctx.scroll(Scroll::Lines(-((code as isize) * 2 - 129))); + // Transform the reported button codes 64 and 65 into 1 and -1 lines to scroll + self.ctx.scroll(Scroll::Lines(-(code as isize * 2 - 129))); } } } @@ -567,7 +570,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { return; } - self.process_mouse_bindings(&ModifiersState::default(), button); + self.process_mouse_bindings(ModifiersState::default(), button); } /// Process key input @@ -577,11 +580,11 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { &mut self, state: ElementState, key: Option, - mods: &ModifiersState, + mods: ModifiersState, ) { match (key, state) { (Some(key), ElementState::Pressed) => { - *self.ctx.last_modifiers() = *mods; + *self.ctx.last_modifiers() = mods; *self.ctx.received_count() = 0; *self.ctx.suppress_chars() = false; @@ -623,7 +626,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { /// for its action to be executed. /// /// Returns true if an action is executed. - fn process_key_bindings(&mut self, mods: &ModifiersState, key: VirtualKeyCode) -> bool { + fn process_key_bindings(&mut self, mods: ModifiersState, key: VirtualKeyCode) -> bool { for binding in self.key_bindings { if binding.is_triggered_by(self.ctx.terminal_mode(), mods, &key) { // binding was triggered; run the action @@ -641,7 +644,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { /// for its action to be executed. /// /// Returns true if an action is executed. - fn process_mouse_bindings(&mut self, mods: &ModifiersState, button: MouseButton) -> bool { + fn process_mouse_bindings(&mut self, mods: ModifiersState, button: MouseButton) -> bool { for binding in self.mouse_bindings { if binding.is_triggered_by(self.ctx.terminal_mode(), mods, &button) { // binding was triggered; run the action @@ -741,7 +744,7 @@ mod tests { fn last_modifiers(&mut self) -> &mut ModifiersState { &mut self.last_modifiers } - fn change_font_size(&mut self, _delta: i8) { + fn change_font_size(&mut self, _delta: f32) { } fn reset_font_size(&mut self) { } @@ -824,9 +827,9 @@ mod tests { #[test] fn $name() { if $triggers { - assert!($binding.is_triggered_by($mode, &$mods, &KEY)); + assert!($binding.is_triggered_by($mode, $mods, &KEY)); } else { - assert!(!$binding.is_triggered_by($mode, &$mods, &KEY)); + assert!(!$binding.is_triggered_by($mode, $mods, &KEY)); } } } diff --git a/src/lib.rs b/src/lib.rs index a8e18451..d9006fe0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,12 +13,7 @@ // limitations under the License. // //! Alacritty - The GPU Enhanced Terminal -#![cfg_attr(feature = "clippy", feature(plugin))] -#![cfg_attr(feature = "clippy", plugin(clippy))] -#![cfg_attr(feature = "clippy", deny(clippy))] -#![cfg_attr(feature = "clippy", deny(enum_glob_use))] -#![cfg_attr(feature = "clippy", deny(if_not_else))] -#![cfg_attr(feature = "clippy", deny(wrong_pub_self_convention))] +#![cfg_attr(feature = "cargo-clippy", deny(clippy, if_not_else, enum_glob_use, wrong_pub_self_convention))] #![cfg_attr(feature = "nightly", feature(core_intrinsics))] #![cfg_attr(all(test, feature = "bench"), feature(test))] @@ -26,6 +21,7 @@ #[macro_use] extern crate clap; #[macro_use] extern crate log; #[macro_use] extern crate serde_derive; +#[macro_use] extern crate static_assertions; #[cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))] extern crate x11_dl; @@ -115,11 +111,9 @@ impl Mul for Rgb { } -#[cfg_attr(feature = "clippy", allow(too_many_arguments))] -#[cfg_attr(feature = "clippy", allow(doc_markdown))] -#[cfg_attr(feature = "clippy", allow(unreadable_literal))] #[allow(unused_mut)] pub mod gl { #![allow(non_upper_case_globals)] + #![cfg_attr(feature = "cargo-clippy", allow(clippy))] include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); } diff --git a/src/locale.rs b/src/locale.rs index 9fcd2db2..bea68cad 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#![cfg_attr(feature = "cargo-clippy", allow(let_unit_value))] #![cfg(target_os = "macos")] use libc::{LC_CTYPE, setlocale}; use std::ffi::{CString, CStr}; diff --git a/src/logging.rs b/src/logging.rs index 5691eb78..a691778a 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -29,7 +29,7 @@ pub struct Logger { impl Logger { // False positive, see: https://github.com/rust-lang-nursery/rust-clippy/issues/734 - #[cfg_attr(feature = "clippy", allow(new_ret_no_self))] + #[cfg_attr(feature = "cargo-clippy", allow(new_ret_no_self))] pub fn new(output: T, level: log::LevelFilter) -> Logger> { log::set_max_level(level); Logger { diff --git a/src/main.rs b/src/main.rs index 77cb4949..1508103c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,12 +13,7 @@ // limitations under the License. // //! Alacritty - The GPU Enhanced Terminal -#![cfg_attr(feature = "clippy", feature(plugin))] -#![cfg_attr(feature = "clippy", plugin(clippy))] -#![cfg_attr(feature = "clippy", deny(clippy))] -#![cfg_attr(feature = "clippy", deny(enum_glob_use))] -#![cfg_attr(feature = "clippy", deny(if_not_else))] -#![cfg_attr(feature = "clippy", deny(wrong_pub_self_convention))] +#![cfg_attr(feature = "cargo-clippy", deny(clippy, if_not_else, enum_glob_use, wrong_pub_self_convention))] #![cfg_attr(feature = "nightly", feature(core_intrinsics))] #![cfg_attr(all(test, feature = "bench"), feature(test))] @@ -49,7 +44,7 @@ use alacritty::util::fmt::Red; fn main() { // Load command line options and config let options = cli::Options::load(); - let config = load_config(&options); + let config = load_config(&options).update_dynamic_title(&options); // Switch to home directory #[cfg(target_os = "macos")] @@ -183,7 +178,7 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box> { .as_ref() .and_then(|monitor| monitor.pending_config()) { - config = new_config; + config = new_config.update_dynamic_title(&options); display.update_config(&config); processor.update_config(&config); terminal.update_config(&config); diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ed61f7f5..00ac9eb2 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -183,7 +183,7 @@ impl GlyphCache { // Need to load at least one glyph for the face before calling metrics. // The glyph requested here ('m' at the time of writing) has no special // meaning. - rasterizer.get_glyph(&GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; let metrics = rasterizer.metrics(regular)?; let mut cache = GlyphCache { @@ -211,7 +211,7 @@ impl GlyphCache { ) { let size = self.font_size; for i in RangeInclusive::new(32u8, 128u8) { - self.get(&GlyphKey { + self.get(GlyphKey { font_key: font, c: i as char, size, @@ -273,14 +273,14 @@ impl GlyphCache { .expect("metrics load since font is loaded at glyph cache creation") } - pub fn get<'a, L>(&'a mut self, glyph_key: &GlyphKey, loader: &mut L) -> &'a Glyph + pub fn get<'a, L>(&'a mut self, glyph_key: GlyphKey, loader: &mut L) -> &'a Glyph where L: LoadGlyph { let glyph_offset = self.glyph_offset; let rasterizer = &mut self.rasterizer; let metrics = &self.metrics; self.cache - .entry(*glyph_key) + .entry(glyph_key) .or_insert_with(|| { let mut rasterized = rasterizer.get_glyph(glyph_key) .unwrap_or_else(|_| Default::default()); @@ -306,7 +306,7 @@ impl GlyphCache { let font = font.to_owned().with_size(size); info!("Font size changed: {:?}", font.size); let (regular, bold, italic) = Self::compute_font_keys(&font, &mut self.rasterizer)?; - self.rasterizer.get_glyph(&GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; let metrics = self.rasterizer.metrics(regular)?; self.font_size = font.size; @@ -842,7 +842,7 @@ impl<'a> RenderApi<'a> { // Add cell to batch { - let glyph = glyph_cache.get(&glyph_key, self); + let glyph = glyph_cache.get(glyph_key, self); self.add_render_item(&cell, glyph); } @@ -856,7 +856,7 @@ impl<'a> RenderApi<'a> { c: '_' }; - let underscore = glyph_cache.get(&glyph_key, self); + let underscore = glyph_cache.get(glyph_key, self); self.add_render_item(&cell, underscore); } } diff --git a/src/selection.rs b/src/selection.rs index dca16e26..702599e3 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -157,8 +157,8 @@ impl Selection { Selection::Semantic { ref region } => { Selection::span_semantic(grid, region) }, - Selection::Lines { ref region, ref initial_line } => { - Selection::span_lines(grid, region, *initial_line) + Selection::Lines { ref region, initial_line } => { + Selection::span_lines(grid, region, initial_line) } } } @@ -253,13 +253,12 @@ impl Selection { // Remove last cell if selection ends to the left of a cell if front_side == Side::Left && start != end { - if front.col != Column(0) { - front.col -= 1; - } // Special case when selection starts to left of first cell - else { + if front.col == Column(0) { front.col = cols - 1; front.line += 1; + } else { + front.col -= 1; } } diff --git a/src/term/mod.rs b/src/term/mod.rs index 9f8506d1..3cdd5ff3 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -30,6 +30,7 @@ use selection::{self, Selection, Locations}; use config::{Config, VisualBellAnimation}; use {MouseCursor, Rgb}; use copypasta::{Clipboard, Load, Store}; +use input::FONT_SIZE_STEP; pub mod cell; pub mod color; @@ -129,7 +130,7 @@ impl<'a> RenderableCellsIter<'a> { let inner = grid.display_iter(); let mut selection_range = None; - selection.map(|loc| { + if let Some(loc) = selection { // Get on-screen lines of the selection's locations let start_line = grid.buffer_line_to_visible(loc.start.line); let end_line = grid.buffer_line_to_visible(loc.end.line); @@ -171,17 +172,17 @@ impl<'a> RenderableCellsIter<'a> { // Update the selection selection_range = Some(RangeInclusive::new(start, end)); } - }); + } RenderableCellsIter { - cursor: cursor, - cursor_offset: cursor_offset, - grid: grid, - inner: inner, - mode: mode, + cursor, + cursor_offset, + grid, + inner, + mode, selection: selection_range, - config: config, - colors: colors, + config, + colors, cursor_cells: ArrayDeque::new(), }.initialize(cursor_style) } @@ -313,9 +314,9 @@ impl<'a> RenderableCellsIter<'a> { self.mode.contains(mode::TermMode::SHOW_CURSOR) && self.grid.contains(self.cursor) } - fn compute_fg_rgb(&self, fg: &Color, cell: &Cell) -> Rgb { + fn compute_fg_rgb(&self, fg: Color, cell: &Cell) -> Rgb { use self::cell::Flags; - match *fg { + match fg { Color::Spec(rgb) => rgb, Color::Named(ansi) => { match (self.config.draw_bold_text_with_bright_colors(), cell.flags & Flags::DIM_BOLD) { @@ -346,15 +347,15 @@ impl<'a> RenderableCellsIter<'a> { } #[inline] - fn compute_bg_alpha(&self, bg: &Color) -> f32 { - match *bg { + fn compute_bg_alpha(&self, bg: Color) -> f32 { + match bg { Color::Named(NamedColor::Background) => 0.0, _ => 1.0 } } - fn compute_bg_rgb(&self, bg: &Color) -> Rgb { - match *bg { + fn compute_bg_rgb(&self, bg: Color) -> Rgb { + match bg { Color::Spec(rgb) => rgb, Color::Named(ansi) => self.colors[ansi], Color::Indexed(idx) => self.colors[idx], @@ -403,8 +404,6 @@ impl<'a> Iterator for RenderableCellsIter<'a> { let index = Linear(cell.line.0 * self.grid.num_cols().0 + cell.column.0); - // XXX (jwilm) selection temp disabled - // let selected = self.selection.as_ref() .map(|range| range.contains_(index)) .unwrap_or(false); @@ -429,13 +428,13 @@ impl<'a> Iterator for RenderableCellsIter<'a> { fg_rgb = self.colors[NamedColor::Background]; bg_alpha = 1.0 } else { - bg_rgb = self.compute_fg_rgb(&cell.fg, &cell); - fg_rgb = self.compute_bg_rgb(&cell.bg); + bg_rgb = self.compute_fg_rgb(cell.fg, &cell); + fg_rgb = self.compute_bg_rgb(cell.bg); } } else { - fg_rgb = self.compute_fg_rgb(&cell.fg, &cell); - bg_rgb = self.compute_bg_rgb(&cell.bg); - bg_alpha = self.compute_bg_alpha(&cell.bg); + fg_rgb = self.compute_fg_rgb(cell.fg, &cell); + bg_rgb = self.compute_bg_rgb(cell.bg); + bg_alpha = self.compute_bg_alpha(cell.bg); } return Some(RenderableCell { @@ -445,7 +444,7 @@ impl<'a> Iterator for RenderableCellsIter<'a> { c: cell.c, fg: fg_rgb, bg: bg_rgb, - bg_alpha: bg_alpha, + bg_alpha, }) } } @@ -890,10 +889,10 @@ impl Term { } } - pub fn change_font_size(&mut self, delta: i8) { - // Saturating addition with minimum font size 1 - let new_size = self.font_size + Size::new(f32::from(delta)); - self.font_size = max(new_size, Size::new(1.)); + pub fn change_font_size(&mut self, delta: f32) { + // Saturating addition with minimum font size FONT_SIZE_STEP + let new_size = self.font_size + Size::new(delta); + self.font_size = max(new_size, Size::new(FONT_SIZE_STEP)); self.dirty = true; } @@ -1157,18 +1156,6 @@ impl Term { self.tabs = IndexRange::from(Column(0)..self.grid.num_cols()) .map(|i| (*i as usize) % self.tabspaces == 0) .collect::>(); - - // if num_lines > old_lines { - // // Make sure bottom of terminal is clear - // let template = self.cursor.template; - // self.grid - // .region_mut((self.cursor.point.line + 1)..) - // .each(|c| c.reset(&template)); - // self.alt_grid - // .region_mut((self.cursor_save_alt.point.line + 1)..) - // .each(|c| c.reset(&template)); - // } - } #[inline] @@ -2002,6 +1989,9 @@ mod tests { use ansi::{Handler, CharsetIndex, StandardCharset}; use selection::Selection; use std::mem; + use input::FONT_SIZE_STEP; + use font::Size; + use config::Config; #[test] fn semantic_selection_works() { @@ -2105,6 +2095,72 @@ mod tests { assert_eq!(term.grid()[&cursor].c, '▒'); } + + fn change_font_size_works(font_size: f32) { + let size = SizeInfo { + width: 21.0, + height: 51.0, + cell_width: 3.0, + cell_height: 3.0, + padding_x: 0.0, + padding_y: 0.0, + }; + let config: Config = Default::default(); + let mut term: Term = Term::new(&config, size); + term.change_font_size(font_size); + + let expected_font_size: Size = config.font().size() + Size::new(font_size); + assert_eq!(term.font_size, expected_font_size); + } + + #[test] + fn increase_font_size_works() { + change_font_size_works(10.0); + } + + #[test] + fn decrease_font_size_works() { + change_font_size_works(-10.0); + } + + #[test] + fn prevent_font_below_threshold_works() { + let size = SizeInfo { + width: 21.0, + height: 51.0, + cell_width: 3.0, + cell_height: 3.0, + padding_x: 0.0, + padding_y: 0.0, + }; + let config: Config = Default::default(); + let mut term: Term = Term::new(&config, size); + + term.change_font_size(-100.0); + + let expected_font_size: Size = Size::new(FONT_SIZE_STEP); + assert_eq!(term.font_size, expected_font_size); + } + + #[test] + fn reset_font_size_works() { + let size = SizeInfo { + width: 21.0, + height: 51.0, + cell_width: 3.0, + cell_height: 3.0, + padding_x: 0.0, + padding_y: 0.0, + }; + let config: Config = Default::default(); + let mut term: Term = Term::new(&config, size); + + term.change_font_size(10.0); + term.reset_font_size(); + + let expected_font_size: Size = config.font().size(); + assert_eq!(term.font_size, expected_font_size); + } } #[cfg(all(test, feature = "bench"))] @@ -2161,7 +2217,7 @@ mod benches { mem::swap(&mut terminal.grid, &mut grid); b.iter(|| { - let iter = terminal.renderable_cells(&config, None, false); + let iter = terminal.renderable_cells(&config, false); for cell in iter { test::black_box(cell); } diff --git a/src/tty.rs b/src/tty.rs index 166a788e..4da11c0e 100644 --- a/src/tty.rs +++ b/src/tty.rs @@ -117,7 +117,7 @@ fn set_controlling_terminal(fd: c_int) { // based on architecture (32/64). So a generic cast is used to make sure // there are no issues. To allow such a generic cast the clippy warning // is disabled. - #[cfg_attr(feature = "clippy", allow(cast_lossless))] + #[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))] libc::ioctl(fd, TIOCSCTTY as _, 0) }; diff --git a/src/util.rs b/src/util.rs index f51a41f8..6065e01b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -15,7 +15,6 @@ use std::cmp; #[cfg(not(feature = "nightly"))] #[inline(always)] -#[cfg_attr(feature = "clippy", allow(inline_always))] pub unsafe fn unlikely(x: bool) -> bool { x } @@ -74,24 +73,12 @@ pub mod fmt { /// Write a `Display` or `Debug` escaped with Red pub struct Red => "31"; + /// Write a `Display` or `Debug` escaped with Green + pub struct Green => "32"; + /// Write a `Display` or `Debug` escaped with Yellow pub struct Yellow => "33"; } - - /// Write a `Display` or `Debug` escaped with Red - pub struct Green(pub T); - - impl fmt::Display for Green { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\x1b[32m{}\x1b[0m", self.0) - } - } - - impl fmt::Debug for Green { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\x1b[32m{:?}\x1b[0m", self.0) - } - } } #[cfg(test)] diff --git a/src/window.rs b/src/window.rs index 987332e0..1903360f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -22,8 +22,26 @@ use glutin::GlContext; use MouseCursor; +use cli::Options; use config::WindowConfig; +/// Default text for the window's title bar, if not overriden. +/// +/// In X11, this the default value for the `WM_NAME` property. +pub const DEFAULT_TITLE: &str = "Alacritty"; + +/// Default text for general window class, X11 specific. +/// +/// In X11, this is the default value for the `WM_CLASS` property. The +/// second value of `WM_CLASS` is **never** changed to anything but +/// the default value. +/// +/// ```ignore +/// $ xprop | grep WM_CLASS +/// WM_CLASS(STRING) = "Alacritty", "Alacritty" +/// ``` +pub const DEFAULT_CLASS: &str = "Alacritty"; + /// Window errors #[derive(Debug)] pub enum Error { @@ -199,17 +217,19 @@ impl Window { /// /// This creates a window and fully initializes a window. pub fn new( - title: &str, + options: &Options, window_config: &WindowConfig, ) -> Result { let event_loop = EventsLoop::new(); + let title = options.title.as_ref().map_or(DEFAULT_TITLE, |t| t); + let class = options.class.as_ref().map_or(DEFAULT_CLASS, |c| c); let window_builder = WindowBuilder::new() .with_title(title) .with_visibility(false) .with_transparency(true) .with_decorations(window_config.decorations()); - let window_builder = Window::platform_builder_ext(window_builder); + let window_builder = Window::platform_builder_ext(window_builder, &class); let window = create_gl_window(window_builder.clone(), &event_loop, false) .or_else(|_| create_gl_window(window_builder, &event_loop, true))?; window.show(); @@ -322,13 +342,13 @@ impl Window { } #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))] - fn platform_builder_ext(window_builder: WindowBuilder) -> WindowBuilder { + fn platform_builder_ext(window_builder: WindowBuilder, wm_class: &str) -> WindowBuilder { use glutin::os::unix::WindowBuilderExt; - window_builder.with_class("alacritty".to_owned(), "Alacritty".to_owned()) + window_builder.with_class(wm_class.to_owned(), "Alacritty".to_owned()) } #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd")))] - fn platform_builder_ext(window_builder: WindowBuilder) -> WindowBuilder { + fn platform_builder_ext(window_builder: WindowBuilder, _: &str) -> WindowBuilder { window_builder } -- cgit From b59fd21cac5f84c02e2df161bad708e440834320 Mon Sep 17 00:00:00 2001 From: Th3Fanbus Date: Thu, 26 Jul 2018 17:47:14 +0200 Subject: Replace runtime debug assertions with static asserts on scrollback --- src/grid/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/grid/storage.rs b/src/grid/storage.rs index 6a453da6..ad94cf2b 100644 --- a/src/grid/storage.rs +++ b/src/grid/storage.rs @@ -223,7 +223,7 @@ impl Storage { /// instructions. This implementation achieves the swap in only 8 movups /// instructions. pub fn swap(&mut self, a: usize, b: usize) { - debug_assert!(::std::mem::size_of::>() == 32); + assert_eq_size!(Row, [u32; 8]); let a = self.compute_index(a); let b = self.compute_index(b); -- cgit From 0ca5c7a6956d2af95f798b858e646e5ec8e63403 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Thu, 26 Jul 2018 20:47:33 +0000 Subject: Fix trailing colors when leaving vim after resize There is an issue where the terminal would use the template cell to fill new space after resizing the terminal. However this leads to issues since the template cell is not always empty and thus can create some blocks of color appearing out of nowhere. This should fix this problem by always initializing cells with the default cell after resizing. --- src/term/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/term/mod.rs b/src/term/mod.rs index 3cdd5ff3..ca3b5025 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1138,8 +1138,8 @@ impl Term { debug!("num_cols, num_lines = {}, {}", num_cols, num_lines); // Resize grids to new size - self.grid.resize(num_lines, num_cols, &self.cursor.template); - self.alt_grid.resize(num_lines, num_cols, &self.cursor_save_alt.template); + self.grid.resize(num_lines, num_cols, &Cell::default()); + self.alt_grid.resize(num_lines, num_cols, &Cell::default()); // Reset scrolling region to new size self.scroll_region = Line(0)..self.grid.num_lines(); -- cgit From c4a0f9c4cb7e8d73914800c4ba64413970f98540 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 28 Jul 2018 23:10:13 +0000 Subject: Merge master into scrollback * Allow disabling DPI scaling This makes it possible to disable DPI scaling completely, instead the the display pixel ration will always be fixed to 1.0. By default nothing has changed and DPI is still enabled, this just seems like a better way than running `WINIT_HIDPI_FACTOR=1.0 alacritty` every time the user wants to start alacritty. It would be possible to allow specifying any DPR, however I've decided against this since I'd assume it's a very rare usecase. It's also still possible to make use of `WINIT_HIDPI_FACTOR` to do this on X11. Currently this is not updated at runtime using the live config update, there is not really much of a technical limitation why this woudn't be possible, however a solution for that issue should be first added in jwilm/alacritty#1346, once a system is established for changing DPI at runtime, porting that functionality to this PR should be simple. * Add working --class and --title CLI parameters * Reduce Increase-/DecreaseFontSize step to 0.5 Until now the Increase-/DecreaseFontSize keybinds hand a step size of 1.0. Since the font size however is multiplied by two to allow more granular font size control, this lead to the bindings skipping one font size (incrementing/decrementing by +-2). To fix this the step size of the Increase-/DecreaseFontSize bindings has been reduced to the minimum step size that exists with the current font configuration (0.5). This should allow users to increment and decrement the font size by a single point instead of two. This also adds a few tests to make sure the methods for increasing/decreasing/resetting font size work properly. * Add Copy/Cut/Paste keys This just adds support for the Copy/Cut/Paste keys and sets up Copy/Paste as alternative defaults for Ctrl+Shift+C/V. * Move to cargo clippy Using clippy as a library has been deprecated, instead the `cargo clippy` command should be used instead. To comply with this change clippy has been removed from the `Cargo.toml` and is now installed with cargo when building in CI. This has also lead to a few new clippy issues to show up, this includes everything in the `font` subdirectory. This has been fixed and `font` should now be covered by clippy CI too. This also upgrades all dependencies, as a result this fixes #1341 and this fixes #1344. * Override dynamic_title when --title is specified * Change green implementation to use the macro * Ignore mouse input if window is unfocused * Make compilation of binary a phony target * Add opensuse zypper install method to readme * Fix clippy issues * Update manpage to document all CLI options The introduction of `--class` has added a flag to the CLI without adding it to the manpage. This has been fixed by updating the manpage. This also adds the default values of `--class` and `--title` to the CLI options. * Remove unnecessary clippy lint annotations We moved to "cargo clippy" in 5ba34d4f9766a55a06ed5e3e44cc384af1b09f65 and removing the clippy lint annotations in `src/lib.rs` does not cause any additional warnings. This also changes `cargo clippy` to use the flags required for checking integration tests. * Enable clippy in font/copypasta crates Enabled clippy in the sub-crates font and copypasta. All issues that were discovered by this change have also been fixed. * Remove outdated comment about NixOS * Replace debug asserts with static_assertions To check that transmutes will work correctly without having to rely on error-prone runtime checking, the `static_assertions` crate has been introduced. This allows comparing the size of types at compile time, preventing potentially silent breakage. This fixes #1417. * Add `cargo deb` build instructions Updated the `Cargo.toml` file and added a `package.metadata.deb` subsection to define how to build a debian "deb" install file using `cargo deb`. This will allow debian/ubuntu users to install `alacritty` using their system's package manager. It also will make it easier to provide pre-built binaries for those systems. Also fixed a stray debug line in the bash autocomplete script that was writting to a tempfile. * Add config for unfocused window cursor change * Add support for cursor shape escape sequence * Add bright foreground color option It was requested in jwilm/alacritty#825 that it should be possible to add an optional bright foreground color. This is now added to the primary colors structure and allows the user to set a foreground color for bold normal text. This has no effect unless the draw_bold_text_with_bright_colors option is also enabled. If the color is not specified, the bright foreground color will fall back to the normal foreground color. This fixes #825. * Fix clone URL in deb install instructions * Fix 'cargo-deb' desktop file name * Remove redundant dependency from deb build * Switch from deprecated `std::env::home_dir` to `dirs::home_dir` * Allow specifying modifiers for mouse bindings * Send newline with NumpadEnter * Add support for LCD-V pixel mode * Add binding action for hiding the window * Switch to rustup clippy component * Add optional dim foreground color Add optional color for the dim foreground (`\e[2m;`) Defaults to 2/3 of the foreground color. (same as other colors). If a bright color is dimmed, it's displayed as the normal color. The exception for this is when the bright foreground is dimmed when no bright foreground color is set. In that case it's treated as a normal foreground color and dimmed to DimForeground. To minimize the surprise for the user, the bright and dim colors have been completely removed from the default configuration file. Some documentation has also been added to make it clear to users what these options can be used for. This fixes #1448. * Fix clippy lints and run font tests on travis This fixes some existing clippy issues and runs the `font` tests through travis. Testing of copypasta crate was omitted due to problens when running on headless travis-ci environment (x11 clipboard would fail). * Ignore errors when logger can't write to output The (e)print macro will panic when there is no output available to write to, however in our scenario where we only log user errors to stderr, the better choice would be to ignore when writing to stdout or stderr is not possible. This changes the (e)print macro to make use of `write` and ignore any potential errors. Since (e)println rely on (e)print, this also solves potential failuers when calling (e)println. With this change implemented, all of logging, (e)println and (e)print should never fail even if the stdout/stderr is not available. --- src/ansi.rs | 23 +++++++++++++++++++++++ src/config.rs | 36 +++++++++++++++++++++++++++++++++++- src/event.rs | 36 ++++++++++++++++++++++++++++++++++++ src/input.rs | 15 +++++++++++++-- src/logging.rs | 2 +- src/macros.rs | 16 ++++++++++++++++ src/main.rs | 6 ++++-- src/term/color.rs | 11 ++++++++++- src/term/mod.rs | 16 ++++++++++++---- src/window.rs | 5 +++++ 10 files changed, 155 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/ansi.rs b/src/ansi.rs index e37e25f1..f1ca759a 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -558,11 +558,16 @@ pub enum NamedColor { DimCyan, /// Dim white DimWhite, + /// The bright foreground color + BrightForeground, + /// Dim foreground + DimForeground, } impl NamedColor { pub fn to_bright(self) -> Self { match self { + NamedColor::Foreground => NamedColor::BrightForeground, NamedColor::Black => NamedColor::BrightBlack, NamedColor::Red => NamedColor::BrightRed, NamedColor::Green => NamedColor::BrightGreen, @@ -571,6 +576,7 @@ impl NamedColor { NamedColor::Magenta => NamedColor::BrightMagenta, NamedColor::Cyan => NamedColor::BrightCyan, NamedColor::White => NamedColor::BrightWhite, + NamedColor::DimForeground => NamedColor::Foreground, NamedColor::DimBlack => NamedColor::Black, NamedColor::DimRed => NamedColor::Red, NamedColor::DimGreen => NamedColor::Green, @@ -593,6 +599,7 @@ impl NamedColor { NamedColor::Magenta => NamedColor::DimMagenta, NamedColor::Cyan => NamedColor::DimCyan, NamedColor::White => NamedColor::DimWhite, + NamedColor::Foreground => NamedColor::DimForeground, NamedColor::BrightBlack => NamedColor::Black, NamedColor::BrightRed => NamedColor::Red, NamedColor::BrightGreen => NamedColor::Green, @@ -601,6 +608,7 @@ impl NamedColor { NamedColor::BrightMagenta => NamedColor::Magenta, NamedColor::BrightCyan => NamedColor::Cyan, NamedColor::BrightWhite => NamedColor::White, + NamedColor::BrightForeground => NamedColor::Foreground, val => val } } @@ -814,6 +822,21 @@ impl<'a, H, W> vte::Perform for Performer<'a, H, W> unhandled(params); } + // Set cursor style + b"50" => { + if params.len() >= 2 && params[1].len() >= 13 && params[1][0..12] == *b"CursorShape=" { + let style = match params[1][12] as char { + '0' => CursorStyle::Block, + '1' => CursorStyle::Beam, + '2' => CursorStyle::Underline, + _ => return unhandled(params), + }; + self.handler.set_cursor_style(Some(style)); + return; + } + unhandled(params); + } + // Set clipboard b"52" => { if params.len() < 3 { diff --git a/src/config.rs b/src/config.rs index 7d8600f4..00c3a933 100644 --- a/src/config.rs +++ b/src/config.rs @@ -373,6 +373,10 @@ pub struct Config { #[serde(default, deserialize_with = "failure_default")] cursor_style: CursorStyle, + /// Use hollow block cursor when unfocused + #[serde(default="true_bool", deserialize_with = "default_true_bool")] + unfocused_hollow_cursor: bool, + /// Live config reload #[serde(default="true_bool", deserialize_with = "default_true_bool")] live_config_reload: bool, @@ -593,7 +597,8 @@ impl<'a> de::Deserialize<'a> for ActionWrapper { fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, \ - ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollToTop, ScrollToBottom or Quit") + ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollToTop, \ + ScrollToBottom, Hide, or Quit") } fn visit_str(self, value: &str) -> ::std::result::Result @@ -610,6 +615,7 @@ impl<'a> de::Deserialize<'a> for ActionWrapper { "ScrollPageDown" => Action::ScrollPageDown, "ScrollToTop" => Action::ScrollToTop, "ScrollToBottom" => Action::ScrollToBottom, + "Hide" => Action::Hide, "Quit" => Action::Quit, _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), })) @@ -1070,6 +1076,26 @@ pub struct PrimaryColors { pub background: Rgb, #[serde(deserialize_with = "rgb_from_hex")] pub foreground: Rgb, + #[serde(default, deserialize_with = "deserialize_optional_color")] + pub bright_foreground: Option, + #[serde(default, deserialize_with = "deserialize_optional_color")] + pub dim_foreground: Option, +} + +fn deserialize_optional_color<'a, D>(deserializer: D) -> ::std::result::Result, D::Error> + where D: de::Deserializer<'a> +{ + match Option::deserialize(deserializer) { + Ok(Some(color)) => { + let color: serde_yaml::Value = color; + Ok(Some(rgb_from_hex(color).unwrap())) + }, + Ok(None) => Ok(None), + Err(err) => { + eprintln!("problem with config: {}; Using standard foreground color", err); + Ok(None) + }, + } } impl Default for PrimaryColors { @@ -1077,6 +1103,8 @@ impl Default for PrimaryColors { PrimaryColors { background: Rgb { r: 0, g: 0, b: 0 }, foreground: Rgb { r: 0xea, g: 0xea, b: 0xea }, + bright_foreground: None, + dim_foreground: None, } } } @@ -1415,6 +1443,12 @@ impl Config { self.cursor_style } + /// Use hollow block cursor when unfocused + #[inline] + pub fn unfocused_hollow_cursor(&self) -> bool { + self.unfocused_hollow_cursor + } + /// Live config reload #[inline] pub fn live_config_reload(&self) -> bool { diff --git a/src/event.rs b/src/event.rs index 3af417bd..de0faf7f 100644 --- a/src/event.rs +++ b/src/event.rs @@ -39,6 +39,7 @@ pub struct ActionContext<'a, N: 'a> { pub received_count: &'a mut usize, pub suppress_chars: &'a mut bool, pub last_modifiers: &'a mut ModifiersState, + pub window_changes: &'a mut WindowChanges, } impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { @@ -138,6 +139,33 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { fn last_modifiers(&mut self) -> &mut ModifiersState { &mut self.last_modifiers } + + #[inline] + fn hide_window(&mut self) { + self.window_changes.hide = true; + } +} + +/// The ActionContext can't really have direct access to the Window +/// with the current design. Event handlers that want to change the +/// window must set these flags instead. The processor will trigger +/// the actual changes. +pub struct WindowChanges { + pub hide: bool, +} + +impl WindowChanges { + fn clear(&mut self) { + self.hide = false; + } +} + +impl Default for WindowChanges { + fn default() -> WindowChanges { + WindowChanges { + hide: false, + } + } } pub enum ClickState { @@ -204,6 +232,7 @@ pub struct Processor { suppress_chars: bool, last_modifiers: ModifiersState, pending_events: Vec, + window_changes: WindowChanges, } /// Notify that the terminal was resized @@ -246,6 +275,7 @@ impl Processor { suppress_chars: false, last_modifiers: Default::default(), pending_events: Vec::with_capacity(4), + window_changes: Default::default(), } } @@ -398,6 +428,7 @@ impl Processor { received_count: &mut self.received_count, suppress_chars: &mut self.suppress_chars, last_modifiers: &mut self.last_modifiers, + window_changes: &mut self.window_changes, }; processor = input::Processor { @@ -442,6 +473,11 @@ impl Processor { window.is_focused = window_is_focused; } + if self.window_changes.hide { + window.hide(); + } + + self.window_changes.clear(); self.wait_for_event = !terminal.dirty; terminal diff --git a/src/input.rs b/src/input.rs index f2337d6a..cccb3bb4 100644 --- a/src/input.rs +++ b/src/input.rs @@ -69,6 +69,7 @@ pub trait ActionContext { fn change_font_size(&mut self, delta: f32); fn reset_font_size(&mut self); fn scroll(&mut self, scroll: Scroll); + fn hide_window(&mut self); } /// Describes a state and action to take in that state @@ -185,6 +186,9 @@ pub enum Action { /// Run given command Command(String, Vec), + /// Hides the Alacritty window + Hide, + /// Quits Alacritty. Quit, } @@ -240,6 +244,9 @@ impl Action { }, } }, + Action::Hide => { + ctx.hide_window(); + }, Action::Quit => { // FIXME should do a more graceful shutdown ::std::process::exit(0); @@ -570,7 +577,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { return; } - self.process_mouse_bindings(ModifiersState::default(), button); + self.process_mouse_bindings(modifiers, button); } /// Process key input @@ -665,7 +672,7 @@ mod tests { use glutin::{VirtualKeyCode, Event, WindowEvent, ElementState, MouseButton, ModifiersState}; use term::{SizeInfo, Term, TermMode}; - use event::{Mouse, ClickState}; + use event::{Mouse, ClickState, WindowChanges}; use config::{self, Config, ClickHandler}; use index::{Point, Side}; use selection::Selection; @@ -691,6 +698,7 @@ mod tests { pub received_count: usize, pub suppress_chars: bool, pub last_modifiers: ModifiersState, + pub window_changes: &'a mut WindowChanges, } impl <'a>super::ActionContext for ActionContext<'a> { @@ -748,6 +756,8 @@ mod tests { } fn reset_font_size(&mut self) { } + fn hide_window(&mut self) { + } } macro_rules! test_clickstate { @@ -786,6 +796,7 @@ mod tests { received_count: 0, suppress_chars: false, last_modifiers: ModifiersState::default(), + window_changes: &mut WindowChanges::default(), }; let mut processor = Processor { diff --git a/src/logging.rs b/src/logging.rs index a691778a..10929980 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -47,7 +47,7 @@ impl log::Log for Logger { fn log(&self, record: &log::Record) { if self.enabled(record.metadata()) && record.target().starts_with("alacritty") { if let Ok(ref mut writer) = self.output.lock() { - writer.write_all(format!("{}\n", record.args()).as_ref()).expect("Error while logging!"); + let _ = writer.write_all(format!("{}\n", record.args()).as_ref()); } } } diff --git a/src/macros.rs b/src/macros.rs index 35e69f2d..464110e6 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -29,3 +29,19 @@ macro_rules! maybe { } } } + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => {{ + use std::io::Write; + let _ = write!(::std::io::stdout(), $($arg)*); + }}; +} + +#[macro_export] +macro_rules! eprint { + ($($arg:tt)*) => {{ + use std::io::Write; + let _ = write!(::std::io::stderr(), $($arg)*); + }}; +} diff --git a/src/main.rs b/src/main.rs index 1508103c..0888c9b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,8 @@ extern crate alacritty; #[macro_use] extern crate log; +#[cfg(target_os = "macos")] +extern crate dirs; use std::error::Error; use std::sync::Arc; @@ -48,7 +50,7 @@ fn main() { // Switch to home directory #[cfg(target_os = "macos")] - env::set_current_dir(env::home_dir().unwrap()).unwrap(); + env::set_current_dir(dirs::home_dir().unwrap()).unwrap(); // Set locale #[cfg(target_os = "macos")] locale::set_locale_environment(); @@ -178,7 +180,7 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box> { .as_ref() .and_then(|monitor| monitor.pending_config()) { - config = new_config.update_dynamic_title(&options); + config = new_config.update_dynamic_title(options); display.update_config(&config); processor.update_config(&config); terminal.update_config(&config); diff --git a/src/term/color.rs b/src/term/color.rs index d25f2f3d..6acd092a 100644 --- a/src/term/color.rs +++ b/src/term/color.rs @@ -4,7 +4,7 @@ use std::fmt; use {Rgb, ansi}; use config::Colors; -pub const COUNT: usize = 268; +pub const COUNT: usize = 270; /// List of indexed colors /// @@ -13,6 +13,7 @@ pub const COUNT: usize = 268; /// the configured foreground color, item 257 is the configured background /// color, item 258 is the cursor foreground color, item 259 is the cursor /// background color. Following that are 8 positions for dim colors. +/// Item 268 is the bright foreground color, 269 the dim foreground. #[derive(Copy, Clone)] pub struct List([Rgb; COUNT]); @@ -50,6 +51,10 @@ impl List { self[ansi::NamedColor::BrightMagenta] = colors.bright.magenta; self[ansi::NamedColor::BrightCyan] = colors.bright.cyan; self[ansi::NamedColor::BrightWhite] = colors.bright.white; + self[ansi::NamedColor::BrightForeground] = colors + .primary + .bright_foreground + .unwrap_or(colors.primary.foreground); // Foreground and background self[ansi::NamedColor::Foreground] = colors.primary.foreground; @@ -60,6 +65,10 @@ impl List { self[ansi::NamedColor::Cursor] = colors.cursor.cursor; // Dims + self[ansi::NamedColor::DimForeground] = colors + .primary + .dim_foreground + .unwrap_or(colors.primary.foreground * 0.66); match colors.dim { Some(ref dim) => { trace!("Using config-provided dim colors"); diff --git a/src/term/mod.rs b/src/term/mod.rs index ca3b5025..97f8b779 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -320,11 +320,18 @@ impl<'a> RenderableCellsIter<'a> { Color::Spec(rgb) => rgb, Color::Named(ansi) => { match (self.config.draw_bold_text_with_bright_colors(), cell.flags & Flags::DIM_BOLD) { + // If no bright foreground is set, treat it like the BOLD flag doesn't exist + (_, self::cell::Flags::DIM_BOLD) + if ansi == NamedColor::Foreground + && self.config.colors().primary.bright_foreground.is_none() => + { + self.colors[NamedColor::DimForeground] + } // Draw bold text in bright colors *and* contains bold flag. - (true, self::cell::Flags::DIM_BOLD) | - (true, self::cell::Flags::BOLD) => self.colors[ansi.to_bright()], + (true, self::cell::Flags::BOLD) => self.colors[ansi.to_bright()], // Cell is marked as dim and not bold - (_, self::cell::Flags::DIM) => self.colors[ansi.to_dim()], + (_, self::cell::Flags::DIM) | + (false, self::cell::Flags::DIM_BOLD) => self.colors[ansi.to_dim()], // None of the above, keep original color. _ => self.colors[ansi] } @@ -412,6 +419,7 @@ impl<'a> Iterator for RenderableCellsIter<'a> { if cell.is_empty() && !selected { continue; } + (cell, selected) }; @@ -1071,7 +1079,7 @@ impl Term { .map(|span| { span.to_locations() }); - let cursor = if window_focused { + let cursor = if window_focused || !config.unfocused_hollow_cursor() { self.cursor_style.unwrap_or(self.default_cursor_style) } else { CursorStyle::HollowBlock diff --git a/src/window.rs b/src/window.rs index 1903360f..51a42232 100644 --- a/src/window.rs +++ b/src/window.rs @@ -379,6 +379,11 @@ impl Window { pub fn get_window_id(&self) -> Option { None } + + /// Hide the window + pub fn hide(&self) { + self.window.hide(); + } } pub trait OsExtensions { -- cgit From facab5beef95c4b1808f7bcba92aa74bf792daf0 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 3 Aug 2018 22:12:23 +0000 Subject: Reset visible area when RIS is received When running bash and executing `echo -ne '\033c'`, the terminal should be cleared. However there was an issue with the visible area not being cleared, so all the text previously printed would still remain visible. To fix this, whenever a `reset` call is received now, the complete visible area is reset to `Cell::default()` (the default Cell) and the length of the available scrollback history is reset to `0`, which results in the scrollback history being cleared from the perspective of the user. This fixes #1483. --- src/grid/mod.rs | 2 +- src/term/mod.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index ef587ffd..05fae373 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -413,7 +413,7 @@ impl Grid { self.cols } - pub fn reset(&mut self) { + pub fn clear_history(&mut self) { self.scroll_limit = 0; } diff --git a/src/term/mod.rs b/src/term/mod.rs index 2cc60e30..1b78c39c 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1825,7 +1825,8 @@ impl ansi::Handler for Term { self.colors = self.original_colors; self.color_modified = [false; color::COUNT]; self.cursor_style = None; - self.grid.reset(); + self.grid.clear_history(); + self.grid.region_mut(..).each(|c| c.reset(&Cell::default())); } #[inline] -- cgit From 8e8ecdd0f98dd8005cd940d19dc0a922661d64fc Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 15 Aug 2018 20:09:59 +0000 Subject: Scroll visible area when growing window Since Alacritty never had any scrollback history, the behavior when the window height was increased was to just keep the prompt on the same line it has been before the resize. However the usual behavior of terminal emulators is to keep the distance from the prompt to the bottom of the screen consistent whenever possible. This fixes this behavior by loading lines from the scrollback buffer when the window height is increased. This is only done when scrollback is available, so there are only N lines available, the maximum amount of lines which will be loaded when growing the height is N. Since the number of lines available in the alternate screen buffer is 0, it still behaves the same way it did before this patch. Different terminal emulators have different behaviors when this is done in the alt screen buffer, XTerm for example loads history from the normal screen buffer when growing the height of the window from the alternate screen buffer. Since this seems wrong (the alt screen is not supposed to have any scrollback), the behavior of Termite (VTE) has been chosen instead. In Termite the alt screen buffer never loads any scrollback history itself, however when the terminal height is grown while the alternate screen is active, the normal screen's scrollback history lines are loaded. This fixes #1502. --- src/grid/mod.rs | 12 ++++++++++-- src/term/mod.rs | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 05fae373..6359a4ee 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -238,8 +238,11 @@ impl Grid { self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, template)); self.lines = new_line_count; - // Add new lines to bottom - self.scroll_up(&(Line(0)..new_line_count), lines_added, template); + // Move existing lines up if there is no scrollback to fill new lines + if lines_added.0 > self.scroll_limit { + let scroll_lines = lines_added - self.scroll_limit; + self.scroll_up(&(Line(0)..new_line_count), scroll_lines, template); + } self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added); } @@ -417,6 +420,11 @@ impl Grid { self.scroll_limit = 0; } + #[inline] + pub fn scroll_limit(&self) -> usize { + self.scroll_limit + } + /// Total number of lines in the buffer, this includes scrollback + visible lines #[inline] pub fn len(&self) -> usize { diff --git a/src/term/mod.rs b/src/term/mod.rs index 1b78c39c..ff7a53e9 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1144,6 +1144,18 @@ impl Term { let lines = self.cursor_save_alt.point.line - num_lines + 1; self.alt_grid.scroll_up(&(Line(0)..old_lines), lines, &self.cursor_save_alt.template); } + + // Move prompt down when growing if scrollback lines are available + if num_lines > old_lines { + if self.mode.contains(TermMode::ALT_SCREEN) { + let growage = min(num_lines - old_lines, Line(self.alt_grid.scroll_limit())); + self.cursor_save.point.line += growage; + } else { + let growage = min(num_lines - old_lines, Line(self.grid.scroll_limit())); + self.cursor.point.line += growage; + } + } + debug!("num_cols, num_lines = {}, {}", num_cols, num_lines); // Resize grids to new size -- cgit From 72495172c25e00799f29eb2e79fe40ddfa189866 Mon Sep 17 00:00:00 2001 From: Nathan Lilienthal Date: Sat, 1 Sep 2018 20:30:03 -0400 Subject: Implement `ansi::ClearMode::Saved` The clearing the screen for the `ansi::ClearMode::Saved` enum value has been implemented. This is used to clear all lines which are currently outside of the visible region but still inside the scrollback buffer. The specifications of XTerm indicate that the clearing of saved lines should only clear the saved lines and not the saved lines plus the currently visible part of the grid. Applications like `clear` send both the escape for clearing history plus the escape for clearing history when requested, so all sources seem to agree here. To allow both clearing the screen and the saved lines when a key is pressed the `process_key_bindings` method has been altered so multiple bindings can be specified. So it is now possible to execute both `^L` and `ClearHistory` with just a single binding. The `process_mouse_bindings` method has also been changed for consistency. To make sure everything works properly a test has been added which clears the history and then attempts to scroll. Since scrolling is the only way for a user to check if scrollback is available, this seems like a nice abstraction to check if there is a scrollback. --- src/config.rs | 3 ++- src/event.rs | 5 +++++ src/grid/mod.rs | 11 +++-------- src/input.rs | 19 +++++++++++++++---- src/term/mod.rs | 33 ++++++++++++++++++++++++++++++--- 5 files changed, 55 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 00c3a933..f4da671c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -598,7 +598,7 @@ impl<'a> de::Deserialize<'a> for ActionWrapper { fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, \ ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollToTop, \ - ScrollToBottom, Hide, or Quit") + ScrollToBottom, ClearHistory, Hide, or Quit") } fn visit_str(self, value: &str) -> ::std::result::Result @@ -615,6 +615,7 @@ impl<'a> de::Deserialize<'a> for ActionWrapper { "ScrollPageDown" => Action::ScrollPageDown, "ScrollToTop" => Action::ScrollToTop, "ScrollToBottom" => Action::ScrollToBottom, + "ClearHistory" => Action::ClearHistory, "Hide" => Action::Hide, "Quit" => Action::Quit, _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), diff --git a/src/event.rs b/src/event.rs index ea3d1d74..589f1a42 100644 --- a/src/event.rs +++ b/src/event.rs @@ -10,6 +10,7 @@ use parking_lot::MutexGuard; use glutin::{self, ModifiersState, Event, ElementState}; use copypasta::{Clipboard, Load, Store}; +use ansi::{Handler, ClearMode}; use grid::Scroll; use config::{self, Config}; use cli::Options; @@ -59,6 +60,10 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { self.terminal.scroll_display(scroll); } + fn clear_history(&mut self) { + self.terminal.clear_screen(ClearMode::Saved); + } + fn copy_selection(&self, buffer: ::copypasta::Buffer) { if let Some(selected) = self.terminal.selection_to_string() { if !selected.is_empty() { diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 6359a4ee..680aa7bd 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -219,14 +219,9 @@ impl Grid { /// Add lines to the visible area /// - /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the - /// bottom of the screen as long as there is scrollback available. Once - /// scrollback is exhausted, new lines are simply added to the bottom of the - /// screen. - /// - /// Alacritty takes a different approach. Rather than trying to move with - /// the scrollback, we simply pull additional lines from the back of the - /// buffer in order to populate the new area. + /// Alacritty keeps the cursor at the bottom of the terminal as long as there + /// is scrollback available. Once scrollback is exhausted, new lines are + /// simply added to the bottom of the screen. fn grow_lines( &mut self, new_line_count: index::Line, diff --git a/src/input.rs b/src/input.rs index cccb3bb4..3c20fe36 100644 --- a/src/input.rs +++ b/src/input.rs @@ -69,6 +69,7 @@ pub trait ActionContext { fn change_font_size(&mut self, delta: f32); fn reset_font_size(&mut self); fn scroll(&mut self, scroll: Scroll); + fn clear_history(&mut self); fn hide_window(&mut self); } @@ -183,6 +184,9 @@ pub enum Action { /// Scroll all the way to the bottom ScrollToBottom, + /// Clear the display buffer(s) to remove history + ClearHistory, + /// Run given command Command(String, Vec), @@ -272,6 +276,9 @@ impl Action { Action::ScrollToBottom => { ctx.scroll(Scroll::Bottom); }, + Action::ClearHistory => { + ctx.clear_history(); + }, } } @@ -634,15 +641,16 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { /// /// Returns true if an action is executed. fn process_key_bindings(&mut self, mods: ModifiersState, key: VirtualKeyCode) -> bool { + let mut has_binding = false; for binding in self.key_bindings { if binding.is_triggered_by(self.ctx.terminal_mode(), mods, &key) { // binding was triggered; run the action binding.execute(&mut self.ctx); - return true; + has_binding = true; } } - false + has_binding } /// Attempts to find a binding and execute its action @@ -652,15 +660,16 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { /// /// Returns true if an action is executed. fn process_mouse_bindings(&mut self, mods: ModifiersState, button: MouseButton) -> bool { + let mut has_binding = false; for binding in self.mouse_bindings { if binding.is_triggered_by(self.ctx.terminal_mode(), mods, &button) { // binding was triggered; run the action binding.execute(&mut self.ctx); - return true; + has_binding = true; } } - false + has_binding } } @@ -756,6 +765,8 @@ mod tests { } fn reset_font_size(&mut self) { } + fn clear_history(&mut self) { + } fn hide_window(&mut self) { } } diff --git a/src/term/mod.rs b/src/term/mod.rs index ff7a53e9..21c97671 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1797,7 +1797,9 @@ impl ansi::Handler for Term { } }, // If scrollback is implemented, this should clear it - ansi::ClearMode::Saved => return + ansi::ClearMode::Saved => { + self.grid.clear_history(); + } } } @@ -2006,9 +2008,9 @@ mod tests { use super::{Cell, Term, SizeInfo}; use term::cell; - use grid::Grid; + use grid::{Grid, Scroll}; use index::{Point, Line, Column}; - use ansi::{Handler, CharsetIndex, StandardCharset}; + use ansi::{self, Handler, CharsetIndex, StandardCharset}; use selection::Selection; use std::mem; use input::FONT_SIZE_STEP; @@ -2183,6 +2185,31 @@ mod tests { let expected_font_size: Size = config.font().size(); assert_eq!(term.font_size, expected_font_size); } + + #[test] + fn clear_saved_lines() { + let size = SizeInfo { + width: 21.0, + height: 51.0, + cell_width: 3.0, + cell_height: 3.0, + padding_x: 0.0, + padding_y: 0.0, + }; + let config: Config = Default::default(); + let mut term: Term = Term::new(&config, size); + + // Add one line of scrollback + term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), &Cell::default()); + + // Clear the history + term.clear_screen(ansi::ClearMode::Saved); + + // Make sure that scrolling does not change the grid + let mut scrolled_grid = term.grid.clone(); + scrolled_grid.scroll_display(Scroll::Top); + assert_eq!(term.grid, scrolled_grid); + } } #[cfg(all(test, feature = "bench"))] -- cgit From 43882ade33d4c14ee7248e489a2d33395faaa0b1 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 5 Sep 2018 21:15:16 +0000 Subject: Fix substraction underflow with IL sequence The IL escape sequence (CSI Ps L) allows inserting blank, uninitialized lines. `Ps` is a placeholder for the number of lines that should be inserted. Before this change Alacritty would crash when a large number of lines was passed as `Ps` parameter. The issue was caused whenever the current line of the cursor plus the lines that should be inserted would leave the bottom of the terminal, since this makes indexing impossible. This patch makes sure that the biggest amount of lines inserted does never exceed the end of the visible region minus the current line of the curser, which fixes the underflow issue. This fixes #1515. --- src/term/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/term/mod.rs b/src/term/mod.rs index 21c97671..04d110af 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1209,9 +1209,10 @@ impl Term { /// Text moves down; clear at bottom /// Expects origin to be in scroll range. #[inline] - fn scroll_down_relative(&mut self, origin: Line, lines: Line) { + fn scroll_down_relative(&mut self, origin: Line, mut lines: Line) { trace!("scroll_down_relative: origin={}, lines={}", origin, lines); - let lines = min(lines, self.scroll_region.end - self.scroll_region.start); + lines = min(lines, self.scroll_region.end - self.scroll_region.start); + lines = min(lines, self.scroll_region.end - origin); // Scroll between origin and bottom self.grid.scroll_down(&(origin..self.scroll_region.end), lines, &self.cursor.template); -- cgit From ca1b75b4c094460e19e62241cc2fadb934ba56a5 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Mon, 17 Sep 2018 14:54:47 +0000 Subject: Bump version number to 0.2.0 (#1492) * Change deb installation from crates.io to git There have been a number of issues an PRs opened since the cargo-deb installation does not work with the latest version from crates.io. To help out users until the crates.io version is updated, the installation instructions have been temporarily changed to install `cargo-deb` through github. * Revert cargo-deb install back to use crates.io Since `cargo-deb` has been updated on crates.io it is now possible to just install it from crates.io and build Alacritty's deb without having to rely on github. * Update dependencies This fixes an `illegal hardware instruction (core dumped)` error when building in release mode. * Remove redundant copy when selecting font_key * Bump version number to 0.2.0 Since the Scrollback branch introduces some major changes, this bumps the version number from 0.1.0 to 0.2.0. The versions of Alacritty have not been updated regularly to this point, so the scrollback branch is a good point in time to start updating Alacritty's version on a regular basis. Further changes to the readme, like dropping the 'alpha' status and updating it to 'beta' could also be introduced with this branch. This way there will be a clean cut which updates everything as soon as scrollback is merged. Building versions is another thing which would be a good thing to start reasonably quickly. However starting this on the main branch after scrollback has been merged seems like a more reliable way to move forward. This fixes #1240. * Add a CHANGELOG file A CHANGELOG file has been added to offer a bit more transparency over which features have been changed, added and potentially removed in Alacritty. There are various formats available for the CHANGELOG file but the most common and sensible one seems to be the one defined by https://keepachangelog.com/en/1.0.0. Following the template proposed by this it should be possible to create a clear CHANGELOG which makes it simple for new contributors to figure out exactly which formatting should be used for it. Since there have been quite a few changes to Alacritty already, not all changes have been added to the changelog. However a few entries have been ported just to give a bit of an example what the format should look like. This also helps with the 0.2.0 version since it will not be completely empty in the changelog. This fixes #1534. * Update CHANGELOG This updates the CHANGELOG to include the changes introduced by 43882ade33d4c14ee7248e489a2d33395faaa0b1. --- src/renderer/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 00ac9eb2..68335e1c 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -827,12 +827,13 @@ impl<'a> RenderApi<'a> { for cell in cells { // Get font key for cell // FIXME this is super inefficient. - let mut font_key = glyph_cache.font_key; - if cell.flags.contains(cell::Flags::BOLD) { - font_key = glyph_cache.bold_key; + let font_key = if cell.flags.contains(cell::Flags::BOLD) { + glyph_cache.bold_key } else if cell.flags.contains(cell::Flags::ITALIC) { - font_key = glyph_cache.italic_key; - } + glyph_cache.italic_key + } else { + glyph_cache.font_key + }; let glyph_key = GlyphKey { font_key, -- cgit