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 mappings::colors::convert_color,
39 terminal_view::{DeployContextMenu, TerminalView},
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 TerminalElement {
197 terminal: WeakModelHandle<Terminal>,
198 view: WeakViewHandle<TerminalView>,
199 modal: bool,
200 focused: bool,
201 cursor_visible: bool,
202}
203
204impl TerminalElement {
205 pub fn new(
206 view: WeakViewHandle<TerminalView>,
207 terminal: WeakModelHandle<Terminal>,
208 modal: bool,
209 focused: bool,
210 cursor_visible: bool,
211 ) -> TerminalElement {
212 TerminalElement {
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 = TerminalElement::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 TerminalElement::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 TerminalElement::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 TerminalElement::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
504 // Mouse mode handlers:
505 // All mouse modes need the extra click handlers
506 if mode.intersects(TermMode::MOUSE_MODE) {
507 region = region
508 .on_down(
509 MouseButton::Right,
510 TerminalElement::generic_button_handler(
511 connection,
512 origin,
513 move |terminal, origin, e, _cx| {
514 terminal.mouse_down(&e, origin);
515 },
516 ),
517 )
518 .on_down(
519 MouseButton::Middle,
520 TerminalElement::generic_button_handler(
521 connection,
522 origin,
523 move |terminal, origin, e, _cx| {
524 terminal.mouse_down(&e, origin);
525 },
526 ),
527 )
528 .on_up(
529 MouseButton::Right,
530 TerminalElement::generic_button_handler(
531 connection,
532 origin,
533 move |terminal, origin, e, _cx| {
534 terminal.mouse_up(&e, origin);
535 },
536 ),
537 )
538 .on_up(
539 MouseButton::Middle,
540 TerminalElement::generic_button_handler(
541 connection,
542 origin,
543 move |terminal, origin, e, _cx| {
544 terminal.mouse_up(&e, origin);
545 },
546 ),
547 )
548 }
549 //Mouse move manages both dragging and motion events
550 if mode.intersects(TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION) {
551 region = region
552 //TODO: This does not fire on right-mouse-down-move events.
553 .on_move(move |event, cx| {
554 if cx.is_parent_view_focused() {
555 if let Some(conn_handle) = connection.upgrade(cx.app) {
556 conn_handle.update(cx.app, |terminal, cx| {
557 terminal.mouse_move(&event, origin);
558 cx.notify();
559 })
560 }
561 }
562 })
563 }
564
565 cx.scene.push_mouse_region(region);
566 }
567
568 ///Configures a text style from the current settings.
569 pub fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
570 // Pull the font family from settings properly overriding
571 let family_id = settings
572 .terminal_overrides
573 .font_family
574 .as_ref()
575 .or(settings.terminal_defaults.font_family.as_ref())
576 .and_then(|family_name| font_cache.load_family(&[family_name]).log_err())
577 .unwrap_or(settings.buffer_font_family);
578
579 let font_size = settings
580 .terminal_overrides
581 .font_size
582 .or(settings.terminal_defaults.font_size)
583 .unwrap_or(settings.buffer_font_size);
584
585 let font_id = font_cache
586 .select_font(family_id, &Default::default())
587 .unwrap();
588
589 TextStyle {
590 color: settings.theme.editor.text_color,
591 font_family_id: family_id,
592 font_family_name: font_cache.family_name(family_id).unwrap(),
593 font_id,
594 font_size,
595 font_properties: Default::default(),
596 underline: Default::default(),
597 }
598 }
599}
600
601impl Element for TerminalElement {
602 type LayoutState = LayoutState;
603 type PaintState = ();
604
605 fn layout(
606 &mut self,
607 constraint: gpui::SizeConstraint,
608 cx: &mut gpui::LayoutContext,
609 ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
610 let settings = cx.global::<Settings>();
611 let font_cache = cx.font_cache();
612
613 //Setup layout information
614 let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone.
615 let text_style = TerminalElement::make_text_style(font_cache, settings);
616 let selection_color = settings.theme.editor.selection.selection;
617 let dimensions = {
618 let line_height = font_cache.line_height(text_style.font_size);
619 let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size);
620 TerminalSize::new(line_height, cell_width, constraint.max)
621 };
622
623 let background_color = if self.modal {
624 terminal_theme.colors.modal_background
625 } else {
626 terminal_theme.colors.background
627 };
628
629 let (cells, selection, cursor, display_offset, cursor_text, mode) = self
630 .terminal
631 .upgrade(cx)
632 .unwrap()
633 .update(cx.app, |terminal, mcx| {
634 terminal.set_size(dimensions);
635 terminal.render_lock(mcx, |content, cursor_text| {
636 let mut cells = vec![];
637 cells.extend(
638 content
639 .display_iter
640 //TODO: Add this once there's a way to retain empty lines
641 // .filter(|ic| {
642 // !ic.flags.contains(Flags::HIDDEN)
643 // && !(ic.bg == Named(NamedColor::Background)
644 // && ic.c == ' '
645 // && !ic.flags.contains(Flags::INVERSE))
646 // })
647 .map(|ic| IndexedCell {
648 point: ic.point,
649 cell: ic.cell.clone(),
650 }),
651 );
652 (
653 cells,
654 content.selection,
655 content.cursor,
656 content.display_offset,
657 cursor_text,
658 content.mode,
659 )
660 })
661 });
662
663 let (cells, rects, highlights) = TerminalElement::layout_grid(
664 cells,
665 &text_style,
666 &terminal_theme,
667 cx.text_layout_cache,
668 cx.font_cache(),
669 self.modal,
670 selection,
671 );
672
673 //Layout cursor. Rectangle is used for IME, so we should lay it out even
674 //if we don't end up showing it.
675 let cursor = if let AlacCursorShape::Hidden = cursor.shape {
676 None
677 } else {
678 let cursor_point = DisplayCursor::from(cursor.point, display_offset);
679 let cursor_text = {
680 let str_trxt = cursor_text.to_string();
681
682 let color = if self.focused {
683 terminal_theme.colors.background
684 } else {
685 terminal_theme.colors.foreground
686 };
687
688 cx.text_layout_cache.layout_str(
689 &str_trxt,
690 text_style.font_size,
691 &[(
692 str_trxt.len(),
693 RunStyle {
694 font_id: text_style.font_id,
695 color,
696 underline: Default::default(),
697 },
698 )],
699 )
700 };
701
702 TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map(
703 move |(cursor_position, block_width)| {
704 let shape = match cursor.shape {
705 AlacCursorShape::Block if !self.focused => CursorShape::Hollow,
706 AlacCursorShape::Block => CursorShape::Block,
707 AlacCursorShape::Underline => CursorShape::Underscore,
708 AlacCursorShape::Beam => CursorShape::Bar,
709 AlacCursorShape::HollowBlock => CursorShape::Hollow,
710 //This case is handled in the if wrapping the whole cursor layout
711 AlacCursorShape::Hidden => unreachable!(),
712 };
713
714 Cursor::new(
715 cursor_position,
716 block_width,
717 dimensions.line_height,
718 terminal_theme.colors.cursor,
719 shape,
720 Some(cursor_text),
721 )
722 },
723 )
724 };
725
726 //Done!
727 (
728 constraint.max,
729 LayoutState {
730 cells,
731 cursor,
732 background_color,
733 selection_color,
734 size: dimensions,
735 rects,
736 highlights,
737 mode,
738 },
739 )
740 }
741
742 fn paint(
743 &mut self,
744 bounds: gpui::geometry::rect::RectF,
745 visible_bounds: gpui::geometry::rect::RectF,
746 layout: &mut Self::LayoutState,
747 cx: &mut gpui::PaintContext,
748 ) -> Self::PaintState {
749 //Setup element stuff
750 let clip_bounds = Some(visible_bounds);
751
752 cx.paint_layer(clip_bounds, |cx| {
753 let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
754
755 //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
756 self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, layout.mode, cx);
757
758 cx.paint_layer(clip_bounds, |cx| {
759 //Start with a background color
760 cx.scene.push_quad(Quad {
761 bounds: RectF::new(bounds.origin(), bounds.size()),
762 background: Some(layout.background_color),
763 border: Default::default(),
764 corner_radius: 0.,
765 });
766
767 for rect in &layout.rects {
768 rect.paint(origin, layout, cx)
769 }
770 });
771
772 //Draw Selection
773 cx.paint_layer(clip_bounds, |cx| {
774 let start_y = layout.highlights.get(0).map(|highlight| {
775 origin.y() + highlight.line_index as f32 * layout.size.line_height
776 });
777
778 if let Some(y) = start_y {
779 let range_lines = layout
780 .highlights
781 .iter()
782 .map(|relative_highlight| {
783 relative_highlight.to_highlighted_range_line(origin, layout)
784 })
785 .collect::<Vec<HighlightedRangeLine>>();
786
787 let hr = HighlightedRange {
788 start_y: y, //Need to change this
789 line_height: layout.size.line_height,
790 lines: range_lines,
791 color: layout.selection_color,
792 //Copied from editor. TODO: move to theme or something
793 corner_radius: 0.15 * layout.size.line_height,
794 };
795 hr.paint(bounds, cx.scene);
796 }
797 });
798
799 //Draw the text cells
800 cx.paint_layer(clip_bounds, |cx| {
801 for cell in &layout.cells {
802 cell.paint(origin, layout, visible_bounds, cx);
803 }
804 });
805
806 //Draw cursor
807 if self.cursor_visible {
808 if let Some(cursor) = &layout.cursor {
809 cx.paint_layer(clip_bounds, |cx| {
810 cursor.paint(origin, cx);
811 })
812 }
813 }
814 });
815 }
816
817 fn dispatch_event(
818 &mut self,
819 event: &gpui::Event,
820 bounds: gpui::geometry::rect::RectF,
821 visible_bounds: gpui::geometry::rect::RectF,
822 layout: &mut Self::LayoutState,
823 _paint: &mut Self::PaintState,
824 cx: &mut gpui::EventContext,
825 ) -> bool {
826 match event {
827 Event::ScrollWheel(e) => visible_bounds
828 .contains_point(e.position)
829 .then(|| {
830 let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
831
832 if let Some(terminal) = self.terminal.upgrade(cx.app) {
833 terminal.update(cx.app, |term, _| term.scroll(e, origin));
834 cx.notify();
835 }
836 })
837 .is_some(),
838 Event::KeyDown(KeyDownEvent { keystroke, .. }) => {
839 if !cx.is_parent_view_focused() {
840 return false;
841 }
842
843 if let Some(view) = self.view.upgrade(cx.app) {
844 view.update(cx.app, |view, cx| {
845 view.clear_bel(cx);
846 view.pause_cursor_blinking(cx);
847 })
848 }
849
850 self.terminal
851 .upgrade(cx.app)
852 .map(|model_handle| {
853 model_handle.update(cx.app, |term, _| term.try_keystroke(keystroke))
854 })
855 .unwrap_or(false)
856 }
857 _ => false,
858 }
859 }
860
861 fn metadata(&self) -> Option<&dyn std::any::Any> {
862 None
863 }
864
865 fn debug(
866 &self,
867 _bounds: gpui::geometry::rect::RectF,
868 _layout: &Self::LayoutState,
869 _paint: &Self::PaintState,
870 _cx: &gpui::DebugContext,
871 ) -> gpui::serde_json::Value {
872 json!({
873 "type": "TerminalElement",
874 })
875 }
876
877 fn rect_for_text_range(
878 &self,
879 _: Range<usize>,
880 bounds: RectF,
881 _: RectF,
882 layout: &Self::LayoutState,
883 _: &Self::PaintState,
884 _: &gpui::MeasurementContext,
885 ) -> Option<RectF> {
886 // Use the same origin that's passed to `Cursor::paint` in the paint
887 // method bove.
888 let mut origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
889
890 // TODO - Why is it necessary to move downward one line to get correct
891 // positioning? I would think that we'd want the same rect that is
892 // painted for the cursor.
893 origin += vec2f(0., layout.size.line_height);
894
895 Some(layout.cursor.as_ref()?.bounding_rect(origin))
896 }
897}