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}