Add colored underline support

This commit adds support for colored underline and refines the dynamic
extra storage. The extra storage now is using `Arc` making cloning it way
faster compared to `Box` approach which scales really well when it comes
to cloning in `Term::write_at_cursor`, since cloning `Arc` is constant
time.

Fixes #4142.
This commit is contained in:
Kirill Chibisov 2022-03-16 19:27:55 +03:00 committed by GitHub
parent 589c1e9c6b
commit f4bdf5fb36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 94 additions and 38 deletions

View file

@ -17,6 +17,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Escape sequence for undercurl, dotted and dashed underlines (`CSI 4 : [3-5] m`)
- `ToggleMaximized` key binding action to (un-)maximize the active window, not bound by default
- Support for OpenGL ES 2.0
- Escape sequence to set underline color (`CSI 58 : 2 : Ps : Ps : Ps m`/`CSI 58 : 5 : Ps m`)
- Escape sequence to reset underline color (`CSI 59 m`)
### Changed

View file

@ -186,6 +186,7 @@ pub struct RenderableCell {
pub fg: Rgb,
pub bg: Rgb,
pub bg_alpha: f32,
pub underline: Rgb,
pub flags: Flags,
}
@ -251,14 +252,20 @@ impl RenderableCell {
let cell_point = cell.point;
let point = display::point_to_viewport(display_offset, cell_point).unwrap();
let flags = cell.flags;
let underline = cell
.underline_color()
.map_or(fg, |underline| Self::compute_fg_rgb(content, underline, flags));
RenderableCell {
zerowidth: cell.zerowidth().map(|zerowidth| zerowidth.to_vec()),
flags: cell.flags,
flags,
character,
bg_alpha,
point,
fg,
bg,
underline,
}
}

View file

@ -135,6 +135,7 @@ impl Renderer {
bg_alpha: 1.0,
fg,
bg,
underline: fg,
});
self.draw_cells(size_info, glyph_cache, cells);

View file

@ -193,6 +193,9 @@ impl RenderLines {
return;
}
// The underline color escape does not apply to strikeout.
let color = if flag.contains(Flags::STRIKEOUT) { cell.fg } else { cell.underline };
// Include wide char spacer if the current cell is a wide char.
let mut end = cell.point;
if cell.flags.contains(Flags::WIDE_CHAR) {
@ -201,7 +204,7 @@ impl RenderLines {
// Check if there's an active line.
if let Some(line) = self.inner.get_mut(&flag).and_then(|lines| lines.last_mut()) {
if cell.fg == line.color
if color == line.color
&& cell.point.column == line.end.column + 1
&& cell.point.line == line.end.line
{
@ -212,7 +215,7 @@ impl RenderLines {
}
// Start new line if there currently is none.
let line = RenderLine { start: cell.point, end, color: cell.fg };
let line = RenderLine { start: cell.point, end, color };
match self.inner.get_mut(&flag) {
Some(lines) => lines.push(line),
None => {

View file

@ -17,7 +17,7 @@ version = "0.1.0"
libc = "0.2"
bitflags = "1"
parking_lot = "0.11.0"
serde = { version = "1", features = ["derive"] }
serde = { version = "1", features = ["derive", "rc"] }
serde_yaml = "0.8"
vte = { version = "0.10.0", default-features = false }
mio = "0.6.20"

View file

@ -806,6 +806,8 @@ pub enum Attr {
Foreground(Color),
/// Set indexed background color.
Background(Color),
/// Underline color.
UnderlineColor(Option<Color>),
}
/// Identifiers which can be assigned to a graphic character set.
@ -1364,13 +1366,7 @@ fn attrs_from_sgr_parameters(params: &mut ParamsIter<'_>) -> Vec<Option<Attr>> {
let mut iter = params.map(|param| param[0]);
parse_sgr_color(&mut iter).map(Attr::Foreground)
},
[38, params @ ..] => {
let rgb_start = if params.len() > 4 { 2 } else { 1 };
let rgb_iter = params[rgb_start..].iter().copied();
let mut iter = iter::once(params[0]).chain(rgb_iter);
parse_sgr_color(&mut iter).map(Attr::Foreground)
},
[38, params @ ..] => handle_colon_rgb(params).map(Attr::Foreground),
[39] => Some(Attr::Foreground(Color::Named(NamedColor::Foreground))),
[40] => Some(Attr::Background(Color::Named(NamedColor::Black))),
[41] => Some(Attr::Background(Color::Named(NamedColor::Red))),
@ -1384,14 +1380,16 @@ fn attrs_from_sgr_parameters(params: &mut ParamsIter<'_>) -> Vec<Option<Attr>> {
let mut iter = params.map(|param| param[0]);
parse_sgr_color(&mut iter).map(Attr::Background)
},
[48, params @ ..] => {
let rgb_start = if params.len() > 4 { 2 } else { 1 };
let rgb_iter = params[rgb_start..].iter().copied();
let mut iter = iter::once(params[0]).chain(rgb_iter);
parse_sgr_color(&mut iter).map(Attr::Background)
},
[48, params @ ..] => handle_colon_rgb(params).map(Attr::Background),
[49] => Some(Attr::Background(Color::Named(NamedColor::Background))),
[58] => {
let mut iter = params.map(|param| param[0]);
parse_sgr_color(&mut iter).map(|color| Attr::UnderlineColor(Some(color)))
},
[58, params @ ..] => {
handle_colon_rgb(params).map(|color| Attr::UnderlineColor(Some(color)))
},
[59] => Some(Attr::UnderlineColor(None)),
[90] => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlack))),
[91] => Some(Attr::Foreground(Color::Named(NamedColor::BrightRed))),
[92] => Some(Attr::Foreground(Color::Named(NamedColor::BrightGreen))),
@ -1416,6 +1414,16 @@ fn attrs_from_sgr_parameters(params: &mut ParamsIter<'_>) -> Vec<Option<Attr>> {
attrs
}
/// Handle colon separated rgb color escape sequence.
#[inline]
fn handle_colon_rgb(params: &[u16]) -> Option<Color> {
let rgb_start = if params.len() > 4 { 2 } else { 1 };
let rgb_iter = params[rgb_start..].iter().copied();
let mut iter = iter::once(params[0]).chain(rgb_iter);
parse_sgr_color(&mut iter)
}
/// Parse a color specifier from list of attributes.
fn parse_sgr_color(params: &mut dyn Iterator<Item = u16>) -> Option<Color> {
match params.next() {

View file

@ -1,4 +1,4 @@
use std::boxed::Box;
use std::sync::Arc;
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
@ -57,19 +57,20 @@ impl ResetDiscriminant<Color> for Cell {
/// allocation required ahead of time for every cell, with some additional overhead when the extra
/// storage is actually required.
#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)]
struct CellExtra {
pub struct CellExtra {
zerowidth: Vec<char>,
underline_color: Option<Color>,
}
/// Content and attributes of a single cell in the terminal grid.
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct Cell {
pub c: char,
pub fg: Color,
pub bg: Color,
pub flags: Flags,
#[serde(default)]
extra: Option<Box<CellExtra>>,
pub extra: Option<Arc<CellExtra>>,
}
impl Default for Cell {
@ -94,25 +95,39 @@ impl Cell {
/// Write a new zerowidth character to this cell.
#[inline]
pub fn push_zerowidth(&mut self, c: char) {
self.extra.get_or_insert_with(Default::default).zerowidth.push(c);
}
/// Free all dynamically allocated cell storage.
#[inline]
pub fn drop_extra(&mut self) {
if self.extra.is_some() {
self.extra = None;
}
pub fn push_zerowidth(&mut self, character: char) {
let extra = self.extra.get_or_insert(Default::default());
Arc::make_mut(extra).zerowidth.push(character);
}
/// Remove all wide char data from a cell.
#[inline(never)]
pub fn clear_wide(&mut self) {
self.flags.remove(Flags::WIDE_CHAR);
self.drop_extra();
if let Some(extra) = self.extra.as_mut() {
Arc::make_mut(extra).zerowidth = Vec::new();
}
self.c = ' ';
}
/// Set underline color on the cell.
pub fn set_underline_color(&mut self, color: Option<Color>) {
// If we reset color and we don't have zerowidth we should drop extra storage.
if color.is_none() && self.extra.as_ref().map_or(true, |extra| !extra.zerowidth.is_empty())
{
self.extra = None;
return;
}
let extra = self.extra.get_or_insert(Default::default());
Arc::make_mut(extra).underline_color = color;
}
/// Underline color stored in this cell.
#[inline]
pub fn underline_color(&self) -> Option<Color> {
self.extra.as_ref()?.underline_color
}
}
impl GridCell for Cell {
@ -184,11 +199,22 @@ impl LineLength for grid::Row<Cell> {
#[cfg(test)]
mod tests {
use super::{Cell, LineLength};
use super::*;
use std::mem;
use crate::grid::Row;
use crate::index::Column;
#[test]
fn cell_size_is_below_cap() {
// Expected cell size on 64-bit architectures.
const EXPECTED_CELL_SIZE: usize = 24;
// Ensure that cell size isn't growning by accident.
assert!(mem::size_of::<Cell>() <= EXPECTED_CELL_SIZE);
}
#[test]
fn line_length_works() {
let mut row = Row::<Cell>::new(10);

View file

@ -1047,6 +1047,7 @@ impl<T> Term<T> {
let fg = self.grid.cursor.template.fg;
let bg = self.grid.cursor.template.bg;
let flags = self.grid.cursor.template.flags;
let extra = self.grid.cursor.template.extra.clone();
let mut cursor_cell = self.grid.cursor_cell();
@ -1070,12 +1071,11 @@ impl<T> Term<T> {
cursor_cell = self.grid.cursor_cell();
}
cursor_cell.drop_extra();
cursor_cell.c = c;
cursor_cell.fg = fg;
cursor_cell.bg = bg;
cursor_cell.flags = flags;
cursor_cell.extra = extra;
}
#[inline]
@ -1826,10 +1826,12 @@ impl<T: EventListener> Handler for Term<T> {
match attr {
Attr::Foreground(color) => cursor.template.fg = color,
Attr::Background(color) => cursor.template.bg = color,
Attr::UnderlineColor(color) => cursor.template.set_underline_color(color),
Attr::Reset => {
cursor.template.fg = Color::Named(NamedColor::Foreground);
cursor.template.bg = Color::Named(NamedColor::Background);
cursor.template.flags = Flags::empty();
cursor.template.set_underline_color(None);
},
Attr::Reverse => cursor.template.flags.insert(Flags::INVERSE),
Attr::CancelReverse => cursor.template.flags.remove(Flags::INVERSE),

View file

@ -52,6 +52,7 @@ ref_tests! {
zerowidth
selective_erasure
colored_reset
colored_underline
delete_lines
delete_chars_reset
alt_reset

View file

@ -0,0 +1,3 @@
% ]133;A]2;kchibisov@Zodiac:~/src/rust/alacritty-workspace/fork fork on  colored-underlines-v3 via 🦀 v1.59.0 ➜ [?2004hcargo run -- --ref-test --config-file /dev/null -o scrolling.history=0echo -e '\e[58;2;255;0;255m\e[4:1mUNDERLINE\e[4:2mDOUBLE\e[58:5:196m\e[4:3mUh̷̗ERCURL\e[4:4mDOTTED\e[4:5mDASHED\e[59mNOT_COLORED_DASH\e[0m'[?2004l
]2;echo -e '\e[58;2;255;0;255m\e[4:1mUNDERLINE\e[4:2mDOUBLE\e[58:5:196m\e[4:3mUh̷̗ERCURL\e[4:4mDOTTED\e[4:5mDASHED\e[59mNOT_COLORED_DASH\e[0m'[4:1mUNDERLINE[4:2mDOUBLE[58:5:196m[4:3mUh̷̗ERCURL[4:4mDOTTED[4:5mDASHEDNOT_COLORED_DASH
% ]133;A]2;kchibisov@Zodiac:~/src/rust/alacritty-workspace/fork fork on  colored-underlines-v3 via 🦀 v1.59.0 ➜ [?2004h[?2004l

View file

@ -0,0 +1 @@
{"history_size":0}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"width":1262.0,"height":690.0,"cell_width":9.0,"cell_height":18.0,"padding_x":0.0,"padding_y":0.0,"screen_lines":38,"columns":140}

View file

@ -65,7 +65,7 @@ brevity.
| `CSI l` | PARTIAL | See `CSI h` for supported modes |
| `CSI ? l` | PARTIAL | See `CSI ? h` for supported modes |
| `CSI M` | IMPLEMENTED | |
| `CSI m` | PARTIAL | Only singular straight underlines are supported |
| `CSI m` | IMPLEMENTED | |
| `CSI n` | IMPLEMENTED | |
| `CSI P` | IMPLEMENTED | |
| `CSI SP q` | IMPLEMENTED | |