connected_el.rs

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