1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2025-11-06 22:44:18 -05:00

Unify the grid line indexing types

Previously Alacritty was using two different ways to reference lines in
the terminal. Either a `usize`, or a `Line(usize)`. These indexing
systems both served different purposes, but made it difficult to reason
about logic involving these systems because of its inconsistency.

To resolve this issue, a single new `Line(i32)` type has been
introduced.  All existing references to lines and points now rely on
this definition of a line.

The indexing starts at the top of the terminal region with the line 0,
which matches the line 1 used by escape sequences. Each line in the
history becomes increasingly negative and the bottommost line is equal
to the number of visible lines minus one.

Having a system which goes into the negatives allows following the
escape sequence's indexing system closely, while at the same time making
it trivial to implement `Ord` for points.

The Alacritty UI crate is the only place which has a different indexing
system, since rendering and input puts the zero line at the top of the
viewport, rather than the top of the terminal region.

All instances which refer to a number of lines/columns instead of just a
single Line/Column have also been changed to use a `usize` instead. This
way a Line/Column will always refer to a specific place in the grid and
no confusion is created by having a count of lines as a possible index
into the grid storage.
This commit is contained in:
Christian Duerr 2021-03-30 23:25:38 +00:00 committed by GitHub
parent 974392cdc6
commit 3bd5ac221a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
108 changed files with 1545 additions and 2005 deletions

View file

@ -8,7 +8,7 @@ use serde::{Deserialize, Deserializer};
use alacritty_config_derive::ConfigDeserialize;
use alacritty_terminal::config::LOG_TARGET_CONFIG;
use alacritty_terminal::index::{Column, Line};
use alacritty_terminal::index::Column;
use crate::config::ui_config::Delta;
@ -74,7 +74,7 @@ impl WindowConfig {
#[inline]
pub fn dimensions(&self) -> Option<Dimensions> {
if self.dimensions.columns.0 != 0
&& self.dimensions.lines.0 != 0
&& self.dimensions.lines != 0
&& self.startup_mode != StartupMode::Maximized
{
Some(self.dimensions)
@ -145,7 +145,7 @@ pub struct Dimensions {
pub columns: Column,
/// Window Height in character lines.
pub lines: Line,
pub lines: usize,
}
/// Window class hint.

View file

@ -1,5 +1,5 @@
use std::borrow::Cow;
use std::cmp::max;
use std::cmp::{max, min};
use std::mem;
use std::ops::{Deref, DerefMut, RangeInclusive};
@ -126,12 +126,17 @@ impl<'a> RenderableContent<'a> {
let text_color = text_color.color(cell.fg, cell.bg);
let cursor_color = cursor_color.color(cell.fg, cell.bg);
// Convert cursor point to viewport position.
let cursor_point = self.terminal_cursor.point;
let line = (cursor_point.line + self.terminal_content.display_offset as i32).0 as usize;
let point = Point::new(line, cursor_point.column);
Some(RenderableCursor {
point: self.terminal_cursor.point,
shape: self.terminal_cursor.shape,
cursor_color,
text_color,
is_wide,
point,
})
}
}
@ -147,9 +152,10 @@ impl<'a> Iterator for RenderableContent<'a> {
fn next(&mut self) -> Option<Self::Item> {
loop {
let cell = self.terminal_content.display_iter.next()?;
let cell_point = cell.point;
let mut cell = RenderableCell::new(self, cell);
if self.terminal_cursor.point == cell.point {
if self.terminal_cursor.point == cell_point {
// Store the cursor which should be rendered.
self.cursor = self.renderable_cursor(&cell).map(|cursor| {
if cursor.shape == CursorShape::Block {
@ -178,16 +184,15 @@ impl<'a> Iterator for RenderableContent<'a> {
pub struct RenderableCell {
pub character: char,
pub zerowidth: Option<Vec<char>>,
pub point: Point,
pub point: Point<usize>,
pub fg: Rgb,
pub bg: Rgb,
pub bg_alpha: f32,
pub flags: Flags,
pub is_match: bool,
}
impl RenderableCell {
fn new<'a>(content: &mut RenderableContent<'a>, cell: Indexed<&Cell, Line>) -> Self {
fn new<'a>(content: &mut RenderableContent<'a>, cell: Indexed<&Cell>) -> Self {
// Lookup RGB values.
let mut fg_rgb = Self::compute_fg_rgb(content, cell.fg, cell.flags);
let mut bg_rgb = Self::compute_bg_rgb(content, cell.bg);
@ -203,7 +208,6 @@ impl RenderableCell {
.terminal_content
.selection
.map_or(false, |selection| selection.contains_cell(&cell, content.terminal_cursor));
let mut is_match = false;
let mut character = cell.c;
@ -233,19 +237,21 @@ impl RenderableCell {
let config_fg = colors.search.matches.foreground;
let config_bg = colors.search.matches.background;
Self::compute_cell_rgb(&mut fg_rgb, &mut bg_rgb, &mut bg_alpha, config_fg, config_bg);
is_match = true;
}
// Convert cell point to viewport position.
let cell_point = cell.point;
let line = (cell_point.line + content.terminal_content.display_offset as i32).0 as usize;
let point = Point::new(line, cell_point.column);
RenderableCell {
character,
zerowidth: cell.zerowidth().map(|zerowidth| zerowidth.to_vec()),
point: cell.point,
flags: cell.flags,
fg: fg_rgb,
bg: bg_rgb,
character,
bg_alpha,
flags: cell.flags,
is_match,
point,
}
}
@ -349,7 +355,7 @@ pub struct RenderableCursor {
cursor_color: Rgb,
text_color: Rgb,
is_wide: bool,
point: Point,
point: Point<usize>,
}
impl RenderableCursor {
@ -365,7 +371,7 @@ impl RenderableCursor {
self.is_wide
}
pub fn point(&self) -> Point {
pub fn point(&self) -> Point<usize> {
self.point
}
}
@ -423,36 +429,23 @@ pub struct RegexMatches(Vec<RangeInclusive<Point>>);
impl RegexMatches {
/// Find all visible matches.
pub fn new<T>(term: &Term<T>, dfas: &RegexSearch) -> Self {
let viewport_end = term.grid().display_offset();
let viewport_start = viewport_end + term.screen_lines().0 - 1;
let viewport_start = Line(-(term.grid().display_offset() as i32));
let viewport_end = viewport_start + term.bottommost_line();
// Compute start of the first and end of the last line.
let start_point = Point::new(viewport_start, Column(0));
let mut start = term.line_search_left(start_point);
let end_point = Point::new(viewport_end, term.cols() - 1);
let end_point = Point::new(viewport_end, term.last_column());
let mut end = term.line_search_right(end_point);
// Set upper bound on search before/after the viewport to prevent excessive blocking.
if start.line > viewport_start + MAX_SEARCH_LINES {
if start.line == 0 {
// Do not highlight anything if this line is the last.
return Self::default();
} else {
// Start at next line if this one is too long.
start.line -= 1;
}
}
end.line = max(end.line, viewport_end.saturating_sub(MAX_SEARCH_LINES));
start.line = max(start.line, viewport_start - MAX_SEARCH_LINES);
end.line = min(end.line, viewport_end + MAX_SEARCH_LINES);
// Create an iterater for the current regex search for all visible matches.
let iter = RegexIter::new(start, end, Direction::Right, term, dfas)
.skip_while(move |rm| rm.end().line > viewport_start)
.take_while(move |rm| rm.start().line >= viewport_end)
.map(|rm| {
let viewport_start = term.grid().clamp_buffer_to_visible(*rm.start());
let viewport_end = term.grid().clamp_buffer_to_visible(*rm.end());
viewport_start..=viewport_end
});
.skip_while(move |rm| rm.end().line < viewport_start)
.take_while(move |rm| rm.start().line <= viewport_end);
Self(iter.collect())
}

View file

@ -17,7 +17,7 @@ impl IntoRects for RenderableCursor {
fn rects(self, size_info: &SizeInfo, thickness: f32) -> CursorRects {
let point = self.point();
let x = point.column.0 as f32 * size_info.cell_width() + size_info.padding_x();
let y = point.line.0 as f32 * size_info.cell_height() + size_info.padding_y();
let y = point.line as f32 * size_info.cell_height() + size_info.padding_y();
let mut width = size_info.cell_width();
let height = size_info.cell_height();

View file

@ -118,9 +118,7 @@ impl HintState {
if label.len() == 1 {
// Get text for the hint's regex match.
let hint_match = &self.matches[index];
let start = term.visible_to_buffer(*hint_match.start());
let end = term.visible_to_buffer(*hint_match.end());
let text = term.bounds_to_string(start, end);
let text = term.bounds_to_string(*hint_match.start(), *hint_match.end());
// Append text as last argument and launch command.
let program = hint.command.program();

View file

@ -27,7 +27,7 @@ use alacritty_terminal::event::{EventListener, OnResize};
use alacritty_terminal::grid::Dimensions as _;
use alacritty_terminal::index::{Column, Direction, Line, Point};
use alacritty_terminal::selection::Selection;
use alacritty_terminal::term::{SizeInfo, Term, TermMode, MIN_COLS, MIN_SCREEN_LINES};
use alacritty_terminal::term::{SizeInfo, Term, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES};
use crate::config::font::Font;
use crate::config::window::Dimensions;
@ -477,12 +477,6 @@ impl Display {
mods: ModifiersState,
search_state: &SearchState,
) {
// Convert search match from viewport to absolute indexing.
let search_active = search_state.regex().is_some();
let viewport_match = search_state
.focused_match()
.and_then(|focused_match| terminal.grid().clamp_buffer_range_to_visible(focused_match));
// Collect renderable content before the terminal is dropped.
let mut content = RenderableContent::new(config, self, &terminal, search_state);
let mut grid_cells = Vec::new();
@ -522,13 +516,15 @@ impl Display {
let glyph_cache = &mut self.glyph_cache;
self.renderer.with_api(&config.ui_config, &size_info, |mut api| {
// Iterate over all non-empty cells in the grid.
let focused_match = search_state.focused_match();
for mut cell in grid_cells {
// Invert the active match during search.
if cell.is_match
&& viewport_match
.as_ref()
.map_or(false, |viewport_match| viewport_match.contains(&cell.point))
{
let focused = focused_match.as_ref().map_or(false, |focused_match| {
let line = Line(cell.point.line as i32) - display_offset;
focused_match.contains(&Point::new(line, cell.point.column))
});
// Invert the focused match during search.
if focused {
let colors = config.ui_config.colors.search.focused_match;
let match_fg = colors.foreground.color(cell.fg, cell.bg);
cell.bg = colors.background.color(cell.fg, cell.bg);
@ -537,7 +533,7 @@ impl Display {
}
// Update URL underlines.
urls.update(size_info.cols(), &cell);
urls.update(&size_info, &cell);
// Update underline/strikeout.
lines.update(&cell);
@ -570,15 +566,16 @@ impl Display {
if let Some(vi_mode_cursor) = vi_mode_cursor {
// Highlight URLs at the vi mode cursor position.
let vi_mode_point = vi_mode_cursor.point;
if let Some(url) = self.urls.find_at(vi_mode_point) {
let vi_point = vi_mode_cursor.point;
let line = (vi_point.line + display_offset).0 as usize;
if let Some(url) = self.urls.find_at(Point::new(line, vi_point.column)) {
rects.append(&mut url.rects(&metrics, &size_info));
}
// Indicate vi mode by showing the cursor's position in the top right corner.
let line = size_info.screen_lines() + display_offset - vi_mode_point.line - 1;
self.draw_line_indicator(config, &size_info, total_lines, Some(vi_mode_point), line.0);
} else if search_active {
let line = (-vi_point.line.0 + size_info.bottommost_line().0) as usize;
self.draw_line_indicator(config, &size_info, total_lines, Some(vi_point), line);
} else if search_state.regex().is_some() {
// Show current display offset in vi-less search to indicate match position.
self.draw_line_indicator(config, &size_info, total_lines, None, display_offset);
}
@ -605,12 +602,12 @@ impl Display {
}
if let Some(message) = message_buffer.message() {
let search_offset = if search_active { 1 } else { 0 };
let search_offset = if search_state.regex().is_some() { 1 } else { 0 };
let text = message.text(&size_info);
// Create a new rectangle for the background.
let start_line = size_info.screen_lines() + search_offset;
let y = size_info.cell_height().mul_add(start_line.0 as f32, size_info.padding_y());
let y = size_info.cell_height().mul_add(start_line as f32, size_info.padding_y());
let bg = match message.ty() {
MessageType::Error => config.ui_config.colors.normal.red,
@ -656,7 +653,8 @@ impl Display {
self.draw_search(config, &size_info, &search_text);
// Compute IME position.
Point::new(size_info.screen_lines() + 1, Column(search_text.chars().count() - 1))
let line = Line(size_info.screen_lines() as i32 + 1);
Point::new(line, Column(search_text.chars().count() - 1))
},
None => cursor_point,
};
@ -703,7 +701,7 @@ impl Display {
formatted_regex.push('_');
// Truncate beginning of the search regex if it exceeds the viewport width.
let num_cols = size_info.cols().0;
let num_cols = size_info.columns();
let label_len = search_label.chars().count();
let regex_len = formatted_regex.chars().count();
let truncate_len = min((regex_len + label_len).saturating_sub(num_cols), regex_len);
@ -722,7 +720,7 @@ impl Display {
/// Draw current search regex.
fn draw_search(&mut self, config: &Config, size_info: &SizeInfo, text: &str) {
let glyph_cache = &mut self.glyph_cache;
let num_cols = size_info.cols().0;
let num_cols = size_info.columns();
// Assure text length is at least num_cols.
let text = format!("{:<1$}", text, num_cols);
@ -745,7 +743,7 @@ impl Display {
let glyph_cache = &mut self.glyph_cache;
let timing = format!("{:.3} usec", self.meter.average());
let point = Point::new(size_info.screen_lines() - 2, Column(0));
let point = Point::new(size_info.screen_lines().saturating_sub(2), Column(0));
let fg = config.ui_config.colors.primary.background;
let bg = config.ui_config.colors.normal.red;
@ -764,16 +762,16 @@ impl Display {
line: usize,
) {
let text = format!("[{}/{}]", line, total_lines - 1);
let column = Column(size_info.cols().0.saturating_sub(text.len()));
let column = Column(size_info.columns().saturating_sub(text.len()));
let colors = &config.ui_config.colors;
let fg = colors.line_indicator.foreground.unwrap_or(colors.primary.background);
let bg = colors.line_indicator.background.unwrap_or(colors.primary.foreground);
// Do not render anything if it would obscure the vi mode cursor.
if vi_mode_point.map_or(true, |point| point.line.0 != 0 || point.column < column) {
if vi_mode_point.map_or(true, |point| point.line != 0 || point.column < column) {
let glyph_cache = &mut self.glyph_cache;
self.renderer.with_api(&config.ui_config, &size_info, |mut api| {
api.render_string(glyph_cache, Point::new(Line(0), column), fg, bg, &text);
api.render_string(glyph_cache, Point::new(0, column), fg, bg, &text);
});
}
}
@ -822,8 +820,8 @@ fn window_size(
) -> PhysicalSize<u32> {
let padding = config.ui_config.window.padding(dpr);
let grid_width = cell_width * dimensions.columns.0.max(MIN_COLS) as f32;
let grid_height = cell_height * dimensions.lines.0.max(MIN_SCREEN_LINES) as f32;
let grid_width = cell_width * dimensions.columns.0.max(MIN_COLUMNS) as f32;
let grid_height = cell_height * dimensions.lines.max(MIN_SCREEN_LINES) as f32;
let width = (padding.0).mul_add(2., grid_width).floor();
let height = (padding.1).mul_add(2., grid_height).floor();

View file

@ -11,7 +11,6 @@ use std::fs;
use std::fs::File;
use std::io::Write;
use std::mem;
use std::ops::RangeInclusive;
use std::path::{Path, PathBuf};
#[cfg(not(any(target_os = "macos", windows)))]
use std::sync::atomic::Ordering;
@ -93,13 +92,13 @@ pub struct SearchState {
direction: Direction,
/// Change in display offset since the beginning of the search.
display_offset_delta: isize,
display_offset_delta: i32,
/// Search origin in viewport coordinates relative to original display offset.
origin: Point,
/// Focused match during active search.
focused_match: Option<RangeInclusive<Point<usize>>>,
focused_match: Option<Match>,
/// Search regex and history.
///
@ -128,7 +127,7 @@ impl SearchState {
}
/// Focused match during vi-less search.
pub fn focused_match(&self) -> Option<&RangeInclusive<Point<usize>>> {
pub fn focused_match(&self) -> Option<&Match> {
self.focused_match.as_ref()
}
@ -195,14 +194,14 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
}
fn scroll(&mut self, scroll: Scroll) {
let old_offset = self.terminal.grid().display_offset() as isize;
let old_offset = self.terminal.grid().display_offset() as i32;
self.terminal.scroll_display(scroll);
// Keep track of manual display offset changes during search.
if self.search_active() {
let display_offset = self.terminal.grid().display_offset();
self.search_state.display_offset_delta += old_offset - display_offset as isize;
self.search_state.display_offset_delta += old_offset - display_offset as i32;
}
// Update selection.
@ -213,9 +212,10 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
} else if self.mouse().left_button_state == ElementState::Pressed
|| self.mouse().right_button_state == ElementState::Pressed
{
let point = self.size_info().pixels_to_coords(self.mouse().x, self.mouse().y);
let cell_side = self.mouse().cell_side;
self.update_selection(point, cell_side);
let point = self.mouse().point;
let line = Line(point.line as i32) - self.terminal.grid().display_offset();
let point = Point::new(line, point.column);
self.update_selection(point, self.mouse().cell_side);
}
*self.dirty = true;
@ -245,11 +245,10 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
};
// Treat motion over message bar like motion over the last line.
point.line = min(point.line, self.terminal.screen_lines() - 1);
point.line = min(point.line, self.terminal.bottommost_line());
// Update selection.
let absolute_point = self.terminal.visible_to_buffer(point);
selection.update(absolute_point, side);
selection.update(point, side);
// Move vi cursor and expand selection.
if self.terminal.mode().contains(TermMode::VI) && !self.search_active() {
@ -262,7 +261,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
}
fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side) {
let point = self.terminal.visible_to_buffer(point);
self.terminal.selection = Some(Selection::new(ty, point, side));
*self.dirty = true;
}
@ -280,17 +278,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
}
}
fn mouse_coords(&self) -> Option<Point> {
let x = self.mouse.x as usize;
let y = self.mouse.y as usize;
if self.display.size_info.contains_point(x, y) {
Some(self.display.size_info.pixels_to_coords(x, y))
} else {
None
}
}
#[inline]
fn mouse_mode(&self) -> bool {
self.terminal.mode().intersects(TermMode::MOUSE_MODE)
@ -393,9 +380,13 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
}
if let Some(ref launcher) = self.config.ui_config.mouse.url.launcher {
let display_offset = self.terminal.grid().display_offset();
let start = url.start();
let start = Point::new(Line(start.line as i32 - display_offset as i32), start.column);
let end = url.end();
let end = Point::new(Line(end.line as i32 - display_offset as i32), end.column);
let mut args = launcher.args().to_vec();
let start = self.terminal.visible_to_buffer(url.start());
let end = self.terminal.visible_to_buffer(url.end());
args.push(self.terminal.bounds_to_string(start, end));
start_daemon(launcher.program(), &args);
@ -430,9 +421,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
#[inline]
fn start_search(&mut self, direction: Direction) {
let num_lines = self.terminal.screen_lines();
let num_cols = self.terminal.cols();
// Only create new history entry if the previous regex wasn't empty.
if self.search_state.history.get(0).map_or(true, |regex| !regex.is_empty()) {
self.search_state.history.push_front(String::new());
@ -448,12 +436,12 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
self.search_state.origin = self.terminal.vi_mode_cursor.point;
self.search_state.display_offset_delta = 0;
} else {
match direction {
Direction::Right => self.search_state.origin = Point::new(Line(0), Column(0)),
Direction::Left => {
self.search_state.origin = Point::new(num_lines - 2, num_cols - 1);
},
}
let screen_lines = self.terminal.screen_lines();
let last_column = self.terminal.last_column();
self.search_state.origin = match direction {
Direction::Right => Point::new(Line(0), Column(0)),
Direction::Left => Point::new(Line(screen_lines as i32 - 2), last_column),
};
}
self.display_update_pending.dirty = true;
@ -483,8 +471,8 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
self.search_reset_state();
} else if let Some(focused_match) = &self.search_state.focused_match {
// Create a selection for the focused match.
let start = self.terminal.grid().clamp_buffer_to_visible(*focused_match.start());
let end = self.terminal.grid().clamp_buffer_to_visible(*focused_match.end());
let start = *focused_match.start();
let end = *focused_match.end();
self.start_selection(SelectionType::Simple, start, Side::Left);
self.update_selection(end, Side::Right);
}
@ -565,19 +553,14 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
// Use focused match as new search origin if available.
if let Some(focused_match) = &self.search_state.focused_match {
let new_origin = match direction {
Direction::Right => {
focused_match.end().add_absolute(self.terminal, Boundary::Wrap, 1)
},
Direction::Left => {
focused_match.start().sub_absolute(self.terminal, Boundary::Wrap, 1)
},
Direction::Right => focused_match.end().add(self.terminal, Boundary::None, 1),
Direction::Left => focused_match.start().sub(self.terminal, Boundary::None, 1),
};
self.terminal.scroll_to_point(new_origin);
let origin_relative = self.terminal.grid().clamp_buffer_to_visible(new_origin);
self.search_state.origin = origin_relative;
self.search_state.display_offset_delta = 0;
self.search_state.origin = new_origin;
}
// Search for the next match using the supplied direction.
@ -600,24 +583,18 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
};
// Store the search origin with display offset by checking how far we need to scroll to it.
let old_display_offset = self.terminal.grid().display_offset() as isize;
let old_display_offset = self.terminal.grid().display_offset() as i32;
self.terminal.scroll_to_point(new_origin);
let new_display_offset = self.terminal.grid().display_offset() as isize;
let new_display_offset = self.terminal.grid().display_offset() as i32;
self.search_state.display_offset_delta = new_display_offset - old_display_offset;
// Store origin and scroll back to the match.
let origin_relative = self.terminal.grid().clamp_buffer_to_visible(new_origin);
self.terminal.scroll_display(Scroll::Delta(-self.search_state.display_offset_delta));
self.search_state.origin = origin_relative;
self.search_state.origin = new_origin;
}
/// Find the next search match.
fn search_next(
&mut self,
origin: Point<usize>,
direction: Direction,
side: Side,
) -> Option<Match> {
fn search_next(&mut self, origin: Point, direction: Direction, side: Side) -> Option<Match> {
self.search_state
.dfas
.as_ref()
@ -746,15 +723,10 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
return;
}
// Reset display offset.
// Reset display offset and cursor position.
self.terminal.scroll_display(Scroll::Delta(self.search_state.display_offset_delta));
self.search_state.display_offset_delta = 0;
// Reset vi mode cursor.
let mut origin = self.search_state.origin;
origin.line = min(origin.line, self.terminal.screen_lines() - 1);
origin.column = min(origin.column, self.terminal.cols() - 1);
self.terminal.vi_mode_cursor.point = origin;
self.terminal.vi_mode_cursor.point = self.search_state.origin;
*self.dirty = true;
}
@ -771,10 +743,10 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
// Jump to the next match.
let direction = self.search_state.direction;
let origin = self.absolute_origin();
match self.terminal.search_next(dfas, origin, direction, Side::Left, limit) {
let clamped_origin = self.search_state.origin.grid_clamp(self.terminal, Boundary::Grid);
match self.terminal.search_next(dfas, clamped_origin, direction, Side::Left, limit) {
Some(regex_match) => {
let old_offset = self.terminal.grid().display_offset() as isize;
let old_offset = self.terminal.grid().display_offset() as i32;
if self.terminal.mode().contains(TermMode::VI) {
// Move vi cursor to the start of the match.
@ -789,7 +761,7 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
// Store number of lines the viewport had to be moved.
let display_offset = self.terminal.grid().display_offset();
self.search_state.display_offset_delta += old_offset - display_offset as isize;
self.search_state.display_offset_delta = old_offset - display_offset as i32;
// Since we found a result, we require no delayed re-search.
self.scheduler.unschedule(TimerId::DelayedSearch);
@ -817,14 +789,6 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
/// Cleanup the search state.
fn exit_search(&mut self) {
// Move vi cursor down if resize will pull content from history.
if self.terminal.history_size() != 0
&& self.terminal.grid().display_offset() == 0
&& self.terminal.screen_lines() > self.terminal.vi_mode_cursor.point.line + 1
{
self.terminal.vi_mode_cursor.point.line += 1;
}
self.display_update_pending.dirty = true;
self.search_state.history_index = None;
*self.dirty = true;
@ -833,20 +797,6 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
self.search_state.focused_match = None;
}
/// Get the absolute position of the search origin.
///
/// This takes the relative motion of the viewport since the start of the search into account.
/// So while the absolute point of the origin might have changed since new content was printed,
/// this will still return the correct absolute position.
fn absolute_origin(&self) -> Point<usize> {
let mut relative_origin = self.search_state.origin;
relative_origin.line = min(relative_origin.line, self.terminal.screen_lines() - 1);
relative_origin.column = min(relative_origin.column, self.terminal.cols() - 1);
let mut origin = self.terminal.visible_to_buffer(relative_origin);
origin.line = (origin.line as isize + self.search_state.display_offset_delta) as usize;
origin
}
/// Update the cursor blinking state.
fn update_cursor_blinking(&mut self) {
// Get config cursor style.
@ -886,8 +836,6 @@ pub enum ClickState {
/// State of the mouse.
#[derive(Debug)]
pub struct Mouse {
pub x: usize,
pub y: usize,
pub left_button_state: ElementState,
pub middle_button_state: ElementState,
pub right_button_state: ElementState,
@ -895,32 +843,32 @@ pub struct Mouse {
pub last_click_button: MouseButton,
pub click_state: ClickState,
pub scroll_px: f64,
pub line: Line,
pub column: Column,
pub cell_side: Side,
pub lines_scrolled: f32,
pub block_url_launcher: bool,
pub inside_text_area: bool,
pub point: Point<usize>,
pub x: usize,
pub y: usize,
}
impl Default for Mouse {
fn default() -> Mouse {
Mouse {
x: 0,
y: 0,
last_click_timestamp: Instant::now(),
last_click_button: MouseButton::Left,
left_button_state: ElementState::Released,
middle_button_state: ElementState::Released,
right_button_state: ElementState::Released,
click_state: ClickState::None,
scroll_px: 0.,
line: Line(0),
column: Column(0),
cell_side: Side::Left,
lines_scrolled: 0.,
block_url_launcher: false,
inside_text_area: false,
block_url_launcher: Default::default(),
inside_text_area: Default::default(),
lines_scrolled: Default::default(),
scroll_px: Default::default(),
point: Default::default(),
x: Default::default(),
y: Default::default(),
}
}
}
@ -1409,14 +1357,12 @@ impl<N: Notify + OnResize> Processor<N> {
{
// Compute cursor positions before resize.
let num_lines = terminal.screen_lines();
let vi_mode = terminal.mode().contains(TermMode::VI);
let cursor_at_bottom = terminal.grid().cursor.point.line + 1 == num_lines;
let origin_at_bottom = if vi_mode {
let origin_at_bottom = if terminal.mode().contains(TermMode::VI) {
terminal.vi_mode_cursor.point.line == num_lines - 1
} else {
self.search_state.direction == Direction::Left
};
let old_display_offset = terminal.grid().display_offset();
self.display.handle_update(
terminal,
@ -1436,15 +1382,6 @@ impl<N: Notify + OnResize> Processor<N> {
} else if display_offset != 0 && origin_at_bottom {
terminal.scroll_display(Scroll::Delta(-1));
}
} else if old_is_searching
&& !new_is_searching
&& old_display_offset == 0
&& cursor_at_bottom
&& origin_at_bottom
&& vi_mode
{
// Pull down the vi cursor if it was moved up when the search was started.
terminal.vi_mode_cursor.point.line += 1;
}
}

View file

@ -71,7 +71,6 @@ pub trait ActionContext<T: EventListener> {
fn selection_is_empty(&self) -> bool;
fn mouse_mut(&mut self) -> &mut Mouse;
fn mouse(&self) -> &Mouse;
fn mouse_coords(&self) -> Option<Point>;
fn received_count(&mut self) -> &mut usize;
fn suppress_chars(&mut self) -> &mut bool;
fn modifiers(&mut self) -> &mut ModifiersState;
@ -100,12 +99,7 @@ pub trait ActionContext<T: EventListener> {
fn search_pop_word(&mut self) {}
fn search_history_previous(&mut self) {}
fn search_history_next(&mut self) {}
fn search_next(
&mut self,
origin: Point<usize>,
direction: Direction,
side: Side,
) -> Option<Match>;
fn search_next(&mut self, origin: Point, direction: Direction, side: Side) -> Option<Match>;
fn advance_search_origin(&mut self, _direction: Direction) {}
fn search_direction(&self) -> Direction;
fn search_active(&self) -> bool;
@ -121,8 +115,7 @@ impl Action {
A: ActionContext<T>,
T: EventListener,
{
let cursor_point = ctx.terminal().vi_mode_cursor.point;
ctx.toggle_selection(ty, cursor_point, Side::Left);
ctx.toggle_selection(ty, ctx.terminal().vi_mode_cursor.point, Side::Left);
// Make sure initial selection is not empty.
if let Some(selection) = &mut ctx.terminal_mut().selection {
@ -171,17 +164,19 @@ impl<T: EventListener> Execute<T> for Action {
},
Action::ViAction(ViAction::Open) => {
ctx.mouse_mut().block_url_launcher = false;
if let Some(url) = ctx.urls().find_at(ctx.terminal().vi_mode_cursor.point) {
let vi_point = ctx.terminal().vi_mode_cursor.point;
let line = (vi_point.line + ctx.terminal().grid().display_offset()).0 as usize;
if let Some(url) = ctx.urls().find_at(Point::new(line, vi_point.column)) {
ctx.launch_url(url);
}
},
Action::ViAction(ViAction::SearchNext) => {
let terminal = ctx.terminal();
let direction = ctx.search_direction();
let vi_point = terminal.visible_to_buffer(terminal.vi_mode_cursor.point);
let vi_point = terminal.vi_mode_cursor.point;
let origin = match direction {
Direction::Right => vi_point.add_absolute(terminal, Boundary::Wrap, 1),
Direction::Left => vi_point.sub_absolute(terminal, Boundary::Wrap, 1),
Direction::Right => vi_point.add(terminal, Boundary::None, 1),
Direction::Left => vi_point.sub(terminal, Boundary::None, 1),
};
if let Some(regex_match) = ctx.search_next(origin, direction, Side::Left) {
@ -192,10 +187,10 @@ impl<T: EventListener> Execute<T> for Action {
Action::ViAction(ViAction::SearchPrevious) => {
let terminal = ctx.terminal();
let direction = ctx.search_direction().opposite();
let vi_point = terminal.visible_to_buffer(terminal.vi_mode_cursor.point);
let vi_point = terminal.vi_mode_cursor.point;
let origin = match direction {
Direction::Right => vi_point.add_absolute(terminal, Boundary::Wrap, 1),
Direction::Left => vi_point.sub_absolute(terminal, Boundary::Wrap, 1),
Direction::Right => vi_point.add(terminal, Boundary::None, 1),
Direction::Left => vi_point.sub(terminal, Boundary::None, 1),
};
if let Some(regex_match) = ctx.search_next(origin, direction, Side::Left) {
@ -205,9 +200,7 @@ impl<T: EventListener> Execute<T> for Action {
},
Action::ViAction(ViAction::SearchStart) => {
let terminal = ctx.terminal();
let origin = terminal
.visible_to_buffer(terminal.vi_mode_cursor.point)
.sub_absolute(terminal, Boundary::Wrap, 1);
let origin = terminal.vi_mode_cursor.point.sub(terminal, Boundary::None, 1);
if let Some(regex_match) = ctx.search_next(origin, Direction::Left, Side::Left) {
ctx.terminal_mut().vi_goto_point(*regex_match.start());
@ -216,9 +209,7 @@ impl<T: EventListener> Execute<T> for Action {
},
Action::ViAction(ViAction::SearchEnd) => {
let terminal = ctx.terminal();
let origin = terminal
.visible_to_buffer(terminal.vi_mode_cursor.point)
.add_absolute(terminal, Boundary::Wrap, 1);
let origin = terminal.vi_mode_cursor.point.add(terminal, Boundary::None, 1);
if let Some(regex_match) = ctx.search_next(origin, Direction::Right, Side::Right) {
ctx.terminal_mut().vi_goto_point(*regex_match.end());
@ -273,7 +264,7 @@ impl<T: EventListener> Execute<T> for Action {
Action::ScrollPageUp => {
// Move vi mode cursor.
let term = ctx.terminal_mut();
let scroll_lines = term.screen_lines().0 as isize;
let scroll_lines = term.screen_lines() as i32;
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
ctx.scroll(Scroll::PageUp);
@ -281,7 +272,7 @@ impl<T: EventListener> Execute<T> for Action {
Action::ScrollPageDown => {
// Move vi mode cursor.
let term = ctx.terminal_mut();
let scroll_lines = -(term.screen_lines().0 as isize);
let scroll_lines = -(term.screen_lines() as i32);
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
ctx.scroll(Scroll::PageDown);
@ -289,7 +280,7 @@ impl<T: EventListener> Execute<T> for Action {
Action::ScrollHalfPageUp => {
// Move vi mode cursor.
let term = ctx.terminal_mut();
let scroll_lines = term.screen_lines().0 as isize / 2;
let scroll_lines = term.screen_lines() as i32 / 2;
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
ctx.scroll(Scroll::Delta(scroll_lines));
@ -297,37 +288,19 @@ impl<T: EventListener> Execute<T> for Action {
Action::ScrollHalfPageDown => {
// Move vi mode cursor.
let term = ctx.terminal_mut();
let scroll_lines = -(term.screen_lines().0 as isize / 2);
let scroll_lines = -(term.screen_lines() as i32 / 2);
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
ctx.scroll(Scroll::Delta(scroll_lines));
},
Action::ScrollLineUp => {
// Move vi mode cursor.
let term = ctx.terminal();
if term.grid().display_offset() != term.history_size()
&& term.vi_mode_cursor.point.line + 1 != term.screen_lines()
{
ctx.terminal_mut().vi_mode_cursor.point.line += 1;
}
ctx.scroll(Scroll::Delta(1));
},
Action::ScrollLineDown => {
// Move vi mode cursor.
if ctx.terminal().grid().display_offset() != 0
&& ctx.terminal().vi_mode_cursor.point.line.0 != 0
{
ctx.terminal_mut().vi_mode_cursor.point.line -= 1;
}
ctx.scroll(Scroll::Delta(-1));
},
Action::ScrollLineUp => ctx.scroll(Scroll::Delta(1)),
Action::ScrollLineDown => ctx.scroll(Scroll::Delta(-1)),
Action::ScrollToTop => {
ctx.scroll(Scroll::Top);
// Move vi mode cursor.
ctx.terminal_mut().vi_mode_cursor.point.line = Line(0);
let topmost_line = ctx.terminal().topmost_line();
ctx.terminal_mut().vi_mode_cursor.point.line = topmost_line;
ctx.terminal_mut().vi_motion(ViMotion::FirstOccupied);
ctx.mark_dirty();
},
@ -336,7 +309,7 @@ impl<T: EventListener> Execute<T> for Action {
// Move vi mode cursor.
let term = ctx.terminal_mut();
term.vi_mode_cursor.point.line = term.screen_lines() - 1;
term.vi_mode_cursor.point.line = term.bottommost_line();
// Move to beginning twice, to always jump across linewraps.
term.vi_motion(ViMotion::FirstOccupied);
@ -414,11 +387,10 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
self.ctx.mouse_mut().y = y;
let inside_text_area = size_info.contains_point(x, y);
let point = size_info.pixels_to_coords(x, y);
let cell_side = self.get_mouse_side();
let point = self.coords_to_point(x, y);
let cell_side = self.cell_side(x);
let cell_changed =
point.line != self.ctx.mouse().line || point.column != self.ctx.mouse().column;
let cell_changed = point != self.ctx.mouse().point;
// Update mouse state and check for URL change.
let mouse_state = self.mouse_state();
@ -435,14 +407,15 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
self.ctx.mouse_mut().inside_text_area = inside_text_area;
self.ctx.mouse_mut().cell_side = cell_side;
self.ctx.mouse_mut().line = point.line;
self.ctx.mouse_mut().column = point.column;
self.ctx.mouse_mut().point = point;
// Don't launch URLs if mouse has moved.
self.ctx.mouse_mut().block_url_launcher = true;
if (lmb_pressed || rmb_pressed) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode())
{
let line = Line(point.line as i32) - self.ctx.terminal().grid().display_offset();
let point = Point::new(line, point.column);
self.ctx.update_selection(point, cell_side);
} else if cell_changed
&& point.line < self.ctx.terminal().screen_lines()
@ -460,9 +433,26 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
}
fn get_mouse_side(&self) -> Side {
/// Convert window space pixels to terminal grid coordinates.
///
/// If the coordinates are outside of the terminal grid, like positions inside the padding, the
/// coordinates will be clamped to the closest grid coordinates.
#[inline]
fn coords_to_point(&self, x: usize, y: usize) -> Point<usize> {
let size = self.ctx.size_info();
let column = x.saturating_sub(size.padding_x() as usize) / (size.cell_width() as usize);
let column = min(Column(column), size.last_column());
let line = y.saturating_sub(size.padding_y() as usize) / (size.cell_height() as usize);
let line = min(line, size.bottommost_line().0 as usize);
Point::new(line, column)
}
/// Check which side of a cell an X coordinate lies on.
fn cell_side(&self, x: usize) -> Side {
let size_info = self.ctx.size_info();
let x = self.ctx.mouse().x;
let cell_x =
x.saturating_sub(size_info.padding_x() as usize) % size_info.cell_width() as usize;
@ -483,12 +473,12 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
fn normal_mouse_report(&mut self, button: u8) {
let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column);
let Point { line, column } = self.ctx.mouse().point;
let utf8 = self.ctx.terminal().mode().contains(TermMode::UTF8_MOUSE);
let max_point = if utf8 { 2015 } else { 223 };
if line >= Line(max_point) || column >= Column(max_point) {
if line >= max_point || column >= Column(max_point) {
return;
}
@ -507,17 +497,17 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
msg.push(32 + 1 + column.0 as u8);
}
if utf8 && line >= Line(95) {
msg.append(&mut mouse_pos_encode(line.0));
if utf8 && line >= 95 {
msg.append(&mut mouse_pos_encode(line));
} else {
msg.push(32 + 1 + line.0 as u8);
msg.push(32 + 1 + line as u8);
}
self.ctx.write_to_pty(msg);
}
fn sgr_mouse_report(&mut self, button: u8, state: ElementState) {
let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column);
let Point { line, column } = self.ctx.mouse().point;
let c = match state {
ElementState::Pressed => 'M',
ElementState::Released => 'm',
@ -589,9 +579,10 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
};
// Load mouse point, treating message bar and padding as the closest cell.
let mouse = self.ctx.mouse();
let mut point = self.ctx.size_info().pixels_to_coords(mouse.x, mouse.y);
point.line = min(point.line, self.ctx.terminal().screen_lines() - 1);
let point = self.ctx.mouse().point;
let display_offset = self.ctx.terminal().grid().display_offset();
let absolute_line = Line(point.line as i32) - display_offset;
let point = Point::new(absolute_line, point.column);
match button {
MouseButton::Left => self.on_left_click(point),
@ -751,24 +742,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
let lines = self.ctx.mouse().scroll_px / height;
// Store absolute position of vi mode cursor.
let term = self.ctx.terminal();
let absolute = term.visible_to_buffer(term.vi_mode_cursor.point);
self.ctx.scroll(Scroll::Delta(lines as isize));
// Try to restore vi mode cursor position, to keep it above its previous content.
let term = self.ctx.terminal_mut();
term.vi_mode_cursor.point = term.grid().clamp_buffer_to_visible(absolute);
term.vi_mode_cursor.point.column = absolute.column;
// Update selection.
if term.mode().contains(TermMode::VI) {
let point = term.vi_mode_cursor.point;
if !self.ctx.selection_is_empty() {
self.ctx.update_selection(point, Side::Right);
}
}
self.ctx.scroll(Scroll::Delta(lines as i32));
}
self.ctx.mouse_mut().scroll_px %= height;
@ -977,13 +951,13 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
// Calculate Y position of the end of the last terminal line.
let size = self.ctx.size_info();
let terminal_end = size.padding_y() as usize
+ size.cell_height() as usize * (size.screen_lines().0 + search_height);
+ size.cell_height() as usize * (size.screen_lines() + search_height);
let mouse = self.ctx.mouse();
if self.ctx.message().is_none() || (mouse.y <= terminal_end) {
None
} else if mouse.y <= terminal_end + size.cell_height() as usize
&& mouse.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.cols()
&& mouse.point.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.columns()
{
Some(MouseState::MessageBarButton)
} else {
@ -1055,7 +1029,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
// Compute the height of the scrolling areas.
let end_top = max(min_height, size.padding_y() as i32);
let text_area_bottom = size.padding_y() + size.screen_lines().0 as f32 * size.cell_height();
let text_area_bottom = size.padding_y() + size.screen_lines() as f32 * size.cell_height();
let start_bottom = min(size.height() as i32 - min_height, text_area_bottom as i32);
// Get distance from closest window boundary.
@ -1069,7 +1043,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
};
// Scale number of lines scrolled based on distance to boundary.
let delta = delta as isize / step as isize;
let delta = delta as i32 / step as i32;
let event = Event::Scroll(Scroll::Delta(delta));
// Schedule event.
@ -1120,7 +1094,7 @@ mod tests {
impl<'a, T: EventListener> super::ActionContext<T> for ActionContext<'a, T> {
fn search_next(
&mut self,
_origin: Point<usize>,
_origin: Point,
_direction: Direction,
_side: Side,
) -> Option<Match> {
@ -1155,17 +1129,6 @@ mod tests {
self.terminal.scroll_display(scroll);
}
fn mouse_coords(&self) -> Option<Point> {
let x = self.mouse.x as usize;
let y = self.mouse.y as usize;
if self.size_info.contains_point(x, y) {
Some(self.size_info.pixels_to_coords(x, y))
} else {
None
}
}
fn mouse_mode(&self) -> bool {
false
}

View file

@ -25,6 +25,7 @@ use log::{error, info};
use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS};
use alacritty_terminal::event_loop::{self, EventLoop, Msg};
use alacritty_terminal::grid::Dimensions;
use alacritty_terminal::sync::FairMutex;
use alacritty_terminal::term::Term;
use alacritty_terminal::tty;
@ -139,7 +140,7 @@ fn run(
info!(
"PTY dimensions: {:?} x {:?}",
display.size_info.screen_lines(),
display.size_info.cols()
display.size_info.columns()
);
// Create the terminal.

View file

@ -1,8 +1,10 @@
use std::collections::VecDeque;
use alacritty_terminal::term::SizeInfo;
use unicode_width::UnicodeWidthChar;
use alacritty_terminal::grid::Dimensions;
use alacritty_terminal::term::SizeInfo;
pub const CLOSE_BUTTON_TEXT: &str = "[X]";
const CLOSE_BUTTON_PADDING: usize = 1;
const MIN_FREE_LINES: usize = 3;
@ -34,7 +36,7 @@ impl Message {
/// Formatted message text lines.
pub fn text(&self, size_info: &SizeInfo) -> Vec<String> {
let num_cols = size_info.cols().0;
let num_cols = size_info.columns();
let total_lines =
(size_info.height() - 2. * size_info.padding_y()) / size_info.cell_height();
let max_lines = (total_lines as usize).saturating_sub(MIN_FREE_LINES);

View file

@ -481,7 +481,7 @@ impl Batch {
self.instances.push(InstanceData {
col: cell.point.column.0 as u16,
row: cell.point.line.0 as u16,
row: cell.point.line as u16,
top: glyph.top,
left: glyph.left,
@ -830,7 +830,7 @@ impl<'a> RenderApi<'a> {
pub fn render_string(
&mut self,
glyph_cache: &mut GlyphCache,
point: Point,
point: Point<usize>,
fg: Rgb,
bg: Rgb,
string: &str,
@ -846,7 +846,6 @@ impl<'a> RenderApi<'a> {
bg_alpha: 1.0,
fg,
bg,
is_match: false,
})
.collect::<Vec<_>>();

View file

@ -3,6 +3,7 @@ use std::mem;
use crossfont::Metrics;
use alacritty_terminal::grid::Dimensions;
use alacritty_terminal::index::{Column, Point};
use alacritty_terminal::term::cell::Flags;
use alacritty_terminal::term::color::Rgb;
@ -31,8 +32,8 @@ impl RenderRect {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct RenderLine {
pub start: Point,
pub end: Point,
pub start: Point<usize>,
pub end: Point<usize>,
pub color: Rgb,
}
@ -42,7 +43,7 @@ impl RenderLine {
let mut start = self.start;
while start.line < self.end.line {
let end = Point::new(start.line, size.cols() - 1);
let end = Point::new(start.line, size.last_column());
Self::push_rects(&mut rects, metrics, size, flag, start, end, self.color);
start = Point::new(start.line + 1, Column(0));
}
@ -57,8 +58,8 @@ impl RenderLine {
metrics: &Metrics,
size: &SizeInfo,
flag: Flags,
start: Point,
end: Point,
start: Point<usize>,
end: Point<usize>,
color: Rgb,
) {
let (position, thickness) = match flag {
@ -99,8 +100,8 @@ impl RenderLine {
fn create_rect(
size: &SizeInfo,
descent: f32,
start: Point,
end: Point,
start: Point<usize>,
end: Point<usize>,
position: f32,
mut thickness: f32,
color: Rgb,
@ -112,7 +113,7 @@ impl RenderLine {
// Make sure lines are always visible.
thickness = thickness.max(1.);
let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height();
let line_bottom = (start.line as f32 + 1.) * size.cell_height();
let baseline = line_bottom + descent;
let mut y = (baseline - position - thickness / 2.).ceil();

View file

@ -5,7 +5,8 @@ use crossfont::Metrics;
use glutin::event::{ElementState, ModifiersState};
use urlocator::{UrlLocation, UrlLocator};
use alacritty_terminal::index::{Column, Point};
use alacritty_terminal::grid::Dimensions;
use alacritty_terminal::index::{Boundary, Column, Line, Point};
use alacritty_terminal::term::cell::Flags;
use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::SizeInfo;
@ -15,14 +16,15 @@ use crate::display::content::RenderableCell;
use crate::event::Mouse;
use crate::renderer::rects::{RenderLine, RenderRect};
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq)]
pub struct Url {
lines: Vec<RenderLine>,
end_offset: u16,
num_cols: Column,
size: SizeInfo,
}
impl Url {
/// Rectangles required for underlining the URL.
pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
let end = self.end();
self.lines
@ -37,20 +39,28 @@ impl Url {
.collect()
}
pub fn start(&self) -> Point {
/// Viewport start point of the URL.
pub fn start(&self) -> Point<usize> {
self.lines[0].start
}
pub fn end(&self) -> Point {
self.lines[self.lines.len() - 1].end.sub(self.num_cols, self.end_offset as usize)
/// Viewport end point of the URL.
pub fn end(&self) -> Point<usize> {
let end = self.lines[self.lines.len() - 1].end;
// Convert to Point<Line> to make use of the grid clamping logic.
let mut end = Point::new(Line(end.line as i32), end.column);
end = end.sub(&self.size, Boundary::Cursor, self.end_offset as usize);
Point::new(end.line.0 as usize, end.column)
}
}
pub struct Urls {
locator: UrlLocator,
urls: Vec<Url>,
scheme_buffer: Vec<(Point, Rgb)>,
last_point: Option<Point>,
scheme_buffer: Vec<(Point<usize>, Rgb)>,
next_point: Point<usize>,
state: UrlLocation,
}
@ -61,7 +71,7 @@ impl Default for Urls {
scheme_buffer: Vec::new(),
urls: Vec::new(),
state: UrlLocation::Reset,
last_point: None,
next_point: Point::new(0, Column(0)),
}
}
}
@ -72,7 +82,7 @@ impl Urls {
}
// Update tracked URLs.
pub fn update(&mut self, num_cols: Column, cell: &RenderableCell) {
pub fn update(&mut self, size: &SizeInfo, cell: &RenderableCell) {
let point = cell.point;
let mut end = point;
@ -82,11 +92,15 @@ impl Urls {
}
// Reset URL when empty cells have been skipped.
if point != Point::default() && Some(point.sub(num_cols, 1)) != self.last_point {
if point != Point::new(0, Column(0)) && point != self.next_point {
self.reset();
}
self.last_point = Some(end);
self.next_point = if end.column.0 + 1 == size.columns() {
Point::new(end.line + 1, Column(0))
} else {
Point::new(end.line, end.column + 1)
};
// Extend current state if a leading wide char spacer is encountered.
if cell.flags.intersects(Flags::LEADING_WIDE_CHAR_SPACER) {
@ -106,7 +120,7 @@ impl Urls {
match (self.state, last_state) {
(UrlLocation::Url(_length, end_offset), UrlLocation::Scheme) => {
// Create empty URL.
self.urls.push(Url { lines: Vec::new(), end_offset, num_cols });
self.urls.push(Url { lines: Vec::new(), end_offset, size: *size });
// Push schemes into URL.
for (scheme_point, scheme_fg) in self.scheme_buffer.split_off(0) {
@ -125,13 +139,13 @@ impl Urls {
}
// Reset at un-wrapped linebreak.
if cell.point.column + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) {
if cell.point.column.0 + 1 == size.columns() && !cell.flags.contains(Flags::WRAPLINE) {
self.reset();
}
}
/// Extend the last URL.
fn extend_url(&mut self, start: Point, end: Point, color: Rgb, end_offset: u16) {
fn extend_url(&mut self, start: Point<usize>, end: Point<usize>, color: Rgb, end_offset: u16) {
let url = self.urls.last_mut().unwrap();
// If color changed, we need to insert a new line.
@ -170,11 +184,11 @@ impl Urls {
return None;
}
self.find_at(Point::new(mouse.line, mouse.column))
self.find_at(mouse.point)
}
/// Find URL at location.
pub fn find_at(&self, point: Point) -> Option<Url> {
pub fn find_at(&self, point: Point<usize>) -> Option<Url> {
for url in &self.urls {
if (url.start()..=url.end()).contains(&point) {
return Some(url.clone());
@ -194,7 +208,7 @@ impl Urls {
mod tests {
use super::*;
use alacritty_terminal::index::{Column, Line};
use alacritty_terminal::index::Column;
fn text_to_cells(text: &str) -> Vec<RenderableCell> {
text.chars()
@ -202,12 +216,11 @@ mod tests {
.map(|(i, character)| RenderableCell {
character,
zerowidth: None,
point: Point::new(Line(0), Column(i)),
point: Point::new(0, Column(i)),
fg: Default::default(),
bg: Default::default(),
bg_alpha: 0.,
flags: Flags::empty(),
is_match: false,
})
.collect()
}
@ -215,14 +228,14 @@ mod tests {
#[test]
fn multi_color_url() {
let mut input = text_to_cells("test https://example.org ing");
let num_cols = input.len();
let size = SizeInfo::new(input.len() as f32, 1., 1.0, 1.0, 0.0, 0.0, false);
input[10].fg = Rgb { r: 0xff, g: 0x00, b: 0xff };
let mut urls = Urls::new();
for cell in input {
urls.update(Column(num_cols), &cell);
urls.update(&size, &cell);
}
let url = urls.urls.first().unwrap();
@ -233,12 +246,12 @@ mod tests {
#[test]
fn multiple_urls() {
let input = text_to_cells("test git:a git:b git:c ing");
let num_cols = input.len();
let size = SizeInfo::new(input.len() as f32, 1., 1.0, 1.0, 0.0, 0.0, false);
let mut urls = Urls::new();
for cell in input {
urls.update(Column(num_cols), &cell);
urls.update(&size, &cell);
}
assert_eq!(urls.urls.len(), 3);
@ -256,12 +269,12 @@ mod tests {
#[test]
fn wide_urls() {
let input = text_to_cells("test https://こんにちは (http:여보세요) ing");
let num_cols = input.len() + 9;
let size = SizeInfo::new(input.len() as f32 + 9., 1., 1.0, 1.0, 0.0, 0.0, false);
let mut urls = Urls::new();
for cell in input {
urls.update(Column(num_cols), &cell);
urls.update(&size, &cell);
}
assert_eq!(urls.urls.len(), 2);

View file

@ -27,7 +27,7 @@ struct Test {
#[config(deprecated = "shouldn't be hit")]
field2: String,
field3: Option<u8>,
#[doc("aaa")]
#[doc(hidden)]
nesting: Test2<usize>,
#[config(flatten)]
flatten: Test3,

View file

@ -299,13 +299,13 @@ pub trait Handler {
fn goto_col(&mut self, _: Column) {}
/// Insert blank characters in current line starting from cursor.
fn insert_blank(&mut self, _: Column) {}
fn insert_blank(&mut self, _: usize) {}
/// Move cursor up `rows`.
fn move_up(&mut self, _: Line) {}
fn move_up(&mut self, _: usize) {}
/// Move cursor down `rows`.
fn move_down(&mut self, _: Line) {}
fn move_down(&mut self, _: usize) {}
/// Identify the terminal (should write back to the pty stream).
fn identify_terminal<W: io::Write>(&mut self, _: &mut W, _intermediate: Option<char>) {}
@ -320,10 +320,10 @@ pub trait Handler {
fn move_backward(&mut self, _: Column) {}
/// Move cursor down `rows` and set to column 1.
fn move_down_and_cr(&mut self, _: Line) {}
fn move_down_and_cr(&mut self, _: usize) {}
/// Move cursor up `rows` and set to column 1.
fn move_up_and_cr(&mut self, _: Line) {}
fn move_up_and_cr(&mut self, _: usize) {}
/// Put `count` tabs.
fn put_tab(&mut self, _count: u16) {}
@ -352,16 +352,16 @@ pub trait Handler {
fn set_horizontal_tabstop(&mut self) {}
/// Scroll up `rows` rows.
fn scroll_up(&mut self, _: Line) {}
fn scroll_up(&mut self, _: usize) {}
/// Scroll down `rows` rows.
fn scroll_down(&mut self, _: Line) {}
fn scroll_down(&mut self, _: usize) {}
/// Insert `count` blank lines.
fn insert_blank_lines(&mut self, _: Line) {}
fn insert_blank_lines(&mut self, _: usize) {}
/// Delete `count` lines.
fn delete_lines(&mut self, _: Line) {}
fn delete_lines(&mut self, _: usize) {}
/// Erase `count` chars in current line following cursor.
///
@ -373,7 +373,7 @@ pub trait Handler {
///
/// Deleting a character is like the delete key on the keyboard - everything
/// to the right of the deleted things is shifted left.
fn delete_chars(&mut self, _: Column) {}
fn delete_chars(&mut self, _: usize) {}
/// Move backward `count` tabs.
fn move_backward_tabs(&mut self, _count: u16) {}
@ -1122,11 +1122,9 @@ where
};
match (action, intermediates) {
('@', []) => handler.insert_blank(Column(next_param_or(1) as usize)),
('A', []) => {
handler.move_up(Line(next_param_or(1) as usize));
},
('B', []) | ('e', []) => handler.move_down(Line(next_param_or(1) as usize)),
('@', []) => handler.insert_blank(next_param_or(1) as usize),
('A', []) => handler.move_up(next_param_or(1) as usize),
('B', []) | ('e', []) => handler.move_down(next_param_or(1) as usize),
('b', []) => {
if let Some(c) = self.state.preceding_char {
for _ in 0..next_param_or(1) {
@ -1141,9 +1139,9 @@ where
handler.identify_terminal(writer, intermediates.get(0).map(|&i| i as char))
},
('D', []) => handler.move_backward(Column(next_param_or(1) as usize)),
('d', []) => handler.goto_line(Line(next_param_or(1) as usize - 1)),
('E', []) => handler.move_down_and_cr(Line(next_param_or(1) as usize)),
('F', []) => handler.move_up_and_cr(Line(next_param_or(1) as usize)),
('d', []) => handler.goto_line(Line(next_param_or(1) as i32 - 1)),
('E', []) => handler.move_down_and_cr(next_param_or(1) as usize),
('F', []) => handler.move_up_and_cr(next_param_or(1) as usize),
('G', []) | ('`', []) => handler.goto_col(Column(next_param_or(1) as usize - 1)),
('g', []) => {
let mode = match next_param_or(0) {
@ -1158,7 +1156,7 @@ where
handler.clear_tabs(mode);
},
('H', []) | ('f', []) => {
let y = next_param_or(1) as usize;
let y = next_param_or(1) as i32;
let x = next_param_or(1) as usize;
handler.goto(Line(y - 1), Column(x - 1));
},
@ -1198,7 +1196,7 @@ where
handler.clear_line(mode);
},
('L', []) => handler.insert_blank_lines(Line(next_param_or(1) as usize)),
('L', []) => handler.insert_blank_lines(next_param_or(1) as usize),
('l', intermediates) => {
for param in params_iter.map(|param| param[0]) {
match Mode::from_primitive(intermediates.get(0), param) {
@ -1207,7 +1205,7 @@ where
}
}
},
('M', []) => handler.delete_lines(Line(next_param_or(1) as usize)),
('M', []) => handler.delete_lines(next_param_or(1) as usize),
('m', []) => {
if params.is_empty() {
handler.terminal_attribute(Attr::Reset);
@ -1221,7 +1219,7 @@ where
}
},
('n', []) => handler.device_status(writer, next_param_or(0) as usize),
('P', []) => handler.delete_chars(Column(next_param_or(1) as usize)),
('P', []) => handler.delete_chars(next_param_or(1) as usize),
('q', [b' ']) => {
// DECSCUSR (CSI Ps SP q) -- Set Cursor Style.
let cursor_style_id = next_param_or(0);
@ -1247,9 +1245,9 @@ where
handler.set_scrolling_region(top, bottom);
},
('S', []) => handler.scroll_up(Line(next_param_or(1) as usize)),
('S', []) => handler.scroll_up(next_param_or(1) as usize),
('s', []) => handler.save_cursor_position(),
('T', []) => handler.scroll_down(Line(next_param_or(1) as usize)),
('T', []) => handler.scroll_down(next_param_or(1) as usize),
('t', []) => match next_param_or(1) as usize {
14 => handler.text_area_size_pixels(writer),
18 => handler.text_area_size_chars(writer),

View file

@ -1,13 +1,13 @@
//! A specialized 2D grid implementation optimized for use in a terminal.
use std::cmp::{max, min};
use std::iter::{Map, TakeWhile};
use std::ops::{Bound, Deref, Index, IndexMut, Range, RangeBounds, RangeInclusive};
use std::iter::TakeWhile;
use std::ops::{Bound, Deref, Index, IndexMut, Range, RangeBounds};
use serde::{Deserialize, Serialize};
use crate::ansi::{CharsetIndex, StandardCharset};
use crate::index::{Column, IndexRange, Line, Point};
use crate::index::{Column, Line, Point};
use crate::term::cell::{Flags, ResetDiscriminant};
pub mod resize;
@ -71,7 +71,7 @@ impl IndexMut<CharsetIndex> for Charsets {
#[derive(Debug, Copy, Clone)]
pub enum Scroll {
Delta(isize),
Delta(i32),
PageUp,
PageDown,
Top,
@ -103,7 +103,7 @@ pub enum Scroll {
/// │ │
/// └─────────────────────────┘ <-- zero
/// ^
/// cols
/// columns
/// ```
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Grid<T> {
@ -120,10 +120,10 @@ pub struct Grid<T> {
raw: Storage<T>,
/// Number of columns.
cols: Column,
columns: usize,
/// Number of visible lines.
lines: Line,
lines: usize,
/// Offset of displayed area.
///
@ -137,15 +137,15 @@ pub struct Grid<T> {
}
impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
pub fn new(lines: Line, cols: Column, max_scroll_limit: usize) -> Grid<T> {
pub fn new(lines: usize, columns: usize, max_scroll_limit: usize) -> Grid<T> {
Grid {
raw: Storage::with_capacity(lines, cols),
raw: Storage::with_capacity(lines, columns),
max_scroll_limit,
display_offset: 0,
saved_cursor: Cursor::default(),
cursor: Cursor::default(),
lines,
cols,
columns,
}
}
@ -161,12 +161,11 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
pub fn scroll_display(&mut self, scroll: Scroll) {
self.display_offset = match scroll {
Scroll::Delta(count) => min(
max((self.display_offset as isize) + count, 0isize) as usize,
self.history_size(),
),
Scroll::PageUp => min(self.display_offset + self.lines.0, self.history_size()),
Scroll::PageDown => self.display_offset.saturating_sub(self.lines.0),
Scroll::Delta(count) => {
min(max((self.display_offset as i32) + count, 0) as usize, self.history_size())
},
Scroll::PageUp => min(self.display_offset + self.lines, self.history_size()),
Scroll::PageDown => self.display_offset.saturating_sub(self.lines),
Scroll::Top => self.history_size(),
Scroll::Bottom => 0,
};
@ -175,7 +174,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
fn increase_scroll_limit(&mut self, count: usize) {
let count = min(count, self.max_scroll_limit - self.history_size());
if count != 0 {
self.raw.initialize(count, self.cols);
self.raw.initialize(count, self.columns);
}
}
@ -188,18 +187,15 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
}
#[inline]
pub fn scroll_down<D>(&mut self, region: &Range<Line>, positions: Line)
pub fn scroll_down<D>(&mut self, region: &Range<Line>, positions: usize)
where
T: ResetDiscriminant<D>,
D: PartialEq,
{
let screen_lines = self.screen_lines().0;
// When rotating the entire region, just reset everything.
if positions >= region.end - region.start {
for i in region.start.0..region.end.0 {
let index = screen_lines - i - 1;
self.raw[index].reset(&self.cursor.template);
if region.end - region.start <= positions {
for i in (region.start.0..region.end.0).map(Line::from) {
self.raw[i].reset(&self.cursor.template);
}
return;
@ -218,32 +214,32 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
//
// We need to start from the top, to make sure the fixed lines aren't swapped with each
// other.
let fixed_lines = screen_lines - region.end.0;
for i in (0..fixed_lines).rev() {
self.raw.swap(i, i + positions.0);
let screen_lines = self.screen_lines() as i32;
for i in (region.end.0..screen_lines).map(Line::from) {
self.raw.swap(i, i - positions as i32);
}
// Rotate the entire line buffer downward.
self.raw.rotate_down(*positions);
self.raw.rotate_down(positions);
// Ensure all new lines are fully cleared.
for i in 0..positions.0 {
let index = screen_lines - i - 1;
self.raw[index].reset(&self.cursor.template);
for i in (0..positions).map(Line::from) {
self.raw[i].reset(&self.cursor.template);
}
// Swap the fixed lines at the top back into position.
for i in 0..region.start.0 {
let index = screen_lines - i - 1;
self.raw.swap(index, index - positions.0);
for i in (0..region.start.0).map(Line::from) {
self.raw.swap(i, i + positions);
}
} else {
// Subregion rotation.
for line in IndexRange((region.start + positions)..region.end).rev() {
self.raw.swap_lines(line, line - positions);
let range = (region.start + positions).0..region.end.0;
for line in range.rev().map(Line::from) {
self.raw.swap(line, line - positions);
}
for line in IndexRange(region.start..(region.start + positions)) {
let range = region.start.0..(region.start + positions).0;
for line in range.rev().map(Line::from) {
self.raw[line].reset(&self.cursor.template);
}
}
@ -252,18 +248,15 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
/// Move lines at the bottom toward the top.
///
/// This is the performance-sensitive part of scrolling.
pub fn scroll_up<D>(&mut self, region: &Range<Line>, positions: Line)
pub fn scroll_up<D>(&mut self, region: &Range<Line>, positions: usize)
where
T: ResetDiscriminant<D>,
D: PartialEq,
{
let screen_lines = self.screen_lines().0;
// When rotating the entire region with fixed lines at the top, just reset everything.
if positions >= region.end - region.start && region.start != Line(0) {
for i in region.start.0..region.end.0 {
let index = screen_lines - i - 1;
self.raw[index].reset(&self.cursor.template);
if region.end - region.start <= positions && region.start != 0 {
for i in (region.start.0..region.end.0).map(Line::from) {
self.raw[i].reset(&self.cursor.template);
}
return;
@ -271,11 +264,11 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
// Update display offset when not pinned to active area.
if self.display_offset != 0 {
self.display_offset = min(self.display_offset + *positions, self.max_scroll_limit);
self.display_offset = min(self.display_offset + positions, self.max_scroll_limit);
}
// Create scrollback for the new lines.
self.increase_scroll_limit(*positions);
self.increase_scroll_limit(positions);
// Swap the lines fixed at the top to their target positions after rotation.
//
@ -285,23 +278,22 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
//
// We need to start from the bottom, to make sure the fixed lines aren't swapped with each
// other.
for i in (0..region.start.0).rev() {
let index = screen_lines - i - 1;
self.raw.swap(index, index - positions.0);
for i in (0..region.start.0).rev().map(Line::from) {
self.raw.swap(i, i + positions);
}
// Rotate the entire line buffer upward.
self.raw.rotate(-(positions.0 as isize));
self.raw.rotate(-(positions as isize));
// Ensure all new lines are fully cleared.
for i in 0..positions.0 {
let screen_lines = self.screen_lines();
for i in ((screen_lines - positions)..screen_lines).map(Line::from) {
self.raw[i].reset(&self.cursor.template);
}
// Swap the fixed lines at the bottom back into position.
let fixed_lines = screen_lines - region.end.0;
for i in 0..fixed_lines {
self.raw.swap(i, i + positions.0);
for i in (region.end.0..(screen_lines as i32)).rev().map(Line::from) {
self.raw.swap(i, i - positions);
}
}
@ -311,16 +303,16 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
D: PartialEq,
{
// Determine how many lines to scroll up by.
let end = Point { line: 0, column: self.cols() };
let end = Point::new(Line(self.lines as i32 - 1), Column(self.columns()));
let mut iter = self.iter_from(end);
while let Some(cell) = iter.prev() {
if !cell.is_empty() || cell.point.line >= *self.lines {
if !cell.is_empty() || cell.point.line < 0 {
break;
}
}
debug_assert!(iter.point.line <= *self.lines);
let positions = self.lines - iter.point.line;
let region = Line(0)..self.screen_lines();
debug_assert!(iter.point.line >= -1);
let positions = (iter.point.line.0 + 1) as usize;
let region = Line(0)..Line(self.lines as i32);
// Reset display offset.
self.display_offset = 0;
@ -329,8 +321,8 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
self.scroll_up(&region, positions);
// Reset rotated lines.
for i in positions.0..self.lines.0 {
self.raw[i].reset(&self.cursor.template);
for line in (0..(self.lines - positions)).map(Line::from) {
self.raw[line].reset(&self.cursor.template);
}
}
@ -347,8 +339,9 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
self.display_offset = 0;
// Reset all visible lines.
for row in 0..self.raw.len() {
self.raw[row].reset(&self.cursor.template);
let range = self.topmost_line().0..(self.screen_lines() as i32);
for line in range.map(Line::from) {
self.raw[line].reset(&self.cursor.template);
}
}
}
@ -369,59 +362,17 @@ impl<T> Grid<T> {
let end = match bounds.end_bound() {
Bound::Included(line) => *line + 1,
Bound::Excluded(line) => *line,
Bound::Unbounded => self.screen_lines(),
Bound::Unbounded => Line(self.screen_lines() as i32),
};
debug_assert!(start < self.screen_lines());
debug_assert!(end <= self.screen_lines());
debug_assert!(start < self.screen_lines() as i32);
debug_assert!(end <= self.screen_lines() as i32);
for row in start.0..end.0 {
self.raw[Line(row)].reset(&self.cursor.template);
for line in (start.0..end.0).map(Line::from) {
self.raw[line].reset(&self.cursor.template);
}
}
/// Clamp a buffer point to the visible region.
pub fn clamp_buffer_to_visible(&self, point: Point<usize>) -> Point {
if point.line < self.display_offset {
Point::new(self.lines - 1, self.cols - 1)
} else if point.line >= self.display_offset + self.lines.0 {
Point::new(Line(0), Column(0))
} else {
// Since edgecases are handled, conversion is identical as visible to buffer.
self.visible_to_buffer(point.into()).into()
}
}
// Clamp a buffer point based range to the viewport.
//
// This will make sure the content within the range is visible and return `None` whenever the
// entire range is outside the visible region.
pub fn clamp_buffer_range_to_visible(
&self,
range: &RangeInclusive<Point<usize>>,
) -> Option<RangeInclusive<Point>> {
let start = range.start();
let end = range.end();
// Check if the range is completely offscreen
let viewport_end = self.display_offset;
let viewport_start = viewport_end + self.lines.0 - 1;
if end.line > viewport_start || start.line < viewport_end {
return None;
}
let start = self.clamp_buffer_to_visible(*start);
let end = self.clamp_buffer_to_visible(*end);
Some(start..=end)
}
/// Convert viewport relative point to global buffer indexing.
#[inline]
pub fn visible_to_buffer(&self, point: Point) -> Point<usize> {
Point { line: self.lines.0 + self.display_offset - point.line.0 - 1, column: point.column }
}
#[inline]
pub fn clear_history(&mut self) {
// Explicitly purge all lines from history.
@ -438,7 +389,7 @@ impl<T> Grid<T> {
self.truncate();
// Initialize everything with empty new lines.
self.raw.initialize(self.max_scroll_limit - self.history_size(), self.cols);
self.raw.initialize(self.max_scroll_limit - self.history_size(), self.columns);
}
/// This is used only for truncating before saving ref-tests.
@ -448,28 +399,21 @@ impl<T> Grid<T> {
}
#[inline]
pub fn iter_from(&self, point: Point<usize>) -> GridIterator<'_, T> {
pub fn iter_from(&self, point: Point) -> GridIterator<'_, T> {
GridIterator { grid: self, point }
}
/// Iterator over all visible cells.
#[inline]
pub fn display_iter(&self) -> DisplayIter<'_, T> {
let start = Point::new(self.display_offset + self.lines.0, self.cols() - 1);
let end = Point::new(self.display_offset, self.cols());
let start = Point::new(Line(-(self.display_offset as i32) - 1), self.last_column());
let end = Point::new(start.line + self.lines, Column(self.columns));
let iter = GridIterator { grid: self, point: start };
let display_offset = self.display_offset;
let lines = self.lines.0;
let take_while: DisplayIterTakeFun<'_, T> =
Box::new(move |indexed: &Indexed<&T>| indexed.point <= end);
let map: DisplayIterMapFun<'_, T> = Box::new(move |indexed: Indexed<&T>| {
let line = Line(lines + display_offset - indexed.point.line - 1);
Indexed { point: Point::new(line, indexed.point.column), cell: indexed.cell }
});
iter.take_while(take_while).map(map)
iter.take_while(take_while)
}
#[inline]
@ -488,7 +432,7 @@ impl<T: PartialEq> PartialEq for Grid<T> {
fn eq(&self, other: &Self) -> bool {
// Compare struct fields and check result of grid comparison.
self.raw.eq(&other.raw)
&& self.cols.eq(&other.cols)
&& self.columns.eq(&other.columns)
&& self.lines.eq(&other.lines)
&& self.display_offset.eq(&other.display_offset)
}
@ -510,38 +454,6 @@ impl<T> IndexMut<Line> for Grid<T> {
}
}
impl<T> Index<usize> for Grid<T> {
type Output = Row<T>;
#[inline]
fn index(&self, index: usize) -> &Row<T> {
&self.raw[index]
}
}
impl<T> IndexMut<usize> for Grid<T> {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut Row<T> {
&mut self.raw[index]
}
}
impl<T> Index<Point<usize>> for Grid<T> {
type Output = T;
#[inline]
fn index(&self, point: Point<usize>) -> &T {
&self[point.line][point.column]
}
}
impl<T> IndexMut<Point<usize>> for Grid<T> {
#[inline]
fn index_mut(&mut self, point: Point<usize>) -> &mut T {
&mut self[point.line][point.column]
}
}
impl<T> Index<Point> for Grid<T> {
type Output = T;
@ -564,15 +476,33 @@ pub trait Dimensions {
fn total_lines(&self) -> usize;
/// Height of the viewport in lines.
fn screen_lines(&self) -> Line;
fn screen_lines(&self) -> usize;
/// Width of the terminal in columns.
fn cols(&self) -> Column;
fn columns(&self) -> usize;
/// Index for the last column.
#[inline]
fn last_column(&self) -> Column {
Column(self.columns() - 1)
}
/// Line farthest up in the grid history.
#[inline]
fn topmost_line(&self) -> Line {
Line(-(self.history_size() as i32))
}
/// Line farthest down in the grid history.
#[inline]
fn bottommost_line(&self) -> Line {
Line(self.screen_lines() as i32 - 1)
}
/// Number of invisible lines part of the scrollback history.
#[inline]
fn history_size(&self) -> usize {
self.total_lines() - self.screen_lines().0
self.total_lines() - self.screen_lines()
}
}
@ -583,38 +513,38 @@ impl<G> Dimensions for Grid<G> {
}
#[inline]
fn screen_lines(&self) -> Line {
fn screen_lines(&self) -> usize {
self.lines
}
#[inline]
fn cols(&self) -> Column {
self.cols
fn columns(&self) -> usize {
self.columns
}
}
#[cfg(test)]
impl Dimensions for (Line, Column) {
impl Dimensions for (usize, usize) {
fn total_lines(&self) -> usize {
*self.0
}
fn screen_lines(&self) -> Line {
self.0
}
fn cols(&self) -> Column {
fn screen_lines(&self) -> usize {
self.0
}
fn columns(&self) -> usize {
self.1
}
}
#[derive(Debug, PartialEq)]
pub struct Indexed<T, L = usize> {
pub point: Point<L>,
pub struct Indexed<T> {
pub point: Point,
pub cell: T,
}
impl<T, L> Deref for Indexed<T, L> {
impl<T> Deref for Indexed<T> {
type Target = T;
#[inline]
@ -629,12 +559,12 @@ pub struct GridIterator<'a, T> {
grid: &'a Grid<T>,
/// Current position of the iterator within the grid.
point: Point<usize>,
point: Point,
}
impl<'a, T> GridIterator<'a, T> {
/// Current iteratior position.
pub fn point(&self) -> Point<usize> {
pub fn point(&self) -> Point {
self.point
}
@ -648,12 +578,17 @@ impl<'a, T> Iterator for GridIterator<'a, T> {
type Item = Indexed<&'a T>;
fn next(&mut self) -> Option<Self::Item> {
let last_col = self.grid.cols() - 1;
let last_column = self.grid.last_column();
// Stop once we've reached the end of the grid.
if self.point == Point::new(self.grid.bottommost_line(), last_column) {
return None;
}
match self.point {
Point { line, column: col } if line == 0 && col == last_col => return None,
Point { column: col, .. } if (col == last_col) => {
self.point.line -= 1;
Point { column, .. } if column == last_column => {
self.point.column = Column(0);
self.point.line += 1;
},
_ => self.point.column += Column(1),
}
@ -669,15 +604,18 @@ pub trait BidirectionalIterator: Iterator {
impl<'a, T> BidirectionalIterator for GridIterator<'a, T> {
fn prev(&mut self) -> Option<Self::Item> {
let last_col = self.grid.cols() - 1;
let topmost_line = self.grid.topmost_line();
let last_column = self.grid.last_column();
// Stop once we've reached the end of the grid.
if self.point == Point::new(topmost_line, Column(0)) {
return None;
}
match self.point {
Point { line, column: Column(0) } if line == self.grid.total_lines() - 1 => {
return None
},
Point { column: Column(0), .. } => {
self.point.line += 1;
self.point.column = last_col;
self.point.column = last_column;
self.point.line -= 1;
},
_ => self.point.column -= Column(1),
}
@ -686,7 +624,5 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> {
}
}
pub type DisplayIter<'a, T> =
Map<TakeWhile<GridIterator<'a, T>, DisplayIterTakeFun<'a, T>>, DisplayIterMapFun<'a, T>>;
pub type DisplayIter<'a, T> = TakeWhile<GridIterator<'a, T>, DisplayIterTakeFun<'a, T>>;
type DisplayIterTakeFun<'a, T> = Box<dyn Fn(&Indexed<&'a T>) -> bool>;
type DisplayIterMapFun<'a, T> = Box<dyn FnMut(Indexed<&'a T>) -> Indexed<&'a T, Line>>;

View file

@ -1,9 +1,9 @@
//! Grid resize and reflow.
use std::cmp::{min, Ordering};
use std::cmp::{max, min, Ordering};
use std::mem;
use crate::index::{Column, Line};
use crate::index::{Boundary, Column, Line};
use crate::term::cell::{Flags, ResetDiscriminant};
use crate::grid::row::Row;
@ -11,7 +11,7 @@ use crate::grid::{Dimensions, Grid, GridCell};
impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
/// Resize the grid's width and/or height.
pub fn resize<D>(&mut self, reflow: bool, lines: Line, cols: Column)
pub fn resize<D>(&mut self, reflow: bool, lines: usize, columns: usize)
where
T: ResetDiscriminant<D>,
D: PartialEq,
@ -25,9 +25,9 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
Ordering::Equal => (),
}
match self.cols.cmp(&cols) {
Ordering::Less => self.grow_cols(reflow, cols),
Ordering::Greater => self.shrink_cols(reflow, cols),
match self.columns.cmp(&columns) {
Ordering::Less => self.grow_columns(reflow, columns),
Ordering::Greater => self.shrink_columns(reflow, columns),
Ordering::Equal => (),
}
@ -40,32 +40,32 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
/// 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<D>(&mut self, new_line_count: Line)
fn grow_lines<D>(&mut self, target: usize)
where
T: ResetDiscriminant<D>,
D: PartialEq,
{
let lines_added = new_line_count - self.lines;
let lines_added = target - self.lines;
// Need to resize before updating buffer.
self.raw.grow_visible_lines(new_line_count);
self.lines = new_line_count;
self.raw.grow_visible_lines(target);
self.lines = target;
let history_size = self.history_size();
let from_history = min(history_size, lines_added.0);
let from_history = min(history_size, lines_added);
// Move existing lines up for every line that couldn't be pulled from history.
if from_history != lines_added.0 {
if from_history != lines_added {
let delta = lines_added - from_history;
self.scroll_up(&(Line(0)..new_line_count), delta);
self.scroll_up(&(Line(0)..Line(target as i32)), delta);
}
// Move cursor down for every line pulled from history.
self.saved_cursor.point.line += from_history;
self.cursor.point.line += from_history;
self.display_offset = self.display_offset.saturating_sub(*lines_added);
self.decrease_scroll_limit(*lines_added);
self.display_offset = self.display_offset.saturating_sub(lines_added);
self.decrease_scroll_limit(lines_added);
}
/// Remove lines from the visible area.
@ -75,37 +75,37 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
/// of the terminal window.
///
/// Alacritty takes the same approach.
fn shrink_lines<D>(&mut self, target: Line)
fn shrink_lines<D>(&mut self, target: usize)
where
T: ResetDiscriminant<D>,
D: PartialEq,
{
// Scroll up to keep content inside the window.
let required_scrolling = (self.cursor.point.line + 1).saturating_sub(target.0);
let required_scrolling = (self.cursor.point.line.0 as usize + 1).saturating_sub(target);
if required_scrolling > 0 {
self.scroll_up(&(Line(0)..self.lines), Line(required_scrolling));
self.scroll_up(&(Line(0)..Line(self.lines as i32)), required_scrolling);
// Clamp cursors to the new viewport size.
self.cursor.point.line = min(self.cursor.point.line, target - 1);
self.cursor.point.line = min(self.cursor.point.line, Line(target as i32 - 1));
}
// Clamp saved cursor, since only primary cursor is scrolled into viewport.
self.saved_cursor.point.line = min(self.saved_cursor.point.line, target - 1);
self.saved_cursor.point.line = min(self.saved_cursor.point.line, Line(target as i32 - 1));
self.raw.rotate((self.lines - target).0 as isize);
self.raw.rotate((self.lines - target) as isize);
self.raw.shrink_visible_lines(target);
self.lines = target;
}
/// Grow number of columns in each row, reflowing if necessary.
fn grow_cols(&mut self, reflow: bool, cols: Column) {
fn grow_columns(&mut self, reflow: bool, columns: usize) {
// Check if a row needs to be wrapped.
let should_reflow = |row: &Row<T>| -> bool {
let len = Column(row.len());
reflow && len.0 > 0 && len < cols && row[len - 1].flags().contains(Flags::WRAPLINE)
reflow && len.0 > 0 && len < columns && row[len - 1].flags().contains(Flags::WRAPLINE)
};
self.cols = cols;
self.columns = columns;
let mut reversed: Vec<Row<T>> = Vec::with_capacity(self.raw.len());
let mut cursor_line_delta = 0;
@ -138,12 +138,12 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
if last_len >= 1
&& last_row[Column(last_len - 1)].flags().contains(Flags::LEADING_WIDE_CHAR_SPACER)
{
last_row.shrink(Column(last_len - 1));
last_row.shrink(last_len - 1);
last_len -= 1;
}
// Don't try to pull more cells from the next line than available.
let mut num_wrapped = cols.0 - last_len;
let mut num_wrapped = columns - last_len;
let len = min(row.len(), num_wrapped);
// Insert leading spacer when there's not enough room for reflowing wide char.
@ -164,30 +164,30 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
// Add removed cells to previous row and reflow content.
last_row.append(&mut cells);
let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1;
if i == cursor_buffer_line && reflow {
// Resize cursor's line and reflow the cursor if necessary.
let mut target = self.cursor.point.sub(cols, num_wrapped);
let mut target = self.cursor.point.sub(self, Boundary::Cursor, num_wrapped);
// Clamp to the last column, if no content was reflown with the cursor.
if target.column.0 == 0 && row.is_clear() {
self.cursor.input_needs_wrap = true;
target = target.sub(cols, 1);
target = target.sub(self, Boundary::Cursor, 1);
}
self.cursor.point.column = target.column;
// Get required cursor line changes. Since `num_wrapped` is smaller than `cols`
// Get required cursor line changes. Since `num_wrapped` is smaller than `columns`
// this will always be either `0` or `1`.
let line_delta = (self.cursor.point.line - target.line).0;
let line_delta = self.cursor.point.line - target.line;
if line_delta != 0 && row.is_clear() {
continue;
}
cursor_line_delta += line_delta;
cursor_line_delta += line_delta.0 as usize;
} else if row.is_clear() {
if i + reversed.len() >= self.lines.0 {
if i + reversed.len() >= self.lines {
// Since we removed a line, rotate down the viewport.
self.display_offset = self.display_offset.saturating_sub(1);
}
@ -210,27 +210,27 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
}
// Make sure we have at least the viewport filled.
if reversed.len() < self.lines.0 {
let delta = self.lines.0 - reversed.len();
self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(delta);
reversed.resize_with(self.lines.0, || Row::new(cols));
if reversed.len() < self.lines {
let delta = (self.lines - reversed.len()) as i32;
self.cursor.point.line = max(self.cursor.point.line - delta, Line(0));
reversed.resize_with(self.lines, || Row::new(columns));
}
// Pull content down to put cursor in correct position, or move cursor up if there's no
// more lines to delete below the cursor.
if cursor_line_delta != 0 {
let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
let available = min(cursor_buffer_line, reversed.len() - self.lines.0);
let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1;
let available = min(cursor_buffer_line, reversed.len() - self.lines);
let overflow = cursor_line_delta.saturating_sub(available);
reversed.truncate(reversed.len() + overflow - cursor_line_delta);
self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(overflow);
self.cursor.point.line = max(self.cursor.point.line - overflow, Line(0));
}
// Reverse iterator and fill all rows that are still too short.
let mut new_raw = Vec::with_capacity(reversed.len());
for mut row in reversed.drain(..).rev() {
if row.len() < cols.0 {
row.grow(cols);
if row.len() < columns {
row.grow(columns);
}
new_raw.push(row);
}
@ -242,8 +242,8 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
}
/// Shrink number of columns in each row, reflowing if necessary.
fn shrink_cols(&mut self, reflow: bool, cols: Column) {
self.cols = cols;
fn shrink_columns(&mut self, reflow: bool, columns: usize) {
self.columns = columns;
// Remove the linewrap special case, by moving the cursor outside of the grid.
if self.cursor.input_needs_wrap && reflow {
@ -260,7 +260,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
if let Some(buffered) = buffered.take() {
// Add a column for every cell added before the cursor, if it goes beyond the new
// width it is then later reflown.
let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1;
if i == cursor_buffer_line {
self.cursor.point.column += buffered.len();
}
@ -270,11 +270,11 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
loop {
// Remove all cells which require reflowing.
let mut wrapped = match row.shrink(cols) {
let mut wrapped = match row.shrink(columns) {
Some(wrapped) if reflow => wrapped,
_ => {
let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
if reflow && i == cursor_buffer_line && self.cursor.point.column > cols {
let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1;
if reflow && i == cursor_buffer_line && self.cursor.point.column > columns {
// If there are empty cells before the cursor, we assume it is explicit
// whitespace and need to wrap it like normal content.
Vec::new()
@ -287,11 +287,13 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
};
// Insert spacer if a wide char would be wrapped into the last column.
if row.len() >= cols.0 && row[cols - 1].flags().contains(Flags::WIDE_CHAR) {
if row.len() >= columns
&& row[Column(columns - 1)].flags().contains(Flags::WIDE_CHAR)
{
let mut spacer = T::default();
spacer.flags_mut().insert(Flags::LEADING_WIDE_CHAR_SPACER);
let wide_char = mem::replace(&mut row[cols - 1], spacer);
let wide_char = mem::replace(&mut row[Column(columns - 1)], spacer);
wrapped.insert(0, wide_char);
}
@ -299,7 +301,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
let len = wrapped.len();
if len > 0 && wrapped[len - 1].flags().contains(Flags::LEADING_WIDE_CHAR_SPACER) {
if len == 1 {
row[cols - 1].flags_mut().insert(Flags::WRAPLINE);
row[Column(columns - 1)].flags_mut().insert(Flags::WRAPLINE);
new_raw.push(row);
break;
} else {
@ -320,7 +322,7 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
.last()
.map(|c| c.flags().contains(Flags::WRAPLINE) && i >= 1)
.unwrap_or(false)
&& wrapped.len() < cols.0
&& wrapped.len() < columns
{
// Make sure previous wrap flag doesn't linger around.
if let Some(cell) = wrapped.last_mut() {
@ -332,24 +334,24 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
break;
} else {
// Reflow cursor if a line below it is deleted.
let cursor_buffer_line = (self.lines - self.cursor.point.line - 1).0;
if (i == cursor_buffer_line && self.cursor.point.column < cols)
let cursor_buffer_line = self.lines - self.cursor.point.line.0 as usize - 1;
if (i == cursor_buffer_line && self.cursor.point.column < columns)
|| i < cursor_buffer_line
{
self.cursor.point.line.0 = self.cursor.point.line.saturating_sub(1);
self.cursor.point.line = max(self.cursor.point.line - 1, Line(0));
}
// Reflow the cursor if it is on this line beyond the width.
if i == cursor_buffer_line && self.cursor.point.column >= cols {
// Since only a single new line is created, we subtract only `cols`
if i == cursor_buffer_line && self.cursor.point.column >= columns {
// Since only a single new line is created, we subtract only `columns`
// from the cursor instead of reflowing it completely.
self.cursor.point.column -= cols;
self.cursor.point.column -= columns;
}
// Make sure new row is at least as long as new width.
let occ = wrapped.len();
if occ < cols.0 {
wrapped.resize_with(cols.0, T::default);
if occ < columns {
wrapped.resize_with(columns, T::default);
}
row = Row::from_vec(wrapped, occ);
}
@ -358,22 +360,22 @@ impl<T: GridCell + Default + PartialEq + Clone> Grid<T> {
// Reverse iterator and use it as the new grid storage.
let mut reversed: Vec<Row<T>> = new_raw.drain(..).rev().collect();
reversed.truncate(self.max_scroll_limit + self.lines.0);
reversed.truncate(self.max_scroll_limit + self.lines);
self.raw.replace_inner(reversed);
// Reflow the primary cursor, or clamp it if reflow is disabled.
if !reflow {
self.cursor.point.column = min(self.cursor.point.column, cols - 1);
} else if self.cursor.point.column == cols
&& !self[self.cursor.point.line][cols - 1].flags().contains(Flags::WRAPLINE)
self.cursor.point.column = min(self.cursor.point.column, Column(columns - 1));
} else if self.cursor.point.column == columns
&& !self[self.cursor.point.line][Column(columns - 1)].flags().contains(Flags::WRAPLINE)
{
self.cursor.input_needs_wrap = true;
self.cursor.point.column -= 1;
} else {
self.cursor.point = self.cursor.point.add(cols, 0);
self.cursor.point = self.cursor.point.grid_clamp(self, Boundary::Cursor);
}
// Clamp the saved cursor to the grid.
self.saved_cursor.point.column = min(self.saved_cursor.point.column, cols - 1);
self.saved_cursor.point.column = min(self.saved_cursor.point.column, Column(columns - 1));
}
}

View file

@ -34,22 +34,22 @@ impl<T: Clone + Default> Row<T> {
/// Create a new terminal row.
///
/// Ideally the `template` should be `Copy` in all performance sensitive scenarios.
pub fn new(columns: Column) -> Row<T> {
debug_assert!(columns.0 >= 1);
pub fn new(columns: usize) -> Row<T> {
debug_assert!(columns >= 1);
let mut inner: Vec<T> = Vec::with_capacity(columns.0);
let mut inner: Vec<T> = Vec::with_capacity(columns);
// This is a slightly optimized version of `std::vec::Vec::resize`.
unsafe {
let mut ptr = inner.as_mut_ptr();
for _ in 1..columns.0 {
for _ in 1..columns {
ptr::write(ptr, T::default());
ptr = ptr.offset(1);
}
ptr::write(ptr, T::default());
inner.set_len(columns.0);
inner.set_len(columns);
}
Row { inner, occ: 0 }
@ -57,31 +57,31 @@ impl<T: Clone + Default> Row<T> {
/// Increase the number of columns in the row.
#[inline]
pub fn grow(&mut self, cols: Column) {
if self.inner.len() >= cols.0 {
pub fn grow(&mut self, columns: usize) {
if self.inner.len() >= columns {
return;
}
self.inner.resize_with(cols.0, T::default);
self.inner.resize_with(columns, T::default);
}
/// Reduce the number of columns in the row.
///
/// This will return all non-empty cells that were removed.
pub fn shrink(&mut self, cols: Column) -> Option<Vec<T>>
pub fn shrink(&mut self, columns: usize) -> Option<Vec<T>>
where
T: GridCell,
{
if self.inner.len() <= cols.0 {
if self.inner.len() <= columns {
return None;
}
// Split off cells for a new row.
let mut new_row = self.inner.split_off(cols.0);
let mut new_row = self.inner.split_off(columns);
let index = new_row.iter().rposition(|c| !c.is_empty()).map(|i| i + 1).unwrap_or(0);
new_row.truncate(index);
self.occ = min(self.occ, cols.0);
self.occ = min(self.occ, columns);
if new_row.is_empty() {
None

View file

@ -5,7 +5,7 @@ use std::ops::{Index, IndexMut};
use serde::{Deserialize, Serialize};
use super::Row;
use crate::index::{Column, Line};
use crate::index::Line;
/// Maximum number of buffered lines outside of the grid for performance optimization.
const MAX_CACHE_SIZE: usize = 1_000;
@ -38,7 +38,7 @@ pub struct Storage<T> {
zero: usize,
/// Number of visible lines.
visible_lines: Line,
visible_lines: usize,
/// Total number of lines currently active in the terminal (scrollback + visible)
///
@ -61,28 +61,28 @@ impl<T: PartialEq> PartialEq for Storage<T> {
impl<T> Storage<T> {
#[inline]
pub fn with_capacity(visible_lines: Line, cols: Column) -> Storage<T>
pub fn with_capacity(visible_lines: usize, columns: usize) -> Storage<T>
where
T: Clone + Default,
{
// Initialize visible lines; the scrollback buffer is initialized dynamically.
let mut inner = Vec::with_capacity(visible_lines.0);
inner.resize_with(visible_lines.0, || Row::new(cols));
let mut inner = Vec::with_capacity(visible_lines);
inner.resize_with(visible_lines, || Row::new(columns));
Storage { inner, zero: 0, visible_lines, len: visible_lines.0 }
Storage { inner, zero: 0, visible_lines, len: visible_lines }
}
/// Increase the number of lines in the buffer.
#[inline]
pub fn grow_visible_lines(&mut self, next: Line)
pub fn grow_visible_lines(&mut self, next: usize)
where
T: Clone + Default,
{
// Number of lines the buffer needs to grow.
let growage = next - self.visible_lines;
let cols = self[0].len();
self.initialize(growage.0, Column(cols));
let columns = self[Line(0)].len();
self.initialize(growage, columns);
// Update visible lines.
self.visible_lines = next;
@ -90,10 +90,10 @@ impl<T> Storage<T> {
/// Decrease the number of lines in the buffer.
#[inline]
pub fn shrink_visible_lines(&mut self, next: Line) {
pub fn shrink_visible_lines(&mut self, next: usize) {
// Shrink the size without removing any lines.
let shrinkage = self.visible_lines - next;
self.shrink_lines(shrinkage.0);
self.shrink_lines(shrinkage);
// Update visible lines.
self.visible_lines = next;
@ -120,7 +120,7 @@ impl<T> Storage<T> {
/// Dynamically grow the storage buffer at runtime.
#[inline]
pub fn initialize(&mut self, additional_rows: usize, cols: Column)
pub fn initialize(&mut self, additional_rows: usize, columns: usize)
where
T: Clone + Default,
{
@ -128,7 +128,7 @@ impl<T> Storage<T> {
self.rezero();
let realloc_size = self.inner.len() + max(additional_rows, MAX_CACHE_SIZE);
self.inner.resize_with(realloc_size, || Row::new(cols));
self.inner.resize_with(realloc_size, || Row::new(columns));
}
self.len += additional_rows;
@ -139,14 +139,6 @@ impl<T> Storage<T> {
self.len
}
#[inline]
pub fn swap_lines(&mut self, a: Line, b: Line) {
let offset = self.inner.len() + self.zero + *self.visible_lines - 1;
let a = (offset - *a) % self.inner.len();
let b = (offset - *b) % self.inner.len();
self.inner.swap(a, b);
}
/// Swap implementation for Row<T>.
///
/// Exploits the known size of Row<T> to produce a slightly more efficient
@ -155,7 +147,7 @@ impl<T> Storage<T> {
/// The default implementation from swap generates 8 movups and 4 movaps
/// instructions. This implementation achieves the swap in only 8 movups
/// instructions.
pub fn swap(&mut self, a: usize, b: usize) {
pub fn swap(&mut self, a: Line, b: Line) {
debug_assert_eq!(mem::size_of::<Row<T>>(), mem::size_of::<usize>() * 4);
let a = self.compute_index(a);
@ -222,10 +214,14 @@ impl<T> Storage<T> {
/// Compute actual index in underlying storage given the requested index.
#[inline]
fn compute_index(&self, requested: usize) -> usize {
debug_assert!(requested < self.len);
fn compute_index(&self, requested: Line) -> usize {
debug_assert!(requested.0 < self.visible_lines as i32);
let zeroed = self.zero + requested;
let positive = -(requested - self.visible_lines).0 as usize - 1;
debug_assert!(positive < self.len);
let zeroed = self.zero + positive;
// Use if/else instead of remainder here to improve performance.
//
@ -250,38 +246,21 @@ impl<T> Storage<T> {
}
}
impl<T> Index<usize> for Storage<T> {
type Output = Row<T>;
#[inline]
fn index(&self, index: usize) -> &Self::Output {
&self.inner[self.compute_index(index)]
}
}
impl<T> IndexMut<usize> for Storage<T> {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
let index = self.compute_index(index); // borrowck
&mut self.inner[index]
}
}
impl<T> Index<Line> for Storage<T> {
type Output = Row<T>;
#[inline]
fn index(&self, index: Line) -> &Self::Output {
let index = self.visible_lines - 1 - index;
&self[*index]
let index = self.compute_index(index);
&self.inner[index]
}
}
impl<T> IndexMut<Line> for Storage<T> {
#[inline]
fn index_mut(&mut self, index: Line) -> &mut Self::Output {
let index = self.visible_lines - 1 - index;
&mut self[*index]
let index = self.compute_index(index);
&mut self.inner[index]
}
}
@ -313,43 +292,39 @@ mod tests {
#[test]
fn with_capacity() {
let storage = Storage::<char>::with_capacity(Line(3), Column(1));
let storage = Storage::<char>::with_capacity(3, 1);
assert_eq!(storage.inner.len(), 3);
assert_eq!(storage.len, 3);
assert_eq!(storage.zero, 0);
assert_eq!(storage.visible_lines, Line(3));
assert_eq!(storage.visible_lines, 3);
}
#[test]
fn indexing() {
let mut storage = Storage::<char>::with_capacity(Line(3), Column(1));
let mut storage = Storage::<char>::with_capacity(3, 1);
storage[0] = filled_row('0');
storage[1] = filled_row('1');
storage[2] = filled_row('2');
assert_eq!(storage[0], filled_row('0'));
assert_eq!(storage[1], filled_row('1'));
assert_eq!(storage[2], filled_row('2'));
storage[Line(0)] = filled_row('0');
storage[Line(1)] = filled_row('1');
storage[Line(2)] = filled_row('2');
storage.zero += 1;
assert_eq!(storage[0], filled_row('1'));
assert_eq!(storage[1], filled_row('2'));
assert_eq!(storage[2], filled_row('0'));
assert_eq!(storage[Line(0)], filled_row('2'));
assert_eq!(storage[Line(1)], filled_row('0'));
assert_eq!(storage[Line(2)], filled_row('1'));
}
#[test]
#[should_panic]
fn indexing_above_inner_len() {
let storage = Storage::<char>::with_capacity(Line(1), Column(1));
let _ = &storage[2];
let storage = Storage::<char>::with_capacity(1, 1);
let _ = &storage[Line(-1)];
}
#[test]
fn rotate() {
let mut storage = Storage::<char>::with_capacity(Line(3), Column(1));
let mut storage = Storage::<char>::with_capacity(3, 1);
storage.rotate(2);
assert_eq!(storage.zero, 2);
storage.shrink_lines(2);
@ -377,18 +352,18 @@ mod tests {
let mut storage: Storage<char> = Storage {
inner: vec![filled_row('0'), filled_row('1'), filled_row('-')],
zero: 0,
visible_lines: Line(3),
visible_lines: 3,
len: 3,
};
// Grow buffer.
storage.grow_visible_lines(Line(4));
storage.grow_visible_lines(4);
// Make sure the result is correct.
let mut expected = Storage {
inner: vec![filled_row('0'), filled_row('1'), filled_row('-')],
zero: 0,
visible_lines: Line(4),
visible_lines: 4,
len: 4,
};
expected.inner.append(&mut vec![filled_row('\0'); MAX_CACHE_SIZE]);
@ -418,18 +393,18 @@ mod tests {
let mut storage: Storage<char> = Storage {
inner: vec![filled_row('-'), filled_row('0'), filled_row('1')],
zero: 1,
visible_lines: Line(3),
visible_lines: 3,
len: 3,
};
// Grow buffer.
storage.grow_visible_lines(Line(4));
storage.grow_visible_lines(4);
// Make sure the result is correct.
let mut expected = Storage {
inner: vec![filled_row('0'), filled_row('1'), filled_row('-')],
zero: 0,
visible_lines: Line(4),
visible_lines: 4,
len: 4,
};
expected.inner.append(&mut vec![filled_row('\0'); MAX_CACHE_SIZE]);
@ -456,18 +431,18 @@ mod tests {
let mut storage: Storage<char> = Storage {
inner: vec![filled_row('2'), filled_row('0'), filled_row('1')],
zero: 1,
visible_lines: Line(3),
visible_lines: 3,
len: 3,
};
// Shrink buffer.
storage.shrink_visible_lines(Line(2));
storage.shrink_visible_lines(2);
// Make sure the result is correct.
let expected = Storage {
inner: vec![filled_row('2'), filled_row('0'), filled_row('1')],
zero: 1,
visible_lines: Line(2),
visible_lines: 2,
len: 2,
};
assert_eq!(storage.visible_lines, expected.visible_lines);
@ -492,18 +467,18 @@ mod tests {
let mut storage: Storage<char> = Storage {
inner: vec![filled_row('0'), filled_row('1'), filled_row('2')],
zero: 0,
visible_lines: Line(3),
visible_lines: 3,
len: 3,
};
// Shrink buffer.
storage.shrink_visible_lines(Line(2));
storage.shrink_visible_lines(2);
// Make sure the result is correct.
let expected = Storage {
inner: vec![filled_row('0'), filled_row('1'), filled_row('2')],
zero: 0,
visible_lines: Line(2),
visible_lines: 2,
len: 2,
};
assert_eq!(storage.visible_lines, expected.visible_lines);
@ -541,12 +516,12 @@ mod tests {
filled_row('3'),
],
zero: 2,
visible_lines: Line(6),
visible_lines: 6,
len: 6,
};
// Shrink buffer.
storage.shrink_visible_lines(Line(2));
storage.shrink_visible_lines(2);
// Make sure the result is correct.
let expected = Storage {
@ -559,7 +534,7 @@ mod tests {
filled_row('3'),
],
zero: 2,
visible_lines: Line(2),
visible_lines: 2,
len: 2,
};
assert_eq!(storage.visible_lines, expected.visible_lines);
@ -593,7 +568,7 @@ mod tests {
filled_row('3'),
],
zero: 2,
visible_lines: Line(1),
visible_lines: 1,
len: 2,
};
@ -604,7 +579,7 @@ mod tests {
let expected = Storage {
inner: vec![filled_row('0'), filled_row('1')],
zero: 0,
visible_lines: Line(1),
visible_lines: 1,
len: 2,
};
assert_eq!(storage.visible_lines, expected.visible_lines);
@ -628,7 +603,7 @@ mod tests {
let mut storage: Storage<char> = Storage {
inner: vec![filled_row('1'), filled_row('2'), filled_row('0')],
zero: 2,
visible_lines: Line(1),
visible_lines: 1,
len: 2,
};
@ -639,7 +614,7 @@ mod tests {
let expected = Storage {
inner: vec![filled_row('0'), filled_row('1')],
zero: 0,
visible_lines: Line(1),
visible_lines: 1,
len: 2,
};
assert_eq!(storage.visible_lines, expected.visible_lines);
@ -685,7 +660,7 @@ mod tests {
filled_row('3'),
],
zero: 2,
visible_lines: Line(0),
visible_lines: 0,
len: 6,
};
@ -703,7 +678,7 @@ mod tests {
filled_row('3'),
],
zero: 2,
visible_lines: Line(0),
visible_lines: 0,
len: 3,
};
assert_eq!(storage.inner, shrinking_expected.inner);
@ -711,7 +686,7 @@ mod tests {
assert_eq!(storage.len, shrinking_expected.len);
// Grow buffer.
storage.initialize(1, Column(1));
storage.initialize(1, 1);
// Make sure the previously freed elements are reused.
let growing_expected = Storage {
@ -724,7 +699,7 @@ mod tests {
filled_row('3'),
],
zero: 2,
visible_lines: Line(0),
visible_lines: 0,
len: 4,
};
@ -746,13 +721,13 @@ mod tests {
filled_row('3'),
],
zero: 2,
visible_lines: Line(0),
visible_lines: 0,
len: 6,
};
// Initialize additional lines.
let init_size = 3;
storage.initialize(init_size, Column(1));
storage.initialize(init_size, 1);
// Generate expected grid.
let mut expected_inner = vec![
@ -765,8 +740,7 @@ mod tests {
];
let expected_init_size = std::cmp::max(init_size, MAX_CACHE_SIZE);
expected_inner.append(&mut vec![filled_row('\0'); expected_init_size]);
let expected_storage =
Storage { inner: expected_inner, zero: 0, visible_lines: Line(0), len: 9 };
let expected_storage = Storage { inner: expected_inner, zero: 0, visible_lines: 0, len: 9 };
assert_eq!(storage.len, expected_storage.len);
assert_eq!(storage.zero, expected_storage.zero);
@ -778,7 +752,7 @@ mod tests {
let mut storage: Storage<char> = Storage {
inner: vec![filled_row('-'), filled_row('-'), filled_row('-')],
zero: 2,
visible_lines: Line(0),
visible_lines: 0,
len: 3,
};
@ -788,7 +762,7 @@ mod tests {
}
fn filled_row(content: char) -> Row<char> {
let mut row = Row::new(Column(1));
let mut row = Row::new(1);
row[Column(0)] = content;
row
}

View file

@ -22,49 +22,15 @@ impl GridCell for usize {
}
}
#[test]
fn grid_clamp_buffer_point() {
let mut grid = Grid::<usize>::new(Line(10), Column(10), 1_000);
grid.display_offset = 5;
let point = grid.clamp_buffer_to_visible(Point::new(10, Column(3)));
assert_eq!(point, Point::new(Line(4), Column(3)));
let point = grid.clamp_buffer_to_visible(Point::new(15, Column(3)));
assert_eq!(point, Point::new(Line(0), Column(0)));
let point = grid.clamp_buffer_to_visible(Point::new(4, Column(3)));
assert_eq!(point, Point::new(Line(9), Column(9)));
grid.display_offset = 0;
let point = grid.clamp_buffer_to_visible(Point::new(4, Column(3)));
assert_eq!(point, Point::new(Line(5), Column(3)));
}
#[test]
fn visible_to_buffer() {
let mut grid = Grid::<usize>::new(Line(10), Column(10), 1_000);
grid.display_offset = 5;
let point = grid.visible_to_buffer(Point::new(Line(4), Column(3)));
assert_eq!(point, Point::new(10, Column(3)));
grid.display_offset = 0;
let point = grid.visible_to_buffer(Point::new(Line(5), Column(3)));
assert_eq!(point, Point::new(4, Column(3)));
}
// Scroll up moves lines upward.
#[test]
fn scroll_up() {
let mut grid = Grid::<usize>::new(Line(10), Column(1), 0);
let mut grid = Grid::<usize>::new(10, 1, 0);
for i in 0..10 {
grid[Line(i)][Column(0)] = i;
grid[Line(i as i32)][Column(0)] = i;
}
grid.scroll_up::<usize>(&(Line(0)..Line(10)), Line(2));
grid.scroll_up::<usize>(&(Line(0)..Line(10)), 2);
assert_eq!(grid[Line(0)][Column(0)], 2);
assert_eq!(grid[Line(0)].occ, 1);
@ -91,12 +57,44 @@ fn scroll_up() {
// Scroll down moves lines downward.
#[test]
fn scroll_down() {
let mut grid = Grid::<usize>::new(Line(10), Column(1), 0);
let mut grid = Grid::<usize>::new(10, 1, 0);
for i in 0..10 {
grid[Line(i)][Column(0)] = i;
grid[Line(i as i32)][Column(0)] = i;
}
grid.scroll_down::<usize>(&(Line(0)..Line(10)), Line(2));
grid.scroll_down::<usize>(&(Line(0)..Line(10)), 2);
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]
fn scroll_down_with_history() {
let mut grid = Grid::<usize>::new(10, 1, 1);
grid.increase_scroll_limit(1);
for i in 0..10 {
grid[Line(i as i32)][Column(0)] = i;
}
grid.scroll_down::<usize>(&(Line(0)..Line(10)), 2);
assert_eq!(grid[Line(0)][Column(0)], 0); // was 8.
assert_eq!(grid[Line(0)].occ, 0);
@ -127,19 +125,19 @@ fn test_iter() {
assert_eq!(Some(&value), indexed.map(|indexed| indexed.cell));
};
let mut grid = Grid::<usize>::new(Line(5), Column(5), 0);
let mut grid = Grid::<usize>::new(5, 5, 0);
for i in 0..5 {
for j in 0..5 {
grid[Line(i)][Column(j)] = i * 5 + j;
grid[Line(i)][Column(j)] = i as usize * 5 + j;
}
}
let mut iter = grid.iter_from(Point::new(4, Column(0)));
let mut iter = grid.iter_from(Point::new(Line(0), Column(0)));
assert_eq!(None, iter.prev());
assert_indexed(1, iter.next());
assert_eq!(Column(1), iter.point().column);
assert_eq!(4, iter.point().line);
assert_eq!(0, iter.point().line);
assert_indexed(2, iter.next());
assert_indexed(3, iter.next());
@ -148,139 +146,139 @@ fn test_iter() {
// Test line-wrapping.
assert_indexed(5, iter.next());
assert_eq!(Column(0), iter.point().column);
assert_eq!(3, iter.point().line);
assert_eq!(1, iter.point().line);
assert_indexed(4, iter.prev());
assert_eq!(Column(4), iter.point().column);
assert_eq!(4, iter.point().line);
assert_eq!(0, iter.point().line);
// Make sure iter.cell() returns the current iterator position.
assert_eq!(&4, iter.cell());
// Test that iter ends at end of grid.
let mut final_iter = grid.iter_from(Point { line: 0, column: Column(4) });
let mut final_iter = grid.iter_from(Point { line: Line(4), column: Column(4) });
assert_eq!(None, final_iter.next());
assert_indexed(23, final_iter.prev());
}
#[test]
fn shrink_reflow() {
let mut grid = Grid::<Cell>::new(Line(1), Column(5), 2);
let mut grid = Grid::<Cell>::new(1, 5, 2);
grid[Line(0)][Column(0)] = cell('1');
grid[Line(0)][Column(1)] = cell('2');
grid[Line(0)][Column(2)] = cell('3');
grid[Line(0)][Column(3)] = cell('4');
grid[Line(0)][Column(4)] = cell('5');
grid.resize(true, Line(1), Column(2));
grid.resize(true, 1, 2);
assert_eq!(grid.total_lines(), 3);
assert_eq!(grid[2].len(), 2);
assert_eq!(grid[2][Column(0)], cell('1'));
assert_eq!(grid[2][Column(1)], wrap_cell('2'));
assert_eq!(grid[Line(-2)].len(), 2);
assert_eq!(grid[Line(-2)][Column(0)], cell('1'));
assert_eq!(grid[Line(-2)][Column(1)], wrap_cell('2'));
assert_eq!(grid[1].len(), 2);
assert_eq!(grid[1][Column(0)], cell('3'));
assert_eq!(grid[1][Column(1)], wrap_cell('4'));
assert_eq!(grid[Line(-1)].len(), 2);
assert_eq!(grid[Line(-1)][Column(0)], cell('3'));
assert_eq!(grid[Line(-1)][Column(1)], wrap_cell('4'));
assert_eq!(grid[0].len(), 2);
assert_eq!(grid[0][Column(0)], cell('5'));
assert_eq!(grid[0][Column(1)], Cell::default());
assert_eq!(grid[Line(0)].len(), 2);
assert_eq!(grid[Line(0)][Column(0)], cell('5'));
assert_eq!(grid[Line(0)][Column(1)], Cell::default());
}
#[test]
fn shrink_reflow_twice() {
let mut grid = Grid::<Cell>::new(Line(1), Column(5), 2);
let mut grid = Grid::<Cell>::new(1, 5, 2);
grid[Line(0)][Column(0)] = cell('1');
grid[Line(0)][Column(1)] = cell('2');
grid[Line(0)][Column(2)] = cell('3');
grid[Line(0)][Column(3)] = cell('4');
grid[Line(0)][Column(4)] = cell('5');
grid.resize(true, Line(1), Column(4));
grid.resize(true, Line(1), Column(2));
grid.resize(true, 1, 4);
grid.resize(true, 1, 2);
assert_eq!(grid.total_lines(), 3);
assert_eq!(grid[2].len(), 2);
assert_eq!(grid[2][Column(0)], cell('1'));
assert_eq!(grid[2][Column(1)], wrap_cell('2'));
assert_eq!(grid[Line(-2)].len(), 2);
assert_eq!(grid[Line(-2)][Column(0)], cell('1'));
assert_eq!(grid[Line(-2)][Column(1)], wrap_cell('2'));
assert_eq!(grid[1].len(), 2);
assert_eq!(grid[1][Column(0)], cell('3'));
assert_eq!(grid[1][Column(1)], wrap_cell('4'));
assert_eq!(grid[Line(-1)].len(), 2);
assert_eq!(grid[Line(-1)][Column(0)], cell('3'));
assert_eq!(grid[Line(-1)][Column(1)], wrap_cell('4'));
assert_eq!(grid[0].len(), 2);
assert_eq!(grid[0][Column(0)], cell('5'));
assert_eq!(grid[0][Column(1)], Cell::default());
assert_eq!(grid[Line(0)].len(), 2);
assert_eq!(grid[Line(0)][Column(0)], cell('5'));
assert_eq!(grid[Line(0)][Column(1)], Cell::default());
}
#[test]
fn shrink_reflow_empty_cell_inside_line() {
let mut grid = Grid::<Cell>::new(Line(1), Column(5), 3);
let mut grid = Grid::<Cell>::new(1, 5, 3);
grid[Line(0)][Column(0)] = cell('1');
grid[Line(0)][Column(1)] = Cell::default();
grid[Line(0)][Column(2)] = cell('3');
grid[Line(0)][Column(3)] = cell('4');
grid[Line(0)][Column(4)] = Cell::default();
grid.resize(true, Line(1), Column(2));
grid.resize(true, 1, 2);
assert_eq!(grid.total_lines(), 2);
assert_eq!(grid[1].len(), 2);
assert_eq!(grid[1][Column(0)], cell('1'));
assert_eq!(grid[1][Column(1)], wrap_cell(' '));
assert_eq!(grid[Line(-1)].len(), 2);
assert_eq!(grid[Line(-1)][Column(0)], cell('1'));
assert_eq!(grid[Line(-1)][Column(1)], wrap_cell(' '));
assert_eq!(grid[0].len(), 2);
assert_eq!(grid[0][Column(0)], cell('3'));
assert_eq!(grid[0][Column(1)], cell('4'));
assert_eq!(grid[Line(0)].len(), 2);
assert_eq!(grid[Line(0)][Column(0)], cell('3'));
assert_eq!(grid[Line(0)][Column(1)], cell('4'));
grid.resize(true, Line(1), Column(1));
grid.resize(true, 1, 1);
assert_eq!(grid.total_lines(), 4);
assert_eq!(grid[3].len(), 1);
assert_eq!(grid[3][Column(0)], wrap_cell('1'));
assert_eq!(grid[Line(-3)].len(), 1);
assert_eq!(grid[Line(-3)][Column(0)], wrap_cell('1'));
assert_eq!(grid[2].len(), 1);
assert_eq!(grid[2][Column(0)], wrap_cell(' '));
assert_eq!(grid[Line(-2)].len(), 1);
assert_eq!(grid[Line(-2)][Column(0)], wrap_cell(' '));
assert_eq!(grid[1].len(), 1);
assert_eq!(grid[1][Column(0)], wrap_cell('3'));
assert_eq!(grid[Line(-1)].len(), 1);
assert_eq!(grid[Line(-1)][Column(0)], wrap_cell('3'));
assert_eq!(grid[0].len(), 1);
assert_eq!(grid[0][Column(0)], cell('4'));
assert_eq!(grid[Line(0)].len(), 1);
assert_eq!(grid[Line(0)][Column(0)], cell('4'));
}
#[test]
fn grow_reflow() {
let mut grid = Grid::<Cell>::new(Line(2), Column(2), 0);
let mut grid = Grid::<Cell>::new(2, 2, 0);
grid[Line(0)][Column(0)] = cell('1');
grid[Line(0)][Column(1)] = wrap_cell('2');
grid[Line(1)][Column(0)] = cell('3');
grid[Line(1)][Column(1)] = Cell::default();
grid.resize(true, Line(2), Column(3));
grid.resize(true, 2, 3);
assert_eq!(grid.total_lines(), 2);
assert_eq!(grid[1].len(), 3);
assert_eq!(grid[1][Column(0)], cell('1'));
assert_eq!(grid[1][Column(1)], cell('2'));
assert_eq!(grid[1][Column(2)], cell('3'));
assert_eq!(grid[Line(0)].len(), 3);
assert_eq!(grid[Line(0)][Column(0)], cell('1'));
assert_eq!(grid[Line(0)][Column(1)], cell('2'));
assert_eq!(grid[Line(0)][Column(2)], cell('3'));
// Make sure rest of grid is empty.
assert_eq!(grid[0].len(), 3);
assert_eq!(grid[0][Column(0)], Cell::default());
assert_eq!(grid[0][Column(1)], Cell::default());
assert_eq!(grid[0][Column(2)], Cell::default());
assert_eq!(grid[Line(1)].len(), 3);
assert_eq!(grid[Line(1)][Column(0)], Cell::default());
assert_eq!(grid[Line(1)][Column(1)], Cell::default());
assert_eq!(grid[Line(1)][Column(2)], Cell::default());
}
#[test]
fn grow_reflow_multiline() {
let mut grid = Grid::<Cell>::new(Line(3), Column(2), 0);
let mut grid = Grid::<Cell>::new(3, 2, 0);
grid[Line(0)][Column(0)] = cell('1');
grid[Line(0)][Column(1)] = wrap_cell('2');
grid[Line(1)][Column(0)] = cell('3');
@ -288,20 +286,20 @@ fn grow_reflow_multiline() {
grid[Line(2)][Column(0)] = cell('5');
grid[Line(2)][Column(1)] = cell('6');
grid.resize(true, Line(3), Column(6));
grid.resize(true, 3, 6);
assert_eq!(grid.total_lines(), 3);
assert_eq!(grid[2].len(), 6);
assert_eq!(grid[2][Column(0)], cell('1'));
assert_eq!(grid[2][Column(1)], cell('2'));
assert_eq!(grid[2][Column(2)], cell('3'));
assert_eq!(grid[2][Column(3)], cell('4'));
assert_eq!(grid[2][Column(4)], cell('5'));
assert_eq!(grid[2][Column(5)], cell('6'));
assert_eq!(grid[Line(0)].len(), 6);
assert_eq!(grid[Line(0)][Column(0)], cell('1'));
assert_eq!(grid[Line(0)][Column(1)], cell('2'));
assert_eq!(grid[Line(0)][Column(2)], cell('3'));
assert_eq!(grid[Line(0)][Column(3)], cell('4'));
assert_eq!(grid[Line(0)][Column(4)], cell('5'));
assert_eq!(grid[Line(0)][Column(5)], cell('6'));
// Make sure rest of grid is empty.
for r in 0..2 {
for r in (1..3).map(Line::from) {
assert_eq!(grid[r].len(), 6);
for c in 0..6 {
assert_eq!(grid[r][Column(c)], Cell::default());
@ -311,43 +309,43 @@ fn grow_reflow_multiline() {
#[test]
fn grow_reflow_disabled() {
let mut grid = Grid::<Cell>::new(Line(2), Column(2), 0);
let mut grid = Grid::<Cell>::new(2, 2, 0);
grid[Line(0)][Column(0)] = cell('1');
grid[Line(0)][Column(1)] = wrap_cell('2');
grid[Line(1)][Column(0)] = cell('3');
grid[Line(1)][Column(1)] = Cell::default();
grid.resize(false, Line(2), Column(3));
grid.resize(false, 2, 3);
assert_eq!(grid.total_lines(), 2);
assert_eq!(grid[1].len(), 3);
assert_eq!(grid[1][Column(0)], cell('1'));
assert_eq!(grid[1][Column(1)], wrap_cell('2'));
assert_eq!(grid[1][Column(2)], Cell::default());
assert_eq!(grid[Line(0)].len(), 3);
assert_eq!(grid[Line(0)][Column(0)], cell('1'));
assert_eq!(grid[Line(0)][Column(1)], wrap_cell('2'));
assert_eq!(grid[Line(0)][Column(2)], Cell::default());
assert_eq!(grid[0].len(), 3);
assert_eq!(grid[0][Column(0)], cell('3'));
assert_eq!(grid[0][Column(1)], Cell::default());
assert_eq!(grid[0][Column(2)], Cell::default());
assert_eq!(grid[Line(1)].len(), 3);
assert_eq!(grid[Line(1)][Column(0)], cell('3'));
assert_eq!(grid[Line(1)][Column(1)], Cell::default());
assert_eq!(grid[Line(1)][Column(2)], Cell::default());
}
#[test]
fn shrink_reflow_disabled() {
let mut grid = Grid::<Cell>::new(Line(1), Column(5), 2);
let mut grid = Grid::<Cell>::new(1, 5, 2);
grid[Line(0)][Column(0)] = cell('1');
grid[Line(0)][Column(1)] = cell('2');
grid[Line(0)][Column(2)] = cell('3');
grid[Line(0)][Column(3)] = cell('4');
grid[Line(0)][Column(4)] = cell('5');
grid.resize(false, Line(1), Column(2));
grid.resize(false, 1, 2);
assert_eq!(grid.total_lines(), 1);
assert_eq!(grid[0].len(), 2);
assert_eq!(grid[0][Column(0)], cell('1'));
assert_eq!(grid[0][Column(1)], cell('2'));
assert_eq!(grid[Line(0)].len(), 2);
assert_eq!(grid[Line(0)][Column(0)], cell('1'));
assert_eq!(grid[Line(0)][Column(1)], cell('2'));
}
// https://github.com/rust-lang/rust-clippy/pull/6375

View file

@ -1,9 +1,9 @@
//! Line and Column newtypes for strongly typed tty/grid/terminal APIs.
/// Indexing types and implementations for Grid and Line.
use std::cmp::{Ord, Ordering};
use std::cmp::{max, min, Ord, Ordering};
use std::fmt;
use std::ops::{self, Add, AddAssign, Deref, Range, Sub, SubAssign};
use std::ops::{Add, AddAssign, Deref, Sub, SubAssign};
use serde::{Deserialize, Serialize};
@ -28,172 +28,136 @@ impl Direction {
}
}
/// Behavior for handling grid boundaries.
/// Terminal grid boundaries.
pub enum Boundary {
/// Clamp to grid boundaries.
/// Cursor's range of motion in the grid.
///
/// When an operation exceeds the grid boundaries, the last point will be returned no matter
/// how far the boundaries were exceeded.
Clamp,
/// This is equal to the viewport when the user isn't scrolled into the history.
Cursor,
/// Wrap around grid bondaries.
///
/// When an operation exceeds the grid boundaries, the point will wrap around the entire grid
/// history and continue at the other side.
Wrap,
/// Topmost line in history until the bottommost line in the terminal.
Grid,
/// Unbounded.
None,
}
/// Index in the grid using row, column notation.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, Eq, PartialEq)]
pub struct Point<L = Line> {
pub struct Point<L = Line, C = Column> {
pub line: L,
pub column: Column,
pub column: C,
}
impl<L> Point<L> {
pub fn new(line: L, col: Column) -> Point<L> {
Point { line, column: col }
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn sub(mut self, num_cols: Column, rhs: usize) -> Point<L>
where
L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>,
{
let num_cols = num_cols.0;
let line_changes = (rhs + num_cols - 1).saturating_sub(self.column.0) / num_cols;
if self.line.into() >= Line(line_changes) {
self.line = self.line - line_changes;
self.column = Column((num_cols + self.column.0 - rhs % num_cols) % num_cols);
self
} else {
Point::new(L::default(), Column(0))
}
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn add(mut self, num_cols: Column, rhs: usize) -> Point<L>
where
L: Copy + Default + Into<Line> + Add<usize, Output = L> + Sub<usize, Output = L>,
{
let num_cols = num_cols.0;
self.line = self.line + (rhs + self.column.0) / num_cols;
self.column = Column((self.column.0 + rhs) % num_cols);
self
impl<L, C> Point<L, C> {
pub fn new(line: L, column: C) -> Point<L, C> {
Point { line, column }
}
}
impl Point<usize> {
impl Point {
/// Subtract a number of columns from a point.
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn sub_absolute<D>(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Point<usize>
pub fn sub<D>(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Self
where
D: Dimensions,
{
let total_lines = dimensions.total_lines();
let num_cols = dimensions.cols().0;
self.line += (rhs + num_cols - 1).saturating_sub(self.column.0) / num_cols;
self.column = Column((num_cols + self.column.0 - rhs % num_cols) % num_cols);
if self.line >= total_lines {
match boundary {
Boundary::Clamp => Point::new(total_lines - 1, Column(0)),
Boundary::Wrap => Point::new(self.line - total_lines, self.column),
}
} else {
self
}
let cols = dimensions.columns();
let line_changes = (rhs + cols - 1).saturating_sub(self.column.0) / cols;
self.line -= line_changes;
self.column = Column((cols + self.column.0 - rhs % cols) % cols);
self.grid_clamp(dimensions, boundary)
}
/// Add a number of columns to a point.
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn add_absolute<D>(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Point<usize>
pub fn add<D>(mut self, dimensions: &D, boundary: Boundary, rhs: usize) -> Self
where
D: Dimensions,
{
let num_cols = dimensions.cols();
let cols = dimensions.columns();
self.line += (rhs + self.column.0) / cols;
self.column = Column((self.column.0 + rhs) % cols);
self.grid_clamp(dimensions, boundary)
}
let line_delta = (rhs + self.column.0) / num_cols.0;
/// Clamp a point to a grid boundary.
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn grid_clamp<D>(mut self, dimensions: &D, boundary: Boundary) -> Self
where
D: Dimensions,
{
let last_column = dimensions.last_column();
self.column = min(self.column, last_column);
if self.line >= line_delta {
self.line -= line_delta;
self.column = Column((self.column.0 + rhs) % num_cols.0);
self
} else {
match boundary {
Boundary::Clamp => Point::new(0, num_cols - 1),
Boundary::Wrap => {
let col = Column((self.column.0 + rhs) % num_cols.0);
let line = dimensions.total_lines() + self.line - line_delta;
Point::new(line, col)
},
}
let topmost_line = dimensions.topmost_line();
let bottommost_line = dimensions.bottommost_line();
match boundary {
Boundary::Cursor if self.line < 0 => Point::new(Line(0), Column(0)),
Boundary::Grid if self.line < topmost_line => Point::new(topmost_line, Column(0)),
Boundary::Cursor | Boundary::Grid if self.line > bottommost_line => {
Point::new(bottommost_line, last_column)
},
Boundary::None => {
self.line = self.line.grid_clamp(dimensions, boundary);
self
},
_ => self,
}
}
}
impl PartialOrd for Point {
fn partial_cmp(&self, other: &Point) -> Option<Ordering> {
impl<L: Ord, C: Ord> PartialOrd for Point<L, C> {
fn partial_cmp(&self, other: &Point<L, C>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Point {
fn cmp(&self, other: &Point) -> Ordering {
impl<L: Ord, C: Ord> Ord for Point<L, C> {
fn cmp(&self, other: &Point<L, C>) -> Ordering {
match (self.line.cmp(&other.line), self.column.cmp(&other.column)) {
(Ordering::Equal, ord) | (ord, _) => ord,
}
}
}
impl PartialOrd for Point<usize> {
fn partial_cmp(&self, other: &Point<usize>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Point<usize> {
fn cmp(&self, other: &Point<usize>) -> Ordering {
match (self.line.cmp(&other.line), self.column.cmp(&other.column)) {
(Ordering::Equal, ord) => ord,
(Ordering::Less, _) => Ordering::Greater,
(Ordering::Greater, _) => Ordering::Less,
}
}
}
impl From<Point<usize>> for Point<isize> {
fn from(point: Point<usize>) -> Self {
Point::new(point.line as isize, point.column)
}
}
impl From<Point<usize>> for Point<Line> {
fn from(point: Point<usize>) -> Self {
Point::new(Line(point.line), point.column)
}
}
impl From<Point<isize>> for Point<usize> {
fn from(point: Point<isize>) -> Self {
Point::new(point.line as usize, point.column)
}
}
impl From<Point> for Point<usize> {
fn from(point: Point) -> Self {
Point::new(point.line.0, point.column)
}
}
/// A line.
///
/// Newtype to avoid passing values incorrectly.
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd)]
pub struct Line(pub usize);
pub struct Line(pub i32);
impl Line {
/// Clamp a line to a grid boundary.
pub fn grid_clamp<D: Dimensions>(self, dimensions: &D, boundary: Boundary) -> Self {
match boundary {
Boundary::Cursor => max(Line(0), min(dimensions.bottommost_line(), self)),
Boundary::Grid => {
let bottommost_line = dimensions.bottommost_line();
let topmost_line = dimensions.topmost_line();
max(topmost_line, min(bottommost_line, self))
},
Boundary::None => {
let screen_lines = dimensions.screen_lines() as i32;
let total_lines = dimensions.total_lines() as i32;
if self >= screen_lines {
let topmost_line = dimensions.topmost_line();
let extra = (self.0 - screen_lines) % total_lines;
topmost_line + extra
} else {
let bottommost_line = dimensions.bottommost_line();
let extra = (self.0 - screen_lines + 1) % total_lines;
bottommost_line + extra
}
},
}
}
}
impl fmt::Display for Line {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -201,6 +165,58 @@ impl fmt::Display for Line {
}
}
impl From<usize> for Line {
fn from(source: usize) -> Self {
Self(source as i32)
}
}
impl Add<usize> for Line {
type Output = Line;
#[inline]
fn add(self, rhs: usize) -> Line {
self + rhs as i32
}
}
impl AddAssign<usize> for Line {
#[inline]
fn add_assign(&mut self, rhs: usize) {
*self += rhs as i32;
}
}
impl Sub<usize> for Line {
type Output = Line;
#[inline]
fn sub(self, rhs: usize) -> Line {
self - rhs as i32
}
}
impl SubAssign<usize> for Line {
#[inline]
fn sub_assign(&mut self, rhs: usize) {
*self -= rhs as i32;
}
}
impl PartialOrd<usize> for Line {
#[inline]
fn partial_cmp(&self, other: &usize) -> Option<Ordering> {
self.0.partial_cmp(&(*other as i32))
}
}
impl PartialEq<usize> for Line {
#[inline]
fn eq(&self, other: &usize) -> bool {
self.0.eq(&(*other as i32))
}
}
/// A column.
///
/// Newtype to avoid passing values incorrectly.
@ -213,66 +229,25 @@ impl fmt::Display for Column {
}
}
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// implements binary operators "&T op U", "T op &U", "&T op &U"
// based on "T op U" where T and U are expected to be `Copy`able
macro_rules! forward_ref_binop {
(impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
impl<'a> $imp<$u> for &'a $t {
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, other: $u) -> <$t as $imp<$u>>::Output {
$imp::$method(*self, other)
}
}
impl<'a> $imp<&'a $u> for $t {
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output {
$imp::$method(self, *other)
}
}
impl<'a, 'b> $imp<&'a $u> for &'b $t {
type Output = <$t as $imp<$u>>::Output;
#[inline]
fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output {
$imp::$method(*self, *other)
}
}
};
}
/// Macro for deriving deref.
macro_rules! deref {
($ty:ty, $target:ty) => {
macro_rules! ops {
($ty:ty, $construct:expr, $primitive:ty) => {
impl Deref for $ty {
type Target = $target;
type Target = $primitive;
#[inline]
fn deref(&self) -> &$target {
fn deref(&self) -> &$primitive {
&self.0
}
}
};
}
macro_rules! add {
($ty:ty, $construct:expr) => {
impl ops::Add<$ty> for $ty {
impl From<$primitive> for $ty {
#[inline]
fn from(val: $primitive) -> $ty {
$construct(val)
}
}
impl Add<$ty> for $ty {
type Output = $ty;
#[inline]
@ -280,182 +255,94 @@ macro_rules! add {
$construct(self.0 + rhs.0)
}
}
};
}
macro_rules! sub {
($ty:ty, $construct:expr) => {
impl ops::Sub<$ty> for $ty {
type Output = $ty;
#[inline]
fn sub(self, rhs: $ty) -> $ty {
$construct(self.0 - rhs.0)
}
}
impl<'a> ops::Sub<$ty> for &'a $ty {
type Output = $ty;
#[inline]
fn sub(self, rhs: $ty) -> $ty {
$construct(self.0 - rhs.0)
}
}
impl<'a> ops::Sub<&'a $ty> for $ty {
type Output = $ty;
#[inline]
fn sub(self, rhs: &'a $ty) -> $ty {
$construct(self.0 - rhs.0)
}
}
impl<'a, 'b> ops::Sub<&'a $ty> for &'b $ty {
type Output = $ty;
#[inline]
fn sub(self, rhs: &'a $ty) -> $ty {
$construct(self.0 - rhs.0)
}
}
};
}
/// This exists because we can't implement Iterator on Range
/// and the existing impl needs the unstable Step trait
/// This should be removed and replaced with a Step impl
/// in the ops macro when `step_by` is stabilized.
pub struct IndexRange<T>(pub Range<T>);
impl<T> From<Range<T>> for IndexRange<T> {
fn from(from: Range<T>) -> Self {
IndexRange(from)
}
}
macro_rules! ops {
($ty:ty, $construct:expr) => {
add!($ty, $construct);
sub!($ty, $construct);
deref!($ty, usize);
forward_ref_binop!(impl Add, add for $ty, $ty);
impl $ty {
#[inline]
fn steps_between(start: $ty, end: $ty, by: $ty) -> Option<usize> {
if by == $construct(0) { return None; }
if start < end {
// Note: We assume $t <= usize here.
let diff = (end - start).0;
let by = by.0;
if diff % by > 0 {
Some(diff / by + 1)
} else {
Some(diff / by)
}
} else {
Some(0)
}
}
#[inline]
fn steps_between_by_one(start: $ty, end: $ty) -> Option<usize> {
Self::steps_between(start, end, $construct(1))
}
}
impl Iterator for IndexRange<$ty> {
type Item = $ty;
#[inline]
fn next(&mut self) -> Option<$ty> {
if self.0.start < self.0.end {
let old = self.0.start;
self.0.start = old + 1;
Some(old)
} else {
None
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
match Self::Item::steps_between_by_one(self.0.start, self.0.end) {
Some(hint) => (hint, Some(hint)),
None => (0, None)
}
}
}
impl DoubleEndedIterator for IndexRange<$ty> {
#[inline]
fn next_back(&mut self) -> Option<$ty> {
if self.0.start < self.0.end {
let new = self.0.end - 1;
self.0.end = new;
Some(new)
} else {
None
}
}
}
impl AddAssign<$ty> for $ty {
#[inline]
fn add_assign(&mut self, rhs: $ty) {
self.0 += rhs.0
self.0 += rhs.0;
}
}
impl Add<$primitive> for $ty {
type Output = $ty;
#[inline]
fn add(self, rhs: $primitive) -> $ty {
$construct(self.0 + rhs)
}
}
impl AddAssign<$primitive> for $ty {
#[inline]
fn add_assign(&mut self, rhs: $primitive) {
self.0 += rhs
}
}
impl Sub<$ty> for $ty {
type Output = $ty;
#[inline]
fn sub(self, rhs: $ty) -> $ty {
$construct(self.0 - rhs.0)
}
}
impl SubAssign<$ty> for $ty {
#[inline]
fn sub_assign(&mut self, rhs: $ty) {
self.0 -= rhs.0
self.0 -= rhs.0;
}
}
impl AddAssign<usize> for $ty {
impl Sub<$primitive> for $ty {
type Output = $ty;
#[inline]
fn add_assign(&mut self, rhs: usize) {
self.0 += rhs
fn sub(self, rhs: $primitive) -> $ty {
$construct(self.0 - rhs)
}
}
impl SubAssign<usize> for $ty {
impl SubAssign<$primitive> for $ty {
#[inline]
fn sub_assign(&mut self, rhs: usize) {
fn sub_assign(&mut self, rhs: $primitive) {
self.0 -= rhs
}
}
impl From<usize> for $ty {
impl PartialEq<$ty> for $primitive {
#[inline]
fn from(val: usize) -> $ty {
$construct(val)
fn eq(&self, other: &$ty) -> bool {
self.eq(&other.0)
}
}
impl Add<usize> for $ty {
type Output = $ty;
impl PartialEq<$primitive> for $ty {
#[inline]
fn add(self, rhs: usize) -> $ty {
$construct(self.0 + rhs)
fn eq(&self, other: &$primitive) -> bool {
self.0.eq(other)
}
}
impl Sub<usize> for $ty {
type Output = $ty;
impl PartialOrd<$ty> for $primitive {
#[inline]
fn sub(self, rhs: usize) -> $ty {
$construct(self.0 - rhs)
fn partial_cmp(&self, other: &$ty) -> Option<Ordering> {
self.partial_cmp(&other.0)
}
}
}
impl PartialOrd<$primitive> for $ty {
#[inline]
fn partial_cmp(&self, other: &$primitive) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
};
}
ops!(Line, Line);
ops!(Column, Column);
ops!(Column, Column, usize);
ops!(Line, Line, i32);
#[cfg(test)]
mod tests {
@ -469,154 +356,106 @@ mod tests {
assert!(Point::new(Line(1), Column(1)) > Point::new(Line(0), Column(0)));
assert!(Point::new(Line(1), Column(1)) > Point::new(Line(0), Column(1)));
assert!(Point::new(Line(1), Column(1)) > Point::new(Line(1), Column(0)));
assert!(Point::new(Line(0), Column(0)) > Point::new(Line(-1), Column(0)));
}
#[test]
fn sub() {
let num_cols = Column(42);
let point = Point::new(0, Column(13));
let size = (10, 42);
let point = Point::new(Line(0), Column(13));
let result = point.sub(num_cols, 1);
let result = point.sub(&size, Boundary::Cursor, 1);
assert_eq!(result, Point::new(0, point.column - 1));
assert_eq!(result, Point::new(Line(0), point.column - 1));
}
#[test]
fn sub_wrap() {
let num_cols = Column(42);
let point = Point::new(1, Column(0));
let size = (10, 42);
let point = Point::new(Line(1), Column(0));
let result = point.sub(num_cols, 1);
let result = point.sub(&size, Boundary::Cursor, 1);
assert_eq!(result, Point::new(0, num_cols - 1));
assert_eq!(result, Point::new(Line(0), size.last_column()));
}
#[test]
fn sub_clamp() {
let num_cols = Column(42);
let point = Point::new(0, Column(0));
let size = (10, 42);
let point = Point::new(Line(0), Column(0));
let result = point.sub(num_cols, 1);
let result = point.sub(&size, Boundary::Cursor, 1);
assert_eq!(result, point);
}
#[test]
fn sub_grid_clamp() {
let size = (0, 42);
let point = Point::new(Line(0), Column(0));
let result = point.sub(&size, Boundary::Grid, 1);
assert_eq!(result, point);
}
#[test]
fn sub_none_clamp() {
let size = (10, 42);
let point = Point::new(Line(0), Column(0));
let result = point.sub(&size, Boundary::None, 1);
assert_eq!(result, Point::new(Line(9), Column(41)));
}
#[test]
fn add() {
let num_cols = Column(42);
let point = Point::new(0, Column(13));
let size = (10, 42);
let point = Point::new(Line(0), Column(13));
let result = point.add(num_cols, 1);
let result = point.add(&size, Boundary::Cursor, 1);
assert_eq!(result, Point::new(0, point.column + 1));
assert_eq!(result, Point::new(Line(0), point.column + 1));
}
#[test]
fn add_wrap() {
let num_cols = Column(42);
let point = Point::new(0, num_cols - 1);
let size = (10, 42);
let point = Point::new(Line(0), size.last_column());
let result = point.add(num_cols, 1);
let result = point.add(&size, Boundary::Cursor, 1);
assert_eq!(result, Point::new(1, Column(0)));
assert_eq!(result, Point::new(Line(1), Column(0)));
}
#[test]
fn add_absolute() {
let point = Point::new(0, Column(13));
fn add_clamp() {
let size = (10, 42);
let point = Point::new(Line(9), Column(41));
let result = point.add_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1);
assert_eq!(result, Point::new(0, point.column + 1));
}
#[test]
fn add_absolute_wrapline() {
let point = Point::new(1, Column(41));
let result = point.add_absolute(&(Line(2), Column(42)), Boundary::Clamp, 1);
assert_eq!(result, Point::new(0, Column(0)));
}
#[test]
fn add_absolute_multiline_wrapline() {
let point = Point::new(2, Column(9));
let result = point.add_absolute(&(Line(3), Column(10)), Boundary::Clamp, 11);
assert_eq!(result, Point::new(0, Column(0)));
}
#[test]
fn add_absolute_clamp() {
let point = Point::new(0, Column(41));
let result = point.add_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1);
let result = point.add(&size, Boundary::Cursor, 1);
assert_eq!(result, point);
}
#[test]
fn add_absolute_wrap() {
let point = Point::new(0, Column(41));
fn add_grid_clamp() {
let size = (10, 42);
let point = Point::new(Line(9), Column(41));
let result = point.add_absolute(&(Line(3), Column(42)), Boundary::Wrap, 1);
let result = point.add(&size, Boundary::Grid, 1);
assert_eq!(result, Point::new(2, Column(0)));
assert_eq!(result, point);
}
#[test]
fn add_absolute_multiline_wrap() {
let point = Point::new(0, Column(9));
fn add_none_clamp() {
let size = (10, 42);
let point = Point::new(Line(9), Column(41));
let result = point.add_absolute(&(Line(3), Column(10)), Boundary::Wrap, 11);
let result = point.add(&size, Boundary::None, 1);
assert_eq!(result, Point::new(1, Column(0)));
}
#[test]
fn sub_absolute() {
let point = Point::new(0, Column(13));
let result = point.sub_absolute(&(Line(1), Column(42)), Boundary::Clamp, 1);
assert_eq!(result, Point::new(0, point.column - 1));
}
#[test]
fn sub_absolute_wrapline() {
let point = Point::new(0, Column(0));
let result = point.sub_absolute(&(Line(2), Column(42)), Boundary::Clamp, 1);
assert_eq!(result, Point::new(1, Column(41)));
}
#[test]
fn sub_absolute_multiline_wrapline() {
let point = Point::new(0, Column(0));
let result = point.sub_absolute(&(Line(3), Column(10)), Boundary::Clamp, 11);
assert_eq!(result, Point::new(2, Column(9)));
}
#[test]
fn sub_absolute_wrap() {
let point = Point::new(2, Column(0));
let result = point.sub_absolute(&(Line(3), Column(42)), Boundary::Wrap, 1);
assert_eq!(result, Point::new(0, Column(41)));
}
#[test]
fn sub_absolute_multiline_wrap() {
let point = Point::new(2, Column(0));
let result = point.sub_absolute(&(Line(3), Column(10)), Boundary::Wrap, 11);
assert_eq!(result, Point::new(1, Column(9)));
assert_eq!(result, Point::new(Line(0), Column(0)));
}
}

View file

@ -5,7 +5,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::convert::TryFrom;
use std::cmp::min;
use std::mem;
use std::ops::{Bound, Range, RangeBounds};
@ -18,34 +18,34 @@ use crate::term::{RenderableCursor, Term};
/// A Point and side within that point.
#[derive(Debug, Copy, Clone, PartialEq)]
struct Anchor {
point: Point<usize>,
point: Point,
side: Side,
}
impl Anchor {
fn new(point: Point<usize>, side: Side) -> Anchor {
fn new(point: Point, side: Side) -> Anchor {
Anchor { point, side }
}
}
/// Represents a range of selected cells.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct SelectionRange<L = usize> {
pub struct SelectionRange {
/// Start point, top left of the selection.
pub start: Point<L>,
pub start: Point,
/// End point, bottom right of the selection.
pub end: Point<L>,
pub end: Point,
/// Whether this selection is a block selection.
pub is_block: bool,
}
impl<L> SelectionRange<L> {
pub fn new(start: Point<L>, end: Point<L>, is_block: bool) -> Self {
impl SelectionRange {
pub fn new(start: Point, end: Point, is_block: bool) -> Self {
Self { start, end, is_block }
}
}
impl SelectionRange<Line> {
impl SelectionRange {
/// Check if a point lies within the selection.
pub fn contains(&self, point: Point) -> bool {
self.start.line <= point.line
@ -56,7 +56,7 @@ impl SelectionRange<Line> {
}
/// Check if the cell at a point is part of the selection.
pub fn contains_cell(&self, indexed: &Indexed<&Cell, Line>, cursor: RenderableCursor) -> bool {
pub fn contains_cell(&self, indexed: &Indexed<&Cell>, cursor: RenderableCursor) -> bool {
// Do not invert block cursor at selection boundaries.
if cursor.shape == CursorShape::Block
&& cursor.point == indexed.point
@ -116,7 +116,7 @@ pub struct Selection {
}
impl Selection {
pub fn new(ty: SelectionType, location: Point<usize>, side: Side) -> Selection {
pub fn new(ty: SelectionType, location: Point, side: Side) -> Selection {
Self {
region: Range { start: Anchor::new(location, side), end: Anchor::new(location, side) },
ty,
@ -124,7 +124,7 @@ impl Selection {
}
/// Update the end of the selection.
pub fn update(&mut self, point: Point<usize>, side: Side) {
pub fn update(&mut self, point: Point, side: Side) {
self.region.end = Anchor::new(point, side);
}
@ -132,56 +132,52 @@ impl Selection {
mut self,
dimensions: &D,
range: &Range<Line>,
delta: isize,
delta: i32,
) -> Option<Selection> {
let num_lines = dimensions.screen_lines().0;
let num_cols = dimensions.cols().0;
let range_bottom = range.start.0;
let range_top = range.end.0;
let bottommost_line = dimensions.bottommost_line();
let range_bottom = range.end;
let range_top = range.start;
let (mut start, mut end) = (&mut self.region.start, &mut self.region.end);
if Selection::points_need_swap(start.point, end.point) {
if start.point > end.point {
mem::swap(&mut start, &mut end);
}
// Rotate start of selection.
if (start.point.line < range_top || range_top == num_lines)
&& start.point.line >= range_bottom
{
start.point.line = usize::try_from(start.point.line as isize + delta).unwrap_or(0);
if (start.point.line >= range_top || range_top == 0) && start.point.line < range_bottom {
start.point.line = min(start.point.line - delta, bottommost_line);
// If end is within the same region, delete selection once start rotates out.
if start.point.line < range_bottom && end.point.line >= range_bottom {
if start.point.line >= range_bottom && end.point.line < range_bottom {
return None;
}
// Clamp selection to start of region.
if start.point.line >= range_top && range_top != num_lines {
if start.point.line < range_top && range_top != 0 {
if self.ty != SelectionType::Block {
start.point.column = Column(0);
start.side = Side::Left;
}
start.point.line = range_top - 1;
start.point.line = range_top;
}
}
// Rotate end of selection.
if (end.point.line < range_top || range_top == num_lines) && end.point.line >= range_bottom
{
end.point.line = usize::try_from(end.point.line as isize + delta).unwrap_or(0);
if (end.point.line >= range_top || range_top == 0) && end.point.line < range_bottom {
end.point.line = min(end.point.line - delta, bottommost_line);
// Delete selection if end has overtaken the start.
if end.point.line > start.point.line {
if end.point.line < start.point.line {
return None;
}
// Clamp selection to end of region.
if end.point.line < range_bottom {
if end.point.line >= range_bottom {
if self.ty != SelectionType::Block {
end.point.column = Column(num_cols - 1);
end.point.column = dimensions.last_column();
end.side = Side::Right;
}
end.point.line = range_bottom;
end.point.line = range_bottom - 1;
}
}
@ -192,7 +188,7 @@ impl Selection {
match self.ty {
SelectionType::Simple => {
let (mut start, mut end) = (self.region.start, self.region.end);
if Self::points_need_swap(start.point, end.point) {
if start.point > end.point {
mem::swap(&mut start, &mut end);
}
@ -223,27 +219,27 @@ impl Selection {
}
/// Check whether selection contains any point in a given range.
pub fn intersects_range<R: RangeBounds<usize>>(&self, range: R) -> bool {
pub fn intersects_range<R: RangeBounds<Line>>(&self, range: R) -> bool {
let mut start = self.region.start.point.line;
let mut end = self.region.end.point.line;
if Self::points_need_swap(self.region.start.point, self.region.end.point) {
if start > end {
mem::swap(&mut start, &mut end);
}
let range_start = match range.start_bound() {
let range_top = match range.start_bound() {
Bound::Included(&range_start) => range_start,
Bound::Excluded(&range_start) => range_start.saturating_add(1),
Bound::Unbounded => 0,
Bound::Excluded(&range_start) => range_start + 1,
Bound::Unbounded => Line(i32::min_value()),
};
let range_end = match range.end_bound() {
let range_bottom = match range.end_bound() {
Bound::Included(&range_end) => range_end,
Bound::Excluded(&range_end) => range_end.saturating_sub(1),
Bound::Unbounded => usize::max_value(),
Bound::Excluded(&range_end) => range_end - 1,
Bound::Unbounded => Line(i32::max_value()),
};
range_start <= start && range_end >= end
range_bottom >= start && range_top <= end
}
/// Expand selection sides to include all cells.
@ -257,7 +253,7 @@ impl Selection {
(Side::Right, Side::Left)
},
SelectionType::Block => (Side::Left, Side::Right),
_ if Self::points_need_swap(start, end) => (Side::Right, Side::Left),
_ if start > end => (Side::Right, Side::Left),
_ => (Side::Left, Side::Right),
};
@ -268,63 +264,25 @@ impl Selection {
/// Convert selection to grid coordinates.
pub fn to_range<T>(&self, term: &Term<T>) -> Option<SelectionRange> {
let grid = term.grid();
let num_cols = grid.cols();
let columns = grid.columns();
// Order start above the end.
let mut start = self.region.start;
let mut end = self.region.end;
if Self::points_need_swap(start.point, end.point) {
if start.point > end.point {
mem::swap(&mut start, &mut end);
}
// Clamp to inside the grid buffer.
let is_block = self.ty == SelectionType::Block;
let (start, end) = Self::grid_clamp(start, end, is_block, grid.total_lines()).ok()?;
match self.ty {
SelectionType::Simple => self.range_simple(start, end, num_cols),
SelectionType::Simple => self.range_simple(start, end, columns),
SelectionType::Block => self.range_block(start, end),
SelectionType::Semantic => Some(Self::range_semantic(term, start.point, end.point)),
SelectionType::Lines => Some(Self::range_lines(term, start.point, end.point)),
}
}
/// Bring start and end points in the correct order.
fn points_need_swap(start: Point<usize>, end: Point<usize>) -> bool {
start.line < end.line || start.line == end.line && start.column > end.column
}
/// Clamp selection inside grid to prevent OOB.
fn grid_clamp(
mut start: Anchor,
end: Anchor,
is_block: bool,
lines: usize,
) -> Result<(Anchor, Anchor), ()> {
// Clamp selection inside of grid to prevent OOB.
if start.point.line >= lines {
// Remove selection if it is fully out of the grid.
if end.point.line >= lines {
return Err(());
}
// Clamp to grid if it is still partially visible.
if !is_block {
start.side = Side::Left;
start.point.column = Column(0);
}
start.point.line = lines - 1;
}
Ok((start, end))
}
fn range_semantic<T>(
term: &Term<T>,
mut start: Point<usize>,
mut end: Point<usize>,
) -> SelectionRange {
fn range_semantic<T>(term: &Term<T>, mut start: Point, mut end: Point) -> SelectionRange {
if start == end {
if let Some(matching) = term.bracket_search(start) {
if (matching.line == start.line && matching.column < start.column)
@ -339,19 +297,15 @@ impl Selection {
}
}
start = term.semantic_search_left(start);
end = term.semantic_search_right(end);
let start = term.semantic_search_left(start);
let end = term.semantic_search_right(end);
SelectionRange { start, end, is_block: false }
}
fn range_lines<T>(
term: &Term<T>,
mut start: Point<usize>,
mut end: Point<usize>,
) -> SelectionRange {
start = term.line_search_left(start);
end = term.line_search_right(end);
fn range_lines<T>(term: &Term<T>, start: Point, end: Point) -> SelectionRange {
let start = term.line_search_left(start);
let end = term.line_search_right(end);
SelectionRange { start, end, is_block: false }
}
@ -360,7 +314,7 @@ impl Selection {
&self,
mut start: Anchor,
mut end: Anchor,
num_cols: Column,
columns: usize,
) -> Option<SelectionRange> {
if self.is_empty() {
return None;
@ -369,9 +323,9 @@ impl Selection {
// Remove last cell if selection ends to the left of a cell.
if end.side == Side::Left && start.point != end.point {
// Special case when selection ends to left of first cell.
if end.point.column == Column(0) {
end.point.column = num_cols - 1;
end.point.line += 1;
if end.point.column == 0 {
end.point.column = Column(columns - 1);
end.point.line -= 1;
} else {
end.point.column -= 1;
}
@ -382,8 +336,9 @@ impl Selection {
start.point.column += 1;
// Wrap to next line when selection starts to the right of last column.
if start.point.column == num_cols {
start.point = Point::new(start.point.line.saturating_sub(1), Column(0));
if start.point.column == columns {
start.point.column = Column(0);
start.point.line += 1;
}
}
@ -429,7 +384,7 @@ mod tests {
use super::*;
use crate::config::MockConfig;
use crate::index::{Column, Line, Point, Side};
use crate::index::{Column, Point, Side};
use crate::term::{SizeInfo, Term};
fn term(height: usize, width: usize) -> Term<()> {
@ -444,7 +399,7 @@ mod tests {
/// 3. [BE]
#[test]
fn single_cell_left_to_right() {
let location = Point { line: 0, column: Column(0) };
let location = Point::new(Line(0), Column(0));
let mut selection = Selection::new(SelectionType::Simple, location, Side::Left);
selection.update(location, Side::Right);
@ -462,7 +417,7 @@ mod tests {
/// 3. [EB]
#[test]
fn single_cell_right_to_left() {
let location = Point { line: 0, column: Column(0) };
let location = Point::new(Line(0), Column(0));
let mut selection = Selection::new(SelectionType::Simple, location, Side::Right);
selection.update(location, Side::Left);
@ -481,8 +436,8 @@ mod tests {
#[test]
fn between_adjacent_cells_left_to_right() {
let mut selection =
Selection::new(SelectionType::Simple, Point::new(0, Column(0)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Left);
Selection::new(SelectionType::Simple, Point::new(Line(0), Column(0)), Side::Right);
selection.update(Point::new(Line(0), Column(1)), Side::Left);
assert_eq!(selection.to_range(&term(1, 2)), None);
}
@ -495,8 +450,8 @@ mod tests {
#[test]
fn between_adjacent_cells_right_to_left() {
let mut selection =
Selection::new(SelectionType::Simple, Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(0, Column(0)), Side::Right);
Selection::new(SelectionType::Simple, Point::new(Line(0), Column(1)), Side::Left);
selection.update(Point::new(Line(0), Column(0)), Side::Right);
assert_eq!(selection.to_range(&term(1, 2)), None);
}
@ -512,12 +467,12 @@ mod tests {
#[test]
fn across_adjacent_lines_upward_final_cell_exclusive() {
let mut selection =
Selection::new(SelectionType::Simple, Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(0, Column(1)), Side::Right);
Selection::new(SelectionType::Simple, Point::new(Line(0), Column(1)), Side::Right);
selection.update(Point::new(Line(1), Column(1)), Side::Right);
assert_eq!(selection.to_range(&term(2, 5)).unwrap(), SelectionRange {
start: Point::new(1, Column(2)),
end: Point::new(0, Column(1)),
start: Point::new(Line(0), Column(2)),
end: Point::new(Line(1), Column(1)),
is_block: false,
});
}
@ -535,73 +490,73 @@ mod tests {
#[test]
fn selection_bigger_then_smaller() {
let mut selection =
Selection::new(SelectionType::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);
Selection::new(SelectionType::Simple, Point::new(Line(1), Column(1)), Side::Right);
selection.update(Point::new(Line(0), Column(1)), Side::Right);
selection.update(Point::new(Line(0), Column(0)), Side::Right);
assert_eq!(selection.to_range(&term(2, 5)).unwrap(), SelectionRange {
start: Point::new(1, Column(1)),
end: Point::new(0, Column(1)),
start: Point::new(Line(0), Column(1)),
end: Point::new(Line(1), Column(1)),
is_block: false,
});
}
#[test]
fn line_selection() {
let size = (Line(10), Column(5));
let size = (10, 5);
let mut selection =
Selection::new(SelectionType::Lines, Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
Selection::new(SelectionType::Lines, Point::new(Line(9), Column(1)), Side::Left);
selection.update(Point::new(Line(4), Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..Line(size.0 as i32)), 7).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(4)),
assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange {
start: Point::new(Line(-3), Column(0)),
end: Point::new(Line(2), Column(4)),
is_block: false,
});
}
#[test]
fn semantic_selection() {
let size = (Line(10), Column(5));
let size = (10, 5);
let mut selection =
Selection::new(SelectionType::Semantic, Point::new(0, Column(3)), Side::Left);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
Selection::new(SelectionType::Semantic, Point::new(Line(9), Column(3)), Side::Left);
selection.update(Point::new(Line(4), Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..Line(size.0 as i32)), 4).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(3)),
assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange {
start: Point::new(Line(0), Column(1)),
end: Point::new(Line(5), Column(3)),
is_block: false,
});
}
#[test]
fn simple_selection() {
let size = (Line(10), Column(5));
let size = (10, 5);
let mut selection =
Selection::new(SelectionType::Simple, Point::new(0, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
Selection::new(SelectionType::Simple, Point::new(Line(9), Column(3)), Side::Right);
selection.update(Point::new(Line(4), Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..Line(size.0 as i32)), 7).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(0)),
end: Point::new(7, Column(3)),
assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange {
start: Point::new(Line(-3), Column(2)),
end: Point::new(Line(2), Column(3)),
is_block: false,
});
}
#[test]
fn block_selection() {
let size = (Line(10), Column(5));
let size = (10, 5);
let mut selection =
Selection::new(SelectionType::Block, Point::new(0, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..size.0), 7).unwrap();
Selection::new(SelectionType::Block, Point::new(Line(9), Column(3)), Side::Right);
selection.update(Point::new(Line(4), Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(0)..Line(size.0 as i32)), 7).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(9, Column(2)),
end: Point::new(7, Column(3)),
assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange {
start: Point::new(Line(-3), Column(2)),
end: Point::new(Line(2), Column(3)),
is_block: true
});
}
@ -609,72 +564,72 @@ mod tests {
#[test]
fn simple_is_empty() {
let mut selection =
Selection::new(SelectionType::Simple, Point::new(0, Column(0)), Side::Right);
Selection::new(SelectionType::Simple, Point::new(Line(1), Column(0)), Side::Right);
assert!(selection.is_empty());
selection.update(Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(Line(1), Column(1)), Side::Left);
assert!(selection.is_empty());
selection.update(Point::new(1, Column(0)), Side::Right);
selection.update(Point::new(Line(0), Column(0)), Side::Right);
assert!(!selection.is_empty());
}
#[test]
fn block_is_empty() {
let mut selection =
Selection::new(SelectionType::Block, Point::new(0, Column(0)), Side::Right);
Selection::new(SelectionType::Block, Point::new(Line(1), Column(0)), Side::Right);
assert!(selection.is_empty());
selection.update(Point::new(0, Column(1)), Side::Left);
selection.update(Point::new(Line(1), Column(1)), Side::Left);
assert!(selection.is_empty());
selection.update(Point::new(0, Column(1)), Side::Right);
selection.update(Point::new(Line(1), Column(1)), Side::Right);
assert!(!selection.is_empty());
selection.update(Point::new(1, Column(0)), Side::Right);
selection.update(Point::new(Line(0), Column(0)), Side::Right);
assert!(selection.is_empty());
selection.update(Point::new(1, Column(1)), Side::Left);
selection.update(Point::new(Line(0), Column(1)), Side::Left);
assert!(selection.is_empty());
selection.update(Point::new(1, Column(1)), Side::Right);
selection.update(Point::new(Line(0), Column(1)), Side::Right);
assert!(!selection.is_empty());
}
#[test]
fn rotate_in_region_up() {
let size = (Line(10), Column(5));
let size = (10, 5);
let mut selection =
Selection::new(SelectionType::Simple, Point::new(2, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), 4).unwrap();
Selection::new(SelectionType::Simple, Point::new(Line(7), Column(3)), Side::Right);
selection.update(Point::new(Line(4), Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(1)..Line(size.0 as i32 - 1)), 4).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(8, Column(0)),
end: Point::new(6, Column(3)),
assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange {
start: Point::new(Line(1), Column(0)),
end: Point::new(Line(3), Column(3)),
is_block: false,
});
}
#[test]
fn rotate_in_region_down() {
let size = (Line(10), Column(5));
let size = (10, 5);
let mut selection =
Selection::new(SelectionType::Simple, Point::new(5, Column(3)), Side::Right);
selection.update(Point::new(8, Column(1)), Side::Left);
selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), -5).unwrap();
Selection::new(SelectionType::Simple, Point::new(Line(4), Column(3)), Side::Right);
selection.update(Point::new(Line(1), Column(1)), Side::Left);
selection = selection.rotate(&size, &(Line(1)..Line(size.0 as i32 - 1)), -5).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(3, Column(1)),
end: Point::new(1, size.1 - 1),
assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange {
start: Point::new(Line(6), Column(1)),
end: Point::new(Line(8), size.last_column()),
is_block: false,
});
}
#[test]
fn rotate_in_region_up_block() {
let size = (Line(10), Column(5));
let size = (10, 5);
let mut selection =
Selection::new(SelectionType::Block, Point::new(2, Column(3)), Side::Right);
selection.update(Point::new(5, Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(1)..(size.0 - 1)), 4).unwrap();
Selection::new(SelectionType::Block, Point::new(Line(7), Column(3)), Side::Right);
selection.update(Point::new(Line(4), Column(1)), Side::Right);
selection = selection.rotate(&size, &(Line(1)..Line(size.0 as i32 - 1)), 4).unwrap();
assert_eq!(selection.to_range(&term(*size.0, *size.1)).unwrap(), SelectionRange {
start: Point::new(8, Column(2)),
end: Point::new(6, Column(3)),
assert_eq!(selection.to_range(&term(size.0, size.1)).unwrap(), SelectionRange {
start: Point::new(Line(1), Column(2)),
end: Point::new(Line(3), Column(3)),
is_block: true,
});
}
@ -682,17 +637,17 @@ mod tests {
#[test]
fn range_intersection() {
let mut selection =
Selection::new(SelectionType::Lines, Point::new(6, Column(1)), Side::Left);
selection.update(Point::new(3, Column(1)), Side::Right);
Selection::new(SelectionType::Lines, Point::new(Line(3), Column(1)), Side::Left);
selection.update(Point::new(Line(6), Column(1)), Side::Right);
assert!(selection.intersects_range(..));
assert!(selection.intersects_range(2..));
assert!(selection.intersects_range(2..=4));
assert!(selection.intersects_range(2..=7));
assert!(selection.intersects_range(4..=5));
assert!(selection.intersects_range(5..8));
assert!(selection.intersects_range(Line(2)..));
assert!(selection.intersects_range(Line(2)..=Line(4)));
assert!(selection.intersects_range(Line(2)..=Line(7)));
assert!(selection.intersects_range(Line(4)..=Line(5)));
assert!(selection.intersects_range(Line(5)..Line(8)));
assert!(!selection.intersects_range(..=2));
assert!(!selection.intersects_range(7..=8));
assert!(!selection.intersects_range(..=Line(2)));
assert!(!selection.intersects_range(Line(7)..=Line(8)));
}
}

View file

@ -178,7 +178,7 @@ mod tests {
#[test]
fn line_length_works() {
let mut row = Row::<Cell>::new(Column(10));
let mut row = Row::<Cell>::new(10);
row[Column(5)].c = 'a';
assert_eq!(row.line_length(), Column(6));
@ -186,7 +186,7 @@ mod tests {
#[test]
fn line_length_works_with_wrapline() {
let mut row = Row::<Cell>::new(Column(10));
let mut row = Row::<Cell>::new(10);
row[Column(9)].flags.insert(super::Flags::WRAPLINE);
assert_eq!(row.line_length(), Column(10));

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
use std::cmp::min;
use std::cmp::max;
use std::mem;
use std::ops::RangeInclusive;
@ -12,7 +12,7 @@ use crate::term::Term;
/// Used to match equal brackets, when performing a bracket-pair selection.
const BRACKET_PAIRS: [(char, char); 4] = [('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')];
pub type Match = RangeInclusive<Point<usize>>;
pub type Match = RangeInclusive<Point>;
/// Terminal regex search state.
#[derive(Clone, Debug)]
@ -53,7 +53,7 @@ impl<T> Term<T> {
pub fn search_next(
&self,
dfas: &RegexSearch,
mut origin: Point<usize>,
mut origin: Point,
direction: Direction,
side: Side,
mut max_lines: Option<usize>,
@ -72,7 +72,7 @@ impl<T> Term<T> {
fn next_match_right(
&self,
dfas: &RegexSearch,
origin: Point<usize>,
origin: Point,
side: Side,
max_lines: Option<usize>,
) -> Option<Match> {
@ -80,13 +80,12 @@ impl<T> Term<T> {
let mut end = start;
// Limit maximum number of lines searched.
let total_lines = self.total_lines();
end = match max_lines {
Some(max_lines) => {
let line = (start.line + total_lines - max_lines) % total_lines;
Point::new(line, self.cols() - 1)
let line = (start.line + max_lines).grid_clamp(self, Boundary::Grid);
Point::new(line, self.last_column())
},
_ => end.sub_absolute(self, Boundary::Wrap, 1),
_ => end.sub(self, Boundary::None, 1),
};
let mut regex_iter = RegexIter::new(start, end, Direction::Right, &self, dfas).peekable();
@ -99,8 +98,8 @@ impl<T> Term<T> {
let match_point = Self::match_side(&regex_match, side);
// If the match's point is beyond the origin, we're done.
match_point.line > start.line
|| match_point.line < origin.line
match_point.line < start.line
|| match_point.line > origin.line
|| (match_point.line == origin.line && match_point.column >= origin.column)
})
.unwrap_or(first_match);
@ -112,7 +111,7 @@ impl<T> Term<T> {
fn next_match_left(
&self,
dfas: &RegexSearch,
origin: Point<usize>,
origin: Point,
side: Side,
max_lines: Option<usize>,
) -> Option<Match> {
@ -121,8 +120,11 @@ impl<T> Term<T> {
// Limit maximum number of lines searched.
end = match max_lines {
Some(max_lines) => Point::new((start.line + max_lines) % self.total_lines(), Column(0)),
_ => end.add_absolute(self, Boundary::Wrap, 1),
Some(max_lines) => {
let line = (start.line - max_lines).grid_clamp(self, Boundary::Grid);
Point::new(line, Column(0))
},
_ => end.add(self, Boundary::None, 1),
};
let mut regex_iter = RegexIter::new(start, end, Direction::Left, &self, dfas).peekable();
@ -135,8 +137,8 @@ impl<T> Term<T> {
let match_point = Self::match_side(&regex_match, side);
// If the match's point is beyond the origin, we're done.
match_point.line < start.line
|| match_point.line > origin.line
match_point.line > start.line
|| match_point.line < origin.line
|| (match_point.line == origin.line && match_point.column <= origin.column)
})
.unwrap_or(first_match);
@ -145,7 +147,7 @@ impl<T> Term<T> {
}
/// Get the side of a match.
fn match_side(regex_match: &Match, side: Side) -> Point<usize> {
fn match_side(regex_match: &Match, side: Side) -> Point {
match side {
Side::Right => *regex_match.end(),
Side::Left => *regex_match.start(),
@ -155,12 +157,7 @@ impl<T> Term<T> {
/// Find the next regex match to the left of the origin point.
///
/// The origin is always included in the regex.
pub fn regex_search_left(
&self,
dfas: &RegexSearch,
start: Point<usize>,
end: Point<usize>,
) -> Option<Match> {
pub fn regex_search_left(&self, dfas: &RegexSearch, start: Point, end: Point) -> Option<Match> {
// Find start and end of match.
let match_start = self.regex_search(start, end, Direction::Left, &dfas.left_fdfa)?;
let match_end = self.regex_search(match_start, start, Direction::Right, &dfas.left_rdfa)?;
@ -174,8 +171,8 @@ impl<T> Term<T> {
pub fn regex_search_right(
&self,
dfas: &RegexSearch,
start: Point<usize>,
end: Point<usize>,
start: Point,
end: Point,
) -> Option<Match> {
// Find start and end of match.
let match_end = self.regex_search(start, end, Direction::Right, &dfas.right_fdfa)?;
@ -189,13 +186,14 @@ impl<T> Term<T> {
/// This will always return the side of the first match which is farthest from the start point.
fn regex_search(
&self,
start: Point<usize>,
end: Point<usize>,
start: Point,
end: Point,
direction: Direction,
dfa: &impl DFA,
) -> Option<Point<usize>> {
let last_line = self.total_lines() - 1;
let last_col = self.cols() - 1;
) -> Option<Point> {
let topmost_line = self.topmost_line();
let screen_lines = self.screen_lines() as i32;
let last_column = self.last_column();
// Advance the iterator.
let next = match direction {
@ -250,7 +248,8 @@ impl<T> Term<T> {
Some(Indexed { cell, .. }) => cell,
None => {
// Wrap around to other end of the scrollback buffer.
let start = Point::new(last_line - point.line, last_col - point.column);
let line = topmost_line - point.line + screen_lines - 1;
let start = Point::new(line, last_column - point.column);
iter = self.grid.iter_from(start);
iter.cell()
},
@ -262,8 +261,8 @@ impl<T> Term<T> {
let last_point = mem::replace(&mut point, iter.point());
// Handle linebreaks.
if (last_point.column == last_col && point.column == Column(0) && !last_wrapped)
|| (last_point.column == Column(0) && point.column == last_col && !wrapped)
if (last_point.column == last_column && point.column == Column(0) && !last_wrapped)
|| (last_point.column == Column(0) && point.column == last_column && !wrapped)
{
match regex_match {
Some(_) => break,
@ -299,7 +298,7 @@ impl<T> Term<T> {
*cell = new_cell;
}
let prev = iter.point().sub_absolute(self, Boundary::Clamp, 1);
let prev = iter.point().sub(self, Boundary::Grid, 1);
if self.grid[prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER) {
iter.prev();
}
@ -309,8 +308,8 @@ impl<T> Term<T> {
}
/// Find next matching bracket.
pub fn bracket_search(&self, point: Point<usize>) -> Option<Point<usize>> {
let start_char = self.grid[point.line][point.column].c;
pub fn bracket_search(&self, point: Point) -> Option<Point> {
let start_char = self.grid[point].c;
// Find the matching bracket we're looking for
let (forward, end_char) = BRACKET_PAIRS.iter().find_map(|(open, close)| {
@ -353,12 +352,12 @@ impl<T> Term<T> {
}
/// Find left end of semantic block.
pub fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> {
pub 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.total_lines() - 1);
point.line = max(point.line, self.topmost_line());
let mut iter = self.grid.iter_from(point);
let last_col = self.cols() - Column(1);
let last_column = self.columns() - 1;
let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER;
while let Some(cell) = iter.prev() {
@ -366,7 +365,7 @@ impl<T> Term<T> {
break;
}
if cell.point.column == last_col && !cell.flags.contains(Flags::WRAPLINE) {
if cell.point.column == last_column && !cell.flags.contains(Flags::WRAPLINE) {
break; // cut off if on new line or hit escape char
}
@ -377,12 +376,12 @@ impl<T> Term<T> {
}
/// Find right end of semantic block.
pub fn semantic_search_right(&self, mut point: Point<usize>) -> Point<usize> {
pub 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.total_lines() - 1);
point.line = max(point.line, self.topmost_line());
let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER;
let last_col = self.cols() - 1;
let last_column = self.columns() - 1;
for cell in self.grid.iter_from(point) {
if !cell.flags.intersects(wide) && self.semantic_escape_chars.contains(cell.c) {
@ -391,7 +390,7 @@ impl<T> Term<T> {
point = cell.point;
if point.column == last_col && !cell.flags.contains(Flags::WRAPLINE) {
if point.column == last_column && !cell.flags.contains(Flags::WRAPLINE) {
break; // cut off if on new line or hit escape char
}
}
@ -400,11 +399,11 @@ impl<T> Term<T> {
}
/// Find the beginning of the current line across linewraps.
pub fn line_search_left(&self, mut point: Point<usize>) -> Point<usize> {
while point.line + 1 < self.total_lines()
&& self.grid[point.line + 1][self.cols() - 1].flags.contains(Flags::WRAPLINE)
pub fn line_search_left(&self, mut point: Point) -> Point {
while point.line > self.topmost_line()
&& self.grid[point.line - 1i32][self.last_column()].flags.contains(Flags::WRAPLINE)
{
point.line += 1;
point.line -= 1;
}
point.column = Column(0);
@ -413,14 +412,14 @@ impl<T> Term<T> {
}
/// Find the end of the current line across linewraps.
pub fn line_search_right(&self, mut point: Point<usize>) -> Point<usize> {
while point.line > 0
&& self.grid[point.line][self.cols() - 1].flags.contains(Flags::WRAPLINE)
pub fn line_search_right(&self, mut point: Point) -> Point {
while point.line + 1 < self.screen_lines()
&& self.grid[point.line][self.last_column()].flags.contains(Flags::WRAPLINE)
{
point.line -= 1;
point.line += 1;
}
point.column = self.cols() - 1;
point.column = self.last_column();
point
}
@ -428,8 +427,8 @@ impl<T> Term<T> {
/// Iterator over regex matches.
pub struct RegexIter<'a, T> {
point: Point<usize>,
end: Point<usize>,
point: Point,
end: Point,
direction: Direction,
dfas: &'a RegexSearch,
term: &'a Term<T>,
@ -438,8 +437,8 @@ pub struct RegexIter<'a, T> {
impl<'a, T> RegexIter<'a, T> {
pub fn new(
start: Point<usize>,
end: Point<usize>,
start: Point,
end: Point,
direction: Direction,
term: &'a Term<T>,
dfas: &'a RegexSearch,
@ -452,8 +451,8 @@ impl<'a, T> RegexIter<'a, T> {
self.point = self.term.expand_wide(self.point, self.direction);
self.point = match self.direction {
Direction::Right => self.point.add_absolute(self.term, Boundary::Wrap, 1),
Direction::Left => self.point.sub_absolute(self.term, Boundary::Wrap, 1),
Direction::Right => self.point.add(self.term, Boundary::None, 1),
Direction::Left => self.point.sub(self.term, Boundary::None, 1),
};
}
@ -498,7 +497,7 @@ impl<'a, T> Iterator for RegexIter<'a, T> {
mod tests {
use super::*;
use crate::index::Column;
use crate::index::{Column, Line};
use crate::term::test::mock_term;
#[test]
@ -514,10 +513,10 @@ mod tests {
// Check regex across wrapped and unwrapped lines.
let dfas = RegexSearch::new("Ala.*123").unwrap();
let start = Point::new(3, Column(0));
let end = Point::new(0, Column(2));
let match_start = Point::new(3, Column(0));
let match_end = Point::new(2, Column(2));
let start = Point::new(Line(1), Column(0));
let end = Point::new(Line(4), Column(2));
let match_start = Point::new(Line(1), Column(0));
let match_end = Point::new(Line(2), Column(2));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
}
@ -534,10 +533,10 @@ mod tests {
// Check regex across wrapped and unwrapped lines.
let dfas = RegexSearch::new("Ala.*123").unwrap();
let start = Point::new(0, Column(2));
let end = Point::new(3, Column(0));
let match_start = Point::new(3, Column(0));
let match_end = Point::new(2, Column(2));
let start = Point::new(Line(4), Column(2));
let end = Point::new(Line(1), Column(0));
let match_start = Point::new(Line(1), Column(0));
let match_end = Point::new(Line(2), Column(2));
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
}
@ -551,14 +550,14 @@ mod tests {
// Greedy stopped at linebreak.
let dfas = RegexSearch::new("Ala.*critty").unwrap();
let start = Point::new(1, Column(0));
let end = Point::new(1, Column(25));
let start = Point::new(Line(0), Column(0));
let end = Point::new(Line(0), Column(25));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
// Greedy stopped at dead state.
let dfas = RegexSearch::new("Ala[^y]*critty").unwrap();
let start = Point::new(1, Column(0));
let end = Point::new(1, Column(15));
let start = Point::new(Line(0), Column(0));
let end = Point::new(Line(0), Column(15));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
}
@ -572,8 +571,8 @@ mod tests {
");
let dfas = RegexSearch::new("nothing").unwrap();
let start = Point::new(2, Column(0));
let end = Point::new(0, Column(4));
let start = Point::new(Line(2), Column(0));
let end = Point::new(Line(0), Column(4));
assert_eq!(term.regex_search_right(&dfas, start, end), None);
}
@ -587,8 +586,8 @@ mod tests {
");
let dfas = RegexSearch::new("nothing").unwrap();
let start = Point::new(0, Column(4));
let end = Point::new(2, Column(0));
let start = Point::new(Line(0), Column(4));
let end = Point::new(Line(2), Column(0));
assert_eq!(term.regex_search_left(&dfas, start, end), None);
}
@ -602,10 +601,10 @@ mod tests {
// Make sure the cell containing the linebreak is not skipped.
let dfas = RegexSearch::new("te.*123").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(1, Column(0));
let match_start = Point::new(1, Column(0));
let match_end = Point::new(1, Column(9));
let start = Point::new(Line(1), Column(0));
let end = Point::new(Line(0), Column(0));
let match_start = Point::new(Line(0), Column(0));
let match_end = Point::new(Line(0), Column(9));
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
}
@ -619,9 +618,9 @@ mod tests {
// Make sure the cell containing the linebreak is not skipped.
let dfas = RegexSearch::new("te.*123").unwrap();
let start = Point::new(1, Column(2));
let end = Point::new(0, Column(9));
let match_start = Point::new(0, Column(0));
let start = Point::new(Line(0), Column(2));
let end = Point::new(Line(1), Column(9));
let match_start = Point::new(Line(1), Column(0));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end));
}
@ -631,8 +630,8 @@ mod tests {
// Make sure dead state cell is skipped when reversing.
let dfas = RegexSearch::new("alacrit").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(6));
let start = Point::new(Line(0), Column(0));
let end = Point::new(Line(0), Column(6));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
}
@ -642,10 +641,10 @@ mod tests {
// Make sure the reverse DFA operates the same as a forward DFA.
let dfas = RegexSearch::new("zoo").unwrap();
let start = Point::new(0, Column(9));
let end = Point::new(0, Column(0));
let match_start = Point::new(0, Column(0));
let match_end = Point::new(0, Column(2));
let start = Point::new(Line(0), Column(9));
let end = Point::new(Line(0), Column(0));
let match_start = Point::new(Line(0), Column(0));
let match_end = Point::new(Line(0), Column(2));
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
}
@ -654,13 +653,13 @@ mod tests {
let term = mock_term("testвосибing");
let dfas = RegexSearch::new("te.*ing").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(11));
let start = Point::new(Line(0), Column(0));
let end = Point::new(Line(0), Column(11));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
let dfas = RegexSearch::new("te.*ing").unwrap();
let start = Point::new(0, Column(11));
let end = Point::new(0, Column(0));
let start = Point::new(Line(0), Column(11));
let end = Point::new(Line(0), Column(0));
assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start));
}
@ -669,13 +668,13 @@ mod tests {
let term = mock_term("a🦇x🦇");
let dfas = RegexSearch::new("[^ ]*").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(5));
let start = Point::new(Line(0), Column(0));
let end = Point::new(Line(0), Column(5));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
let dfas = RegexSearch::new("[^ ]*").unwrap();
let start = Point::new(0, Column(5));
let end = Point::new(0, Column(0));
let start = Point::new(Line(0), Column(5));
let end = Point::new(Line(0), Column(0));
assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start));
}
@ -684,13 +683,13 @@ mod tests {
let term = mock_term("🦇");
let dfas = RegexSearch::new("🦇").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(0, Column(1));
let start = Point::new(Line(0), Column(0));
let end = Point::new(Line(0), Column(1));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(start..=end));
let dfas = RegexSearch::new("🦇").unwrap();
let start = Point::new(0, Column(1));
let end = Point::new(0, Column(0));
let start = Point::new(Line(0), Column(1));
let end = Point::new(Line(0), Column(0));
assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=start));
}
@ -703,15 +702,15 @@ mod tests {
");
let dfas = RegexSearch::new("xxx").unwrap();
let start = Point::new(0, Column(2));
let end = Point::new(1, Column(2));
let match_start = Point::new(1, Column(0));
let start = Point::new(Line(0), Column(2));
let end = Point::new(Line(1), Column(2));
let match_start = Point::new(Line(1), Column(0));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=end));
let dfas = RegexSearch::new("xxx").unwrap();
let start = Point::new(1, Column(0));
let end = Point::new(0, Column(0));
let match_end = Point::new(0, Column(2));
let start = Point::new(Line(1), Column(0));
let end = Point::new(Line(0), Column(0));
let match_end = Point::new(Line(0), Column(2));
assert_eq!(term.regex_search_left(&dfas, start, end), Some(end..=match_end));
}
@ -724,17 +723,17 @@ mod tests {
");
let dfas = RegexSearch::new("🦇x").unwrap();
let start = Point::new(0, Column(0));
let end = Point::new(1, Column(3));
let match_start = Point::new(1, Column(0));
let match_end = Point::new(1, Column(2));
let start = Point::new(Line(1), Column(0));
let end = Point::new(Line(0), Column(3));
let match_start = Point::new(Line(0), Column(0));
let match_end = Point::new(Line(0), Column(2));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
let dfas = RegexSearch::new("x🦇").unwrap();
let start = Point::new(1, Column(2));
let end = Point::new(0, Column(0));
let match_start = Point::new(0, Column(1));
let match_end = Point::new(0, Column(3));
let start = Point::new(Line(0), Column(2));
let end = Point::new(Line(1), Column(0));
let match_start = Point::new(Line(1), Column(1));
let match_end = Point::new(Line(1), Column(3));
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
}
@ -745,34 +744,34 @@ mod tests {
xxx \n\
🦇xx\
");
term.grid[1][Column(3)].flags.insert(Flags::LEADING_WIDE_CHAR_SPACER);
term.grid[Line(0)][Column(3)].flags.insert(Flags::LEADING_WIDE_CHAR_SPACER);
let dfas = RegexSearch::new("🦇x").unwrap();
let start = Point::new(1, Column(0));
let end = Point::new(0, Column(3));
let match_start = Point::new(1, Column(3));
let match_end = Point::new(0, Column(2));
let start = Point::new(Line(0), Column(0));
let end = Point::new(Line(1), Column(3));
let match_start = Point::new(Line(0), Column(3));
let match_end = Point::new(Line(1), Column(2));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
let dfas = RegexSearch::new("🦇x").unwrap();
let start = Point::new(0, Column(3));
let end = Point::new(1, Column(0));
let match_start = Point::new(1, Column(3));
let match_end = Point::new(0, Column(2));
let start = Point::new(Line(1), Column(3));
let end = Point::new(Line(0), Column(0));
let match_start = Point::new(Line(0), Column(3));
let match_end = Point::new(Line(1), Column(2));
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
let dfas = RegexSearch::new("x🦇").unwrap();
let start = Point::new(1, Column(0));
let end = Point::new(0, Column(3));
let match_start = Point::new(1, Column(2));
let match_end = Point::new(0, Column(1));
let start = Point::new(Line(0), Column(0));
let end = Point::new(Line(1), Column(3));
let match_start = Point::new(Line(0), Column(2));
let match_end = Point::new(Line(1), Column(1));
assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end));
let dfas = RegexSearch::new("x🦇").unwrap();
let start = Point::new(0, Column(3));
let end = Point::new(1, Column(0));
let match_start = Point::new(1, Column(2));
let match_end = Point::new(0, Column(1));
let start = Point::new(Line(1), Column(3));
let end = Point::new(Line(0), Column(0));
let match_start = Point::new(Line(0), Column(2));
let match_end = Point::new(Line(1), Column(1));
assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end));
}
}

View file

@ -25,6 +25,7 @@ use signal_hook::{self as sighook, iterator::Signals};
use crate::config::{Config, Program};
use crate::event::OnResize;
use crate::grid::Dimensions;
use crate::term::SizeInfo;
use crate::tty::{ChildEvent, EventedPty, EventedReadWrite};
@ -357,8 +358,8 @@ pub trait ToWinsize {
impl<'a> ToWinsize for &'a SizeInfo {
fn to_winsize(&self) -> winsize {
winsize {
ws_row: self.screen_lines().0 as libc::c_ushort,
ws_col: self.cols().0 as libc::c_ushort,
ws_row: self.screen_lines() as libc::c_ushort,
ws_col: self.columns() as libc::c_ushort,
ws_xpixel: self.width() as libc::c_ushort,
ws_ypixel: self.height() as libc::c_ushort,
}

View file

@ -19,6 +19,7 @@ use winapi::um::wincontypes::{COORD, HPCON};
use crate::config::Config;
use crate::event::OnResize;
use crate::grid::Dimensions;
use crate::term::SizeInfo;
use crate::tty::windows::child::ChildExitWatcher;
use crate::tty::windows::{cmdline, win32_string, Pty};
@ -185,11 +186,11 @@ impl OnResize for Conpty {
/// Helper to build a COORD from a SizeInfo, returning None in overflow cases.
fn coord_from_sizeinfo(size: &SizeInfo) -> Option<COORD> {
let cols = size.cols().0;
let lines = size.screen_lines().0;
let lines = size.screen_lines();
let columns = size.columns();
if cols <= i16::MAX as usize && lines <= i16::MAX as usize {
Some(COORD { X: cols as i16, Y: lines as i16 })
if columns <= i16::MAX as usize && lines <= i16::MAX as usize {
Some(COORD { X: columns as i16, Y: lines as i16 })
} else {
None
}

View file

@ -65,135 +65,126 @@ impl ViModeCursor {
/// Move vi mode cursor.
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn motion<T: EventListener>(mut self, term: &mut Term<T>, motion: ViMotion) -> Self {
let display_offset = term.grid().display_offset();
let lines = term.screen_lines();
let cols = term.cols();
let mut buffer_point = term.visible_to_buffer(self.point);
match motion {
ViMotion::Up => {
if buffer_point.line + 1 < term.total_lines() {
buffer_point.line += 1;
if self.point.line > term.topmost_line() {
self.point.line -= 1;
}
},
ViMotion::Down => {
if self.point.line + 1 < term.screen_lines() as i32 {
self.point.line += 1;
}
},
ViMotion::Down => buffer_point.line = buffer_point.line.saturating_sub(1),
ViMotion::Left => {
buffer_point = term.expand_wide(buffer_point, Direction::Left);
let wrap_point = Point::new(buffer_point.line + 1, cols - 1);
if buffer_point.column.0 == 0
&& buffer_point.line + 1 < term.total_lines()
self.point = term.expand_wide(self.point, Direction::Left);
let wrap_point = Point::new(self.point.line - 1, term.last_column());
if self.point.column == 0
&& self.point.line > term.topmost_line()
&& is_wrap(term, wrap_point)
{
buffer_point = wrap_point;
self.point = wrap_point;
} else {
buffer_point.column = Column(buffer_point.column.saturating_sub(1));
self.point.column = Column(self.point.column.saturating_sub(1));
}
},
ViMotion::Right => {
buffer_point = term.expand_wide(buffer_point, Direction::Right);
if is_wrap(term, buffer_point) {
buffer_point = Point::new(buffer_point.line - 1, Column(0));
self.point = term.expand_wide(self.point, Direction::Right);
if is_wrap(term, self.point) {
self.point = Point::new(self.point.line + 1, Column(0));
} else {
buffer_point.column = min(buffer_point.column + 1, cols - 1);
self.point.column = min(self.point.column + 1, term.last_column());
}
},
ViMotion::First => {
buffer_point = term.expand_wide(buffer_point, Direction::Left);
while buffer_point.column.0 == 0
&& buffer_point.line + 1 < term.total_lines()
&& is_wrap(term, Point::new(buffer_point.line + 1, cols - 1))
self.point = term.expand_wide(self.point, Direction::Left);
while self.point.column == 0
&& self.point.line > term.topmost_line()
&& is_wrap(term, Point::new(self.point.line - 1, term.last_column()))
{
buffer_point.line += 1;
self.point.line -= 1;
}
buffer_point.column = Column(0);
self.point.column = Column(0);
},
ViMotion::Last => buffer_point = last(term, buffer_point),
ViMotion::FirstOccupied => buffer_point = first_occupied(term, buffer_point),
ViMotion::Last => self.point = last(term, self.point),
ViMotion::FirstOccupied => self.point = first_occupied(term, self.point),
ViMotion::High => {
let line = display_offset + lines.0 - 1;
let line = Line(-(term.grid().display_offset() as i32));
let col = first_occupied_in_line(term, line).unwrap_or_default().column;
buffer_point = Point::new(line, col);
self.point = Point::new(line, col);
},
ViMotion::Middle => {
let line = display_offset + lines.0 / 2;
let display_offset = term.grid().display_offset() as i32;
let line = Line(-display_offset + term.screen_lines() as i32 / 2 - 1);
let col = first_occupied_in_line(term, line).unwrap_or_default().column;
buffer_point = Point::new(line, col);
self.point = Point::new(line, col);
},
ViMotion::Low => {
let line = display_offset;
let display_offset = term.grid().display_offset() as i32;
let line = Line(-display_offset + term.screen_lines() as i32 - 1);
let col = first_occupied_in_line(term, line).unwrap_or_default().column;
buffer_point = Point::new(line, col);
self.point = Point::new(line, col);
},
ViMotion::SemanticLeft => {
buffer_point = semantic(term, buffer_point, Direction::Left, Side::Left);
self.point = semantic(term, self.point, Direction::Left, Side::Left);
},
ViMotion::SemanticRight => {
buffer_point = semantic(term, buffer_point, Direction::Right, Side::Left);
self.point = semantic(term, self.point, Direction::Right, Side::Left);
},
ViMotion::SemanticLeftEnd => {
buffer_point = semantic(term, buffer_point, Direction::Left, Side::Right);
self.point = semantic(term, self.point, Direction::Left, Side::Right);
},
ViMotion::SemanticRightEnd => {
buffer_point = semantic(term, buffer_point, Direction::Right, Side::Right);
self.point = semantic(term, self.point, Direction::Right, Side::Right);
},
ViMotion::WordLeft => {
buffer_point = word(term, buffer_point, Direction::Left, Side::Left);
self.point = word(term, self.point, Direction::Left, Side::Left);
},
ViMotion::WordRight => {
buffer_point = word(term, buffer_point, Direction::Right, Side::Left);
self.point = word(term, self.point, Direction::Right, Side::Left);
},
ViMotion::WordLeftEnd => {
buffer_point = word(term, buffer_point, Direction::Left, Side::Right);
self.point = word(term, self.point, Direction::Left, Side::Right);
},
ViMotion::WordRightEnd => {
buffer_point = word(term, buffer_point, Direction::Right, Side::Right);
},
ViMotion::Bracket => {
buffer_point = term.bracket_search(buffer_point).unwrap_or(buffer_point);
self.point = word(term, self.point, Direction::Right, Side::Right);
},
ViMotion::Bracket => self.point = term.bracket_search(self.point).unwrap_or(self.point),
}
term.scroll_to_point(buffer_point);
self.point = term.grid().clamp_buffer_to_visible(buffer_point);
term.scroll_to_point(self.point);
self
}
/// Get target cursor point for vim-like page movement.
#[must_use = "this returns the result of the operation, without modifying the original"]
pub fn scroll<T: EventListener>(mut self, term: &Term<T>, lines: isize) -> Self {
pub fn scroll<T: EventListener>(mut self, term: &Term<T>, lines: i32) -> Self {
// Check number of lines the cursor needs to be moved.
let overscroll = if lines > 0 {
let max_scroll = term.history_size() - term.grid().display_offset();
max(0, lines - max_scroll as isize)
max(0, lines - max_scroll as i32)
} else {
let max_scroll = term.grid().display_offset();
min(0, lines + max_scroll as isize)
min(0, lines + max_scroll as i32)
};
// Clamp movement to within visible region.
let mut line = self.point.line.0 as isize;
line -= overscroll;
line = max(0, min(term.screen_lines().0 as isize - 1, line));
let line = (self.point.line - overscroll).grid_clamp(term, Boundary::Cursor);
// Find the first occupied cell after scrolling has been performed.
let buffer_point = term.visible_to_buffer(self.point);
let mut target_line = buffer_point.line as isize + lines;
target_line = max(0, min(term.total_lines() as isize - 1, target_line));
let col = first_occupied_in_line(term, target_line as usize).unwrap_or_default().column;
let target_line = (self.point.line - lines).grid_clamp(term, Boundary::Grid);
let column = first_occupied_in_line(term, target_line).unwrap_or_default().column;
// Move cursor.
self.point = Point::new(Line(line as usize), col);
self.point = Point::new(line, column);
self
}
}
/// Find next end of line to move to.
fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
let cols = term.cols();
fn last<T>(term: &Term<T>, mut point: Point) -> Point {
// Expand across wide cells.
point = term.expand_wide(point, Direction::Right);
@ -205,35 +196,35 @@ fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
occupied
} else if is_wrap(term, point) {
// Jump to last occupied cell across linewraps.
while point.line > 0 && is_wrap(term, point) {
point.line -= 1;
while is_wrap(term, point) {
point.line += 1;