diff --git a/src/app.rs b/src/app.rs index 71c7072..5425d41 100644 --- a/src/app.rs +++ b/src/app.rs @@ -73,11 +73,11 @@ pub fn open(launch: Launch, cx: &mut App) -> anyhow::Result(term: &Term, ink: &Ink) -> Frame { let spacer = cell .flags .intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER); + let hidden = cell.flags.contains(Flags::HIDDEN); let cluster = cell .zerowidth() + .filter(|_| !hidden) .filter(|marks| !marks.is_empty()) .map(|marks| { std::iter::once(cell.c) .chain(marks.iter().copied()) .collect::() }); - let shown = if spacer || cluster.is_some() { + let shown = if spacer || hidden || cluster.is_some() { ' ' } else { cell.c @@ -111,7 +113,7 @@ pub fn frame(term: &Term, ink: &Ink) -> Frame { column: point.column.0, shape: content.cursor.shape, 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, }); } @@ -140,10 +142,13 @@ fn style(cell: &Cell, ink: &Ink, set: &Colors) -> Style { if flags.contains(Flags::DIM) { fg = fg.opacity(DIM); } - if flags.contains(Flags::HIDDEN) { + let hidden = flags.contains(Flags::HIDDEN); + if hidden { 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 } else if flags.contains(Flags::UNDERCURL) { Underline::Curly @@ -165,7 +170,7 @@ fn style(cell: &Cell, ink: &Ink, set: &Colors) -> Style { underline_color: cell .underline_color() .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] - fn hidden_text_takes_its_ground() { - let (frame, ink) = shown(b"\x1b[8mh"); - assert_eq!(frame.rows[0].runs[0].1.fg, ink.bg); + fn hidden_cells_show_nothing_but_their_ground() { + let (frame, _) = shown("\x1b[8;4;9;41mSe\u{301}\x1b[0m\x1b[2D".as_bytes()); + 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, " "); } } diff --git a/src/term/paint.rs b/src/term/paint.rs index 024e781..1d9f784 100644 --- a/src/term/paint.rs +++ b/src/term/paint.rs @@ -2,9 +2,9 @@ use std::rc::Rc; use alacritty_terminal::vte::ansi::CursorShape; use gpui::{ - App, Bounds, Entity, Font, FontId, FontStyle, FontWeight, Hsla, IntoElement, Pixels, Point, - Size, StrikethroughStyle, Styled, TextAlign, TextRun, UnderlineStyle, Window, canvas, fill, - outline, point, px, size, + Bounds, Entity, Font, FontId, FontStyle, FontWeight, Hsla, IntoElement, Pixels, Point, Size, + StrikethroughStyle, Styled, TextRun, UnderlineStyle, Window, canvas, fill, outline, point, px, + size, }; use super::{ @@ -48,7 +48,7 @@ pub fn grid( } }); }, - move |bounds, _, window, cx| { + move |bounds, _, window, _| { log::trace!( "terminal: paint {:?}, row 0 {:?}", bounds, @@ -56,10 +56,10 @@ pub fn grid( ); for (index, row) in frame.rows.iter().enumerate() { 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 { - 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() } -fn paint_row( - row: &super::frame::Row, - top: Point, - look: &Look, - window: &mut Window, - cx: &mut App, -) { +fn paint_row(row: &super::frame::Row, top: Point, look: &Look, window: &mut Window) { let cell = look.cell; let at = |column: usize| top + point(cell.width * column as f32, px(0.)); let spans = spans(row); @@ -123,17 +117,9 @@ fn paint_row( runs(text, style, style.fg, look, window) }) .collect(); - let shaped = window.text_system().shape_line( - 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:#}"); - } + draw(&row.text, &runs, top, Some(cell.width), look, window); 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 { 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}') } -/// 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( content: &str, color: Hsla, @@ -177,20 +163,67 @@ fn text( origin: Point, look: &Look, window: &mut Window, - cx: &mut App, ) { let runs = runs(content, style, color, look, window); - let shaped = window.text_system().shape_line( - content.to_string().into(), - look.size, - &runs, - Some(look.cell.width), - ); - if let Err(error) = shaped.paint(origin, look.cell.height, TextAlign::Left, None, window, cx) { - log::error!("terminal: `{content}` failed to paint: {error:#}"); + draw(content, &runs, origin, None, look, window); +} + +/// Shapes `content` and paints each glyph on the cell's baseline below `top`; `force` sets every advance. +fn draw( + content: &str, + runs: &[TextRun], + top: Point, + force: Option, + 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. fn decorate(origin: Point, width: Pixels, style: &Style, look: &Look, window: &mut Window) { let rule = look.rule; @@ -252,7 +285,6 @@ fn paint_cursor( origin: Point, look: &Look, window: &mut Window, - cx: &mut App, ) { let cell = look.cell; let corner = origin @@ -272,7 +304,6 @@ fn paint_cursor( corner, look, window, - cx, ); } CursorShape::HollowBlock => { diff --git a/src/term/view.rs b/src/term/view.rs index 5cad768..d944c38 100644 --- a/src/term/view.rs +++ b/src/term/view.rs @@ -47,6 +47,7 @@ pub struct TerminalView { shown: bool, _events: Task<()>, _blink: Task<()>, + _activation: Subscription, _settings: Subscription, } @@ -60,7 +61,7 @@ impl Focusable for TerminalView { impl TerminalView { /// Draws `pty`; the first layout fits its grid to the box. - pub fn new(pty: Pty, events: Events, cx: &mut Context) -> Self { + pub fn new(pty: Pty, events: Events, window: &mut Window, cx: &mut Context) -> Self { let size = { let term = pty.term.lock(); (term.columns(), term.screen_lines()) @@ -74,6 +75,7 @@ impl TerminalView { shown: true, _events: Self::pump(events, cx), _blink: Self::blink(cx), + _activation: cx.observe_window_activation(window, |_, _, cx| cx.notify()), _settings: cx.observe_global::(|view, cx| { view.pty .term @@ -228,7 +230,7 @@ impl Render for TerminalView { let shown = frame(&term, &ink); let blinks = term.cursor_style().blinking; drop(term); - let focused = self.focus.is_focused(window); + let focused = self.focus.is_focused(window) && window.is_window_active(); self.blinking = focused && blinks; if !self.blinking { self.shown = true;