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 ::std::cmp::PartialEq for Grid { 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 { raw: Storage, /// 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 Grid { - pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid { + pub fn new(lines: Line, cols: Column, scrollback: usize, template: T) -> Grid { let raw = Storage::with_capacity(lines, Row::new(cols, &template)); Grid { raw, @@ -213,8 +214,8 @@ impl Grid { 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 Grid { /// 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 Grid { 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| -> 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> = Vec::with_capacity(self.raw.len()); + let mut reversed: Vec> = 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); + } + + // 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); } - // 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 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); - } - - // Add padding lines - new_raw.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() { - if row.len() < cols.0 { - row.grow(cols, template); - } reversed.push(row); } - self.raw.replace_inner(reversed); + // Add padding lines + reversed.append(&mut vec![Row::new(cols, template); new_empty_lines]); + + // Fill remaining cells and reverse iterator + 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); + } + new_raw.push(row); + } + + 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; + }, + }; - 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); + // 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; } - if Some(true) == wrapped_cells.last().map(|c| c.is_wrap() && i >= 1) - && wrapped_cells.len() < cols.0 + // 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); + + // Set line as wrapped if cells got removed + if let Some(cell) = new_raw.last_mut().and_then(|r| r.last_mut()) { + cell.flags_mut().insert(Flags::WRAPLINE); + } + + 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 Grid { } // 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 Grid { /// 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 Grid { /// # 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, - positions: index::Line, - template: &T, - ) { + pub fn scroll_down(&mut self, region: &Range, 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 Grid { /// 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, positions: index::Line, template: &T) { + pub fn scroll_up(&mut self, region: &Range, 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 Grid { #[allow(clippy::len_without_is_empty)] impl Grid { #[inline] - pub fn num_lines(&self) -> index::Line { + pub fn num_lines(&self) -> Line { self.lines } @@ -579,7 +635,7 @@ impl Grid { } #[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 Index for Grid { +impl Index for Grid { type Output = Row; #[inline] - fn index(&self, index: index::Line) -> &Row { + fn index(&self, index: Line) -> &Row { &self.raw[index] } } @@ -712,9 +768,9 @@ impl Index for Grid { } } -impl IndexMut for Grid { +impl IndexMut for Grid { #[inline] - fn index_mut(&mut self, index: index::Line) -> &mut Row { + fn index_mut(&mut self, index: Line) -> &mut Row { &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 Point { #[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 + pub fn sub(mut self, num_cols: usize, length: usize, absolute_indexing: bool) -> Point where - L: Copy + Sub, + L: Copy + Add + Sub, { 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 + pub fn add(mut self, num_cols: usize, length: usize, absolute_indexing: bool) -> Point where - L: Copy + Add, + L: Copy + Add + Sub, { - 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, 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 Search for Term { 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 Search for Term { 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 Term { 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 TermInfo for Term { @@ -1195,7 +1235,7 @@ impl TermInfo for Term { } } -impl ansi::Handler for Term { +impl Handler for Term { #[inline] #[cfg(not(windows))] fn set_title(&mut self, title: &str) { @@ -1238,77 +1278,61 @@ impl ansi::Handler for Term { self.scroll_display(Scroll::Bottom); } - if self.input_needs_wrap { - if !self.mode.contains(TermMode::LINE_WRAP) { - return; - } - - trace!("Wrapping input"); - - { - let location = Point { line: self.cursor.point.line, col: self.cursor.point.col }; - - let cell = &mut self.grid[&location]; - cell.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; - } - // Number of cells the char will occupy - if let Some(width) = c.width() { - let num_cols = self.grid.num_cols(); + let width = match c.width() { + Some(width) => width, + None => return, + }; - // 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); - } + // 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; + } - // 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; - } + // Move cursor to next line + if self.input_needs_wrap { + self.wrapline(); + } - let cell = &mut self.grid[&self.cursor.point]; - *cell = self.cursor.template; - cell.c = self.cursor.charsets[self.active_charset].map(c); + let num_cols = self.grid.num_cols(); - // Handle wide chars - if width == 2 { - cell.flags.insert(Flags::WIDE_CHAR); + // 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.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); - } + let src = line[col..].as_ptr(); + let dst = line[(col + width)..].as_mut_ptr(); + unsafe { + ptr::copy(src, dst, (num_cols - col - width).0); } } - if (self.cursor.point.col + 1) < self.grid.num_cols() { + 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(); + } + + // Write full width glyph to current cursor cell + self.write_at_cursor(c).flags.insert(Flags::WIDE_CHAR); + + // 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 < num_cols { self.cursor.point.col += 1; } else { self.input_needs_wrap = true;