mirror of
https://github.com/alacritty/alacritty.git
synced 2025-04-14 17:53:03 -04:00
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:
parent
589c1e9c6b
commit
f4bdf5fb36
14 changed files with 94 additions and 38 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -135,6 +135,7 @@ impl Renderer {
|
|||
bg_alpha: 1.0,
|
||||
fg,
|
||||
bg,
|
||||
underline: fg,
|
||||
});
|
||||
|
||||
self.draw_cells(size_info, glyph_cache, cells);
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -52,6 +52,7 @@ ref_tests! {
|
|||
zerowidth
|
||||
selective_erasure
|
||||
colored_reset
|
||||
colored_underline
|
||||
delete_lines
|
||||
delete_chars_reset
|
||||
alt_reset
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[1m[7m%[27m[1m[0m
]133;A]2;kchibisov@Zodiac:~/src/rust/alacritty-workspace/fork
[0m[27m[24m[J[1;34mfork[0m on [1;35m [0m[1;35mcolored-underlines-v3[0m via [1;31m🦀 [0m[1;31mv1.59.0[0m[1;31m [0m[1;37m➜[0m [K[?2004h[1m[92mcargo[0m[39m run -- --ref-test --config-file [4m/dev/null[24m -o scrolling.history=0[70D[1m[92me[1m[92mc[1m[92mh[1m[92mo[0m[39m[0m[39m -e [92m'[92m\[92me[92m[[92m5[92m8[92m;[92m2[92m;[92m2[92m5[92m5[92m;[92m0[92m;[92m2[92m5[92m5[92mm[92m\[92me[92m[[92m4[92m:[92m1[92mm[92mU[92mN[92mD[92mE[24m[92mR[24m[92mL[24m[92mI[24m[92mN[24m[92mE[24m[92m\[24m[92me[24m[92m[[24m[92m4[92m:[92m2[92mm[92mD[92mO[92mU[92mB[92mL[92mE[92m\[92me[92m[[92m5[92m8[92m:[92m5[92m:[92m1[92m9[92m6[92mm[92m\[92me[92m[4:3mUh̷̗ERCURL\e[4:4mD[92mO[92mTTED\e[4:5mDASHED\e[59mNOT_COLORED_DASH\e[0m'[39m[K[?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'[58;2;255;0;255m[4:1mUNDERLINE[4:2mDOUBLE[58:5:196m[4:3mUh̷̗ERCURL[4:4mDOTTED[4:5mDASHED[59mNOT_COLORED_DASH[0m
|
||||
[1m[7m%[27m[1m[0m
]133;A]2;kchibisov@Zodiac:~/src/rust/alacritty-workspace/fork
[0m[27m[24m[J[1;34mfork[0m on [1;35m [0m[1;35mcolored-underlines-v3[0m via [1;31m🦀 [0m[1;31mv1.59.0[0m[1;31m [0m[1;37m➜[0m [K[?2004h[?2004l
|
|
@ -0,0 +1 @@
|
|||
{"history_size":0}
|
1
alacritty_terminal/tests/ref/colored_underline/grid.json
Normal file
1
alacritty_terminal/tests/ref/colored_underline/grid.json
Normal file
File diff suppressed because one or more lines are too long
1
alacritty_terminal/tests/ref/colored_underline/size.json
Normal file
1
alacritty_terminal/tests/ref/colored_underline/size.json
Normal 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}
|
|
@ -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 | |
|
||||
|
|
Loading…
Add table
Reference in a new issue