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