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