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(KeyDownEvent {
364                input: Some(input), ..
365            }) => cx
366                .is_parent_view_focused()
367                .then(|| {
368                    cx.dispatch_action(Input(input.to_string()));
369                })
370                .is_some(),
371            _ => false,
372        }
373    }
374
375    fn debug(
376        &self,
377        _bounds: gpui::geometry::rect::RectF,
378        _layout: &Self::LayoutState,
379        _paint: &Self::PaintState,
380        _cx: &gpui::DebugContext,
381    ) -> gpui::serde_json::Value {
382        json!({
383            "type": "TerminalElement",
384        })
385    }
386}
387
388pub fn mouse_to_cell_data(
389    pos: Vector2F,
390    origin: Vector2F,
391    cur_size: SizeInfo,
392    display_offset: usize,
393) -> (Point, alacritty_terminal::index::Direction) {
394    let relative_pos = relative_pos(pos, origin);
395    let point = grid_cell(&relative_pos, cur_size, display_offset);
396    let side = cell_side(&relative_pos, cur_size);
397    (point, side)
398}
399
400///Configures a text style from the current settings.
401fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
402    TextStyle {
403        color: settings.theme.editor.text_color,
404        font_family_id: settings.buffer_font_family,
405        font_family_name: font_cache.family_name(settings.buffer_font_family).unwrap(),
406        font_id: font_cache
407            .select_font(settings.buffer_font_family, &Default::default())
408            .unwrap(),
409        font_size: settings.buffer_font_size,
410        font_properties: Default::default(),
411        underline: Default::default(),
412    }
413}
414
415///Configures a size info object from the given information.
416fn make_new_size(
417    constraint: SizeConstraint,
418    cell_width: &CellWidth,
419    line_height: &LineHeight,
420) -> SizeInfo {
421    SizeInfo::new(
422        constraint.max.x() - cell_width.0,
423        constraint.max.y(),
424        cell_width.0,
425        line_height.0,
426        0.,
427        0.,
428        false,
429    )
430}
431
432fn layout_lines(
433    grid: GridIterator<Cell>,
434    text_style: &TextStyle,
435    terminal_theme: &TerminalStyle,
436    text_layout_cache: &TextLayoutCache,
437    modal: bool,
438    selection_range: Option<SelectionRange>,
439) -> Vec<LayoutLine> {
440    let lines = grid.group_by(|i| i.point.line);
441    lines
442        .into_iter()
443        .enumerate()
444        .map(|(line_index, (_, line))| {
445            let mut highlighted_range = None;
446            let cells = line
447                .enumerate()
448                .map(|(x_index, indexed_cell)| {
449                    if selection_range
450                        .map(|range| range.contains(indexed_cell.point))
451                        .unwrap_or(false)
452                    {
453                        let mut range = highlighted_range.take().unwrap_or(x_index..x_index);
454                        range.end = range.end.max(x_index);
455                        highlighted_range = Some(range);
456                    }
457
458                    let cell_text = &indexed_cell.c.to_string();
459
460                    let cell_style = cell_style(&indexed_cell, terminal_theme, text_style, modal);
461
462                    //This is where we might be able to get better performance
463                    let layout_cell = text_layout_cache.layout_str(
464                        cell_text,
465                        text_style.font_size,
466                        &[(cell_text.len(), cell_style)],
467                    );
468
469                    LayoutCell::new(
470                        Point::new(line_index as i32, indexed_cell.point.column.0 as i32),
471                        layout_cell,
472                        convert_color(&indexed_cell.bg, &terminal_theme.colors, modal),
473                    )
474                })
475                .collect::<Vec<LayoutCell>>();
476
477            LayoutLine {
478                cells,
479                highlighted_range,
480            }
481        })
482        .collect::<Vec<LayoutLine>>()
483}
484
485// Compute the cursor position and expected block width, may return a zero width if x_for_index returns
486// the same position for sequential indexes. Use em_width instead
487//TODO: This function is messy, too many arguments and too many ifs. Simplify.
488fn get_cursor_shape(
489    line: usize,
490    line_index: usize,
491    display_offset: usize,
492    line_height: &LineHeight,
493    cell_width: &CellWidth,
494    total_lines: usize,
495    text_fragment: &Line,
496) -> Option<(Vector2F, f32)> {
497    let cursor_line = line + display_offset;
498    if cursor_line <= total_lines {
499        let cursor_width = if text_fragment.width() == 0. {
500            cell_width.0
501        } else {
502            text_fragment.width()
503        };
504
505        Some((
506            vec2f(
507                line_index as f32 * cell_width.0,
508                cursor_line as f32 * line_height.0,
509            ),
510            cursor_width,
511        ))
512    } else {
513        None
514    }
515}
516
517///Convert the Alacritty cell styles to GPUI text styles and background color
518fn cell_style(
519    indexed: &Indexed<&Cell>,
520    style: &TerminalStyle,
521    text_style: &TextStyle,
522    modal: bool,
523) -> RunStyle {
524    let flags = indexed.cell.flags;
525    let fg = convert_color(&indexed.cell.fg, &style.colors, modal);
526
527    let underline = flags
528        .contains(Flags::UNDERLINE)
529        .then(|| Underline {
530            color: Some(fg),
531            squiggly: false,
532            thickness: OrderedFloat(1.),
533        })
534        .unwrap_or_default();
535
536    RunStyle {
537        color: fg,
538        font_id: text_style.font_id,
539        underline,
540    }
541}
542
543fn attach_mouse_handlers(
544    origin: Vector2F,
545    cur_size: SizeInfo,
546    view_id: usize,
547    terminal_mutex: &Arc<FairMutex<Term<ZedListener>>>,
548    visible_bounds: RectF,
549    cx: &mut PaintContext,
550) {
551    let click_mutex = terminal_mutex.clone();
552    let drag_mutex = terminal_mutex.clone();
553    let mouse_down_mutex = terminal_mutex.clone();
554
555    cx.scene.push_mouse_region(MouseRegion {
556        view_id,
557        mouse_down: Some(Rc::new(move |pos, _| {
558            let mut term = mouse_down_mutex.lock();
559            let (point, side) = mouse_to_cell_data(
560                pos,
561                origin,
562                cur_size,
563                term.renderable_content().display_offset,
564            );
565            term.selection = Some(Selection::new(SelectionType::Simple, point, side))
566        })),
567        click: Some(Rc::new(move |pos, click_count, cx| {
568            let mut term = click_mutex.lock();
569
570            let (point, side) = mouse_to_cell_data(
571                pos,
572                origin,
573                cur_size,
574                term.renderable_content().display_offset,
575            );
576
577            let selection_type = match click_count {
578                0 => return, //This is a release
579                1 => Some(SelectionType::Simple),
580                2 => Some(SelectionType::Semantic),
581                3 => Some(SelectionType::Lines),
582                _ => None,
583            };
584
585            let selection =
586                selection_type.map(|selection_type| Selection::new(selection_type, point, side));
587
588            term.selection = selection;
589            cx.focus_parent_view();
590            cx.notify();
591        })),
592        bounds: visible_bounds,
593        drag: Some(Rc::new(move |_delta, pos, cx| {
594            let mut term = drag_mutex.lock();
595
596            let (point, side) = mouse_to_cell_data(
597                pos,
598                origin,
599                cur_size,
600                term.renderable_content().display_offset,
601            );
602
603            if let Some(mut selection) = term.selection.take() {
604                selection.update(point, side);
605                term.selection = Some(selection);
606            }
607
608            cx.notify();
609        })),
610        ..Default::default()
611    });
612}
613
614///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()
615fn cell_side(pos: &PaneRelativePos, cur_size: SizeInfo) -> Side {
616    let x = pos.0.x() as usize;
617    let cell_x = x.saturating_sub(cur_size.cell_width() as usize) % cur_size.cell_width() as usize;
618    let half_cell_width = (cur_size.cell_width() / 2.0) as usize;
619
620    let additional_padding =
621        (cur_size.width() - cur_size.cell_width() * 2.) % cur_size.cell_width();
622    let end_of_grid = cur_size.width() - cur_size.cell_width() - additional_padding;
623
624    if cell_x > half_cell_width
625            // Edge case when mouse leaves the window.
626            || x as f32 >= end_of_grid
627    {
628        Side::Right
629    } else {
630        Side::Left
631    }
632}
633
634///Copied (with modifications) from alacritty/src/event.rs > Mouse::point()
635///Position is a pane-relative position. That means the top left corner of the mouse
636///Region should be (0,0)
637fn grid_cell(pos: &PaneRelativePos, cur_size: SizeInfo, display_offset: usize) -> Point {
638    let pos = pos.0;
639    let col = pos.x() / cur_size.cell_width(); //TODO: underflow...
640    let col = min(GridCol(col as usize), cur_size.last_column());
641
642    let line = pos.y() / cur_size.cell_height();
643    let line = min(line as i32, cur_size.bottommost_line().0);
644
645    //when clicking, need to ADD to get to the top left cell
646    //e.g. total_lines - viewport_height, THEN subtract display offset
647    //0 -> total_lines - viewport_height - display_offset + mouse_line
648
649    Point::new(GridLine(line - display_offset as i32), col)
650}
651
652///Draws the grid as Alacritty sees it. Useful for checking if there is an inconsistency between
653///Display and conceptual grid.
654#[cfg(debug_assertions)]
655fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
656    let width = layout.cur_size.width();
657    let height = layout.cur_size.height();
658    //Alacritty uses 'as usize', so shall we.
659    for col in 0..(width / layout.em_width.0).round() as usize {
660        cx.scene.push_quad(Quad {
661            bounds: RectF::new(
662                bounds.origin() + vec2f((col + 1) as f32 * layout.em_width.0, 0.),
663                vec2f(1., height),
664            ),
665            background: Some(Color::green()),
666            border: Default::default(),
667            corner_radius: 0.,
668        });
669    }
670    for row in 0..((height / layout.line_height.0) + 1.0).round() as usize {
671        cx.scene.push_quad(Quad {
672            bounds: RectF::new(
673                bounds.origin() + vec2f(layout.em_width.0, row as f32 * layout.line_height.0),
674                vec2f(width, 1.),
675            ),
676            background: Some(Color::green()),
677            border: Default::default(),
678            corner_radius: 0.,
679        });
680    }
681}
682
683mod test {
684
685    #[test]
686    fn test_mouse_to_selection() {
687        let term_width = 100.;
688        let term_height = 200.;
689        let cell_width = 10.;
690        let line_height = 20.;
691        let mouse_pos_x = 100.; //Window relative
692        let mouse_pos_y = 100.; //Window relative
693        let origin_x = 10.;
694        let origin_y = 20.;
695
696        let cur_size = alacritty_terminal::term::SizeInfo::new(
697            term_width,
698            term_height,
699            cell_width,
700            line_height,
701            0.,
702            0.,
703            false,
704        );
705
706        let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
707        let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
708        let (point, _) =
709            crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
710        assert_eq!(
711            point,
712            alacritty_terminal::index::Point::new(
713                alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
714                alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
715            )
716        );
717    }
718
719    #[test]
720    fn test_mouse_to_selection_off_edge() {
721        let term_width = 100.;
722        let term_height = 200.;
723        let cell_width = 10.;
724        let line_height = 20.;
725        let mouse_pos_x = 100.; //Window relative
726        let mouse_pos_y = 100.; //Window relative
727        let origin_x = 10.;
728        let origin_y = 20.;
729
730        let cur_size = alacritty_terminal::term::SizeInfo::new(
731            term_width,
732            term_height,
733            cell_width,
734            line_height,
735            0.,
736            0.,
737            false,
738        );
739
740        let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
741        let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
742        let (point, _) =
743            crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
744        assert_eq!(
745            point,
746            alacritty_terminal::index::Point::new(
747                alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
748                alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
749            )
750        );
751    }
752}