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()
|
||||
};
|
||||
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| {
|
||||
window
|
||||
.observe_window_appearance(|_, cx| themes::apply(cx))
|
||||
.detach();
|
||||
let terminal = cx.new(|cx| TerminalView::new(pty, events, window, cx));
|
||||
window.focus(&terminal.focus_handle(cx), cx);
|
||||
cx.new(|cx| Workspace {
|
||||
focus: cx.focus_handle(),
|
||||
|
||||
+18
-8
@@ -91,15 +91,17 @@ pub fn frame<T: EventListener>(term: &Term<T>, 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::<String>()
|
||||
});
|
||||
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<T: EventListener>(term: &Term<T>, 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, " ");
|
||||
}
|
||||
}
|
||||
+66
-35
@@ -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<Pixels>,
|
||||
look: &Look,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
fn paint_row(row: &super::frame::Row, top: Point<Pixels>, 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<Pixels>,
|
||||
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<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.
|
||||
fn decorate(origin: Point<Pixels>, width: Pixels, style: &Style, look: &Look, window: &mut Window) {
|
||||
let rule = look.rule;
|
||||
@@ -252,7 +285,6 @@ fn paint_cursor(
|
||||
origin: Point<Pixels>,
|
||||
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 => {
|
||||
|
||||
+4
-2
@@ -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>) -> Self {
|
||||
pub fn new(pty: Pty, events: Events, window: &mut Window, cx: &mut Context<Self>) -> 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::<SettingsStore>(|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;
|
||||
|
||||
Reference in new issue
Block a user