element.rs

  1use alacritty_terminal::{
  2    ansi::Color as AnsiColor,
  3    event_loop::Msg,
  4    grid::{Indexed, Scroll},
  5    index::Point,
  6    sync::FairMutex,
  7    term::{
  8        cell::{Cell, Flags},
  9        RenderableCursor, SizeInfo,
 10    },
 11    Term,
 12};
 13use gpui::{
 14    color::Color,
 15    elements::*,
 16    fonts::{HighlightStyle, TextStyle, Underline},
 17    geometry::{rect::RectF, vector::vec2f},
 18    json::json,
 19    text_layout::Line,
 20    Event, PaintContext, Quad,
 21};
 22use mio_extras::channel::Sender;
 23use ordered_float::OrderedFloat;
 24use settings::Settings;
 25use std::sync::Arc;
 26
 27use crate::{Input, ZedListener};
 28
 29const DEBUG_GRID: bool = false;
 30const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.;
 31
 32pub struct TerminalEl {
 33    term: Arc<FairMutex<Term<ZedListener>>>,
 34    pty_tx: Sender<Msg>,
 35    size: SizeInfo,
 36}
 37
 38impl TerminalEl {
 39    pub fn new(
 40        term: Arc<FairMutex<Term<ZedListener>>>,
 41        pty_tx: Sender<Msg>,
 42        size: SizeInfo,
 43    ) -> TerminalEl {
 44        TerminalEl { term, pty_tx, size }
 45    }
 46}
 47
 48pub struct LayoutState {
 49    lines: Vec<Line>,
 50    line_height: f32,
 51    em_width: f32,
 52    cursor: Option<RectF>,
 53}
 54
 55impl Element for TerminalEl {
 56    type LayoutState = LayoutState;
 57    type PaintState = ();
 58
 59    fn layout(
 60        &mut self,
 61        constraint: gpui::SizeConstraint,
 62        cx: &mut gpui::LayoutContext,
 63    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
 64        let size = constraint.max;
 65        let settings = cx.global::<Settings>();
 66        let theme = &settings.theme.editor;
 67        //Get terminal
 68        let mut term = self.term.lock();
 69
 70        //Set up text rendering
 71        let font_cache = cx.font_cache();
 72
 73        let font_family_id = settings.buffer_font_family;
 74        let font_family_name = cx.font_cache().family_name(font_family_id).unwrap();
 75        let font_properties = Default::default();
 76        let font_id = font_cache
 77            .select_font(font_family_id, &font_properties)
 78            .unwrap();
 79        let font_size = settings.buffer_font_size;
 80
 81        let text_style = TextStyle {
 82            color: theme.text_color,
 83            font_family_id: settings.buffer_font_family,
 84            font_family_name,
 85            font_id,
 86            font_size,
 87            font_properties: Default::default(),
 88            underline: Default::default(),
 89        };
 90
 91        let line_height = cx.font_cache.line_height(text_style.font_size);
 92        let em_width = cx
 93            .font_cache()
 94            .em_width(text_style.font_id, text_style.font_size)
 95            + 2.;
 96
 97        //Resize terminal
 98        let new_size = SizeInfo::new(size.x(), size.y(), em_width, line_height, 0., 0., false);
 99        if !new_size.eq(&self.size) {
100            self.pty_tx.send(Msg::Resize(new_size)).ok();
101            term.resize(new_size);
102            self.size = new_size;
103        }
104
105        //Start rendering
106        let content = term.renderable_content();
107
108        let mut cursor = None;
109        let mut lines: Vec<(String, Option<HighlightStyle>)> = vec![];
110        let mut last_line = 0;
111        let mut line_count = 1;
112        let mut cur_chunk = String::new();
113
114        let mut cur_highlight = HighlightStyle {
115            color: Some(Color::white()),
116            ..Default::default()
117        };
118
119        for cell in content.display_iter {
120            let Indexed {
121                point: Point { line, .. },
122                cell: Cell {
123                    c, fg, flags, .. // TODO: Add bg and flags
124                }, //TODO: Learn what 'CellExtra does'
125            } = cell;
126
127            if cell.point == content.cursor.point {
128                cursor = make_cursor(em_width, line_height, content.cursor);
129            }
130
131            let new_highlight = make_style_from_cell(fg, flags);
132
133            if line != last_line {
134                line_count += 1;
135                cur_chunk.push('\n');
136                last_line = line.0;
137            }
138
139            if new_highlight != cur_highlight {
140                lines.push((cur_chunk.clone(), Some(cur_highlight.clone())));
141                cur_chunk.clear();
142                cur_highlight = new_highlight;
143            }
144            cur_chunk.push(*c)
145        }
146        lines.push((cur_chunk, Some(cur_highlight)));
147
148        let shaped_lines = layout_highlighted_chunks(
149            lines.iter().map(|(text, style)| (text.as_str(), *style)),
150            &text_style,
151            cx.text_layout_cache,
152            &cx.font_cache,
153            usize::MAX,
154            line_count,
155        );
156
157        (
158            constraint.max,
159            LayoutState {
160                lines: shaped_lines,
161                line_height,
162                em_width,
163                cursor,
164            },
165        )
166    }
167
168    fn paint(
169        &mut self,
170        bounds: gpui::geometry::rect::RectF,
171        visible_bounds: gpui::geometry::rect::RectF,
172        layout: &mut Self::LayoutState,
173        cx: &mut gpui::PaintContext,
174    ) -> Self::PaintState {
175        let origin = bounds.origin() + vec2f(layout.em_width, 0.);
176
177        let mut line_origin = origin;
178        for line in &layout.lines {
179            let boundaries = RectF::new(line_origin, vec2f(bounds.width(), layout.line_height));
180
181            if boundaries.intersects(visible_bounds) {
182                line.paint(line_origin, visible_bounds, layout.line_height, cx);
183            }
184
185            line_origin.set_y(boundaries.max_y());
186        }
187
188        if let Some(c) = layout.cursor {
189            let new_origin = origin + c.origin();
190            let new_cursor = RectF::new(new_origin, c.size());
191            cx.scene.push_quad(Quad {
192                bounds: new_cursor,
193                background: Some(Color::red()),
194                border: Default::default(),
195                corner_radius: 0.,
196            });
197        }
198
199        if DEBUG_GRID {
200            draw_debug_grid(bounds, layout, cx);
201        }
202    }
203
204    fn dispatch_event(
205        &mut self,
206        event: &gpui::Event,
207        _bounds: gpui::geometry::rect::RectF,
208        _visible_bounds: gpui::geometry::rect::RectF,
209        layout: &mut Self::LayoutState,
210        _paint: &mut Self::PaintState,
211        cx: &mut gpui::EventContext,
212    ) -> bool {
213        match event {
214            Event::ScrollWheel { delta, .. } => {
215                let vertical_scroll =
216                    (delta.y() / layout.line_height) * ALACRITTY_SCROLL_MULTIPLIER;
217                let scroll = Scroll::Delta(vertical_scroll.round() as i32);
218                self.term.lock().scroll_display(scroll);
219                true
220            }
221            Event::KeyDown {
222                input: Some(input), ..
223            } => {
224                cx.dispatch_action(Input(input.to_string()));
225                true
226            }
227            _ => false,
228        }
229    }
230
231    fn debug(
232        &self,
233        _bounds: gpui::geometry::rect::RectF,
234        _layout: &Self::LayoutState,
235        _paint: &Self::PaintState,
236        _cx: &gpui::DebugContext,
237    ) -> gpui::serde_json::Value {
238        json!({
239            "type": "TerminalElement",
240        })
241    }
242}
243
244fn make_style_from_cell(fg: &AnsiColor, flags: &Flags) -> HighlightStyle {
245    let fg = Some(alac_color_to_gpui_color(fg));
246    let underline = if flags.contains(Flags::UNDERLINE) {
247        Some(Underline {
248            color: fg,
249            squiggly: false,
250            thickness: OrderedFloat(1.),
251        })
252    } else {
253        None
254    };
255    HighlightStyle {
256        color: fg,
257        underline,
258        ..Default::default()
259    }
260}
261
262fn alac_color_to_gpui_color(allac_color: &AnsiColor) -> Color {
263    match allac_color {
264        alacritty_terminal::ansi::Color::Named(n) => match n {
265            alacritty_terminal::ansi::NamedColor::Black => Color::black(),
266            alacritty_terminal::ansi::NamedColor::Red => Color::red(),
267            alacritty_terminal::ansi::NamedColor::Green => Color::green(),
268            alacritty_terminal::ansi::NamedColor::Yellow => Color::yellow(),
269            alacritty_terminal::ansi::NamedColor::Blue => Color::blue(),
270            alacritty_terminal::ansi::NamedColor::Magenta => Color::new(188, 63, 188, 1),
271            alacritty_terminal::ansi::NamedColor::Cyan => Color::new(17, 168, 205, 1),
272            alacritty_terminal::ansi::NamedColor::White => Color::white(),
273            alacritty_terminal::ansi::NamedColor::BrightBlack => Color::new(102, 102, 102, 1),
274            alacritty_terminal::ansi::NamedColor::BrightRed => Color::new(102, 102, 102, 1),
275            alacritty_terminal::ansi::NamedColor::BrightGreen => Color::new(35, 209, 139, 1),
276            alacritty_terminal::ansi::NamedColor::BrightYellow => Color::new(245, 245, 67, 1),
277            alacritty_terminal::ansi::NamedColor::BrightBlue => Color::new(59, 142, 234, 1),
278            alacritty_terminal::ansi::NamedColor::BrightMagenta => Color::new(214, 112, 214, 1),
279            alacritty_terminal::ansi::NamedColor::BrightCyan => Color::new(41, 184, 219, 1),
280            alacritty_terminal::ansi::NamedColor::BrightWhite => Color::new(229, 229, 229, 1),
281            alacritty_terminal::ansi::NamedColor::Foreground => Color::white(),
282            alacritty_terminal::ansi::NamedColor::Background => Color::black(),
283            alacritty_terminal::ansi::NamedColor::Cursor => Color::white(),
284            alacritty_terminal::ansi::NamedColor::DimBlack => Color::white(),
285            alacritty_terminal::ansi::NamedColor::DimRed => Color::white(),
286            alacritty_terminal::ansi::NamedColor::DimGreen => Color::white(),
287            alacritty_terminal::ansi::NamedColor::DimYellow => Color::white(),
288            alacritty_terminal::ansi::NamedColor::DimBlue => Color::white(),
289            alacritty_terminal::ansi::NamedColor::DimMagenta => Color::white(),
290            alacritty_terminal::ansi::NamedColor::DimCyan => Color::white(),
291            alacritty_terminal::ansi::NamedColor::DimWhite => Color::white(),
292            alacritty_terminal::ansi::NamedColor::BrightForeground => Color::white(),
293            alacritty_terminal::ansi::NamedColor::DimForeground => Color::white(),
294        }, //Theme defined
295        alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, 1),
296        alacritty_terminal::ansi::Color::Indexed(_) => Color::white(), //Color cube weirdness
297    }
298}
299
300fn make_cursor(em_width: f32, line_height: f32, cursor: RenderableCursor) -> Option<RectF> {
301    Some(RectF::new(
302        vec2f(
303            cursor.point.column.0 as f32 * em_width,
304            cursor.point.line.0 as f32 * line_height,
305        ),
306        vec2f(em_width, line_height),
307    ))
308}
309
310fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
311    for col in 0..(bounds.0[2] / layout.em_width) as usize {
312        let rect_origin = bounds.origin() + vec2f(col as f32 * layout.em_width, 0.);
313        let line = RectF::new(rect_origin, vec2f(1., bounds.0[3]));
314        cx.scene.push_quad(Quad {
315            bounds: line,
316            background: Some(Color::green()),
317            border: Default::default(),
318            corner_radius: 0.,
319        });
320    }
321    for row in 0..(bounds.0[3] / layout.line_height) as usize {
322        let rect_origin = bounds.origin() + vec2f(0., row as f32 * layout.line_height);
323        let line = RectF::new(rect_origin, vec2f(bounds.0[2], 1.));
324        cx.scene.push_quad(Quad {
325            bounds: line,
326            background: Some(Color::green()),
327            border: Default::default(),
328            corner_radius: 0.,
329        });
330    }
331}