T06: hidden cells blank, inactive window hollows the cursor, glyphs painted on one baseline with CoreText's upward offsets flipped
This commit is contained in:
4 files changed
+89
-46
No files matched your search
+1
-1
@@ -73,11 +73,11 @@ pub fn open(launch: Launch, cx: &mut App) -> anyhow::Result<WindowHandle<Workspa
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let (pty, events) = spawn(&launch, (80, 24), (8, 16), config(SettingsStore::get(cx)))?;
|
let (pty, events) = spawn(&launch, (80, 24), (8, 16), config(SettingsStore::get(cx)))?;
|
||||||
let terminal = cx.new(|cx| TerminalView::new(pty, events, cx));
|
|
||||||
let window = cx.open_window(options, |window, cx| {
|
let window = cx.open_window(options, |window, cx| {
|
||||||
window
|
window
|
||||||
.observe_window_appearance(|_, cx| themes::apply(cx))
|
.observe_window_appearance(|_, cx| themes::apply(cx))
|
||||||
.detach();
|
.detach();
|
||||||
|
let terminal = cx.new(|cx| TerminalView::new(pty, events, window, cx));
|
||||||
window.focus(&terminal.focus_handle(cx), cx);
|
window.focus(&terminal.focus_handle(cx), cx);
|
||||||
cx.new(|cx| Workspace {
|
cx.new(|cx| Workspace {
|
||||||
focus: cx.focus_handle(),
|
focus: cx.focus_handle(),
|
||||||
|
|||||||
+18
-8
@@ -91,15 +91,17 @@ pub fn frame<T: EventListener>(term: &Term<T>, ink: &Ink) -> Frame {
|
|||||||
let spacer = cell
|
let spacer = cell
|
||||||
.flags
|
.flags
|
||||||
.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER);
|
.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER);
|
||||||
|
let hidden = cell.flags.contains(Flags::HIDDEN);
|
||||||
let cluster = cell
|
let cluster = cell
|
||||||
.zerowidth()
|
.zerowidth()
|
||||||
|
.filter(|_| !hidden)
|
||||||
.filter(|marks| !marks.is_empty())
|
.filter(|marks| !marks.is_empty())
|
||||||
.map(|marks| {
|
.map(|marks| {
|
||||||
std::iter::once(cell.c)
|
std::iter::once(cell.c)
|
||||||
.chain(marks.iter().copied())
|
.chain(marks.iter().copied())
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
});
|
});
|
||||||
let shown = if spacer || cluster.is_some() {
|
let shown = if spacer || hidden || cluster.is_some() {
|
||||||
' '
|
' '
|
||||||
} else {
|
} else {
|
||||||
cell.c
|
cell.c
|
||||||
@@ -111,7 +113,7 @@ pub fn frame<T: EventListener>(term: &Term<T>, ink: &Ink) -> Frame {
|
|||||||
column: point.column.0,
|
column: point.column.0,
|
||||||
shape: content.cursor.shape,
|
shape: content.cursor.shape,
|
||||||
wide: cell.flags.contains(Flags::WIDE_CHAR),
|
wide: cell.flags.contains(Flags::WIDE_CHAR),
|
||||||
text: cluster.clone().unwrap_or_else(|| cell.c.to_string()),
|
text: cluster.clone().unwrap_or_else(|| shown.to_string()),
|
||||||
style: look,
|
style: look,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -140,10 +142,13 @@ fn style(cell: &Cell, ink: &Ink, set: &Colors) -> Style {
|
|||||||
if flags.contains(Flags::DIM) {
|
if flags.contains(Flags::DIM) {
|
||||||
fg = fg.opacity(DIM);
|
fg = fg.opacity(DIM);
|
||||||
}
|
}
|
||||||
if flags.contains(Flags::HIDDEN) {
|
let hidden = flags.contains(Flags::HIDDEN);
|
||||||
|
if hidden {
|
||||||
fg = bg;
|
fg = bg;
|
||||||
}
|
}
|
||||||
let underline = if flags.contains(Flags::DOUBLE_UNDERLINE) {
|
let underline = if hidden {
|
||||||
|
Underline::None
|
||||||
|
} else if flags.contains(Flags::DOUBLE_UNDERLINE) {
|
||||||
Underline::Double
|
Underline::Double
|
||||||
} else if flags.contains(Flags::UNDERCURL) {
|
} else if flags.contains(Flags::UNDERCURL) {
|
||||||
Underline::Curly
|
Underline::Curly
|
||||||
@@ -165,7 +170,7 @@ fn style(cell: &Cell, ink: &Ink, set: &Colors) -> Style {
|
|||||||
underline_color: cell
|
underline_color: cell
|
||||||
.underline_color()
|
.underline_color()
|
||||||
.map_or(fg, |color| ink.resolve(color, set)),
|
.map_or(fg, |color| ink.resolve(color, set)),
|
||||||
strike: flags.contains(Flags::STRIKEOUT),
|
strike: flags.contains(Flags::STRIKEOUT) && !hidden,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,8 +257,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hidden_text_takes_its_ground() {
|
fn hidden_cells_show_nothing_but_their_ground() {
|
||||||
let (frame, ink) = shown(b"\x1b[8mh");
|
let (frame, _) = shown("\x1b[8;4;9;41mSe\u{301}\x1b[0m\x1b[2D".as_bytes());
|
||||||
assert_eq!(frame.rows[0].runs[0].1.fg, ink.bg);
|
assert_eq!(frame.rows[0].text.trim_end(), "");
|
||||||
|
assert!(frame.rows[0].clusters.is_empty());
|
||||||
|
let (_, style) = frame.rows[0].runs[0];
|
||||||
|
assert_eq!((style.underline, style.strike), (Underline::None, false));
|
||||||
|
assert!(style.bg.is_some(), "the ground stays");
|
||||||
|
assert_eq!(frame.cursor.expect("a cursor").text, " ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+66
-35
@@ -2,9 +2,9 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use alacritty_terminal::vte::ansi::CursorShape;
|
use alacritty_terminal::vte::ansi::CursorShape;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Bounds, Entity, Font, FontId, FontStyle, FontWeight, Hsla, IntoElement, Pixels, Point,
|
Bounds, Entity, Font, FontId, FontStyle, FontWeight, Hsla, IntoElement, Pixels, Point, Size,
|
||||||
Size, StrikethroughStyle, Styled, TextAlign, TextRun, UnderlineStyle, Window, canvas, fill,
|
StrikethroughStyle, Styled, TextRun, UnderlineStyle, Window, canvas, fill, outline, point, px,
|
||||||
outline, point, px, size,
|
size,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -48,7 +48,7 @@ pub fn grid(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
move |bounds, _, window, cx| {
|
move |bounds, _, window, _| {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"terminal: paint {:?}, row 0 {:?}",
|
"terminal: paint {:?}, row 0 {:?}",
|
||||||
bounds,
|
bounds,
|
||||||
@@ -56,10 +56,10 @@ pub fn grid(
|
|||||||
);
|
);
|
||||||
for (index, row) in frame.rows.iter().enumerate() {
|
for (index, row) in frame.rows.iter().enumerate() {
|
||||||
let top = bounds.origin + point(px(0.), cell.height * index as f32);
|
let top = bounds.origin + point(px(0.), cell.height * index as f32);
|
||||||
paint_row(row, top, &look, window, cx);
|
paint_row(row, top, &look, window);
|
||||||
}
|
}
|
||||||
if let Some((cursor, shape)) = &cursor {
|
if let Some((cursor, shape)) = &cursor {
|
||||||
paint_cursor(cursor, *shape, bounds.origin, &look, window, cx);
|
paint_cursor(cursor, *shape, bounds.origin, &look, window);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -97,13 +97,7 @@ fn spans(row: &super::frame::Row) -> Vec<(usize, usize, Style)> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_row(
|
fn paint_row(row: &super::frame::Row, top: Point<Pixels>, look: &Look, window: &mut Window) {
|
||||||
row: &super::frame::Row,
|
|
||||||
top: Point<Pixels>,
|
|
||||||
look: &Look,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) {
|
|
||||||
let cell = look.cell;
|
let cell = look.cell;
|
||||||
let at = |column: usize| top + point(cell.width * column as f32, px(0.));
|
let at = |column: usize| top + point(cell.width * column as f32, px(0.));
|
||||||
let spans = spans(row);
|
let spans = spans(row);
|
||||||
@@ -123,17 +117,9 @@ fn paint_row(
|
|||||||
runs(text, style, style.fg, look, window)
|
runs(text, style, style.fg, look, window)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let shaped = window.text_system().shape_line(
|
draw(&row.text, &runs, top, Some(cell.width), look, window);
|
||||||
row.text.clone().into(),
|
|
||||||
look.size,
|
|
||||||
&runs,
|
|
||||||
Some(cell.width),
|
|
||||||
);
|
|
||||||
if let Err(error) = shaped.paint(top, cell.height, TextAlign::Left, None, window, cx) {
|
|
||||||
log::error!("terminal: a row failed to paint: {error:#}");
|
|
||||||
}
|
|
||||||
for (column, cluster, style) in &row.clusters {
|
for (column, cluster, style) in &row.clusters {
|
||||||
text(cluster, style.fg, style, at(*column), look, window, cx);
|
text(cluster, style.fg, style, at(*column), look, window);
|
||||||
}
|
}
|
||||||
for (column, columns, style) in spans {
|
for (column, columns, style) in spans {
|
||||||
let width = cell.width * columns as f32;
|
let width = cell.width * columns as f32;
|
||||||
@@ -169,7 +155,7 @@ fn private(ch: char) -> bool {
|
|||||||
matches!(ch, '\u{E000}'..='\u{F8FF}' | '\u{F0000}'..='\u{FFFFD}' | '\u{100000}'..='\u{10FFFD}')
|
matches!(ch, '\u{E000}'..='\u{F8FF}' | '\u{F0000}'..='\u{FFFFD}' | '\u{100000}'..='\u{10FFFD}')
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paints `content` alone at `origin`, a cell's worth.
|
/// Paints `content` alone at `origin`, a cell's top-left, its marks where shaping puts them.
|
||||||
fn text(
|
fn text(
|
||||||
content: &str,
|
content: &str,
|
||||||
color: Hsla,
|
color: Hsla,
|
||||||
@@ -177,20 +163,67 @@ fn text(
|
|||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
look: &Look,
|
look: &Look,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
|
||||||
) {
|
) {
|
||||||
let runs = runs(content, style, color, look, window);
|
let runs = runs(content, style, color, look, window);
|
||||||
let shaped = window.text_system().shape_line(
|
draw(content, &runs, origin, None, look, window);
|
||||||
content.to_string().into(),
|
}
|
||||||
look.size,
|
|
||||||
&runs,
|
/// Shapes `content` and paints each glyph on the cell's baseline below `top`; `force` sets every advance.
|
||||||
Some(look.cell.width),
|
fn draw(
|
||||||
);
|
content: &str,
|
||||||
if let Err(error) = shaped.paint(origin, look.cell.height, TextAlign::Left, None, window, cx) {
|
runs: &[TextRun],
|
||||||
log::error!("terminal: `{content}` failed to paint: {error:#}");
|
top: Point<Pixels>,
|
||||||
|
force: Option<Pixels>,
|
||||||
|
look: &Look,
|
||||||
|
window: &mut Window,
|
||||||
|
) {
|
||||||
|
let shaped =
|
||||||
|
window
|
||||||
|
.text_system()
|
||||||
|
.shape_line(content.to_string().into(), look.size, runs, force);
|
||||||
|
let baseline = top.y + look.baseline;
|
||||||
|
for run in &shaped.runs {
|
||||||
|
for glyph in &run.glyphs {
|
||||||
|
let color = color_at(runs, glyph.index);
|
||||||
|
let at = point(
|
||||||
|
top.x + glyph.position.x,
|
||||||
|
baseline + upward(glyph.position.y),
|
||||||
|
);
|
||||||
|
let painted = if glyph.is_emoji {
|
||||||
|
window.paint_emoji(at, run.font_id, glyph.id, look.size)
|
||||||
|
} else {
|
||||||
|
window.paint_glyph(at, run.font_id, glyph.id, look.size, color)
|
||||||
|
};
|
||||||
|
if let Err(error) = painted {
|
||||||
|
log::error!("terminal: a glyph of `{content}` failed to paint: {error:#}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The color of the run holding byte `index`.
|
||||||
|
fn color_at(runs: &[TextRun], index: usize) -> Hsla {
|
||||||
|
let mut end = 0;
|
||||||
|
runs.iter()
|
||||||
|
.find(|run| {
|
||||||
|
end += run.len;
|
||||||
|
index < end
|
||||||
|
})
|
||||||
|
.expect("a glyph indexes its own text")
|
||||||
|
.color
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CoreText gives glyph offsets growing upward; the screen grows downward.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn upward(y: Pixels) -> Pixels {
|
||||||
|
-y
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
fn upward(y: Pixels) -> Pixels {
|
||||||
|
y
|
||||||
|
}
|
||||||
|
|
||||||
/// Underlines and strikethrough under `width` from `origin`, a cell's top-left.
|
/// Underlines and strikethrough under `width` from `origin`, a cell's top-left.
|
||||||
fn decorate(origin: Point<Pixels>, width: Pixels, style: &Style, look: &Look, window: &mut Window) {
|
fn decorate(origin: Point<Pixels>, width: Pixels, style: &Style, look: &Look, window: &mut Window) {
|
||||||
let rule = look.rule;
|
let rule = look.rule;
|
||||||
@@ -252,7 +285,6 @@ fn paint_cursor(
|
|||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
look: &Look,
|
look: &Look,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
|
||||||
) {
|
) {
|
||||||
let cell = look.cell;
|
let cell = look.cell;
|
||||||
let corner = origin
|
let corner = origin
|
||||||
@@ -272,7 +304,6 @@ fn paint_cursor(
|
|||||||
corner,
|
corner,
|
||||||
look,
|
look,
|
||||||
window,
|
window,
|
||||||
cx,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
CursorShape::HollowBlock => {
|
CursorShape::HollowBlock => {
|
||||||
|
|||||||
+4
-2
@@ -47,6 +47,7 @@ pub struct TerminalView {
|
|||||||
shown: bool,
|
shown: bool,
|
||||||
_events: Task<()>,
|
_events: Task<()>,
|
||||||
_blink: Task<()>,
|
_blink: Task<()>,
|
||||||
|
_activation: Subscription,
|
||||||
_settings: Subscription,
|
_settings: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ impl Focusable for TerminalView {
|
|||||||
|
|
||||||
impl TerminalView {
|
impl TerminalView {
|
||||||
/// Draws `pty`; the first layout fits its grid to the box.
|
/// Draws `pty`; the first layout fits its grid to the box.
|
||||||
pub fn new(pty: Pty, events: Events, cx: &mut Context<Self>) -> Self {
|
pub fn new(pty: Pty, events: Events, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let size = {
|
let size = {
|
||||||
let term = pty.term.lock();
|
let term = pty.term.lock();
|
||||||
(term.columns(), term.screen_lines())
|
(term.columns(), term.screen_lines())
|
||||||
@@ -74,6 +75,7 @@ impl TerminalView {
|
|||||||
shown: true,
|
shown: true,
|
||||||
_events: Self::pump(events, cx),
|
_events: Self::pump(events, cx),
|
||||||
_blink: Self::blink(cx),
|
_blink: Self::blink(cx),
|
||||||
|
_activation: cx.observe_window_activation(window, |_, _, cx| cx.notify()),
|
||||||
_settings: cx.observe_global::<SettingsStore>(|view, cx| {
|
_settings: cx.observe_global::<SettingsStore>(|view, cx| {
|
||||||
view.pty
|
view.pty
|
||||||
.term
|
.term
|
||||||
@@ -228,7 +230,7 @@ impl Render for TerminalView {
|
|||||||
let shown = frame(&term, &ink);
|
let shown = frame(&term, &ink);
|
||||||
let blinks = term.cursor_style().blinking;
|
let blinks = term.cursor_style().blinking;
|
||||||
drop(term);
|
drop(term);
|
||||||
let focused = self.focus.is_focused(window);
|
let focused = self.focus.is_focused(window) && window.is_window_active();
|
||||||
self.blinking = focused && blinks;
|
self.blinking = focused && blinks;
|
||||||
if !self.blinking {
|
if !self.blinking {
|
||||||
self.shown = true;
|
self.shown = true;
|
||||||
|
|||||||
Reference in new issue
Block a user