terminal_element.rs

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