1use alacritty_terminal::{
2 ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor},
3 grid::Dimensions,
4 index::Point,
5 selection::SelectionRange,
6 term::{
7 cell::{Cell, Flags},
8 TermMode,
9 },
10};
11use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
12use gpui::{
13 color::Color,
14 fonts::{Properties, Style::Italic, TextStyle, Underline, Weight},
15 geometry::{
16 rect::RectF,
17 vector::{vec2f, Vector2F},
18 },
19 serde_json::json,
20 text_layout::{Line, RunStyle},
21 Element, Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton,
22 MouseButtonEvent, MouseRegion, PaintContext, Quad, TextLayoutCache, WeakModelHandle,
23 WeakViewHandle,
24};
25use itertools::Itertools;
26use ordered_float::OrderedFloat;
27use settings::Settings;
28use theme::TerminalStyle;
29use util::ResultExt;
30
31use std::fmt::Debug;
32use std::{
33 mem,
34 ops::{Deref, Range},
35};
36
37use crate::{
38 connected_view::{ConnectedView, DeployContextMenu},
39 mappings::colors::convert_color,
40 Terminal, TerminalSize,
41};
42
43///The information generated during layout that is nescessary for painting
44pub struct LayoutState {
45 cells: Vec<LayoutCell>,
46 rects: Vec<LayoutRect>,
47 highlights: Vec<RelativeHighlightedRange>,
48 cursor: Option<Cursor>,
49 background_color: Color,
50 selection_color: Color,
51 size: TerminalSize,
52 mode: TermMode,
53}
54
55#[derive(Debug)]
56struct IndexedCell {
57 point: Point,
58 cell: Cell,
59}
60
61impl Deref for IndexedCell {
62 type Target = Cell;
63
64 #[inline]
65 fn deref(&self) -> &Cell {
66 &self.cell
67 }
68}
69
70///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
71struct DisplayCursor {
72 line: i32,
73 col: usize,
74}
75
76impl DisplayCursor {
77 fn from(cursor_point: Point, display_offset: usize) -> Self {
78 Self {
79 line: cursor_point.line.0 + display_offset as i32,
80 col: cursor_point.column.0,
81 }
82 }
83
84 pub fn line(&self) -> i32 {
85 self.line
86 }
87
88 pub fn col(&self) -> usize {
89 self.col
90 }
91}
92
93#[derive(Clone, Debug, Default)]
94struct LayoutCell {
95 point: Point<i32, i32>,
96 text: Line,
97}
98
99impl LayoutCell {
100 fn new(point: Point<i32, i32>, text: Line) -> LayoutCell {
101 LayoutCell { point, text }
102 }
103
104 fn paint(
105 &self,
106 origin: Vector2F,
107 layout: &LayoutState,
108 visible_bounds: RectF,
109 cx: &mut PaintContext,
110 ) {
111 let pos = {
112 let point = self.point;
113 vec2f(
114 (origin.x() + point.column as f32 * layout.size.cell_width).floor(),
115 origin.y() + point.line as f32 * layout.size.line_height,
116 )
117 };
118
119 self.text
120 .paint(pos, visible_bounds, layout.size.line_height, cx);
121 }
122}
123
124#[derive(Clone, Debug, Default)]
125struct LayoutRect {
126 point: Point<i32, i32>,
127 num_of_cells: usize,
128 color: Color,
129}
130
131impl LayoutRect {
132 fn new(point: Point<i32, i32>, num_of_cells: usize, color: Color) -> LayoutRect {
133 LayoutRect {
134 point,
135 num_of_cells,
136 color,
137 }
138 }
139
140 fn extend(&self) -> Self {
141 LayoutRect {
142 point: self.point,
143 num_of_cells: self.num_of_cells + 1,
144 color: self.color,
145 }
146 }
147
148 fn paint(&self, origin: Vector2F, layout: &LayoutState, cx: &mut PaintContext) {
149 let position = {
150 let point = self.point;
151 vec2f(
152 (origin.x() + point.column as f32 * layout.size.cell_width).floor(),
153 origin.y() + point.line as f32 * layout.size.line_height,
154 )
155 };
156 let size = vec2f(
157 (layout.size.cell_width * self.num_of_cells as f32).ceil(),
158 layout.size.line_height,
159 );
160
161 cx.scene.push_quad(Quad {
162 bounds: RectF::new(position, size),
163 background: Some(self.color),
164 border: Default::default(),
165 corner_radius: 0.,
166 })
167 }
168}
169
170#[derive(Clone, Debug, Default)]
171struct RelativeHighlightedRange {
172 line_index: usize,
173 range: Range<usize>,
174}
175
176impl RelativeHighlightedRange {
177 fn new(line_index: usize, range: Range<usize>) -> Self {
178 RelativeHighlightedRange { line_index, range }
179 }
180
181 fn to_highlighted_range_line(
182 &self,
183 origin: Vector2F,
184 layout: &LayoutState,
185 ) -> HighlightedRangeLine {
186 let start_x = origin.x() + self.range.start as f32 * layout.size.cell_width;
187 let end_x =
188 origin.x() + self.range.end as f32 * layout.size.cell_width + layout.size.cell_width;
189
190 HighlightedRangeLine { start_x, end_x }
191 }
192}
193
194///The GPUI element that paints the terminal.
195///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
196pub struct TerminalEl {
197 terminal: WeakModelHandle<Terminal>,
198 view: WeakViewHandle<ConnectedView>,
199 modal: bool,
200 focused: bool,
201 cursor_visible: bool,
202}
203
204impl TerminalEl {
205 pub fn new(
206 view: WeakViewHandle<ConnectedView>,
207 terminal: WeakModelHandle<Terminal>,
208 modal: bool,
209 focused: bool,
210 cursor_visible: bool,
211 ) -> TerminalEl {
212 TerminalEl {
213 view,
214 terminal,
215 modal,
216 focused,
217 cursor_visible,
218 }
219 }
220
221 fn layout_grid(
222 grid: Vec<IndexedCell>,
223 text_style: &TextStyle,
224 terminal_theme: &TerminalStyle,
225 text_layout_cache: &TextLayoutCache,
226 font_cache: &FontCache,
227 modal: bool,
228 selection_range: Option<SelectionRange>,
229 ) -> (
230 Vec<LayoutCell>,
231 Vec<LayoutRect>,
232 Vec<RelativeHighlightedRange>,
233 ) {
234 let mut cells = vec![];
235 let mut rects = vec![];
236 let mut highlight_ranges = vec![];
237
238 let mut cur_rect: Option<LayoutRect> = None;
239 let mut cur_alac_color = None;
240 let mut highlighted_range = None;
241
242 let linegroups = grid.into_iter().group_by(|i| i.point.line);
243 for (line_index, (_, line)) in linegroups.into_iter().enumerate() {
244 for (x_index, cell) in line.enumerate() {
245 let mut fg = cell.fg;
246 let mut bg = cell.bg;
247 if cell.flags.contains(Flags::INVERSE) {
248 mem::swap(&mut fg, &mut bg);
249 }
250
251 //Increase selection range
252 {
253 if selection_range
254 .map(|range| range.contains(cell.point))
255 .unwrap_or(false)
256 {
257 let mut range = highlighted_range.take().unwrap_or(x_index..x_index);
258 range.end = range.end.max(x_index);
259 highlighted_range = Some(range);
260 }
261 }
262
263 //Expand background rect range
264 {
265 if matches!(bg, Named(NamedColor::Background)) {
266 //Continue to next cell, resetting variables if nescessary
267 cur_alac_color = None;
268 if let Some(rect) = cur_rect {
269 rects.push(rect);
270 cur_rect = None
271 }
272 } else {
273 match cur_alac_color {
274 Some(cur_color) => {
275 if bg == cur_color {
276 cur_rect = cur_rect.take().map(|rect| rect.extend());
277 } else {
278 cur_alac_color = Some(bg);
279 if cur_rect.is_some() {
280 rects.push(cur_rect.take().unwrap());
281 }
282 cur_rect = Some(LayoutRect::new(
283 Point::new(line_index as i32, cell.point.column.0 as i32),
284 1,
285 convert_color(&bg, &terminal_theme.colors, modal),
286 ));
287 }
288 }
289 None => {
290 cur_alac_color = Some(bg);
291 cur_rect = Some(LayoutRect::new(
292 Point::new(line_index as i32, cell.point.column.0 as i32),
293 1,
294 convert_color(&bg, &terminal_theme.colors, modal),
295 ));
296 }
297 }
298 }
299 }
300
301 //Layout current cell text
302 {
303 let cell_text = &cell.c.to_string();
304 if cell_text != " " {
305 let cell_style = TerminalEl::cell_style(
306 &cell,
307 fg,
308 terminal_theme,
309 text_style,
310 font_cache,
311 modal,
312 );
313
314 let layout_cell = text_layout_cache.layout_str(
315 cell_text,
316 text_style.font_size,
317 &[(cell_text.len(), cell_style)],
318 );
319
320 cells.push(LayoutCell::new(
321 Point::new(line_index as i32, cell.point.column.0 as i32),
322 layout_cell,
323 ))
324 }
325 };
326 }
327
328 if highlighted_range.is_some() {
329 highlight_ranges.push(RelativeHighlightedRange::new(
330 line_index,
331 highlighted_range.take().unwrap(),
332 ))
333 }
334
335 if cur_rect.is_some() {
336 rects.push(cur_rect.take().unwrap());
337 }
338 }
339 (cells, rects, highlight_ranges)
340 }
341
342 // Compute the cursor position and expected block width, may return a zero width if x_for_index returns
343 // the same position for sequential indexes. Use em_width instead
344 fn shape_cursor(
345 cursor_point: DisplayCursor,
346 size: TerminalSize,
347 text_fragment: &Line,
348 ) -> Option<(Vector2F, f32)> {
349 if cursor_point.line() < size.total_lines() as i32 {
350 let cursor_width = if text_fragment.width() == 0. {
351 size.cell_width()
352 } else {
353 text_fragment.width()
354 };
355
356 //Cursor should always surround as much of the text as possible,
357 //hence when on pixel boundaries round the origin down and the width up
358 Some((
359 vec2f(
360 (cursor_point.col() as f32 * size.cell_width()).floor(),
361 (cursor_point.line() as f32 * size.line_height()).floor(),
362 ),
363 cursor_width.ceil(),
364 ))
365 } else {
366 None
367 }
368 }
369
370 ///Convert the Alacritty cell styles to GPUI text styles and background color
371 fn cell_style(
372 indexed: &IndexedCell,
373 fg: AnsiColor,
374 style: &TerminalStyle,
375 text_style: &TextStyle,
376 font_cache: &FontCache,
377 modal: bool,
378 ) -> RunStyle {
379 let flags = indexed.cell.flags;
380 let fg = convert_color(&fg, &style.colors, modal);
381
382 let underline = flags
383 .intersects(Flags::ALL_UNDERLINES)
384 .then(|| Underline {
385 color: Some(fg),
386 squiggly: flags.contains(Flags::UNDERCURL),
387 thickness: OrderedFloat(1.),
388 })
389 .unwrap_or_default();
390
391 let mut properties = Properties::new();
392 if indexed
393 .flags
394 .intersects(Flags::BOLD | Flags::BOLD_ITALIC | Flags::DIM_BOLD)
395 {
396 properties = *properties.weight(Weight::BOLD);
397 }
398 if indexed.flags.intersects(Flags::ITALIC | Flags::BOLD_ITALIC) {
399 properties = *properties.style(Italic);
400 }
401
402 let font_id = font_cache
403 .select_font(text_style.font_family_id, &properties)
404 .unwrap_or(text_style.font_id);
405
406 RunStyle {
407 color: fg,
408 font_id,
409 underline,
410 }
411 }
412
413 fn generic_button_handler(
414 connection: WeakModelHandle<Terminal>,
415 origin: Vector2F,
416 f: impl Fn(&mut Terminal, Vector2F, MouseButtonEvent, &mut ModelContext<Terminal>),
417 ) -> impl Fn(MouseButtonEvent, &mut EventContext) {
418 move |event, cx| {
419 cx.focus_parent_view();
420 if let Some(conn_handle) = connection.upgrade(cx.app) {
421 conn_handle.update(cx.app, |terminal, cx| {
422 f(terminal, origin, event, cx);
423
424 cx.notify();
425 })
426 }
427 }
428 }
429
430 fn attach_mouse_handlers(
431 &self,
432 origin: Vector2F,
433 view_id: usize,
434 visible_bounds: RectF,
435 mode: TermMode,
436 cx: &mut PaintContext,
437 ) {
438 let connection = self.terminal;
439
440 let mut region = MouseRegion::new(view_id, None, visible_bounds);
441
442 //Terminal Emulator controlled behavior:
443 region = region
444 //Start selections
445 .on_down(
446 MouseButton::Left,
447 TerminalEl::generic_button_handler(
448 connection,
449 origin,
450 move |terminal, origin, e, _cx| {
451 terminal.mouse_down(&e, origin);
452 },
453 ),
454 )
455 //Update drag selections
456 .on_drag(MouseButton::Left, move |_prev, event, cx| {
457 if cx.is_parent_view_focused() {
458 if let Some(conn_handle) = connection.upgrade(cx.app) {
459 conn_handle.update(cx.app, |terminal, cx| {
460 terminal.mouse_drag(event, origin);
461 cx.notify();
462 })
463 }
464 }
465 })
466 //Copy on up behavior
467 .on_up(
468 MouseButton::Left,
469 TerminalEl::generic_button_handler(
470 connection,
471 origin,
472 move |terminal, origin, e, _cx| {
473 terminal.mouse_up(&e, origin);
474 },
475 ),
476 )
477 //Handle click based selections
478 .on_click(
479 MouseButton::Left,
480 TerminalEl::generic_button_handler(
481 connection,
482 origin,
483 move |terminal, origin, e, _cx| {
484 terminal.left_click(&e, origin);
485 },
486 ),
487 )
488 //Context menu
489 .on_click(
490 MouseButton::Right,
491 move |e @ MouseButtonEvent { position, .. }, cx| {
492 let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) {
493 conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift))
494 } else {
495 //If we can't get the model handle, probably can't deploy the context menu
496 true
497 };
498 if !mouse_mode {
499 cx.dispatch_action(DeployContextMenu { position });
500 }
501 },
502 )
503 //This handles both drag mode and mouse motion mode
504 //Mouse Move TODO
505 //This cannot be done conditionally for unknown reasons. Pending drag and drop rework.
506 //This also does not fire on right-mouse-down-move events wild.
507 .on_move(move |event, cx| {
508 if cx.is_parent_view_focused() {
509 if let Some(conn_handle) = connection.upgrade(cx.app) {
510 conn_handle.update(cx.app, |terminal, cx| {
511 terminal.mouse_move(&event, origin);
512 cx.notify();
513 })
514 }
515 }
516 });
517
518 if mode.contains(TermMode::MOUSE_MODE) {
519 region = region
520 .on_down(
521 MouseButton::Right,
522 TerminalEl::generic_button_handler(
523 connection,
524 origin,
525 move |terminal, origin, e, _cx| {
526 terminal.mouse_down(&e, origin);
527 },
528 ),
529 )
530 .on_down(
531 MouseButton::Middle,
532 TerminalEl::generic_button_handler(
533 connection,
534 origin,
535 move |terminal, origin, e, _cx| {
536 terminal.mouse_down(&e, origin);
537 },
538 ),
539 )
540 .on_up(
541 MouseButton::Right,
542 TerminalEl::generic_button_handler(
543 connection,
544 origin,
545 move |terminal, origin, e, _cx| {
546 terminal.mouse_up(&e, origin);
547 },
548 ),
549 )
550 .on_up(
551 MouseButton::Middle,
552 TerminalEl::generic_button_handler(
553 connection,
554 origin,
555 move |terminal, origin, e, _cx| {
556 terminal.mouse_up(&e, origin);
557 },
558 ),
559 )
560 }
561
562 //TODO: Mouse drag isn't correct
563 //TODO: Nor is mouse motion. Move events aren't happening??
564 cx.scene.push_mouse_region(region);
565 }
566
567 ///Configures a text style from the current settings.
568 pub fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
569 // Pull the font family from settings properly overriding
570 let family_id = settings
571 .terminal_overrides
572 .font_family
573 .as_ref()
574 .or(settings.terminal_defaults.font_family.as_ref())
575 .and_then(|family_name| font_cache.load_family(&[family_name]).log_err())
576 .unwrap_or(settings.buffer_font_family);
577
578 let font_size = settings
579 .terminal_overrides
580 .font_size
581 .or(settings.terminal_defaults.font_size)
582 .unwrap_or(settings.buffer_font_size);
583
584 let font_id = font_cache
585 .select_font(family_id, &Default::default())
586 .unwrap();
587
588 TextStyle {
589 color: settings.theme.editor.text_color,
590 font_family_id: family_id,
591 font_family_name: font_cache.family_name(family_id).unwrap(),
592 font_id,
593 font_size,
594 font_properties: Default::default(),
595 underline: Default::default(),
596 }
597 }
598}
599
600impl Element for TerminalEl {
601 type LayoutState = LayoutState;
602 type PaintState = ();
603
604 fn layout(
605 &mut self,
606 constraint: gpui::SizeConstraint,
607 cx: &mut gpui::LayoutContext,
608 ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
609 let settings = cx.global::<Settings>();
610 let font_cache = cx.font_cache();
611
612 //Setup layout information
613 let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone.
614 let text_style = TerminalEl::make_text_style(font_cache, settings);
615 let selection_color = settings.theme.editor.selection.selection;
616 let dimensions = {
617 let line_height = font_cache.line_height(text_style.font_size);
618 let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size);
619 TerminalSize::new(line_height, cell_width, constraint.max)
620 };
621
622 let background_color = if self.modal {
623 terminal_theme.colors.modal_background
624 } else {
625 terminal_theme.colors.background
626 };
627
628 let (cells, selection, cursor, display_offset, cursor_text, mode) = self
629 .terminal
630 .upgrade(cx)
631 .unwrap()
632 .update(cx.app, |terminal, mcx| {
633 terminal.set_size(dimensions);
634 terminal.render_lock(mcx, |content, cursor_text| {
635 let mut cells = vec![];
636 cells.extend(
637 content
638 .display_iter
639 //TODO: Add this once there's a way to retain empty lines
640 // .filter(|ic| {
641 // !ic.flags.contains(Flags::HIDDEN)
642 // && !(ic.bg == Named(NamedColor::Background)
643 // && ic.c == ' '
644 // && !ic.flags.contains(Flags::INVERSE))
645 // })
646 .map(|ic| IndexedCell {
647 point: ic.point,
648 cell: ic.cell.clone(),
649 }),
650 );
651 (
652 cells,
653 content.selection,
654 content.cursor,
655 content.display_offset,
656 cursor_text,
657 content.mode,
658 )
659 })
660 });
661
662 let (cells, rects, highlights) = TerminalEl::layout_grid(
663 cells,
664 &text_style,
665 &terminal_theme,
666 cx.text_layout_cache,
667 cx.font_cache(),
668 self.modal,
669 selection,
670 );
671
672 //Layout cursor. Rectangle is used for IME, so we should lay it out even
673 //if we don't end up showing it.
674 let cursor = if let AlacCursorShape::Hidden = cursor.shape {
675 None
676 } else {
677 let cursor_point = DisplayCursor::from(cursor.point, display_offset);
678 let cursor_text = {
679 let str_trxt = cursor_text.to_string();
680
681 let color = if self.focused {
682 terminal_theme.colors.background
683 } else {
684 terminal_theme.colors.foreground
685 };
686
687 cx.text_layout_cache.layout_str(
688 &str_trxt,
689 text_style.font_size,
690 &[(
691 str_trxt.len(),
692 RunStyle {
693 font_id: text_style.font_id,
694 color,
695 underline: Default::default(),
696 },
697 )],
698 )
699 };
700
701 TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map(
702 move |(cursor_position, block_width)| {
703 let shape = match cursor.shape {
704 AlacCursorShape::Block if !self.focused => CursorShape::Hollow,
705 AlacCursorShape::Block => CursorShape::Block,
706 AlacCursorShape::Underline => CursorShape::Underscore,
707 AlacCursorShape::Beam => CursorShape::Bar,
708 AlacCursorShape::HollowBlock => CursorShape::Hollow,
709 //This case is handled in the if wrapping the whole cursor layout
710 AlacCursorShape::Hidden => unreachable!(),
711 };
712
713 Cursor::new(
714 cursor_position,
715 block_width,
716 dimensions.line_height,
717 terminal_theme.colors.cursor,
718 shape,
719 Some(cursor_text),
720 )
721 },
722 )
723 };
724
725 //Done!
726 (
727 constraint.max,
728 LayoutState {
729 cells,
730 cursor,
731 background_color,
732 selection_color,
733 size: dimensions,
734 rects,
735 highlights,
736 mode,
737 },
738 )
739 }
740
741 fn paint(
742 &mut self,
743 bounds: gpui::geometry::rect::RectF,
744 visible_bounds: gpui::geometry::rect::RectF,
745 layout: &mut Self::LayoutState,
746 cx: &mut gpui::PaintContext,
747 ) -> Self::PaintState {
748 //Setup element stuff
749 let clip_bounds = Some(visible_bounds);
750
751 cx.paint_layer(clip_bounds, |cx| {
752 let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
753
754 //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
755 self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, layout.mode, cx);
756
757 cx.paint_layer(clip_bounds, |cx| {
758 //Start with a background color
759 cx.scene.push_quad(Quad {
760 bounds: RectF::new(bounds.origin(), bounds.size()),
761 background: Some(layout.background_color),
762 border: Default::default(),
763 corner_radius: 0.,
764 });
765
766 for rect in &layout.rects {
767 rect.paint(origin, layout, cx)
768 }
769 });
770
771 //Draw Selection
772 cx.paint_layer(clip_bounds, |cx| {
773 let start_y = layout.highlights.get(0).map(|highlight| {
774 origin.y() + highlight.line_index as f32 * layout.size.line_height
775 });
776
777 if let Some(y) = start_y {
778 let range_lines = layout
779 .highlights
780 .iter()
781 .map(|relative_highlight| {
782 relative_highlight.to_highlighted_range_line(origin, layout)
783 })
784 .collect::<Vec<HighlightedRangeLine>>();
785
786 let hr = HighlightedRange {
787 start_y: y, //Need to change this
788 line_height: layout.size.line_height,
789 lines: range_lines,
790 color: layout.selection_color,
791 //Copied from editor. TODO: move to theme or something
792 corner_radius: 0.15 * layout.size.line_height,
793 };
794 hr.paint(bounds, cx.scene);
795 }
796 });
797
798 //Draw the text cells
799 cx.paint_layer(clip_bounds, |cx| {
800 for cell in &layout.cells {
801 cell.paint(origin, layout, visible_bounds, cx);
802 }
803 });
804
805 //Draw cursor
806 if self.cursor_visible {
807 if let Some(cursor) = &layout.cursor {
808 cx.paint_layer(clip_bounds, |cx| {
809 cursor.paint(origin, cx);
810 })
811 }
812 }
813 });
814 }
815
816 fn dispatch_event(
817 &mut self,
818 event: &gpui::Event,
819 bounds: gpui::geometry::rect::RectF,
820 visible_bounds: gpui::geometry::rect::RectF,
821 layout: &mut Self::LayoutState,
822 _paint: &mut Self::PaintState,
823 cx: &mut gpui::EventContext,
824 ) -> bool {
825 match event {
826 Event::ScrollWheel(e) => visible_bounds
827 .contains_point(e.position)
828 .then(|| {
829 let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
830
831 if let Some(terminal) = self.terminal.upgrade(cx.app) {
832 terminal.update(cx.app, |term, _| term.scroll(e, origin));
833 cx.notify();
834 }
835 })
836 .is_some(),
837 Event::KeyDown(KeyDownEvent { keystroke, .. }) => {
838 if !cx.is_parent_view_focused() {
839 return false;
840 }
841
842 if let Some(view) = self.view.upgrade(cx.app) {
843 view.update(cx.app, |view, cx| {
844 view.clear_bel(cx);
845 view.pause_cursor_blinking(cx);
846 })
847 }
848
849 self.terminal
850 .upgrade(cx.app)
851 .map(|model_handle| {
852 model_handle.update(cx.app, |term, _| term.try_keystroke(keystroke))
853 })
854 .unwrap_or(false)
855 }
856 _ => false,
857 }
858 }
859
860 fn metadata(&self) -> Option<&dyn std::any::Any> {
861 None
862 }
863
864 fn debug(
865 &self,
866 _bounds: gpui::geometry::rect::RectF,
867 _layout: &Self::LayoutState,
868 _paint: &Self::PaintState,
869 _cx: &gpui::DebugContext,
870 ) -> gpui::serde_json::Value {
871 json!({
872 "type": "TerminalElement",
873 })
874 }
875
876 fn rect_for_text_range(
877 &self,
878 _: Range<usize>,
879 bounds: RectF,
880 _: RectF,
881 layout: &Self::LayoutState,
882 _: &Self::PaintState,
883 _: &gpui::MeasurementContext,
884 ) -> Option<RectF> {
885 // Use the same origin that's passed to `Cursor::paint` in the paint
886 // method bove.
887 let mut origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
888
889 // TODO - Why is it necessary to move downward one line to get correct
890 // positioning? I would think that we'd want the same rect that is
891 // painted for the cursor.
892 origin += vec2f(0., layout.size.line_height);
893
894 Some(layout.cursor.as_ref()?.bounding_rect(origin))
895 }
896}