diff options
author | Joe Wilm <joe@jwilm.com> | 2017-05-28 09:38:10 -0700 |
---|---|---|
committer | Joe Wilm <jwilm@users.noreply.github.com> | 2017-06-11 13:21:13 -0700 |
commit | 529ac47fc81f12e953568826b87ef75568eaa83b (patch) | |
tree | 450024deb1f8d4340142d46a11ae9749cf786b77 | |
parent | 9825adf695a472be5d3316354f707c7d9d2d2730 (diff) | |
download | alacritty-529ac47fc81f12e953568826b87ef75568eaa83b.tar.gz alacritty-529ac47fc81f12e953568826b87ef75568eaa83b.zip |
Add support for Beam, Underline cursors
Notable about this implementation is it takes a different approach for
managing cursor cells that previously. The terminal Grid is now
borrowed *immutably*. Instead of mutating Cells in the Grid, a list is
managed within the RenderableCellsIter. The cell at the cursor location
is skipped over, and instead cells are popped off a list of cursor
cells.
It would be good in the future to share some more code between the
different cursor style implementations for populating the cursor cells
list.
Supercedes #349.
-rw-r--r-- | Cargo.lock | 26 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/ansi.rs | 26 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/term/cell.rs | 11 | ||||
-rw-r--r-- | src/term/mod.rs | 310 |
6 files changed, 272 insertions, 106 deletions
@@ -2,6 +2,7 @@ name = "alacritty" version = "0.1.0" dependencies = [ + "arraydeque 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "cgmath 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -39,6 +40,15 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "arraydeque" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "atty" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -625,6 +635,14 @@ dependencies = [ ] [[package]] +name = "nodrop" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "nom" version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -741,6 +759,11 @@ dependencies = [ ] [[package]] +name = "odds" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "osmesa-sys" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1223,6 +1246,7 @@ dependencies = [ [metadata] "checksum android_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d8289e9637439939cc92b1995b0972117905be88bc28116c86b64d6e589bcd38" "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" +"checksum arraydeque 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "96e774cadb24c2245225280c6799793f9802b918a58a79615e9490607489a717" "checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" "checksum bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "32866f4d103c4e438b1db1158aa1b1a80ee078e5d77a59a2f906fd62a577389c" "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" @@ -1287,6 +1311,7 @@ dependencies = [ "checksum net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "bc01404e7568680f1259aa5729539f221cb1e6d047a0d9053cab4be8a73b5d67" "checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" "checksum nix 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0d95c5fa8b641c10ad0b8887454ebaafa3c92b5cd5350f8fc693adafd178e7b" +"checksum nodrop 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "52cd74cd09beba596430cc6e3091b74007169a56246e1262f0ba451ea95117b2" "checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" "checksum notify 2.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4e0e7eec936337952c4228b023007528a33b2fa039d96c2e8f32d764221a9c07" "checksum num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "98b15ba84e910ea7a1973bccd3df7b31ae282bf9d8bd2897779950c9b8303d40" @@ -1299,6 +1324,7 @@ dependencies = [ "checksum objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "877f30f37acef6749b1841cceab289707f211aecfc756553cd63976190e6cc2e" "checksum objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" "checksum objc_id 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4730aa1c64d722db45f7ccc4113a3e2c465d018de6db4d3e7dfe031e8c8a297" +"checksum odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "c3df9b730298cea3a1c3faa90b7e2f9df3a9c400d0936d6015e6165734eefcba" "checksum osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" "checksum owning_ref 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9d52571ddcb42e9c900c901a18d8d67e393df723fcd51dd59c5b1a85d0acb6cc" "checksum parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fa12d706797d42551663426a45e2db2e0364bd1dbf6aeada87e89c5f981f43e9" @@ -32,7 +32,7 @@ log = "0.3" clap = "2.20" fnv = "1.0.5" unicode-width = "0.1.4" - +arraydeque = "0.2" clippy = { version = "0.0.104", optional = true } [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))'.dependencies] @@ -44,6 +44,7 @@ default = ["err-println"] live-shader-reload = [] err-println = [] nightly = [] +bench = [] [build-dependencies] gl_generator = "0.5" diff --git a/src/ansi.rs b/src/ansi.rs index cca8fe28..5114f7df 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -102,6 +102,9 @@ pub trait Handler { /// OSC to set window title fn set_title(&mut self, &str) {} + /// Set the cursor style + fn set_cursor_style(&mut self, _: CursorStyle) {} + /// A character to be displayed fn input(&mut self, _c: char) {} @@ -261,6 +264,19 @@ pub trait Handler { fn dectest(&mut self) {} } +/// Describes shape of cursor +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub enum CursorStyle { + /// Cursor is a block like `▒` + Block, + + /// Cursor is an underscore like `_` + Underline, + + /// Cursor is a vertical bar `⎸` + Beam, +} + /// Terminal modes #[derive(Debug, Eq, PartialEq)] pub enum Mode { @@ -895,6 +911,16 @@ impl<'a, H, W> vte::Perform for Performer<'a, H, W> }, 's' => handler.save_cursor_position(), 'u' => handler.restore_cursor_position(), + 'q' => { + let style = match arg_or_default!(idx: 0, default: 0) { + 0 ... 2 => CursorStyle::Block, + 3 | 4 => CursorStyle::Underline, + 5 | 6 => CursorStyle::Beam, + _ => unhandled!() + }; + + handler.set_cursor_style(style); + } _ => unhandled!(), } } @@ -20,6 +20,7 @@ #![cfg_attr(feature = "clippy", deny(if_not_else))] #![cfg_attr(feature = "clippy", deny(wrong_pub_self_convention))] #![cfg_attr(feature = "nightly", feature(core_intrinsics))] +#![cfg_attr(all(test, feature = "bench"), feature(test))] #[macro_use] extern crate bitflags; #[macro_use] extern crate clap; @@ -30,6 +31,7 @@ #[cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))] extern crate x11_dl; +extern crate arraydeque; extern crate cgmath; extern crate copypasta; extern crate errno; diff --git a/src/term/cell.rs b/src/term/cell.rs index cb966156..98d46bf2 100644 --- a/src/term/cell.rs +++ b/src/term/cell.rs @@ -86,6 +86,17 @@ impl Cell { } } + /// Get foreground and background colors adjusted for INVERSE flag + /// + /// First color is the foreground, second color is the background. + pub fn colors(&self, force_invert: bool) -> (&Color, &Color) { + if self.flags.contains(INVERSE) || force_invert { + (&self.bg, &self.fg) + } else { + (&self.fg, &self.bg) + } + } + #[inline] pub fn is_empty(&self) -> bool { self.c == ' ' && diff --git a/src/term/mod.rs b/src/term/mod.rs index 1316ac20..ab48aead 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -13,16 +13,16 @@ // limitations under the License. // //! Exports the `Term` type which is a high-level API for the Grid -use std::mem; use std::ops::{Range, Index, IndexMut}; use std::ptr; use std::cmp::{min, max}; use std::io; use std::time::{Duration, Instant}; +use arraydeque::ArrayDeque; use unicode_width::UnicodeWidthChar; -use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset}; +use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset, CursorStyle}; use grid::{BidirectionalIterator, Grid, ClearRegion, ToRange, Indexed}; use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive, Side}; use selection::{Span, Selection}; @@ -43,15 +43,16 @@ use self::cell::LineLength; /// This manages the cursor during a render. The cursor location is inverted to /// draw it, and reverted after drawing to maintain state. pub struct RenderableCellsIter<'a> { - grid: &'a mut Grid<Cell>, + grid: &'a Grid<Cell>, cursor: &'a Point, + cursor_index: index::Linear, mode: TermMode, line: Line, column: Column, config: &'a Config, colors: &'a color::List, selection: Option<RangeInclusive<index::Linear>>, - cursor_original: (Option<Indexed<Cell>>, Option<Indexed<Cell>>), + cursor_cells: ArrayDeque<[Indexed<Cell>; 3]>, } impl<'a> RenderableCellsIter<'a> { @@ -60,72 +61,156 @@ impl<'a> RenderableCellsIter<'a> { /// The cursor and terminal mode are required for properly displaying the /// cursor. fn new<'b>( - grid: &'b mut Grid<Cell>, + grid: &'b Grid<Cell>, cursor: &'b Point, colors: &'b color::List, mode: TermMode, config: &'b Config, selection: &Selection, + cursor_style: CursorStyle, ) -> RenderableCellsIter<'b> { let selection = selection.span() .map(|span| span.to_range(grid.num_cols())); + let cursor_index = Linear(cursor.line.0 * grid.num_cols().0 + cursor.col.0); + RenderableCellsIter { grid: grid, cursor: cursor, + cursor_index: cursor_index, mode: mode, line: Line(0), column: Column(0), selection: selection, config: config, colors: colors, - cursor_original: (None, None), - }.initialize() + cursor_cells: ArrayDeque::new(), + }.initialize(cursor_style) } - fn initialize(mut self) -> Self { - if self.cursor_is_visible() { - self.cursor_original.0 = Some(Indexed { - line: self.cursor.line, - column: self.cursor.col, - inner: self.grid[self.cursor] + fn populate_block_cursor(&mut self) { + let (text_color, cursor_color) = if self.config.custom_cursor_colors() { + ( + Color::Named(NamedColor::CursorText), + Color::Named(NamedColor::Cursor) + ) + } else { + // Swap fg, bg + let cell = &self.grid[self.cursor]; + (cell.bg, cell.fg) + }; + + let mut cell_under_cursor = self.grid[self.cursor]; + cell_under_cursor.fg = text_color; + cell_under_cursor.bg = cursor_color; + + self.cursor_cells.push_back(Indexed { + line: self.cursor.line, + column: self.cursor.col, + inner: cell_under_cursor, + }); + + if self.is_wide_cursor(&cell_under_cursor) { + cell_under_cursor.c = ' '; + self.cursor_cells.push_back(Indexed { + line: self.cursor.line, + column: self.cursor.col + 1, + inner: cell_under_cursor, }); - let mut spacer = false; - let mut location = *self.cursor; + } + } - if self.grid[self.cursor].flags.contains(cell::WIDE_CHAR) && - self.cursor.col + 1 < self.grid.num_cols() - { - spacer = true; - location.col += 1; - self.cursor_original.1 = Some(Indexed { - line: location.line, - column: location.col, - inner: self.grid[&location] - }); - } + #[inline] + fn is_wide_cursor(&self, cell: &Cell) -> bool { + cell.flags.contains(cell::WIDE_CHAR) && (self.cursor.col + 1) < self.grid.num_cols() + } + + fn populate_beam_cursor(&mut self) { + let mut cursor_cell = self.grid[self.cursor]; + self.cursor_cells.push_back(Indexed { + line: self.cursor.line, + column: self.cursor.col, + inner: cursor_cell, + }); + + let cursor_color = self.text_cursor_color(&cursor_cell); + cursor_cell.c = '▎'; + cursor_cell.fg = cursor_color; + self.cursor_cells.push_back(Indexed { + line: self.cursor.line, + column: self.cursor.col, + inner: cursor_cell, + }); + + if self.is_wide_cursor(&cursor_cell) { + cursor_cell.c = ' '; + self.cursor_cells.push_back(Indexed { + line: self.cursor.line, + column: self.cursor.col + 1, + inner: cursor_cell, + }); + } + } - if self.config.custom_cursor_colors() { - { - let cell = &mut self.grid[self.cursor]; - cell.fg = Color::Named(NamedColor::CursorText); - cell.bg = Color::Named(NamedColor::Cursor); - } - if spacer { - let cell = &mut self.grid[&location]; - cell.fg = Color::Named(NamedColor::CursorText); - cell.bg = Color::Named(NamedColor::Cursor); - } - } else { - { - let cell = &mut self.grid[self.cursor]; - mem::swap(&mut cell.fg, &mut cell.bg); - } - if spacer { - let cell = &mut self.grid[&location]; - mem::swap(&mut cell.fg, &mut cell.bg); + fn populate_underline_cursor(&mut self) { + let mut cursor_cell = self.grid[self.cursor]; + self.cursor_cells.push_back(Indexed { + line: self.cursor.line, + column: self.cursor.col, + inner: cursor_cell, + }); + + let cursor_color = self.text_cursor_color(&cursor_cell); + cursor_cell.c = '▁'; + cursor_cell.fg = cursor_color; + self.cursor_cells.push_back(Indexed { + line: self.cursor.line, + column: self.cursor.col, + inner: cursor_cell, + }); + + if self.is_wide_cursor(&cursor_cell) { + self.cursor_cells.push_back(Indexed { + line: self.cursor.line, + column: self.cursor.col + 1, + inner: cursor_cell, + }); + } + } + + fn text_cursor_color(&self, cell: &Cell) -> Color { + if self.config.custom_cursor_colors() { + Color::Named(NamedColor::Cursor) + } else { + // Cursor is same color as text + cell.fg + } + } + + /// Populates list of cursor cells with the original cell + fn populate_no_cursor(&mut self) { + self.cursor_cells.push_back(Indexed { + line: self.cursor.line, + column: self.cursor.col, + inner: self.grid[self.cursor], + }); + } + + fn initialize(mut self, cursor_style: CursorStyle) -> Self { + if self.cursor_is_visible() { + match cursor_style { + CursorStyle::Block => { + self.populate_block_cursor(); + }, + CursorStyle::Beam => { + self.populate_beam_cursor(); + }, + CursorStyle::Underline => { + self.populate_underline_cursor(); } } + } else { + self.populate_no_cursor(); } self } @@ -135,22 +220,39 @@ impl<'a> RenderableCellsIter<'a> { fn cursor_is_visible(&self) -> bool { self.mode.contains(mode::SHOW_CURSOR) && self.grid.contains(self.cursor) } -} -impl<'a> Drop for RenderableCellsIter<'a> { - /// Resets temporary render state on the grid - fn drop(&mut self) { - if self.cursor_is_visible() { - if let Some(ref original) = self.cursor_original.0 { - self.grid[self.cursor] = original.inner; - } - if let Some(ref original) = self.cursor_original.1 { - let mut location = *self.cursor; - location.col += 1; - self.grid[&location] = original.inner; + fn compute_fg_rgb(&self, fg: &Color, cell: &Cell) -> Rgb { + match *fg { + Color::Spec(rgb) => rgb, + Color::Named(ansi) => { + if self.config.draw_bold_text_with_bright_colors() && cell.bold() { + self.colors[ansi.to_bright()] + } else { + self.colors[ansi] + } + }, + Color::Indexed(idx) => { + let idx = if self.config.draw_bold_text_with_bright_colors() + && cell.bold() + && idx < 8 + { + idx + 8 + } else { + idx + }; + + self.colors[idx] } } } + + fn compute_bg_rgb(&self, bg: &Color) -> Rgb { + match *bg { + Color::Spec(rgb) => rgb, + Color::Named(ansi) => self.colors[ansi], + Color::Indexed(idx) => self.colors[idx], + } + } } pub struct RenderableCell { @@ -180,56 +282,39 @@ impl<'a> Iterator for RenderableCellsIter<'a> { let index = Linear(line.0 * self.grid.num_cols().0 + column.0); - // Update state for next iteration - self.column += 1; + let (cell, selected) = if index == self.cursor_index { + // Cursor cell + let cell = self.cursor_cells.pop_front().unwrap(); - let selected = self.selection.as_ref() - .map(|range| range.contains_(index)) - .unwrap_or(false); - - // Skip empty cells - if cell.is_empty() && !selected { - continue; - } - - // fg, bg are dependent on INVERSE flag - let invert = cell.flags.contains(cell::INVERSE) || selected; - let (fg, bg) = if invert { - (&cell.bg, &cell.fg) + // Since there may be multiple cursor cells (for a wide + // char), only update iteration position after all cursor + // cells have been drawn. + if self.cursor_cells.is_empty() { + self.line = cell.line; + self.column = cell.column + 1; + } + (cell.inner, false) } else { - (&cell.fg, &cell.bg) - }; + // Normal cell + self.column += 1; - // Get Rgb value for foreground - let fg = match *fg { - Color::Spec(rgb) => rgb, - Color::Named(ansi) => { - if self.config.draw_bold_text_with_bright_colors() && cell.bold() { - self.colors[ansi.to_bright()] - } else { - self.colors[ansi] - } - }, - Color::Indexed(idx) => { - let idx = if self.config.draw_bold_text_with_bright_colors() - && cell.bold() - && idx < 8 - { - idx + 8 - } else { - idx - }; - - self.colors[idx] + let selected = self.selection.as_ref() + .map(|range| range.contains_(index)) + .unwrap_or(false); + + // Skip empty cells + if cell.is_empty() && !selected { + continue; } + (*cell, selected) }; - // Get Rgb value for background - let bg = match *bg { - Color::Spec(rgb) => rgb, - Color::Named(ansi) => self.colors[ansi], - Color::Indexed(idx) => self.colors[idx], - }; + // `Color` fg, bg + let (fg, bg) = cell.colors(selected); + + // `Rgb` fg, bg + let fg = self.compute_fg_rgb(fg, &cell); + let bg = self.compute_bg_rgb(bg); return Some(RenderableCell { line: line, @@ -247,6 +332,7 @@ impl<'a> Iterator for RenderableCellsIter<'a> { None } + } pub mod mode { @@ -536,6 +622,8 @@ pub struct Term { /// Colors used for rendering colors: color::List, + + cursor_style: CursorStyle, } /// Terminal size info @@ -636,6 +724,7 @@ impl Term { empty_cell: template, colors: color::List::from(config.colors()), semantic_escape_chars: config.selection().semantic_escape_chars.clone(), + cursor_style: CursorStyle::Block, } } @@ -855,17 +944,18 @@ impl Term { /// background color. Cells with an alternate background color are /// considered renderable as are cells with any text content. pub fn renderable_cells<'b>( - &'b mut self, + &'b self, config: &'b Config, selection: &'b Selection ) -> RenderableCellsIter { RenderableCellsIter::new( - &mut self.grid, + &self.grid, &self.cursor.point, &self.colors, self.mode, config, selection, + self.cursor_style ) } @@ -1704,6 +1794,12 @@ impl ansi::Handler for Term { trace!("Activate {:?} character set", index); self.active_charset = index; } + + #[inline] + fn set_cursor_style(&mut self, style: CursorStyle) { + trace!("set_cursor_style {:?}", style); + self.cursor_style = style; + } } #[cfg(test)] @@ -1842,6 +1938,7 @@ mod benches { use grid::Grid; use selection::Selection; + use config::Config; use super::{SizeInfo, Term}; use super::cell::Cell; @@ -1878,11 +1975,14 @@ mod benches { let mut grid: Grid<Cell> = json::from_str(&serialized_grid).unwrap(); let size: SizeInfo = json::from_str(&serialized_size).unwrap(); - let mut terminal = Term::new(&Default::default(), size); + let config = Config::default(); + let selection = Selection::Empty; + + let mut terminal = Term::new(&config, size); mem::swap(&mut terminal.grid, &mut grid); b.iter(|| { - let iter = terminal.renderable_cells(&Selection::Empty); + let iter = terminal.renderable_cells(&config, &selection); for cell in iter { test::black_box(cell); } |