diff options
Diffstat (limited to 'alacritty_terminal/src/term/mod.rs')
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 456 |
1 files changed, 295 insertions, 161 deletions
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index ac5e56b5..89c3723f 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -31,10 +31,11 @@ use crate::event::{Event, EventListener}; use crate::grid::{ BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll, }; -use crate::index::{self, Column, IndexRange, Line, Point}; +use crate::index::{self, Column, IndexRange, Line, Point, Side}; use crate::selection::{Selection, SelectionRange}; use crate::term::cell::{Cell, Flags, LineLength}; use crate::term::color::Rgb; +use crate::vi_mode::{ViModeCursor, ViMotion}; pub mod cell; pub mod color; @@ -180,7 +181,17 @@ impl<T> Search for Term<T> { } } -/// A key for caching cursor glyphs +/// Cursor storing all information relevant for rendering. +#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize)] +struct RenderableCursor { + text_color: Option<Rgb>, + cursor_color: Option<Rgb>, + key: CursorKey, + point: Point, + rendered: bool, +} + +/// A key for caching cursor glyphs. #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)] pub struct CursorKey { pub style: CursorStyle, @@ -198,10 +209,7 @@ pub struct CursorKey { pub struct RenderableCellsIter<'a, C> { inner: DisplayIter<'a, Cell>, grid: &'a Grid<Cell>, - cursor: &'a Point, - cursor_offset: usize, - cursor_key: Option<CursorKey>, - cursor_style: CursorStyle, + cursor: RenderableCursor, config: &'a Config<C>, colors: &'a color::List, selection: Option<SelectionRange<Line>>, @@ -216,12 +224,10 @@ impl<'a, C> RenderableCellsIter<'a, C> { term: &'b Term<T>, config: &'b Config<C>, selection: Option<SelectionRange>, - mut cursor_style: CursorStyle, ) -> RenderableCellsIter<'b, C> { let grid = &term.grid; let num_cols = grid.num_cols(); - let cursor_offset = grid.num_lines().0 - term.cursor.point.line.0 - 1; let inner = grid.display_iter(); let selection_range = selection.and_then(|span| { @@ -242,29 +248,13 @@ impl<'a, C> RenderableCellsIter<'a, C> { Some(SelectionRange::new(start, end, span.is_block)) }); - // Load cursor glyph - let cursor = &term.cursor.point; - let cursor_visible = term.mode.contains(TermMode::SHOW_CURSOR) && grid.contains(cursor); - let cursor_key = if cursor_visible { - let is_wide = - grid[cursor].flags.contains(Flags::WIDE_CHAR) && (cursor.col + 1) < num_cols; - Some(CursorKey { style: cursor_style, is_wide }) - } else { - // Use hidden cursor so text will not get inverted - cursor_style = CursorStyle::Hidden; - None - }; - RenderableCellsIter { - cursor, - cursor_offset, + cursor: term.renderable_cursor(config), grid, inner, selection: selection_range, config, colors: &term.colors, - cursor_key, - cursor_style, } } @@ -275,6 +265,18 @@ impl<'a, C> RenderableCellsIter<'a, C> { None => return false, }; + // Do not invert block cursor at selection boundaries + if self.cursor.key.style == CursorStyle::Block + && self.cursor.point == point + && (selection.start == point + || selection.end == point + || (selection.is_block + && ((selection.start.line == point.line && selection.end.col == point.col) + || (selection.end.line == point.line && selection.start.col == point.col)))) + { + return false; + } + // Point itself is selected if selection.contains(point.col, point.line) { return true; @@ -442,43 +444,46 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> { #[inline] fn next(&mut self) -> Option<Self::Item> { loop { - if self.cursor_offset == self.inner.offset() && self.inner.column() == self.cursor.col { - let selected = self.is_selected(Point::new(self.cursor.line, self.cursor.col)); + if self.cursor.point.line == self.inner.line() + && self.cursor.point.col == self.inner.column() + { + let selected = self.is_selected(self.cursor.point); + + // Handle cell below cursor + if self.cursor.rendered { + let mut cell = + RenderableCell::new(self.config, self.colors, self.inner.next()?, selected); - // Handle cursor - if let Some(cursor_key) = self.cursor_key.take() { + if self.cursor.key.style == CursorStyle::Block { + mem::swap(&mut cell.bg, &mut cell.fg); + + if let Some(color) = self.cursor.text_color { + cell.fg = color; + } + } + + return Some(cell); + } else { + // Handle cursor + self.cursor.rendered = true; + + let buffer_point = self.grid.visible_to_buffer(self.cursor.point); let cell = Indexed { - inner: self.grid[self.cursor], - column: self.cursor.col, - // Using `self.cursor.line` leads to inconsitent cursor position when - // scrolling. See https://github.com/alacritty/alacritty/issues/2570 for more - // info. - line: self.inner.line(), + inner: self.grid[buffer_point.line][buffer_point.col], + column: self.cursor.point.col, + line: self.cursor.point.line, }; let mut renderable_cell = RenderableCell::new(self.config, self.colors, cell, selected); - renderable_cell.inner = RenderableCellContent::Cursor(cursor_key); + renderable_cell.inner = RenderableCellContent::Cursor(self.cursor.key); - if let Some(color) = self.config.cursor_cursor_color() { - renderable_cell.fg = RenderableCell::compute_bg_rgb(self.colors, color); + if let Some(color) = self.cursor.cursor_color { + renderable_cell.fg = color; } return Some(renderable_cell); - } else { - let mut cell = - RenderableCell::new(self.config, self.colors, self.inner.next()?, selected); - - if self.cursor_style == CursorStyle::Block { - std::mem::swap(&mut cell.bg, &mut cell.fg); - - if let Some(color) = self.config.cursor_text_color() { - cell.fg = color; - } - } - - return Some(cell); } } else { let cell = self.inner.next()?; @@ -497,26 +502,27 @@ pub mod mode { use bitflags::bitflags; bitflags! { - pub struct TermMode: u16 { - const SHOW_CURSOR = 0b0000_0000_0000_0001; - const APP_CURSOR = 0b0000_0000_0000_0010; - const APP_KEYPAD = 0b0000_0000_0000_0100; - const MOUSE_REPORT_CLICK = 0b0000_0000_0000_1000; - const BRACKETED_PASTE = 0b0000_0000_0001_0000; - const SGR_MOUSE = 0b0000_0000_0010_0000; - const MOUSE_MOTION = 0b0000_0000_0100_0000; - const LINE_WRAP = 0b0000_0000_1000_0000; - const LINE_FEED_NEW_LINE = 0b0000_0001_0000_0000; - const ORIGIN = 0b0000_0010_0000_0000; - const INSERT = 0b0000_0100_0000_0000; - const FOCUS_IN_OUT = 0b0000_1000_0000_0000; - const ALT_SCREEN = 0b0001_0000_0000_0000; - const MOUSE_DRAG = 0b0010_0000_0000_0000; - const MOUSE_MODE = 0b0010_0000_0100_1000; - const UTF8_MOUSE = 0b0100_0000_0000_0000; - const ALTERNATE_SCROLL = 0b1000_0000_0000_0000; - const ANY = 0b1111_1111_1111_1111; + pub struct TermMode: u32 { const NONE = 0; + const SHOW_CURSOR = 0b0000_0000_0000_0000_0001; + const APP_CURSOR = 0b0000_0000_0000_0000_0010; + const APP_KEYPAD = 0b0000_0000_0000_0000_0100; + const MOUSE_REPORT_CLICK = 0b0000_0000_0000_0000_1000; + const BRACKETED_PASTE = 0b0000_0000_0000_0001_0000; + const SGR_MOUSE = 0b0000_0000_0000_0010_0000; + const MOUSE_MOTION = 0b0000_0000_0000_0100_0000; + const LINE_WRAP = 0b0000_0000_0000_1000_0000; + const LINE_FEED_NEW_LINE = 0b0000_0000_0001_0000_0000; + const ORIGIN = 0b0000_0000_0010_0000_0000; + const INSERT = 0b0000_0000_0100_0000_0000; + const FOCUS_IN_OUT = 0b0000_0000_1000_0000_0000; + const ALT_SCREEN = 0b0000_0001_0000_0000_0000; + const MOUSE_DRAG = 0b0000_0010_0000_0000_0000; + const MOUSE_MODE = 0b0000_0010_0000_0100_1000; + const UTF8_MOUSE = 0b0000_0100_0000_0000_0000; + const ALTERNATE_SCROLL = 0b0000_1000_0000_0000_0000; + const VI = 0b0001_0000_0000_0000_0000; + const ANY = std::u32::MAX; } } @@ -730,11 +736,69 @@ impl VisualBell { } } +/// Terminal size info. +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)] +pub struct SizeInfo { + /// Terminal window width. + pub width: f32, + + /// Terminal window height. + pub height: f32, + + /// Width of individual cell. + pub cell_width: f32, + + /// Height of individual cell. + pub cell_height: f32, + + /// Horizontal window padding. + pub padding_x: f32, + + /// Horizontal window padding. + pub padding_y: f32, + + /// DPI factor of the current window. + #[serde(default)] + pub dpr: f64, +} + +impl SizeInfo { + #[inline] + pub fn lines(&self) -> Line { + Line(((self.height - 2. * self.padding_y) / self.cell_height) as usize) + } + + #[inline] + pub fn cols(&self) -> Column { + Column(((self.width - 2. * self.padding_x) / self.cell_width) as usize) + } + + /// Check if coordinates are inside the terminal grid. + /// + /// The padding is not counted as part of the grid. + pub fn contains_point(&self, x: usize, y: usize) -> bool { + x < (self.width - self.padding_x) as usize + && x >= self.padding_x as usize + && y < (self.height - self.padding_y) as usize + && y >= self.padding_y as usize + } + + pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point { + let col = Column(x.saturating_sub(self.padding_x as usize) / (self.cell_width as usize)); + let line = Line(y.saturating_sub(self.padding_y as usize) / (self.cell_height as usize)); + + Point { + line: min(line, Line(self.lines().saturating_sub(1))), + col: min(col, Column(self.cols().saturating_sub(1))), + } + } +} + pub struct Term<T> { - /// Terminal focus + /// Terminal focus. pub is_focused: bool, - /// The grid + /// The grid. grid: Grid<Cell>, /// Tracks if the next call to input will need to first handle wrapping. @@ -744,23 +808,25 @@ pub struct Term<T> { /// arrays. Without it we would have to sanitize cursor.col every time we used it. input_needs_wrap: bool, - /// Alternate grid + /// Alternate grid. alt_grid: Grid<Cell>, - /// Alt is active + /// Alt is active. alt: bool, - /// The cursor + /// The cursor. cursor: Cursor, - /// The graphic character set, out of `charsets`, which ASCII is currently - /// being mapped to + /// Cursor location for vi mode. + pub vi_mode_cursor: ViModeCursor, + + /// Index into `charsets`, pointing to what ASCII is currently being mapped to. active_charset: CharsetIndex, - /// Tabstops + /// Tabstops. tabs: TabStops, - /// Mode flags + /// Mode flags. mode: TermMode, /// Scroll region. @@ -772,33 +838,36 @@ pub struct Term<T> { pub visual_bell: VisualBell, - /// Saved cursor from main grid + /// Saved cursor from main grid. cursor_save: Cursor, - /// Saved cursor from alt grid + /// Saved cursor from alt grid. cursor_save_alt: Cursor, semantic_escape_chars: String, - /// Colors used for rendering + /// Colors used for rendering. colors: color::List, - /// Is color in `colors` modified or not + /// Is color in `colors` modified or not. color_modified: [bool; color::COUNT], - /// Original colors from config + /// Original colors from config. original_colors: color::List, - /// Current style of the cursor + /// Current style of the cursor. cursor_style: Option<CursorStyle>, - /// Default style for resetting the cursor + /// Default style for resetting the cursor. default_cursor_style: CursorStyle, + /// Style of the vi mode cursor. + vi_mode_cursor_style: Option<CursorStyle>, + /// Clipboard access coupled to the active window clipboard: Clipboard, - /// Proxy for sending events to the event loop + /// Proxy for sending events to the event loop. event_proxy: T, /// Current title of the window. @@ -815,64 +884,6 @@ pub struct Term<T> { title_stack: Vec<Option<String>>, } -/// Terminal size info -#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)] -pub struct SizeInfo { - /// Terminal window width - pub width: f32, - - /// Terminal window height - pub height: f32, - - /// Width of individual cell - pub cell_width: f32, - - /// Height of individual cell - pub cell_height: f32, - - /// Horizontal window padding - pub padding_x: f32, - - /// Horizontal window padding - pub padding_y: f32, - - /// DPI factor of the current window - #[serde(default)] - pub dpr: f64, -} - -impl SizeInfo { - #[inline] - pub fn lines(&self) -> Line { - Line(((self.height - 2. * self.padding_y) / self.cell_height) as usize) - } - - #[inline] - pub fn cols(&self) -> Column { - Column(((self.width - 2. * self.padding_x) / self.cell_width) as usize) - } - - /// Check if coordinates are inside the terminal grid. - /// - /// The padding is not counted as part of the grid. - pub fn contains_point(&self, x: usize, y: usize) -> bool { - x < (self.width - self.padding_x) as usize - && x >= self.padding_x as usize - && y < (self.height - self.padding_y) as usize - && y >= self.padding_y as usize - } - - pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point { - let col = Column(x.saturating_sub(self.padding_x as usize) / (self.cell_width as usize)); - let line = Line(y.saturating_sub(self.padding_y as usize) / (self.cell_height as usize)); - - Point { - line: min(line, Line(self.lines().saturating_sub(1))), - col: min(col, Column(self.cols().saturating_sub(1))), - } - } -} - impl<T> Term<T> { pub fn selection(&self) -> &Option<Selection> { &self.grid.selection @@ -920,6 +931,7 @@ impl<T> Term<T> { alt: false, active_charset: Default::default(), cursor: Default::default(), + vi_mode_cursor: Default::default(), cursor_save: Default::default(), cursor_save_alt: Default::default(), tabs, @@ -931,6 +943,7 @@ impl<T> Term<T> { semantic_escape_chars: config.selection.semantic_escape_chars().to_owned(), cursor_style: None, default_cursor_style: config.cursor.style, + vi_mode_cursor_style: config.cursor.vi_mode_style, dynamic_title: config.dynamic_title(), clipboard, event_proxy, @@ -959,6 +972,7 @@ impl<T> Term<T> { self.mode.remove(TermMode::ALTERNATE_SCROLL); } self.default_cursor_style = config.cursor.style; + self.vi_mode_cursor_style = config.cursor.vi_mode_style; self.default_title = config.window.title.clone(); self.dynamic_title = config.dynamic_title(); @@ -1105,13 +1119,7 @@ impl<T> Term<T> { pub fn renderable_cells<'b, C>(&'b self, config: &'b Config<C>) -> RenderableCellsIter<'_, C> { let selection = self.grid.selection.as_ref().and_then(|s| s.to_range(self)); - let cursor = if self.is_focused || !config.cursor.unfocused_hollow() { - self.cursor_style.unwrap_or(self.default_cursor_style) - } else { - CursorStyle::HollowBlock - }; - - RenderableCellsIter::new(&self, config, selection, cursor) + RenderableCellsIter::new(&self, config, selection) } /// Resize terminal to new dimensions @@ -1129,12 +1137,12 @@ impl<T> Term<T> { self.grid.selection = None; self.alt_grid.selection = None; - // Should not allow less than 1 col, causes all sorts of checks to be required. + // Should not allow less than 2 cols, causes all sorts of checks to be required. if num_cols <= Column(1) { num_cols = Column(2); } - // Should not allow less than 1 line, causes all sorts of checks to be required. + // Should not allow less than 2 lines, causes all sorts of checks to be required. if num_lines <= Line(1) { num_lines = Line(2); } @@ -1178,6 +1186,8 @@ impl<T> Term<T> { self.cursor_save.point.line = min(self.cursor_save.point.line, num_lines - 1); self.cursor_save_alt.point.col = min(self.cursor_save_alt.point.col, num_cols - 1); self.cursor_save_alt.point.line = min(self.cursor_save_alt.point.line, num_lines - 1); + self.vi_mode_cursor.point.col = min(self.vi_mode_cursor.point.col, num_cols - 1); + self.vi_mode_cursor.point.line = min(self.vi_mode_cursor.point.line, num_lines - 1); // Recreate tabs list self.tabs.resize(self.grid.num_cols()); @@ -1200,7 +1210,7 @@ impl<T> Term<T> { } self.alt = !self.alt; - std::mem::swap(&mut self.grid, &mut self.alt_grid); + mem::swap(&mut self.grid, &mut self.alt_grid); } /// Scroll screen down @@ -1258,10 +1268,58 @@ impl<T> Term<T> { self.event_proxy.send_event(Event::Exit); } + #[inline] pub fn clipboard(&mut self) -> &mut Clipboard { &mut self.clipboard } + /// Toggle the vi mode. + #[inline] + pub fn toggle_vi_mode(&mut self) { + self.mode ^= TermMode::VI; + self.grid.selection = None; + + // Reset vi mode cursor position to match primary cursor + if self.mode.contains(TermMode::VI) { + let line = min(self.cursor.point.line + self.grid.display_offset(), self.lines() - 1); + self.vi_mode_cursor = ViModeCursor::new(Point::new(line, self.cursor.point.col)); + } + + self.dirty = true; + } + + /// Move vi mode cursor. + #[inline] + pub fn vi_motion(&mut self, motion: ViMotion) + where + T: EventListener, + { + // Require vi mode to be active + if !self.mode.contains(TermMode::VI) { + return; + } + + // Move cursor + self.vi_mode_cursor = self.vi_mode_cursor.motion(self, motion); + + // Update selection if one is active + let viewport_point = self.visible_to_buffer(self.vi_mode_cursor.point); + if let Some(selection) = &mut self.grid.selection { + // Do not extend empty selections started by single mouse click + if !selection.is_empty() { + selection.update(viewport_point, Side::Left); + selection.include_all(); + } + } + + self.dirty = true; + } + + #[inline] + pub fn semantic_escape_chars(&self) -> &str { + &self.semantic_escape_chars + } + /// Insert a linebreak at the current cursor position. #[inline] fn wrapline(&mut self) @@ -1297,6 +1355,65 @@ impl<T> Term<T> { cell.c = self.cursor.charsets[self.active_charset].map(c); cell } + + /// Get rendering information about the active cursor. + fn renderable_cursor<C>(&self, config: &Config<C>) -> RenderableCursor { + let vi_mode = self.mode.contains(TermMode::VI); + + // Cursor position + let mut point = if vi_mode { + self.vi_mode_cursor.point + } else { + let mut point = self.cursor.point; + point.line += self.grid.display_offset(); + point + }; + + // Cursor shape + let hidden = !self.mode.contains(TermMode::SHOW_CURSOR) || point.line >= self.lines(); + let cursor_style = if hidden && !vi_mode { + point.line = Line(0); + CursorStyle::Hidden + } else if !self.is_focused && config.cursor.unfocused_hollow() { + CursorStyle::HollowBlock + } else { + let cursor_style = self.cursor_style.unwrap_or(self.default_cursor_style); + + if vi_mode { + self.vi_mode_cursor_style.unwrap_or(cursor_style) + } else { + cursor_style + } + }; + + // Cursor colors + let (text_color, cursor_color) = if vi_mode { + (config.vi_mode_cursor_text_color(), config.vi_mode_cursor_cursor_color()) + } else { + let cursor_cursor_color = config.cursor_cursor_color().map(|c| self.colors[c]); + (config.cursor_text_color(), cursor_cursor_color) + }; + + // Expand across wide cell when inside wide char or spacer + let buffer_point = self.visible_to_buffer(point); + let cell = self.grid[buffer_point.line][buffer_point.col]; + let is_wide = if cell.flags.contains(Flags::WIDE_CHAR_SPACER) + && self.grid[buffer_point.line][buffer_point.col - 1].flags.contains(Flags::WIDE_CHAR) + { + point.col -= 1; + true + } else { + cell.flags.contains(Flags::WIDE_CHAR) + }; + + RenderableCursor { + text_color, + cursor_color, + key: CursorKey { style: cursor_style, is_wide }, + point, + rendered: false, + } + } } impl<T> TermInfo for Term<T> { @@ -2184,7 +2301,7 @@ mod tests { use crate::event::{Event, EventListener}; use crate::grid::{Grid, Scroll}; use crate::index::{Column, Line, Point, Side}; - use crate::selection::Selection; + use crate::selection::{Selection, SelectionType}; use crate::term::cell::{Cell, Flags}; use crate::term::{SizeInfo, Term}; @@ -2222,17 +2339,29 @@ mod tests { mem::swap(&mut term.semantic_escape_chars, &mut escape_chars); { - *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(1) })); + *term.selection_mut() = Some(Selection::new( + SelectionType::Semantic, + Point { line: 2, col: Column(1) }, + Side::Left, + )); assert_eq!(term.selection_to_string(), Some(String::from("aa"))); } { - *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(4) })); + *term.selection_mut() = Some(Selection::new( + SelectionType::Semantic, + Point { line: 2, col: Column(4) }, + Side::Left, + )); assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); } { - *term.selection_mut() = Some(Selection::semantic(Point { line: 1, col: Column(1) })); + *term.selection_mut() = Some(Selection::new( + SelectionType::Semantic, + Point { line: 1, col: Column(1) }, + Side::Left, + )); assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); } } @@ -2258,7 +2387,11 @@ mod tests { mem::swap(&mut term.grid, &mut grid); - *term.selection_mut() = Some(Selection::lines(Point { line: 0, col: Column(3) })); + *term.selection_mut() = Some(Selection::new( + SelectionType::Lines, + Point { line: 0, col: Column(3) }, + Side::Left, + )); assert_eq!(term.selection_to_string(), Some(String::from("\"aa\"a\n"))); } @@ -2285,7 +2418,8 @@ mod tests { mem::swap(&mut term.grid, &mut grid); - let mut selection = Selection::simple(Point { line: 2, col: Column(0) }, Side::Left); + let mut selection = + Selection::new(SelectionType::Simple, Point { line: 2, col: Column(0) }, Side::Left); selection.update(Point { line: 0, col: Column(2) }, Side::Right); *term.selection_mut() = Some(selection); assert_eq!(term.selection_to_string(), Some("aaa\n\naaa\n".into())); |