aboutsummaryrefslogtreecommitdiff
path: root/src/term/mod.rs
diff options
context:
space:
mode:
authorJoe Wilm <joe@jwilm.com>2017-05-28 09:38:10 -0700
committerJoe Wilm <jwilm@users.noreply.github.com>2017-06-11 13:21:13 -0700
commit529ac47fc81f12e953568826b87ef75568eaa83b (patch)
tree450024deb1f8d4340142d46a11ae9749cf786b77 /src/term/mod.rs
parent9825adf695a472be5d3316354f707c7d9d2d2730 (diff)
downloadalacritty-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.
Diffstat (limited to 'src/term/mod.rs')
-rw-r--r--src/term/mod.rs310
1 files changed, 205 insertions, 105 deletions
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);
}