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        println!("Painted a terminal element");
222        //Setup element stuff
223        let clip_bounds = Some(visible_bounds);
224
225        cx.paint_layer(clip_bounds, |cx| {
226            let cur_size = layout.cur_size.clone();
227            let origin = bounds.origin() + vec2f(layout.em_width.0, 0.);
228
229            //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
230            attach_mouse_handlers(
231                origin,
232                cur_size,
233                self.view.id(),
234                &layout.terminal,
235                visible_bounds,
236                cx,
237            );
238
239            cx.paint_layer(clip_bounds, |cx| {
240                //Start with a background color
241                cx.scene.push_quad(Quad {
242                    bounds: RectF::new(bounds.origin(), bounds.size()),
243                    background: Some(layout.background_color),
244                    border: Default::default(),
245                    corner_radius: 0.,
246                });
247
248                //Draw cell backgrounds
249                for layout_line in &layout.layout_lines {
250                    for layout_cell in &layout_line.cells {
251                        let position = vec2f(
252                            origin.x() + layout_cell.point.column as f32 * layout.em_width.0,
253                            origin.y() + layout_cell.point.line as f32 * layout.line_height.0,
254                        );
255                        let size = vec2f(layout.em_width.0, layout.line_height.0);
256
257                        cx.scene.push_quad(Quad {
258                            bounds: RectF::new(position, size),
259                            background: Some(layout_cell.background_color),
260                            border: Default::default(),
261                            corner_radius: 0.,
262                        })
263                    }
264                }
265            });
266
267            //Draw Selection
268            cx.paint_layer(clip_bounds, |cx| {
269                let mut highlight_y = None;
270                let highlight_lines = layout
271                    .layout_lines
272                    .iter()
273                    .filter_map(|line| {
274                        if let Some(range) = &line.highlighted_range {
275                            if let None = highlight_y {
276                                highlight_y = Some(
277                                    origin.y()
278                                        + line.cells[0].point.line as f32 * layout.line_height.0,
279                                );
280                            }
281                            let start_x = origin.x()
282                                + line.cells[range.start].point.column as f32 * layout.em_width.0;
283                            let end_x = origin.x()
284                                + line.cells[range.end].point.column as f32 * layout.em_width.0
285                                + layout.em_width.0;
286
287                            return Some(HighlightedRangeLine { start_x, end_x });
288                        } else {
289                            return None;
290                        }
291                    })
292                    .collect::<Vec<HighlightedRangeLine>>();
293
294                if let Some(y) = highlight_y {
295                    let hr = HighlightedRange {
296                        start_y: y, //Need to change this
297                        line_height: layout.line_height.0,
298                        lines: highlight_lines,
299                        color: layout.selection_color,
300                        //Copied from editor. TODO: move to theme or something
301                        corner_radius: 0.15 * layout.line_height.0,
302                    };
303                    hr.paint(bounds, cx.scene);
304                }
305            });
306
307            cx.paint_layer(clip_bounds, |cx| {
308                for layout_line in &layout.layout_lines {
309                    for layout_cell in &layout_line.cells {
310                        let point = layout_cell.point;
311
312                        //Don't actually know the start_x for a line, until here:
313                        let cell_origin = vec2f(
314                            origin.x() + point.column as f32 * layout.em_width.0,
315                            origin.y() + point.line as f32 * layout.line_height.0,
316                        );
317
318                        layout_cell.text.paint(
319                            cell_origin,
320                            visible_bounds,
321                            layout.line_height.0,
322                            cx,
323                        );
324                    }
325                }
326            });
327
328            //Draw cursor
329            if let Some(cursor) = &layout.cursor {
330                cx.paint_layer(clip_bounds, |cx| {
331                    cursor.paint(origin, cx);
332                })
333            }
334
335            #[cfg(debug_assertions)]
336            if DEBUG_GRID {
337                cx.paint_layer(clip_bounds, |cx| {
338                    draw_debug_grid(bounds, layout, cx);
339                })
340            }
341        });
342    }
343
344    fn dispatch_event(
345        &mut self,
346        event: &gpui::Event,
347        _bounds: gpui::geometry::rect::RectF,
348        visible_bounds: gpui::geometry::rect::RectF,
349        layout: &mut Self::LayoutState,
350        _paint: &mut Self::PaintState,
351        cx: &mut gpui::EventContext,
352    ) -> bool {
353        match event {
354            Event::ScrollWheel(ScrollWheelEvent {
355                delta, position, ..
356            }) => visible_bounds
357                .contains_point(*position)
358                .then(|| {
359                    let vertical_scroll =
360                        (delta.y() / layout.line_height.0) * ALACRITTY_SCROLL_MULTIPLIER;
361                    cx.dispatch_action(ScrollTerminal(vertical_scroll.round() as i32));
362                })
363                .is_some(),
364            Event::KeyDown(KeyDownEvent {
365                input: Some(input), ..
366            }) => cx
367                .is_parent_view_focused()
368                .then(|| {
369                    cx.dispatch_action(Input(input.to_string()));
370                })
371                .is_some(),
372            _ => false,
373        }
374    }
375
376    fn debug(
377        &self,
378        _bounds: gpui::geometry::rect::RectF,
379        _layout: &Self::LayoutState,
380        _paint: &Self::PaintState,
381        _cx: &gpui::DebugContext,
382    ) -> gpui::serde_json::Value {
383        json!({
384            "type": "TerminalElement",
385        })
386    }
387}
388
389pub fn mouse_to_cell_data(
390    pos: Vector2F,
391    origin: Vector2F,
392    cur_size: SizeInfo,
393    display_offset: usize,
394) -> (Point, alacritty_terminal::index::Direction) {
395    let relative_pos = relative_pos(pos, origin);
396    let point = grid_cell(&relative_pos, cur_size, display_offset);
397    let side = cell_side(&relative_pos, cur_size);
398    (point, side)
399}
400
401///Configures a text style from the current settings.
402fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
403    TextStyle {
404        color: settings.theme.editor.text_color,
405        font_family_id: settings.buffer_font_family,
406        font_family_name: font_cache.family_name(settings.buffer_font_family).unwrap(),
407        font_id: font_cache
408            .select_font(settings.buffer_font_family, &Default::default())
409            .unwrap(),
410        font_size: settings.buffer_font_size,
411        font_properties: Default::default(),
412        underline: Default::default(),
413    }
414}
415
416///Configures a size info object from the given information.
417fn make_new_size(
418    constraint: SizeConstraint,
419    cell_width: &CellWidth,
420    line_height: &LineHeight,
421) -> SizeInfo {
422    SizeInfo::new(
423        constraint.max.x() - cell_width.0,
424        constraint.max.y(),
425        cell_width.0,
426        line_height.0,
427        0.,
428        0.,
429        false,
430    )
431}
432
433fn layout_lines(
434    grid: GridIterator<Cell>,
435    text_style: &TextStyle,
436    terminal_theme: &TerminalStyle,
437    text_layout_cache: &TextLayoutCache,
438    modal: bool,
439    selection_range: Option<SelectionRange>,
440) -> Vec<LayoutLine> {
441    let lines = grid.group_by(|i| i.point.line);
442    lines
443        .into_iter()
444        .enumerate()
445        .map(|(line_index, (_, line))| {
446            let mut highlighted_range = None;
447            let cells = line
448                .enumerate()
449                .map(|(x_index, indexed_cell)| {
450                    if selection_range
451                        .map(|range| range.contains(indexed_cell.point))
452                        .unwrap_or(false)
453                    {
454                        let mut range = highlighted_range.take().unwrap_or(x_index..x_index);
455                        range.end = range.end.max(x_index);
456                        highlighted_range = Some(range);
457                    }
458
459                    let cell_text = &indexed_cell.c.to_string();
460
461                    let cell_style = cell_style(&indexed_cell, terminal_theme, text_style, modal);
462
463                    //This is where we might be able to get better performance
464                    let layout_cell = text_layout_cache.layout_str(
465                        cell_text,
466                        text_style.font_size,
467                        &[(cell_text.len(), cell_style)],
468                    );
469
470                    LayoutCell::new(
471                        Point::new(line_index as i32, indexed_cell.point.column.0 as i32),
472                        layout_cell,
473                        convert_color(&indexed_cell.bg, &terminal_theme.colors, modal),
474                    )
475                })
476                .collect::<Vec<LayoutCell>>();
477
478            LayoutLine {
479                cells,
480                highlighted_range,
481            }
482        })
483        .collect::<Vec<LayoutLine>>()
484}
485
486// Compute the cursor position and expected block width, may return a zero width if x_for_index returns
487// the same position for sequential indexes. Use em_width instead
488//TODO: This function is messy, too many arguments and too many ifs. Simplify.
489fn get_cursor_shape(
490    line: usize,
491    line_index: usize,
492    display_offset: usize,
493    line_height: &LineHeight,
494    cell_width: &CellWidth,
495    total_lines: usize,
496    text_fragment: &Line,
497) -> Option<(Vector2F, f32)> {
498    let cursor_line = line + display_offset;
499    if cursor_line <= total_lines {
500        let cursor_width = if text_fragment.width() == 0. {
501            cell_width.0
502        } else {
503            text_fragment.width()
504        };
505
506        Some((
507            vec2f(
508                line_index as f32 * cell_width.0,
509                cursor_line as f32 * line_height.0,
510            ),
511            cursor_width,
512        ))
513    } else {
514        None
515    }
516}
517
518///Convert the Alacritty cell styles to GPUI text styles and background color
519fn cell_style(
520    indexed: &Indexed<&Cell>,
521    style: &TerminalStyle,
522    text_style: &TextStyle,
523    modal: bool,
524) -> RunStyle {
525    let flags = indexed.cell.flags;
526    let fg = convert_color(&indexed.cell.fg, &style.colors, modal);
527
528    let underline = flags
529        .contains(Flags::UNDERLINE)
530        .then(|| Underline {
531            color: Some(fg),
532            squiggly: false,
533            thickness: OrderedFloat(1.),
534        })
535        .unwrap_or_default();
536
537    RunStyle {
538        color: fg,
539        font_id: text_style.font_id,
540        underline,
541    }
542}
543
544fn attach_mouse_handlers(
545    origin: Vector2F,
546    cur_size: SizeInfo,
547    view_id: usize,
548    terminal_mutex: &Arc<FairMutex<Term<ZedListener>>>,
549    visible_bounds: RectF,
550    cx: &mut PaintContext,
551) {
552    let click_mutex = terminal_mutex.clone();
553    let drag_mutex = terminal_mutex.clone();
554    let mouse_down_mutex = terminal_mutex.clone();
555
556    cx.scene.push_mouse_region(MouseRegion {
557        view_id,
558        mouse_down: Some(Rc::new(move |pos, _| {
559            let mut term = mouse_down_mutex.lock();
560            let (point, side) = mouse_to_cell_data(
561                pos,
562                origin,
563                cur_size,
564                term.renderable_content().display_offset,
565            );
566            term.selection = Some(Selection::new(SelectionType::Simple, point, side))
567        })),
568        click: Some(Rc::new(move |pos, click_count, cx| {
569            let mut term = click_mutex.lock();
570
571            let (point, side) = mouse_to_cell_data(
572                pos,
573                origin,
574                cur_size,
575                term.renderable_content().display_offset,
576            );
577
578            let selection_type = match click_count {
579                0 => return, //This is a release
580                1 => Some(SelectionType::Simple),
581                2 => Some(SelectionType::Semantic),
582                3 => Some(SelectionType::Lines),
583                _ => None,
584            };
585
586            let selection =
587                selection_type.map(|selection_type| Selection::new(selection_type, point, side));
588
589            term.selection = selection;
590            cx.focus_parent_view();
591            cx.notify();
592        })),
593        bounds: visible_bounds,
594        drag: Some(Rc::new(move |_delta, pos, cx| {
595            let mut term = drag_mutex.lock();
596
597            let (point, side) = mouse_to_cell_data(
598                pos,
599                origin,
600                cur_size,
601                term.renderable_content().display_offset,
602            );
603
604            if let Some(mut selection) = term.selection.take() {
605                selection.update(point, side);
606                term.selection = Some(selection);
607            }
608
609            cx.notify();
610        })),
611        ..Default::default()
612    });
613}
614
615///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()
616fn cell_side(pos: &PaneRelativePos, cur_size: SizeInfo) -> Side {
617    let x = pos.0.x() as usize;
618    let cell_x = x.saturating_sub(cur_size.cell_width() as usize) % cur_size.cell_width() as usize;
619    let half_cell_width = (cur_size.cell_width() / 2.0) as usize;
620
621    let additional_padding =
622        (cur_size.width() - cur_size.cell_width() * 2.) % cur_size.cell_width();
623    let end_of_grid = cur_size.width() - cur_size.cell_width() - additional_padding;
624
625    if cell_x > half_cell_width
626            // Edge case when mouse leaves the window.
627            || x as f32 >= end_of_grid
628    {
629        Side::Right
630    } else {
631        Side::Left
632    }
633}
634
635///Copied (with modifications) from alacritty/src/event.rs > Mouse::point()
636///Position is a pane-relative position. That means the top left corner of the mouse
637///Region should be (0,0)
638fn grid_cell(pos: &PaneRelativePos, cur_size: SizeInfo, display_offset: usize) -> Point {
639    let pos = pos.0;
640    let col = pos.x() / cur_size.cell_width(); //TODO: underflow...
641    let col = min(GridCol(col as usize), cur_size.last_column());
642
643    let line = pos.y() / cur_size.cell_height();
644    let line = min(line as i32, cur_size.bottommost_line().0);
645
646    //when clicking, need to ADD to get to the top left cell
647    //e.g. total_lines - viewport_height, THEN subtract display offset
648    //0 -> total_lines - viewport_height - display_offset + mouse_line
649
650    Point::new(GridLine(line - display_offset as i32), col)
651}
652
653///Draws the grid as Alacritty sees it. Useful for checking if there is an inconsistency between
654///Display and conceptual grid.
655#[cfg(debug_assertions)]
656fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
657    let width = layout.cur_size.width();
658    let height = layout.cur_size.height();
659    //Alacritty uses 'as usize', so shall we.
660    for col in 0..(width / layout.em_width.0).round() as usize {
661        cx.scene.push_quad(Quad {
662            bounds: RectF::new(
663                bounds.origin() + vec2f((col + 1) as f32 * layout.em_width.0, 0.),
664                vec2f(1., height),
665            ),
666            background: Some(Color::green()),
667            border: Default::default(),
668            corner_radius: 0.,
669        });
670    }
671    for row in 0..((height / layout.line_height.0) + 1.0).round() as usize {
672        cx.scene.push_quad(Quad {
673            bounds: RectF::new(
674                bounds.origin() + vec2f(layout.em_width.0, row as f32 * layout.line_height.0),
675                vec2f(width, 1.),
676            ),
677            background: Some(Color::green()),
678            border: Default::default(),
679            corner_radius: 0.,
680        });
681    }
682}
683
684mod test {
685
686    #[test]
687    fn test_mouse_to_selection() {
688        let term_width = 100.;
689        let term_height = 200.;
690        let cell_width = 10.;
691        let line_height = 20.;
692        let mouse_pos_x = 100.; //Window relative
693        let mouse_pos_y = 100.; //Window relative
694        let origin_x = 10.;
695        let origin_y = 20.;
696
697        let cur_size = alacritty_terminal::term::SizeInfo::new(
698            term_width,
699            term_height,
700            cell_width,
701            line_height,
702            0.,
703            0.,
704            false,
705        );
706
707        let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
708        let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
709        let (point, _) =
710            crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
711        assert_eq!(
712            point,
713            alacritty_terminal::index::Point::new(
714                alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
715                alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
716            )
717        );
718    }
719
720    #[test]
721    fn test_mouse_to_selection_off_edge() {
722        let term_width = 100.;
723        let term_height = 200.;
724        let cell_width = 10.;
725        let line_height = 20.;
726        let mouse_pos_x = 100.; //Window relative
727        let mouse_pos_y = 100.; //Window relative
728        let origin_x = 10.;
729        let origin_y = 20.;
730
731        let cur_size = alacritty_terminal::term::SizeInfo::new(
732            term_width,
733            term_height,
734            cell_width,
735            line_height,
736            0.,
737            0.,
738            false,
739        );
740
741        let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
742        let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
743        let (point, _) =
744            crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
745        assert_eq!(
746            point,
747            alacritty_terminal::index::Point::new(
748                alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
749                alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
750            )
751        );
752    }
753}