terminal_element.rs

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