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}