connected_el.rs

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