mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-03 04:34:21 -05:00
Fix rendering of zero-width characters
Instead of rendering zero-width characters as full characters, they are now properly rendered without advancing the cursor. Because of performance limitations, this implementation only supports up to 5 zero-width characters per cell. However, as a result of this limitation there should not be any performance impact. This fixes #1317, fixes #696 and closes #1318.
This commit is contained in:
parent
47f8f1bac6
commit
1cebcd660b
9 changed files with 97 additions and 17 deletions
|
@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Fixed a bad type conversion which could cause underflow on a window resize
|
||||
- Alacritty now spawns a login shell on macOS, as with Terminal.app and iTerm2
|
||||
- Fixed zombie processes sticking around after launching URLs
|
||||
- Zero-width characters are now properly rendered without progressing the cursor
|
||||
|
||||
## Version 0.2.3
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ pub struct ShaderProgram {
|
|||
u_background: GLint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Copy, Debug, Clone)]
|
||||
pub struct Glyph {
|
||||
tex_id: GLuint,
|
||||
top: f32,
|
||||
|
@ -835,7 +835,11 @@ impl<'a> RenderApi<'a> {
|
|||
.map(|(i, c)| RenderableCell {
|
||||
line,
|
||||
column: col + i,
|
||||
c,
|
||||
chars: {
|
||||
let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1];
|
||||
chars[0] = c;
|
||||
chars
|
||||
},
|
||||
bg: color,
|
||||
fg: Rgb { r: 0, g: 0, b: 0 },
|
||||
flags: cell::Flags::empty(),
|
||||
|
@ -879,23 +883,40 @@ impl<'a> RenderApi<'a> {
|
|||
glyph_cache.font_key
|
||||
};
|
||||
|
||||
// Don't render text of HIDDEN cells
|
||||
let chars = if cell.flags.contains(cell::Flags::HIDDEN) {
|
||||
[' '; cell::MAX_ZEROWIDTH_CHARS + 1]
|
||||
} else {
|
||||
cell.chars
|
||||
};
|
||||
|
||||
let mut glyph_key = GlyphKey {
|
||||
font_key,
|
||||
size: glyph_cache.font_size,
|
||||
c: cell.c,
|
||||
c: chars[0],
|
||||
};
|
||||
|
||||
// Don't render text of HIDDEN cells
|
||||
if cell.flags.contains(cell::Flags::HIDDEN) {
|
||||
glyph_key.c = ' ';
|
||||
}
|
||||
|
||||
// Add cell to batch
|
||||
{
|
||||
let glyph = glyph_cache.get(glyph_key, self);
|
||||
self.add_render_item(&cell, glyph);
|
||||
}
|
||||
|
||||
// Render zero-width characters
|
||||
for c in (&chars[1..]).iter().filter(|c| **c != ' ') {
|
||||
glyph_key.c = *c;
|
||||
let mut glyph = *glyph_cache.get(glyph_key, self);
|
||||
|
||||
// The metrics of zero-width characters are based on rendering
|
||||
// the character after the current cell, with the anchor at the
|
||||
// right side of the preceding character. Since we render the
|
||||
// zero-width characters inside the preceding character, the
|
||||
// anchor has been moved to the right by one cell.
|
||||
glyph.left += glyph_cache.metrics.average_advance as f32;
|
||||
|
||||
self.add_render_item(&cell, &glyph);
|
||||
}
|
||||
|
||||
// FIXME This is a super hacky way to do underlined text. During
|
||||
// a time crunch to release 0.1, this seemed like a really
|
||||
// easy, clean hack.
|
||||
|
|
|
@ -15,9 +15,12 @@ use ansi::{NamedColor, Color};
|
|||
use grid;
|
||||
use index::Column;
|
||||
|
||||
// Maximum number of zerowidth characters which will be stored per cell.
|
||||
pub const MAX_ZEROWIDTH_CHARS: usize = 5;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Flags: u32 {
|
||||
pub struct Flags: u16 {
|
||||
const INVERSE = 0b0_0000_0001;
|
||||
const BOLD = 0b0_0000_0010;
|
||||
const ITALIC = 0b0_0000_0100;
|
||||
|
@ -31,12 +34,18 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
const fn default_extra() -> [char; MAX_ZEROWIDTH_CHARS] {
|
||||
[' '; MAX_ZEROWIDTH_CHARS]
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct Cell {
|
||||
pub c: char,
|
||||
pub fg: Color,
|
||||
pub bg: Color,
|
||||
pub flags: Flags,
|
||||
#[serde(default="default_extra")]
|
||||
pub extra: [char; MAX_ZEROWIDTH_CHARS],
|
||||
}
|
||||
|
||||
impl Default for Cell {
|
||||
|
@ -65,7 +74,7 @@ impl LineLength for grid::Row<Cell> {
|
|||
}
|
||||
|
||||
for (index, cell) in self[..].iter().rev().enumerate() {
|
||||
if cell.c != ' ' {
|
||||
if cell.c != ' ' || cell.extra[0] != ' ' {
|
||||
length = Column(self.len() - index);
|
||||
break;
|
||||
}
|
||||
|
@ -93,6 +102,7 @@ impl Cell {
|
|||
|
||||
pub fn new(c: char, fg: Color, bg: Color) -> Cell {
|
||||
Cell {
|
||||
extra: [' '; MAX_ZEROWIDTH_CHARS],
|
||||
c,
|
||||
bg,
|
||||
fg,
|
||||
|
@ -102,9 +112,10 @@ impl Cell {
|
|||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.c == ' ' &&
|
||||
self.bg == Color::Named(NamedColor::Background) &&
|
||||
!self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE)
|
||||
self.c == ' '
|
||||
&& self.extra[0] == ' '
|
||||
&& self.bg == Color::Named(NamedColor::Background)
|
||||
&& !self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -112,6 +123,30 @@ impl Cell {
|
|||
// memcpy template to self
|
||||
*self = *template;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn chars(&self) -> [char; MAX_ZEROWIDTH_CHARS + 1] {
|
||||
unsafe {
|
||||
let mut chars = [std::mem::uninitialized(); MAX_ZEROWIDTH_CHARS + 1];
|
||||
std::ptr::write(&mut chars[0], self.c);
|
||||
std::ptr::copy_nonoverlapping(
|
||||
self.extra.as_ptr(),
|
||||
chars.as_mut_ptr().offset(1),
|
||||
self.extra.len(),
|
||||
);
|
||||
chars
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_extra(&mut self, c: char) {
|
||||
for elem in self.extra.iter_mut() {
|
||||
if elem == &' ' {
|
||||
*elem = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -424,7 +424,7 @@ pub struct RenderableCell {
|
|||
/// A _Display_ line (not necessarily an _Active_ line)
|
||||
pub line: Line,
|
||||
pub column: Column,
|
||||
pub c: char,
|
||||
pub chars: [char; cell::MAX_ZEROWIDTH_CHARS + 1],
|
||||
pub fg: Rgb,
|
||||
pub bg: Rgb,
|
||||
pub bg_alpha: f32,
|
||||
|
@ -488,7 +488,7 @@ impl<'a> Iterator for RenderableCellsIter<'a> {
|
|||
line: cell.line,
|
||||
column: cell.column,
|
||||
flags: cell.flags,
|
||||
c: cell.c,
|
||||
chars: cell.chars(),
|
||||
fg: fg_rgb,
|
||||
bg: bg_rgb,
|
||||
bg_alpha,
|
||||
|
@ -1030,6 +1030,9 @@ impl Term {
|
|||
for cell in &grid_line[cols.start..line_end] {
|
||||
if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) {
|
||||
self.push(cell.c);
|
||||
for c in (&cell.chars()[1..]).iter().filter(|c| **c != ' ') {
|
||||
self.push(*c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1371,7 +1374,9 @@ impl ansi::Handler for Term {
|
|||
let num_cols = self.grid.num_cols();
|
||||
{
|
||||
// If in insert mode, first shift cells to the right.
|
||||
if self.mode.contains(mode::TermMode::INSERT) && self.cursor.point.col + width < num_cols {
|
||||
if self.mode.contains(mode::TermMode::INSERT)
|
||||
&& self.cursor.point.col + width < num_cols
|
||||
{
|
||||
let line = self.cursor.point.line; // borrowck
|
||||
let col = self.cursor.point.col;
|
||||
let line = &mut self.grid[line];
|
||||
|
@ -1383,6 +1388,18 @@ impl ansi::Handler for Term {
|
|||
ptr::copy(src, dst, (num_cols - col - width).0);
|
||||
}
|
||||
}
|
||||
if width == 0 {
|
||||
let mut col = self.cursor.point.col.0.saturating_sub(1);
|
||||
let line = self.cursor.point.line;
|
||||
if self.grid[line][Column(col)]
|
||||
.flags
|
||||
.contains(cell::Flags::WIDE_CHAR_SPACER)
|
||||
{
|
||||
col.saturating_sub(1);
|
||||
}
|
||||
self.grid[line][Column(col)].push_extra(c);
|
||||
return;
|
||||
}
|
||||
|
||||
let cell = &mut self.grid[&self.cursor.point];
|
||||
*cell = self.cursor.template;
|
||||
|
@ -1409,7 +1426,6 @@ impl ansi::Handler for Term {
|
|||
} else {
|
||||
self.input_needs_wrap = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -51,6 +51,7 @@ ref_tests! {
|
|||
zsh_tab_completion
|
||||
history
|
||||
grid_reset
|
||||
zerowidth
|
||||
}
|
||||
|
||||
fn read_u8<P>(path: P) -> Vec<u8>
|
||||
|
|
3
tests/ref/zerowidth/alacritty.recording
Normal file
3
tests/ref/zerowidth/alacritty.recording
Normal file
|
@ -0,0 +1,3 @@
|
|||
[1m[7m%[27m[1m[0m
[0m[27m[24m[J[0;30;101m UL [0m[0;37;100m ~/…/tests/ref/zerowidth [0m[0;30;101m zerowidth-slice [0m [K[?2004he[90mcho "( [7m[90m<0361>[27m[39m[90m° [7m[90m<035c>[27m[39m[90mʖ [7m[90m<0361>[27m[39m[90m°)"[39m[32De[39mc[39mh[39mo[39m [39m"[39m([39m [39m[7m<[39m[7m0[39m[7m3[39m[7m6[39m[7m1[39m[7m>[27m[39m°[39m [39m[7m<[39m[7m0[39m[7m3[39m[7m5[39m[7mc[39m[7m>[27m[39mʖ[39m [39m[7m<[39m[7m0[39m[7m3[39m[7m6[39m[7m1[39m[7m>[27m[39m°[39m)[39m"[?2004l
|
||||
( ͡° ͜ʖ ͡°)
|
||||
[1m[7m%[27m[1m[0m
[0m[27m[24m[J[0;30;101m UL [0m[0;37;100m ~/…/tests/ref/zerowidth [0m[0;30;101m zerowidth-slice [0m [K[?2004h
|
1
tests/ref/zerowidth/config.json
Normal file
1
tests/ref/zerowidth/config.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"history_size":1000}
|
1
tests/ref/zerowidth/grid.json
Normal file
1
tests/ref/zerowidth/grid.json
Normal file
File diff suppressed because one or more lines are too long
1
tests/ref/zerowidth/size.json
Normal file
1
tests/ref/zerowidth/size.json
Normal 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}
|
Loading…
Reference in a new issue