T18 Terminal: a real shell on a pseudo-terminal through alacritty_terminal 0.26, drawn cell by cell in the theme's ANSI colors, with keys, text input, selection, scrollback, links and regex find; replayed output; tabs, splits and toolbar with a shell selector; command blocks and history; AnsiText, LogViewer and ProcessList

This commit is contained in:
ZacharyZhang-NY committed 2026-09-26 01:27:44 -04:00
1 parent dd09d2802a
commit 7da1368cd9
30 files changed
+3527 -12

No files matched your search

+145
View File
@@ -0,0 +1,145 @@
use std::{ops::Range, rc::Rc};
use alacritty_terminal::vte::ansi::CursorShape;
use gpui::{
BorderStyle, Bounds, ElementInputHandler, Entity, Font, FontStyle, FontWeight, Hsla,
IntoElement, Pixels, Size, StrikethroughStyle, Styled, TextRun, UnderlineStyle, canvas, fill,
outline, point, size,
};
use super::{
frame::{Frame, Lit},
links::Target,
view::Terminal,
};
/// How a terminal paints: font and cell, ink, and the colors of what it marks.
pub(crate) struct Look {
pub font: Font,
pub size: Pixels,
pub cell: Size<Pixels>,
pub focused: bool,
pub caret: Hsla,
pub caret_width: Pixels,
pub rule: Pixels,
pub selection: Hsla,
pub found: Hsla,
pub current: Hsla,
pub link: Hsla,
}
/// The grid, fitted to its box: it tells the terminal its size and origin, then paints each row.
pub(crate) fn grid(
terminal: Entity<Terminal>,
frame: Rc<Frame>,
hovered: Option<(usize, Range<usize>, Target)>,
look: Look,
) -> impl IntoElement {
let (fit, typing) = (terminal.clone(), terminal);
let cell = look.cell;
canvas(
move |bounds, window, cx| {
let columns = (bounds.size.width / cell.width).floor() as usize;
let lines = (bounds.size.height / cell.height).floor() as usize;
fit.update(cx, |terminal, cx| {
terminal.origin = bounds.origin;
let before = terminal.size;
terminal.resize(columns, lines);
if terminal.size != before {
cx.notify();
window.request_animation_frame();
}
});
},
move |bounds, _, window, cx| {
let focus = typing.read(cx).focus.clone();
window.handle_input(&focus, ElementInputHandler::new(bounds, typing.clone()), cx);
let at = |row: usize, column: usize| {
bounds.origin + point(cell.width * column as f32, cell.height * row as f32)
};
for (ix, row) in frame.rows.iter().enumerate() {
let runs: Vec<TextRun> = row
.runs
.iter()
.map(|(len, style)| TextRun {
len: *len,
font: Font {
weight: if style.bold {
FontWeight::BOLD
} else {
FontWeight::NORMAL
},
style: if style.italic {
FontStyle::Italic
} else {
FontStyle::Normal
},
..look.font.clone()
},
color: style.fg,
background_color: style.bg,
underline: style.underline.then_some(UnderlineStyle {
thickness: look.rule,
color: Some(style.fg),
wavy: false,
}),
strikethrough: style.strike.then_some(StrikethroughStyle {
thickness: look.rule,
color: Some(style.fg),
}),
})
.collect();
let shaped = window.text_system().shape_line(
row.text.clone().into(),
look.size,
&runs,
Some(cell.width),
);
shaped
.paint_background(at(ix, 0), cell.height, window, cx)
.expect("a terminal row's ground paints");
for (_, columns, why) in frame.lit.iter().filter(|(row, ..)| *row == ix) {
let wash = match why {
Lit::Selected => look.selection,
Lit::Found => look.found,
Lit::Current => look.current,
};
let extent = size(cell.width * columns.len() as f32, cell.height);
window.paint_quad(fill(Bounds::new(at(ix, columns.start), extent), wash));
}
shaped
.paint(at(ix, 0), cell.height, window, cx)
.expect("a terminal row paints");
}
if let Some((row, columns, _)) = &hovered {
let under = at(*row, columns.start) + point(Pixels::ZERO, cell.height - look.rule);
let extent = size(cell.width * columns.len() as f32, look.rule);
window.paint_quad(fill(Bounds::new(under, extent), look.link));
}
if let Some((row, column, shape)) = frame.cursor {
let origin = at(row, column);
let quad = match (shape, look.focused) {
(CursorShape::HollowBlock, _) | (_, false) => {
outline(Bounds::new(origin, cell), look.caret, BorderStyle::Solid)
}
(CursorShape::Beam, true) => fill(
Bounds::new(origin, size(look.caret_width, cell.height)),
look.caret,
),
(CursorShape::Underline, true) => fill(
Bounds::new(
origin + point(Pixels::ZERO, cell.height - look.caret_width),
size(cell.width, look.caret_width),
),
look.caret,
),
(CursorShape::Block | CursorShape::Hidden, true) => {
fill(Bounds::new(origin, cell), look.caret.opacity(0.45))
}
};
window.paint_quad(quad);
}
},
)
.size_full()
}