summaryrefslogtreecommitdiff
path: root/alacritty_terminal
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty_terminal')
-rw-r--r--alacritty_terminal/src/grid/mod.rs248
-rw-r--r--alacritty_terminal/src/grid/storage.rs9
-rw-r--r--alacritty_terminal/src/grid/tests.rs8
-rw-r--r--alacritty_terminal/src/index.rs22
-rw-r--r--alacritty_terminal/src/selection.rs61
-rw-r--r--alacritty_terminal/src/term/cell.rs22
-rw-r--r--alacritty_terminal/src/term/mod.rs146
7 files changed, 321 insertions, 195 deletions
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs
index 87de1d7a..53f7ebea 100644
--- a/alacritty_terminal/src/grid/mod.rs
+++ b/alacritty_terminal/src/grid/mod.rs
@@ -19,8 +19,9 @@ use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo};
use serde::{Deserialize, Serialize};
-use crate::index::{self, Column, IndexRange, Line, Point};
+use crate::index::{Column, IndexRange, Line, Point};
use crate::selection::Selection;
+use crate::term::cell::Flags;
mod row;
pub use self::row::Row;
@@ -68,8 +69,8 @@ impl<T: PartialEq> ::std::cmp::PartialEq for Grid<T> {
pub trait GridCell {
fn is_empty(&self) -> bool;
- fn is_wrap(&self) -> bool;
- fn set_wrap(&mut self, wrap: bool);
+ fn flags(&self) -> &Flags;
+ fn flags_mut(&mut self) -> &mut Flags;
/// Fast equality approximation.
///
@@ -112,10 +113,10 @@ pub struct Grid<T> {
raw: Storage<T>,
/// Number of columns
- cols: index::Column,
+ cols: Column,
/// Number of visible lines.
- lines: index::Line,
+ lines: Line,
/// Offset of displayed area
///
@@ -144,7 +145,7 @@ pub enum Scroll {
}
impl<T: GridCell + PartialEq + Copy> Grid<T> {
- pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid<T> {
+ pub fn new(lines: Line, cols: Column, scrollback: usize, template: T) -> Grid<T> {
let raw = Storage::with_capacity(lines, Row::new(cols, &template));
Grid {
raw,
@@ -213,8 +214,8 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
pub fn resize(
&mut self,
reflow: bool,
- lines: index::Line,
- cols: index::Column,
+ lines: Line,
+ cols: Column,
cursor_pos: &mut Point,
template: &T,
) {
@@ -259,7 +260,7 @@ impl<T: GridCell + PartialEq + Copy> 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(&mut self, new_line_count: index::Line, template: &T) {
+ fn grow_lines(&mut self, new_line_count: Line, template: &T) {
let lines_added = new_line_count - self.lines;
// Need to "resize" before updating buffer
@@ -276,107 +277,173 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
self.display_offset = self.display_offset.saturating_sub(*lines_added);
}
- fn grow_cols(
- &mut self,
- reflow: bool,
- cols: index::Column,
- cursor_pos: &mut Point,
- template: &T,
- ) {
+ // Grow number of columns in each row, reflowing if necessary
+ fn grow_cols(&mut self, reflow: bool, cols: Column, cursor_pos: &mut Point, template: &T) {
+ // Check if a row needs to be wrapped
+ let should_reflow = |row: &Row<T>| -> bool {
+ let len = Column(row.len());
+ reflow && len < cols && row[len - 1].flags().contains(Flags::WRAPLINE)
+ };
+
let mut new_empty_lines = 0;
- let mut new_raw: Vec<Row<T>> = Vec::with_capacity(self.raw.len());
+ let mut reversed: Vec<Row<T>> = Vec::with_capacity(self.raw.len());
for (i, mut row) in self.raw.drain().enumerate().rev() {
- if let Some(last_row) = new_raw.last_mut() {
- // Grow the current line if there's wrapped content available
- if reflow
- && last_row.len() < cols.0
- && last_row.last().map(GridCell::is_wrap) == Some(true)
- {
- // Remove wrap flag before appending additional cells
- if let Some(cell) = last_row.last_mut() {
- cell.set_wrap(false);
- }
+ // FIXME: Rust 1.39.0+ allows moving in pattern guard here
+ // Check if reflowing shoud be performed
+ let mut last_row = reversed.last_mut();
+ let last_row = match last_row {
+ Some(ref mut last_row) if should_reflow(last_row) => last_row,
+ _ => {
+ reversed.push(row);
+ continue;
+ },
+ };
+
+ // Remove wrap flag before appending additional cells
+ if let Some(cell) = last_row.last_mut() {
+ cell.flags_mut().remove(Flags::WRAPLINE);
+ }
- // Append as many cells from the next line as possible
- let len = min(row.len(), cols.0 - last_row.len());
- let mut cells = row.front_split_off(len);
- last_row.append(&mut cells);
-
- if row.is_empty() {
- let raw_len = i + 1 + new_raw.len();
- if raw_len < self.lines.0 || self.scroll_limit == 0 {
- // Add new line and move lines up if we can't pull from history
- cursor_pos.line = Line(cursor_pos.line.saturating_sub(1));
- new_empty_lines += 1;
- } else {
- // Make sure viewport doesn't move if line is outside of the visible
- // area
- if i < self.display_offset {
- self.display_offset = self.display_offset.saturating_sub(1);
- }
-
- // Remove one line from scrollback, since we just moved it to the
- // viewport
- self.scroll_limit = self.scroll_limit.saturating_sub(1);
- self.display_offset = min(self.display_offset, self.scroll_limit);
- }
-
- // Don't push line into the new buffer
- continue;
- } else if let Some(cell) = last_row.last_mut() {
- // Set wrap flag if next line still has cells
- cell.set_wrap(true);
+ // Remove leading spacers when reflowing wide char to the previous line
+ let last_len = last_row.len();
+ if last_len >= 2
+ && !last_row[Column(last_len - 2)].flags().contains(Flags::WIDE_CHAR)
+ && last_row[Column(last_len - 1)].flags().contains(Flags::WIDE_CHAR_SPACER)
+ {
+ last_row.shrink(Column(last_len - 1));
+ }
+
+ // Append as many cells from the next line as possible
+ let len = min(row.len(), cols.0 - last_row.len());
+
+ // Insert leading spacer when there's not enough room for reflowing wide char
+ let mut cells = if row[Column(len - 1)].flags().contains(Flags::WIDE_CHAR) {
+ let mut cells = row.front_split_off(len - 1);
+
+ let mut spacer = *template;
+ spacer.flags_mut().insert(Flags::WIDE_CHAR_SPACER);
+ cells.push(spacer);
+
+ cells
+ } else {
+ row.front_split_off(len)
+ };
+
+ last_row.append(&mut cells);
+
+ if row.is_empty() {
+ let raw_len = i + 1 + reversed.len();
+ if raw_len < self.lines.0 || self.scroll_limit == 0 {
+ // Add new line and move lines up if we can't pull from history
+ cursor_pos.line = Line(cursor_pos.line.saturating_sub(1));
+ new_empty_lines += 1;
+ } else {
+ // Make sure viewport doesn't move if line is outside of the visible
+ // area
+ if i < self.display_offset {
+ self.display_offset = self.display_offset.saturating_sub(1);
}
+
+ // Remove one line from scrollback, since we just moved it to the
+ // viewport
+ self.scroll_limit = self.scroll_limit.saturating_sub(1);
+ self.display_offset = min(self.display_offset, self.scroll_limit);
}
+
+ // Don't push line into the new buffer
+ continue;
+ } else if let Some(cell) = last_row.last_mut() {
+ // Set wrap flag if next line still has cells
+ cell.flags_mut().insert(Flags::WRAPLINE);
}
- new_raw.push(row);
+ reversed.push(row);
}
// Add padding lines
- new_raw.append(&mut vec![Row::new(cols, template); new_empty_lines]);
+ reversed.append(&mut vec![Row::new(cols, template); new_empty_lines]);
// Fill remaining cells and reverse iterator
- let mut reversed = Vec::with_capacity(new_raw.len());
- for mut row in new_raw.drain(..).rev() {
+ let mut new_raw = Vec::with_capacity(reversed.len());
+ for mut row in reversed.drain(..).rev() {
if row.len() < cols.0 {
row.grow(cols, template);
}
- reversed.push(row);
+ new_raw.push(row);
}
- self.raw.replace_inner(reversed);
+ self.raw.replace_inner(new_raw);
self.cols = cols;
}
- fn shrink_cols(&mut self, reflow: bool, cols: index::Column, template: &T) {
+ // Shrink number of columns in each row, reflowing if necessary
+ fn shrink_cols(&mut self, reflow: bool, cols: Column, template: &T) {
let mut new_raw = Vec::with_capacity(self.raw.len());
let mut buffered = None;
for (i, mut row) in self.raw.drain().enumerate().rev() {
+ // Append lines left over from previous row
if let Some(buffered) = buffered.take() {
row.append_front(buffered);
}
- let mut wrapped = row.shrink(cols);
- new_raw.push(row);
+ loop {
+ // FIXME: Rust 1.39.0+ allows moving in pattern guard here
+ // Check if reflowing shoud be performed
+ let wrapped = row.shrink(cols);
+ let mut wrapped = match wrapped {
+ Some(_) if reflow => wrapped.unwrap(),
+ _ => {
+ new_raw.push(row);
+ break;
+ },
+ };
+
+ // 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) {
+ wrapped.insert(0, row[cols - 1]);
+
+ let mut spacer = *template;
+ spacer.flags_mut().insert(Flags::WIDE_CHAR_SPACER);
+ row[cols - 1] = spacer;
+ }
+
+ // Remove wide char spacer before shrinking
+ let len = wrapped.len();
+ if (len == 1 || (len >= 2 && !wrapped[len - 2].flags().contains(Flags::WIDE_CHAR)))
+ && wrapped[len - 1].flags().contains(Flags::WIDE_CHAR_SPACER)
+ {
+ if len == 1 {
+ row[cols - 1].flags_mut().insert(Flags::WRAPLINE);
+ new_raw.push(row);
+ break;
+ } else {
+ wrapped[len - 2].flags_mut().insert(Flags::WRAPLINE);
+ wrapped.truncate(len - 1);
+ }
+ }
+
+ new_raw.push(row);
- while let (Some(mut wrapped_cells), true) = (wrapped.take(), reflow) {
// Set line as wrapped if cells got removed
if let Some(cell) = new_raw.last_mut().and_then(|r| r.last_mut()) {
- cell.set_wrap(true);
+ cell.flags_mut().insert(Flags::WRAPLINE);
}
- if Some(true) == wrapped_cells.last().map(|c| c.is_wrap() && i >= 1)
- && wrapped_cells.len() < cols.0
+ if wrapped
+ .last()
+ .map(|c| c.flags().contains(Flags::WRAPLINE) && i >= 1)
+ .unwrap_or(false)
+ && wrapped.len() < cols.0
{
// Make sure previous wrap flag doesn't linger around
- if let Some(cell) = wrapped_cells.last_mut() {
- cell.set_wrap(false);
+ if let Some(cell) = wrapped.last_mut() {
+ cell.flags_mut().remove(Flags::WRAPLINE);
}
// Add removed cells to start of next row
- buffered = Some(wrapped_cells);
+ buffered = Some(wrapped);
+ break;
} else {
// Make sure viewport doesn't move if line is outside of the visible area
if i < self.display_offset {
@@ -384,17 +451,11 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
}
// Make sure new row is at least as long as new width
- let occ = wrapped_cells.len();
+ let occ = wrapped.len();
if occ < cols.0 {
- wrapped_cells.append(&mut vec![*template; cols.0 - occ]);
+ wrapped.append(&mut vec![*template; cols.0 - occ]);
}
- let mut row = Row::from_vec(wrapped_cells, occ);
-
- // Since inserted might exceed cols, we need to check it again
- wrapped = row.shrink(cols);
-
- // Add new row with all removed cells
- new_raw.push(row);
+ row = Row::from_vec(wrapped, occ);
// Increase scrollback history
self.scroll_limit = min(self.scroll_limit + 1, self.max_scroll_limit);
@@ -415,7 +476,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
/// of the terminal window.
///
/// Alacritty takes the same approach.
- fn shrink_lines(&mut self, target: index::Line) {
+ fn shrink_lines(&mut self, target: Line) {
let prev = self.lines;
self.selection = None;
@@ -429,19 +490,14 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
/// # Panics
///
/// This method will panic if `Line` is larger than the grid dimensions
- pub fn line_to_offset(&self, line: index::Line) -> usize {
+ pub fn line_to_offset(&self, line: Line) -> usize {
assert!(line < self.num_lines());
*(self.num_lines() - line - 1)
}
#[inline]
- pub fn scroll_down(
- &mut self,
- region: &Range<index::Line>,
- positions: index::Line,
- template: &T,
- ) {
+ pub fn scroll_down(&mut self, region: &Range<Line>, positions: Line, template: &T) {
// Whether or not there is a scrolling region active, as long as it
// starts at the top, we can do a full rotation which just involves
// changing the start index.
@@ -482,7 +538,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
/// scroll_up moves lines at the bottom towards the top
///
/// This is the performance-sensitive part of scrolling.
- pub fn scroll_up(&mut self, region: &Range<index::Line>, positions: index::Line, template: &T) {
+ pub fn scroll_up(&mut self, region: &Range<Line>, positions: Line, template: &T) {
if region.start == Line(0) {
// Update display offset when not pinned to active area
if self.display_offset != 0 {
@@ -570,7 +626,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
#[allow(clippy::len_without_is_empty)]
impl<T> Grid<T> {
#[inline]
- pub fn num_lines(&self) -> index::Line {
+ pub fn num_lines(&self) -> Line {
self.lines
}
@@ -579,7 +635,7 @@ impl<T> Grid<T> {
}
#[inline]
- pub fn num_cols(&self) -> index::Column {
+ pub fn num_cols(&self) -> Column {
self.cols
}
@@ -693,11 +749,11 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> {
}
/// Index active region by line
-impl<T> Index<index::Line> for Grid<T> {
+impl<T> Index<Line> for Grid<T> {
type Output = Row<T>;
#[inline]
- fn index(&self, index: index::Line) -> &Row<T> {
+ fn index(&self, index: Line) -> &Row<T> {
&self.raw[index]
}
}
@@ -712,9 +768,9 @@ impl<T> Index<usize> for Grid<T> {
}
}
-impl<T> IndexMut<index::Line> for Grid<T> {
+impl<T> IndexMut<Line> for Grid<T> {
#[inline]
- fn index_mut(&mut self, index: index::Line) -> &mut Row<T> {
+ fn index_mut(&mut self, index: Line) -> &mut Row<T> {
&mut self.raw[index]
}
}
diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs
index 5d9339d5..3182da57 100644
--- a/alacritty_terminal/src/grid/storage.rs
+++ b/alacritty_terminal/src/grid/storage.rs
@@ -330,17 +330,20 @@ mod test {
use crate::grid::storage::Storage;
use crate::grid::GridCell;
use crate::index::{Column, Line};
+ use crate::term::cell::Flags;
impl GridCell for char {
fn is_empty(&self) -> bool {
*self == ' ' || *self == '\t'
}
- fn is_wrap(&self) -> bool {
- false
+ fn flags(&self) -> &Flags {
+ unimplemented!();
}
- fn set_wrap(&mut self, _wrap: bool) {}
+ fn flags_mut(&mut self) -> &mut Flags {
+ unimplemented!();
+ }
fn fast_eq(&self, other: Self) -> bool {
self == &other
diff --git a/alacritty_terminal/src/grid/tests.rs b/alacritty_terminal/src/grid/tests.rs
index f3480b14..e4fdad5c 100644
--- a/alacritty_terminal/src/grid/tests.rs
+++ b/alacritty_terminal/src/grid/tests.rs
@@ -24,11 +24,13 @@ impl GridCell for usize {
*self == 0
}
- fn is_wrap(&self) -> bool {
- false
+ fn flags(&self) -> &Flags {
+ unimplemented!();
}
- fn set_wrap(&mut self, _wrap: bool) {}
+ fn flags_mut(&mut self) -> &mut Flags {
+ unimplemented!();
+ }
fn fast_eq(&self, other: Self) -> bool {
self == &other
diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs
index d40245f3..fb21baa0 100644
--- a/alacritty_terminal/src/index.rs
+++ b/alacritty_terminal/src/index.rs
@@ -44,24 +44,32 @@ impl<L> Point<L> {
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
- pub fn sub(mut self, num_cols: usize, length: usize) -> Point<L>
+ pub fn sub(mut self, num_cols: usize, length: usize, absolute_indexing: bool) -> Point<L>
where
- L: Copy + Sub<usize, Output = L>,
+ L: Copy + Add<usize, Output = L> + Sub<usize, Output = L>,
{
let line_changes = f32::ceil(length.saturating_sub(self.col.0) as f32 / num_cols as f32);
- self.line = self.line - line_changes as usize;
+ if absolute_indexing {
+ self.line = self.line + line_changes as usize;
+ } else {
+ self.line = self.line - line_changes as usize;
+ }
self.col = Column((num_cols + self.col.0 - length % num_cols) % num_cols);
self
}
#[inline]
#[must_use = "this returns the result of the operation, without modifying the original"]
- pub fn add(mut self, num_cols: usize, length: usize) -> Point<L>
+ pub fn add(mut self, num_cols: usize, length: usize, absolute_indexing: bool) -> Point<L>
where
- L: Copy + Add<usize, Output = L>,
+ L: Copy + Add<usize, Output = L> + Sub<usize, Output = L>,
{
- let line_changes = length.saturating_sub(self.col.0) / num_cols;
- self.line = self.line + line_changes;
+ let line_changes = (length + self.col.0) / num_cols;
+ if absolute_indexing {
+ self.line = self.line - line_changes;
+ } else {
+ self.line = self.line + line_changes;
+ }
self.col = Column((self.col.0 + length) % num_cols);
self
}
diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs
index 5f94634f..6e6dd9c8 100644
--- a/alacritty_terminal/src/selection.rs
+++ b/alacritty_terminal/src/selection.rs
@@ -183,9 +183,9 @@ impl Selection {
}
// Clamp to visible region in grid/normal
- let cols = term.dimensions().col;
- let lines = term.dimensions().line.0 as isize;
- let (start, end) = Selection::grid_clamp(start, end, lines, cols)?;
+ let num_cols = term.dimensions().col;
+ let num_lines = term.dimensions().line.0 as isize;
+ let (start, end) = Selection::grid_clamp(start, end, num_lines, num_cols)?;
let span = match *self {
Selection::Simple { ref region } => {
@@ -214,16 +214,49 @@ impl Selection {
span.map(|mut span| {
let grid = term.grid();
- if span.start.col < cols
- && grid[span.start.line][span.start.col].flags.contains(Flags::WIDE_CHAR_SPACER)
- {
- span.start.col = Column(span.start.col.saturating_sub(1));
+ // Helper for checking if cell at `point` contains `flag`
+ let flag_at = |point: Point<usize>, flag: Flags| -> bool {
+ grid[point.line][point.col].flags.contains(flag)
+ };
+
+ // Include all double-width cells and placeholders at top left of selection
+ if span.start.col < num_cols {
+ // Expand from wide char spacer to wide char
+ if span.start.line + 1 != grid.len() || span.start.col.0 != 0 {
+ let prev = span.start.sub(num_cols.0, 1, true);
+ if flag_at(span.start, Flags::WIDE_CHAR_SPACER)
+ && flag_at(prev, Flags::WIDE_CHAR)
+ {
+ span.start = prev;
+ }
+ }
+
+ // Expand from wide char to wide char spacer for linewrapping
+ if span.start.line + 1 != grid.len() || span.start.col.0 != 0 {
+ let prev = span.start.sub(num_cols.0, 1, true);
+ if (prev.line + 1 != grid.len() || prev.col.0 != 0)
+ && flag_at(prev, Flags::WIDE_CHAR_SPACER)
+ && !flag_at(prev.sub(num_cols.0, 1, true), Flags::WIDE_CHAR)
+ {
+ span.start = prev;
+ }
+ }
}
- if span.end.col.0 < cols.saturating_sub(1)
- && grid[span.end.line][span.end.col].flags.contains(Flags::WIDE_CHAR)
- {
- span.end.col += 1;
+ // Include all double-width cells and placeholders at bottom right of selection
+ if span.end.line != 0 || span.end.col < num_cols {
+ // Expand from wide char spacer for linewrapping to wide char
+ if (span.end.line + 1 != grid.len() || span.end.col.0 != 0)
+ && flag_at(span.end, Flags::WIDE_CHAR_SPACER)
+ && !flag_at(span.end.sub(num_cols.0, 1, true), Flags::WIDE_CHAR)
+ {
+ span.end = span.end.add(num_cols.0, 1, true);
+ }
+
+ // Expand from wide char to wide char spacer
+ if flag_at(span.end, Flags::WIDE_CHAR) {
+ span.end = span.end.add(num_cols.0, 1, true);
+ }
}
span
@@ -445,7 +478,7 @@ mod test {
assert_eq!(selection.to_span(&term(1, 1)).unwrap(), Span {
start: location,
end: location,
- is_block: false,
+ is_block: false
});
}
@@ -463,7 +496,7 @@ mod test {
assert_eq!(selection.to_span(&term(1, 1)).unwrap(), Span {
start: location,
end: location,
- is_block: false,
+ is_block: false
});
}
@@ -586,7 +619,7 @@ mod test {
assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span {
start: Point::new(2, Column(4)),
end: Point::new(0, Column(4)),
- is_block: true,
+ is_block: true
});
}
diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs
index 92725336..d57f57cc 100644
--- a/alacritty_terminal/src/term/cell.rs
+++ b/alacritty_terminal/src/term/cell.rs
@@ -67,23 +67,23 @@ impl GridCell for Cell {
&& self.extra[0] == ' '
&& self.bg == Color::Named(NamedColor::Background)
&& self.fg == Color::Named(NamedColor::Foreground)
- && !self
- .flags
- .intersects(Flags::INVERSE | Flags::UNDERLINE | Flags::STRIKEOUT | Flags::WRAPLINE)
+ && !self.flags.intersects(
+ Flags::INVERSE
+ | Flags::UNDERLINE
+ | Flags::STRIKEOUT
+ | Flags::WRAPLINE
+ | Flags::WIDE_CHAR_SPACER,
+ )
}
#[inline]
- fn is_wrap(&self) -> bool {
- self.flags.contains(Flags::WRAPLINE)
+ fn flags(&self) -> &Flags {
+ &self.flags
}
#[inline]
- fn set_wrap(&mut self, wrap: bool) {
- if wrap {
- self.flags.insert(Flags::WRAPLINE);
- } else {
- self.flags.remove(Flags::WRAPLINE);
- }
+ fn flags_mut(&mut self) -> &mut Flags {
+ &mut self.flags
}
#[inline]
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 4aafde1f..acd14e7e 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -69,7 +69,9 @@ impl<T> Search for Term<T> {
let last_col = self.grid.num_cols() - Column(1);
while let Some(cell) = iter.prev() {
- if self.semantic_escape_chars.contains(cell.c) {
+ if !cell.flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER)
+ && self.semantic_escape_chars.contains(cell.c)
+ {
break;
}
@@ -91,7 +93,9 @@ impl<T> Search for Term<T> {
let last_col = self.grid.num_cols() - 1;
while let Some(cell) = iter.next() {
- if self.semantic_escape_chars.contains(cell.c) {
+ if !cell.flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER)
+ && self.semantic_escape_chars.contains(cell.c)
+ {
break;
}
@@ -1181,6 +1185,42 @@ impl<T> Term<T> {
pub fn clipboard(&mut self) -> &mut Clipboard {
&mut self.clipboard
}
+
+ /// Insert a linebreak at the current cursor position.
+ #[inline]
+ fn wrapline(&mut self)
+ where
+ T: EventListener,
+ {
+ if !self.mode.contains(TermMode::LINE_WRAP) {
+ return;
+ }
+
+ trace!("Wrapping input");
+
+ self.grid[&self.cursor.point].flags.insert(Flags::WRAPLINE);
+
+ if (self.cursor.point.line + 1) >= self.scroll_region.end {
+ self.linefeed();
+ } else {
+ self.cursor.point.line += 1;
+ }
+
+ self.cursor.point.col = Column(0);
+ self.input_needs_wrap = false;
+ }
+
+ /// Write `c` to the cell at the cursor position.
+ #[inline]
+ fn write_at_cursor(&mut self, c: char) -> &mut Cell
+ where
+ T: EventListener,
+ {
+ let cell = &mut self.grid[&self.cursor.point];
+ *cell = self.cursor.template;
+ cell.c = self.cursor.charsets[self.active_charset].map(c);
+ cell
+ }
}
impl<T> TermInfo for Term<T> {
@@ -1195,7 +1235,7 @@ impl<T> TermInfo for Term<T> {
}
}
-impl<T: EventListener> ansi::Handler for Term<T> {
+impl<T: EventListener> Handler for Term<T> {
#[inline]
#[cfg(not(windows))]
fn set_title(&mut self, title: &str) {
@@ -1238,77 +1278,61 @@ impl<T: EventListener> ansi::Handler for Term<T> {
self.scroll_display(Scroll::Bottom);
}
- if self.input_needs_wrap {
- if !self.mode.contains(TermMode::LINE_WRAP) {
- return;
+ // Number of cells the char will occupy
+ let width = match c.width() {
+ Some(width) => width,
+ None => return,
+ };
+
+ // Handle zero-width characters
+ if width == 0 {
+ let mut col = self.cursor.point.col.0.saturating_sub(1);
+ let line = self.cursor.point.line;
+ if self.grid[line][Column(col)].flags.contains(Flags::WIDE_CHAR_SPACER) {
+ col = col.saturating_sub(1);
}
+ self.grid[line][Column(col)].push_extra(c);
+ return;
+ }
- trace!("Wrapping input");
+ // Move cursor to next line
+ if self.input_needs_wrap {
+ self.wrapline();
+ }
- {
- let location = Point { line: self.cursor.point.line, col: self.cursor.point.col };
+ let num_cols = self.grid.num_cols();
- let cell = &mut self.grid[&location];
- cell.flags.insert(Flags::WRAPLINE);
- }
+ // If in insert mode, first shift cells to the right
+ if self.mode.contains(TermMode::INSERT) && self.cursor.point.col + width < num_cols {
+ let line = self.cursor.point.line;
+ let col = self.cursor.point.col;
+ let line = &mut self.grid[line];
- if (self.cursor.point.line + 1) >= self.scroll_region.end {
- self.linefeed();
- } else {
- self.cursor.point.line += 1;
+ let src = line[col..].as_ptr();
+ let dst = line[(col + width)..].as_mut_ptr();
+ unsafe {
+ ptr::copy(src, dst, (num_cols - col - width).0);
}
-
- self.cursor.point.col = Column(0);
- self.input_needs_wrap = false;
}
- // Number of cells the char will occupy
- if let Some(width) = c.width() {
- let num_cols = self.grid.num_cols();
-
- // If in insert mode, first shift cells to the right.
- if self.mode.contains(TermMode::INSERT) && self.cursor.point.col + width < num_cols {
- let line = self.cursor.point.line;
- let col = self.cursor.point.col;
- let line = &mut self.grid[line];
-
- let src = line[col..].as_ptr();
- let dst = line[(col + width)..].as_mut_ptr();
- unsafe {
- // memmove
- ptr::copy(src, dst, (num_cols - col - width).0);
- }
+ if width == 1 {
+ self.write_at_cursor(c);
+ } else {
+ // Insert extra placeholder before wide char if glyph doesn't fit in this row anymore
+ if self.cursor.point.col + 1 >= num_cols {
+ self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER);
+ self.wrapline();
}
- // Handle zero-width characters
- if width == 0 {
- let mut col = self.cursor.point.col.0.saturating_sub(1);
- let line = self.cursor.point.line;
- if self.grid[line][Column(col)].flags.contains(Flags::WIDE_CHAR_SPACER) {
- col = col.saturating_sub(1);
- }
- self.grid[line][Column(col)].push_extra(c);
- return;
- }
+ // Write full width glyph to current cursor cell
+ self.write_at_cursor(c).flags.insert(Flags::WIDE_CHAR);
- let cell = &mut self.grid[&self.cursor.point];
- *cell = self.cursor.template;
- cell.c = self.cursor.charsets[self.active_charset].map(c);
-
- // Handle wide chars
- if width == 2 {
- cell.flags.insert(Flags::WIDE_CHAR);
-
- if self.cursor.point.col + 1 < num_cols {
- self.cursor.point.col += 1;
- let spacer = &mut self.grid[&self.cursor.point];
- *spacer = self.cursor.template;
- spacer.flags.insert(Flags::WIDE_CHAR_SPACER);
- }
- }
+ // Write spacer to cell following the wide glyph
+ self.cursor.point.col += 1;
+ self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER);
}
- if (self.cursor.point.col + 1) < self.grid.num_cols() {
+ if self.cursor.point.col + 1 < num_cols {
self.cursor.point.col += 1;
} else {
self.input_needs_wrap = true;