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