terminal_element.rs

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