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