1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-25 14:05:41 -05:00

Add support for double underlines

This adds support for double underlines using the colon separated escape
sequence `CSI 4 : 2 m`.

Alacritty will now also always fallback to the normal underline in case
any of the other underlines like the undercurl are specified. The escape
sequence `CSI 4 : 0 m` can now be used to clear all underlines.

Some terminals support `CSI 21 m` for double underline, but since
Alacritty already uses that as cancel bold which is a little more
consistent, that behavior has not changed. So the colon separated
variant must be used.
This commit is contained in:
Christian Duerr 2020-08-12 16:05:22 +00:00 committed by GitHub
parent 96ea5c445e
commit b904207b19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 143 additions and 60 deletions

View file

@ -18,11 +18,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Secondary device attributes escape (`CSI > 0 c`) - Secondary device attributes escape (`CSI > 0 c`)
- Support for colon separated SGR 38/48 - Support for colon separated SGR 38/48
- New Ctrl+C binding to cancel search and leave vi mode - New Ctrl+C binding to cancel search and leave vi mode
- Escapes for double underlines (`CSI 4 : 2 m`) and underline reset (`CSI 4 : 0 m`)
### Changed ### Changed
- Cursors are now inverted when their fixed color is similar to the cell's background - Cursors are now inverted when their fixed color is similar to the cell's background
- Use working directory of active process instead of shell for SpawnNewInstance action - Use working directory of active process instead of shell for SpawnNewInstance action
- Fallback to normal underline for unsupported underline types in `CSI 4 : ? m` escapes
### Fixed ### Fixed

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use crossfont::Metrics; use crossfont::Metrics;
use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::index::{Line, Point};
use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::cell::Flags;
use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::{RenderableCell, SizeInfo}; use alacritty_terminal::term::{RenderableCell, SizeInfo};
@ -35,51 +35,91 @@ impl RenderLine {
let mut rects = Vec::new(); let mut rects = Vec::new();
let mut start = self.start; let mut start = self.start;
while start.line < self.end.line { for line in start.line.0..=self.end.line.0 {
let mut end = start; let mut end = Point::new(Line(line), self.end.col);
end.col = size.cols() - 1; if line != self.end.line.0 {
rects.push(Self::create_rect(metrics, size, flag, start, end, self.color)); end.col = size.cols() - 1;
}
start.col = Column(0); Self::push_rects(&mut rects, metrics, size, flag, start, end, self.color);
start.line += 1;
start.col.0 = 0;
} }
rects.push(Self::create_rect(metrics, size, flag, start, self.end, self.color));
rects rects
} }
fn create_rect( /// Push all rects required to draw the cell's line.
fn push_rects(
rects: &mut Vec<RenderRect>,
metrics: &Metrics, metrics: &Metrics,
size: &SizeInfo, size: &SizeInfo,
flag: Flags, flag: Flags,
start: Point, start: Point,
end: Point, end: Point,
color: Rgb, color: Rgb,
) -> RenderRect { ) {
let start_x = start.col.0 as f32 * size.cell_width; let (position, thickness) = match flag {
let end_x = (end.col.0 + 1) as f32 * size.cell_width; Flags::DOUBLE_UNDERLINE => {
let width = end_x - start_x; // Position underlines so each one has 50% of descent available.
let top_pos = 0.25 * metrics.descent;
let bottom_pos = 0.75 * metrics.descent;
let (position, mut height) = match flag { rects.push(Self::create_rect(
size,
metrics.descent,
start,
end,
top_pos,
metrics.underline_thickness,
color,
));
(bottom_pos, metrics.underline_thickness)
},
Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness), Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness),
Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness), Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness),
_ => unimplemented!("Invalid flag for cell line drawing specified"), _ => unimplemented!("Invalid flag for cell line drawing specified"),
}; };
rects.push(Self::create_rect(
size,
metrics.descent,
start,
end,
position,
thickness,
color,
));
}
/// Create a line's rect at a position relative to the baseline.
fn create_rect(
size: &SizeInfo,
descent: f32,
start: Point,
end: Point,
position: f32,
mut thickness: f32,
color: Rgb,
) -> RenderRect {
let start_x = start.col.0 as f32 * size.cell_width;
let end_x = (end.col.0 + 1) as f32 * size.cell_width;
let width = end_x - start_x;
// Make sure lines are always visible. // Make sure lines are always visible.
height = height.max(1.); thickness = thickness.max(1.);
let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height; let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height;
let baseline = line_bottom + metrics.descent; let baseline = line_bottom + descent;
let mut y = (baseline - position - height / 2.).ceil(); let mut y = (baseline - position - thickness / 2.).ceil();
let max_y = line_bottom - height; let max_y = line_bottom - thickness;
if y > max_y { if y > max_y {
y = max_y; y = max_y;
} }
RenderRect::new(start_x + size.padding_x, y + size.padding_y, width, height, color, 1.) RenderRect::new(start_x + size.padding_x, y + size.padding_y, width, thickness, color, 1.)
} }
} }
@ -90,10 +130,12 @@ pub struct RenderLines {
} }
impl RenderLines { impl RenderLines {
#[inline]
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
#[inline]
pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> { pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
self.inner self.inner
.iter() .iter()
@ -105,32 +147,38 @@ impl RenderLines {
} }
/// Update the stored lines with the next cell info. /// Update the stored lines with the next cell info.
#[inline]
pub fn update(&mut self, cell: RenderableCell) { pub fn update(&mut self, cell: RenderableCell) {
for flag in &[Flags::UNDERLINE, Flags::STRIKEOUT] { self.update_flag(cell, Flags::UNDERLINE);
if !cell.flags.contains(*flag) { self.update_flag(cell, Flags::DOUBLE_UNDERLINE);
continue; self.update_flag(cell, Flags::STRIKEOUT);
} }
// Check if there's an active line. /// Update the lines for a specific flag.
if let Some(line) = self.inner.get_mut(flag).and_then(|lines| lines.last_mut()) { fn update_flag(&mut self, cell: RenderableCell, flag: Flags) {
if cell.fg == line.color if !cell.flags.contains(flag) {
&& cell.column == line.end.col + 1 return;
&& cell.line == line.end.line }
{
// Update the length of the line.
line.end = cell.into();
continue;
}
}
// Start new line if there currently is none. // Check if there's an active line.
let line = RenderLine { start: cell.into(), end: cell.into(), color: cell.fg }; if let Some(line) = self.inner.get_mut(&flag).and_then(|lines| lines.last_mut()) {
match self.inner.get_mut(flag) { if cell.fg == line.color
Some(lines) => lines.push(line), && cell.column == line.end.col + 1
None => { && cell.line == line.end.line
self.inner.insert(*flag, vec![line]); {
}, // Update the length of the line.
line.end = cell.into();
return;
} }
} }
// Start new line if there currently is none.
let line = RenderLine { start: cell.into(), end: cell.into(), color: cell.fg };
match self.inner.get_mut(&flag) {
Some(lines) => lines.push(line),
None => {
self.inner.insert(flag, vec![line]);
},
}
} }
} }

View file

@ -618,6 +618,8 @@ pub enum Attr {
Italic, Italic,
/// Underline text. /// Underline text.
Underline, Underline,
/// Underlined twice.
DoubleUnderline,
/// Blink cursor slowly. /// Blink cursor slowly.
BlinkSlow, BlinkSlow,
/// Blink cursor fast. /// Blink cursor fast.
@ -634,7 +636,7 @@ pub enum Attr {
CancelBoldDim, CancelBoldDim,
/// Cancel italic. /// Cancel italic.
CancelItalic, CancelItalic,
/// Cancel underline. /// Cancel all underlines.
CancelUnderline, CancelUnderline,
/// Cancel blink. /// Cancel blink.
CancelBlink, CancelBlink,
@ -1151,7 +1153,9 @@ fn attrs_from_sgr_parameters(params: &mut ParamsIter) -> Vec<Option<Attr>> {
[1] => Some(Attr::Bold), [1] => Some(Attr::Bold),
[2] => Some(Attr::Dim), [2] => Some(Attr::Dim),
[3] => Some(Attr::Italic), [3] => Some(Attr::Italic),
[4] => Some(Attr::Underline), [4, 0] => Some(Attr::CancelUnderline),
[4, 2] => Some(Attr::DoubleUnderline),
[4, ..] => Some(Attr::Underline),
[5] => Some(Attr::BlinkSlow), [5] => Some(Attr::BlinkSlow),
[6] => Some(Attr::BlinkFast), [6] => Some(Attr::BlinkFast),
[7] => Some(Attr::Reverse), [7] => Some(Attr::Reverse),

View file

@ -12,19 +12,20 @@ pub const MAX_ZEROWIDTH_CHARS: usize = 5;
bitflags! { bitflags! {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Flags: u16 { pub struct Flags: u16 {
const INVERSE = 0b000_0000_0001; const INVERSE = 0b0000_0000_0000_0001;
const BOLD = 0b000_0000_0010; const BOLD = 0b0000_0000_0000_0010;
const ITALIC = 0b000_0000_0100; const ITALIC = 0b0000_0000_0000_0100;
const BOLD_ITALIC = 0b000_0000_0110; const BOLD_ITALIC = 0b0000_0000_0000_0110;
const UNDERLINE = 0b000_0000_1000; const UNDERLINE = 0b0000_0000_0000_1000;
const WRAPLINE = 0b000_0001_0000; const WRAPLINE = 0b0000_0000_0001_0000;
const WIDE_CHAR = 0b000_0010_0000; const WIDE_CHAR = 0b0000_0000_0010_0000;
const WIDE_CHAR_SPACER = 0b000_0100_0000; const WIDE_CHAR_SPACER = 0b0000_0000_0100_0000;
const DIM = 0b000_1000_0000; const DIM = 0b0000_0000_1000_0000;
const DIM_BOLD = 0b000_1000_0010; const DIM_BOLD = 0b0000_0000_1000_0010;
const HIDDEN = 0b001_0000_0000; const HIDDEN = 0b0000_0001_0000_0000;
const STRIKEOUT = 0b010_0000_0000; const STRIKEOUT = 0b0000_0010_0000_0000;
const LEADING_WIDE_CHAR_SPACER = 0b100_0000_0000; const LEADING_WIDE_CHAR_SPACER = 0b0000_0100_0000_0000;
const DOUBLE_UNDERLINE = 0b0000_1000_0000_0000;
} }
} }
@ -58,6 +59,7 @@ impl GridCell for Cell {
&& !self.flags.intersects( && !self.flags.intersects(
Flags::INVERSE Flags::INVERSE
| Flags::UNDERLINE | Flags::UNDERLINE
| Flags::DOUBLE_UNDERLINE
| Flags::STRIKEOUT | Flags::STRIKEOUT
| Flags::WRAPLINE | Flags::WRAPLINE
| Flags::WIDE_CHAR_SPACER | Flags::WIDE_CHAR_SPACER

View file

@ -311,7 +311,7 @@ impl RenderableCell {
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
self.bg_alpha == 0. self.bg_alpha == 0.
&& !self.flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT) && !self.flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT | Flags::DOUBLE_UNDERLINE)
&& self.inner == RenderableCellContent::Chars([' '; cell::MAX_ZEROWIDTH_CHARS + 1]) && self.inner == RenderableCellContent::Chars([' '; cell::MAX_ZEROWIDTH_CHARS + 1])
} }
@ -2017,8 +2017,17 @@ impl<T: EventListener> Handler for Term<T> {
Attr::CancelBoldDim => cursor.template.flags.remove(Flags::BOLD | Flags::DIM), Attr::CancelBoldDim => cursor.template.flags.remove(Flags::BOLD | Flags::DIM),
Attr::Italic => cursor.template.flags.insert(Flags::ITALIC), Attr::Italic => cursor.template.flags.insert(Flags::ITALIC),
Attr::CancelItalic => cursor.template.flags.remove(Flags::ITALIC), Attr::CancelItalic => cursor.template.flags.remove(Flags::ITALIC),
Attr::Underline => cursor.template.flags.insert(Flags::UNDERLINE), Attr::Underline => {
Attr::CancelUnderline => cursor.template.flags.remove(Flags::UNDERLINE), cursor.template.flags.remove(Flags::DOUBLE_UNDERLINE);
cursor.template.flags.insert(Flags::UNDERLINE);
},
Attr::DoubleUnderline => {
cursor.template.flags.remove(Flags::UNDERLINE);
cursor.template.flags.insert(Flags::DOUBLE_UNDERLINE);
},
Attr::CancelUnderline => {
cursor.template.flags.remove(Flags::UNDERLINE | Flags::DOUBLE_UNDERLINE);
},
Attr::Hidden => cursor.template.flags.insert(Flags::HIDDEN), Attr::Hidden => cursor.template.flags.insert(Flags::HIDDEN),
Attr::CancelHidden => cursor.template.flags.remove(Flags::HIDDEN), Attr::CancelHidden => cursor.template.flags.remove(Flags::HIDDEN),
Attr::Strike => cursor.template.flags.insert(Flags::STRIKEOUT), Attr::Strike => cursor.template.flags.insert(Flags::STRIKEOUT),

View file

@ -66,6 +66,7 @@ ref_tests! {
saved_cursor saved_cursor
saved_cursor_alt saved_cursor_alt
sgr sgr
underline
} }
fn read_u8<P>(path: P) -> Vec<u8> fn read_u8<P>(path: P) -> Vec<u8>

View file

@ -0,0 +1,13 @@
[undeadleech@undeadlap underline]$ [undeadleech@undeadlap underline]$ echo -e "\e[4mUNDERLINED\e[0m"
UNDERLINED
[undeadleech@undeadlap underline]$ echo -e "\e[4:1mUNDERLINED\e[4:0m"
[4:1mUNDERLINED[4:0m
[undeadleech@undeadlap underline]$ echo -e "\e[4:2mUNDERLINED\e[24m"
[4:2mUNDERLINED
[undeadleech@undeadlap underline]$ echo -e "\e[4:3mUNDERLINED\e[21m";mUNDERLINED\e[21m"2mUNDERLINED\e[21m"1mUNDERLINED\e[21m"m"m"0m"
[4:3;21mUNDERLINED
[undeadleech@undeadlap underline]$ echo -e "\e[4;4:2mUNDERLINED\e[0m"
[4;4:2mUNDERLINED
[undeadleech@undeadlap underline]$ echo -e "\e[4;4:2mUNDERLINED\e[0m":;4:2mUNDERLINED\e[0m"1;4:2mUNDERLINED\e[0m";4:2mUNDERLINED\e[0m"2;4:2mUNDERLINED\e[0m"mUNDERLINED\e[0m"1mUNDERLINED\e[0m"
[4:2;4:1mUNDERLINED
[undeadleech@undeadlap underline]$

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":939.0,"height":503.0,"cell_width":8.0,"cell_height":16.0,"padding_x":5.0,"padding_y":3.0,"dpr":1.0}

View file

@ -106,3 +106,4 @@ alacritty+common|base fragment for alacritty,
Cr=\E]112\007, Cs=\E]12;%p1%s\007, Cr=\E]112\007, Cs=\E]12;%p1%s\007,
Ms=\E]52;%p1%s;%p2%s\007, Se=\E[0 q, Ss=\E[%p1%d q, Ms=\E]52;%p1%s;%p2%s\007, Se=\E[0 q, Ss=\E[%p1%d q,
hs, dsl=\E]2;\007, fsl=^G, tsl=\E]2;, hs, dsl=\E]2;\007, fsl=^G, tsl=\E]2;,
Smulx=\E[4\:%p1%dm,