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:
parent
974392cdc6
commit
3bd5ac221a
108 changed files with 1545 additions and 2005 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<_>>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(®ion, 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>>;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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(®ex_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(®ex_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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||