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