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