diff options
author | Christian Duerr <contact@christianduerr.com> | 2020-01-09 23:06:41 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-09 23:06:41 +0000 |
commit | 3fb631b91caec163707858bd1d435015e6e6cb18 (patch) | |
tree | cfc575f224622b7c83a21281c2e23a7f8af83364 | |
parent | 5651c3f7114dff611e616865ad02e682779979d9 (diff) | |
download | alacritty-3fb631b91caec163707858bd1d435015e6e6cb18.tar.gz alacritty-3fb631b91caec163707858bd1d435015e6e6cb18.zip |
Fix cut off full width glyphs in last column
This resolves the issue with full width glyphs getting rendered in the
last column. Since they need at least two glyphs, it is not possible to
properly render them in the last column.
Instead of rendering half of the glyph in the last column, with the
other half cut off, an additional spacer is now inserted before the wide
glyph. This means that the specific glyph in question is then three
cells wide.
Fixes #2385.
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | alacritty/src/url.rs | 29 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/mod.rs | 248 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/storage.rs | 9 | ||||
-rw-r--r-- | alacritty_terminal/src/grid/tests.rs | 8 | ||||
-rw-r--r-- | alacritty_terminal/src/index.rs | 22 | ||||
-rw-r--r-- | alacritty_terminal/src/selection.rs | 61 | ||||
-rw-r--r-- | alacritty_terminal/src/term/cell.rs | 22 | ||||
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 146 |
9 files changed, 339 insertions, 208 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fed829e..a7387fcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Incorrect default config path in `--help` on Windows and macOS +- Semantic selection stopping at full-width glyphs +- Full-width glyphs cut off in last column ## 0.4.1 diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs index e510aa2b..b32a6812 100644 --- a/alacritty/src/url.rs +++ b/alacritty/src/url.rs @@ -42,7 +42,7 @@ impl Url { } pub fn end(&self) -> Point { - self.lines[self.lines.len() - 1].end.sub(self.num_cols, self.end_offset as usize) + self.lines[self.lines.len() - 1].end.sub(self.num_cols, self.end_offset as usize, false) } } @@ -73,11 +73,6 @@ impl Urls { // Update tracked URLs pub fn update(&mut self, num_cols: usize, cell: RenderableCell) { - // Ignore double-width spacers to prevent reset - if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { - return; - } - // Convert cell to character let c = match cell.inner { RenderableCellContent::Chars(chars) => chars[0], @@ -85,20 +80,28 @@ impl Urls { }; let point: Point = cell.into(); - let mut end = point; + let end = point; // Reset URL when empty cells have been skipped - if point != Point::default() && Some(point.sub(num_cols, 1)) != self.last_point { + if point != Point::default() && Some(point.sub(num_cols, 1, false)) != self.last_point { self.reset(); } - // Extend by one cell for double-width characters - if cell.flags.contains(Flags::WIDE_CHAR) { - end.col += 1; - } - self.last_point = Some(end); + // Extend current state if a wide char spacer is encountered + if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { + if let UrlLocation::Url(_, mut end_offset) = self.state { + if end_offset != 0 { + end_offset += 1; + } + + self.extend_url(point, end, cell.fg, end_offset); + } + + return; + } + // Advance parser let last_state = mem::replace(&mut self.state, self.locator.advance(c)); match (self.state, last_state) { 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; |