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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
601a29a374
commit
8374b77906
30 files changed
+3527
-12
No files matched your search
@@ -0,0 +1,180 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use alacritty_terminal::{
|
||||
term::color::Colors,
|
||||
vte::ansi::{Attr, Color, Handler, Processor},
|
||||
};
|
||||
use gpui::{
|
||||
App, FontStyle, FontWeight, HighlightStyle, IntoElement, ParentElement, RenderOnce,
|
||||
SharedString, StrikethroughStyle, Styled, StyledText, UnderlineStyle, Window, div,
|
||||
};
|
||||
|
||||
use super::colors::Ink;
|
||||
use crate::theme::{ActiveTheme, TextSize};
|
||||
|
||||
/// How text prints after the escape codes so far.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub(crate) struct Pen {
|
||||
pub fg: Option<Color>,
|
||||
pub bg: Option<Color>,
|
||||
pub bold: bool,
|
||||
pub dim: bool,
|
||||
pub italic: bool,
|
||||
pub underline: bool,
|
||||
pub strike: bool,
|
||||
pub inverse: bool,
|
||||
}
|
||||
|
||||
/// Collects the printed text and the pen each stretch of it took.
|
||||
#[derive(Default)]
|
||||
struct Printed {
|
||||
text: String,
|
||||
spans: Vec<(Range<usize>, Pen)>,
|
||||
pen: Pen,
|
||||
}
|
||||
|
||||
impl Handler for Printed {
|
||||
fn input(&mut self, ch: char) {
|
||||
let start = self.text.len();
|
||||
self.text.push(ch);
|
||||
let end = self.text.len();
|
||||
match self.spans.last_mut() {
|
||||
Some((range, pen)) if range.end == start && *pen == self.pen => range.end = end,
|
||||
_ if self.pen == Pen::default() => {}
|
||||
_ => self.spans.push((start..end, self.pen)),
|
||||
}
|
||||
}
|
||||
|
||||
fn linefeed(&mut self) {
|
||||
self.text.push('\n');
|
||||
}
|
||||
|
||||
fn put_tab(&mut self, count: u16) {
|
||||
self.text.extend(std::iter::repeat_n('\t', count as usize));
|
||||
}
|
||||
|
||||
fn terminal_attribute(&mut self, attr: Attr) {
|
||||
let pen = &mut self.pen;
|
||||
match attr {
|
||||
Attr::Reset => *pen = Pen::default(),
|
||||
Attr::Bold => pen.bold = true,
|
||||
Attr::Dim => pen.dim = true,
|
||||
Attr::Italic => pen.italic = true,
|
||||
Attr::Underline
|
||||
| Attr::DoubleUnderline
|
||||
| Attr::Undercurl
|
||||
| Attr::DottedUnderline
|
||||
| Attr::DashedUnderline => pen.underline = true,
|
||||
Attr::Reverse => pen.inverse = true,
|
||||
Attr::Strike => pen.strike = true,
|
||||
Attr::CancelBold => pen.bold = false,
|
||||
Attr::CancelBoldDim => (pen.bold, pen.dim) = (false, false),
|
||||
Attr::CancelItalic => pen.italic = false,
|
||||
Attr::CancelUnderline => pen.underline = false,
|
||||
Attr::CancelReverse => pen.inverse = false,
|
||||
Attr::CancelStrike => pen.strike = false,
|
||||
Attr::Foreground(color) => pen.fg = Some(color),
|
||||
Attr::Background(color) => pen.bg = Some(color),
|
||||
Attr::BlinkSlow
|
||||
| Attr::BlinkFast
|
||||
| Attr::CancelBlink
|
||||
| Attr::Hidden
|
||||
| Attr::CancelHidden
|
||||
| Attr::UnderlineColor(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The text `source` prints, carriage returns and other codes gone, and the pens that are not plain.
|
||||
pub(crate) fn printed(source: &str) -> (String, Vec<(Range<usize>, Pen)>) {
|
||||
let mut printed = Printed::default();
|
||||
let mut parser: Processor = Processor::new();
|
||||
parser.advance(&mut printed, source.as_bytes());
|
||||
(printed.text, printed.spans)
|
||||
}
|
||||
|
||||
impl Pen {
|
||||
fn style(&self, ink: &Ink, rule: gpui::Pixels) -> HighlightStyle {
|
||||
let set = Colors::default();
|
||||
let paint =
|
||||
|color: Option<Color>, plain| color.map_or(plain, |color| ink.resolve(color, &set));
|
||||
let (mut fg, mut bg) = (paint(self.fg, ink.fg), paint(self.bg, ink.bg));
|
||||
if self.inverse {
|
||||
std::mem::swap(&mut fg, &mut bg);
|
||||
}
|
||||
if self.dim {
|
||||
fg = fg.opacity(0.7);
|
||||
}
|
||||
HighlightStyle {
|
||||
color: Some(fg),
|
||||
background_color: (self.bg.is_some() || self.inverse).then_some(bg),
|
||||
font_weight: self.bold.then_some(FontWeight::BOLD),
|
||||
font_style: self.italic.then_some(FontStyle::Italic),
|
||||
underline: self.underline.then_some(UnderlineStyle {
|
||||
thickness: rule,
|
||||
color: Some(fg),
|
||||
wavy: false,
|
||||
}),
|
||||
strikethrough: self.strike.then_some(StrikethroughStyle {
|
||||
thickness: rule,
|
||||
color: Some(fg),
|
||||
}),
|
||||
..HighlightStyle::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Text colored by the escape codes in it, as a terminal would print it; other codes drop away.
|
||||
#[derive(IntoElement)]
|
||||
pub struct AnsiText {
|
||||
source: SharedString,
|
||||
}
|
||||
|
||||
impl AnsiText {
|
||||
pub fn new(source: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
source: source.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for AnsiText {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
let ink = Ink::new(&theme.colors);
|
||||
let (text, spans) = printed(&self.source);
|
||||
let rule = theme.underline_thickness();
|
||||
let styles: Vec<(Range<usize>, HighlightStyle)> = spans
|
||||
.into_iter()
|
||||
.map(|(range, pen)| (range, pen.style(&ink, rule)))
|
||||
.collect();
|
||||
div()
|
||||
.font_family(theme.mono_family.clone())
|
||||
.text_size(theme.text_size(TextSize::Sm))
|
||||
.text_color(ink.fg)
|
||||
.child(StyledText::new(text).with_highlights(styles))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alacritty_terminal::vte::ansi::NamedColor;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn codes_become_pens_and_drop_out_of_the_text() {
|
||||
let (text, spans) = printed("\x1b[1;31merror\x1b[0m: bad\r\n\x1b[4mnext\x1b[24m ok");
|
||||
assert_eq!(text, "error: bad\nnext ok");
|
||||
let red = Pen {
|
||||
fg: Some(Color::Named(NamedColor::Red)),
|
||||
bold: true,
|
||||
..Pen::default()
|
||||
};
|
||||
let under = Pen {
|
||||
underline: true,
|
||||
..Pen::default()
|
||||
};
|
||||
assert_eq!(spans, [(0..5, red), (11..15, under)]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user