use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine};use gpui::{ div, fill, point, px, relative, size, AnyElement, App, AvailableSpace, Bounds, ContentMask, Context, DispatchPhase, Element, ElementId, Entity, FocusHandle, Font, FontStyle, FontWeight, GlobalElementId, HighlightStyle, Hitbox, Hsla, InputHandler, InteractiveElement, Interactivity, IntoElement, LayoutId, ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle, UTF16Selection, UnderlineStyle, WeakEntity, WhiteSpace, Window, WindowTextSystem,};use itertools::Itertools;use language::CursorShape;use settings::Settings;use terminal::{ alacritty_terminal::{ grid::Dimensions, index::Point as AlacPoint, term::{cell::Flags, TermMode}, vte::ansi::{ Color::{self as AnsiColor, Named}, CursorShape as AlacCursorShape, NamedColor, }, }, terminal_settings::TerminalSettings, HoveredWord, IndexedCell, Terminal, TerminalBounds, TerminalContent,};use theme::{ActiveTheme, Theme, ThemeSettings};use ui::{ParentElement, Tooltip};use workspace::Workspace; use std::mem;use std::{fmt::Debug, ops::RangeInclusive, rc::Rc}; use crate::{BlockContext, BlockProperties, TerminalView}; /// The information generated during layout that is necessary for painting.pub struct LayoutState { hitbox: Hitbox, cells: Vec<LayoutCell>, rects: Vec<LayoutRect>, relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>, cursor: Option<CursorLayout>, background_color: Hsla, dimensions: TerminalBounds, mode: TermMode, display_offset: usize, hyperlink_tooltip: Option<AnyElement>, gutter: Pixels, last_hovered_word: Option<HoveredWord>, block_below_cursor_element: Option<AnyElement>,} /// Helper struct for converting data between Alacritty's cursor points, and displayed cursor points.struct DisplayCursor { line: i32, col: usize,} impl DisplayCursor { fn from(cursor_point: AlacPoint, display_offset: usize) -> Self { Self { line: cursor_point.line.0 + display_offset as i32, col: cursor_point.column.0, } } pub fn line(&self) -> i32 { self.line } pub fn col(&self) -> usize { self.col }} #[derive(Debug, Default)]pub struct LayoutCell { pub point: AlacPoint<i32, i32>, text: gpui::ShapedLine,} impl LayoutCell { fn new(point: AlacPoint<i32, i32>, text: gpui::ShapedLine) -> LayoutCell { LayoutCell { point, text } } pub fn paint( &self, origin: Point<Pixels>, dimensions: &TerminalBounds, _visible_bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App, ) { let pos = { let point = self.point; Point::new( (origin.x + point.column as f32 * dimensions.cell_width).floor(), origin.y + point.line as f32 * dimensions.line_height, ) }; self.text .paint(pos, dimensions.line_height, window, cx) .ok(); }} #[derive(Clone, Debug, Default)]pub struct LayoutRect { point: AlacPoint<i32, i32>, num_of_cells: usize, color: Hsla,} impl LayoutRect { fn new(point: AlacPoint<i32, i32>, num_of_cells: usize, color: Hsla) -> LayoutRect { LayoutRect { point, num_of_cells, color, } } fn extend(&self) -> Self { LayoutRect { point: self.point, num_of_cells: self.num_of_cells + 1, color: self.color, } } pub fn paint(&self, origin: Point<Pixels>, dimensions: &TerminalBounds, window: &mut Window) { let position = { let alac_point = self.point; point( (origin.x + alac_point.column as f32 * dimensions.cell_width).floor(), origin.y + alac_point.line as f32 * dimensions.line_height, ) }; let size = point( (dimensions.cell_width * self.num_of_cells as f32).ceil(), dimensions.line_height, ) .into(); window.paint_quad(fill(Bounds::new(position, size), self.color)); }} /// The GPUI element that paints the terminal./// We need to keep a reference to the model for mouse events, do we need it for any other terminal stuff, or can we move that to connection?pub struct TerminalElement { terminal: Entity<Terminal>, terminal_view: Entity<TerminalView>, workspace: WeakEntity<Workspace>, focus: FocusHandle, focused: bool, cursor_visible: bool, can_navigate_to_selected_word: bool, interactivity: Interactivity, block_below_cursor: Option<Rc<BlockProperties>>,} impl InteractiveElement for TerminalElement { fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity }} impl StatefulInteractiveElement for TerminalElement {} impl TerminalElement { #[allow(clippy::too_many_arguments)] pub fn new( terminal: Entity<Terminal>, terminal_view: Entity<TerminalView>, workspace: WeakEntity<Workspace>, focus: FocusHandle, focused: bool, cursor_visible: bool, can_navigate_to_selected_word: bool, block_below_cursor: Option<Rc<BlockProperties>>, ) -> TerminalElement { TerminalElement { terminal, terminal_view, workspace, focused, focus: focus.clone(), cursor_visible, can_navigate_to_selected_word, block_below_cursor, interactivity: Default::default(), } .track_focus(&focus) .element } //Vec<Range<AlacPoint>> -> Clip out the parts of the ranges pub fn layout_grid( grid: impl Iterator<Item = IndexedCell>, text_style: &TextStyle, // terminal_theme: &TerminalStyle, text_system: &WindowTextSystem, hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>, window: &Window, cx: &App, ) -> (Vec<LayoutCell>, Vec<LayoutRect>) { let theme = cx.theme(); let mut cells = vec![]; let mut rects = vec![]; let mut cur_rect: Option<LayoutRect> = None; let mut cur_alac_color = None; let linegroups = grid.into_iter().chunk_by(|i| i.point.line); for (line_index, (_, line)) in linegroups.into_iter().enumerate() { for cell in line { let mut fg = cell.fg; let mut bg = cell.bg; if cell.flags.contains(Flags::INVERSE) { mem::swap(&mut fg, &mut bg); } //Expand background rect range { if matches!(bg, Named(NamedColor::Background)) { //Continue to next cell, resetting variables if necessary cur_alac_color = None; if let Some(rect) = cur_rect { rects.push(rect); cur_rect = None } } else { match cur_alac_color { Some(cur_color) => { if bg == cur_color { // `cur_rect` can be None if it was moved to the `rects` vec after wrapping around // from one line to the next. The variables are all set correctly but there is no current // rect, so we create one if necessary. cur_rect = cur_rect.map_or_else( || { Some(LayoutRect::new( AlacPoint::new( line_index as i32, cell.point.column.0 as i32, ), 1, convert_color(&bg, theme), )) }, |rect| Some(rect.extend()), ); } else { cur_alac_color = Some(bg); if cur_rect.is_some() { rects.push(cur_rect.take().unwrap()); } cur_rect = Some(LayoutRect::new( AlacPoint::new( line_index as i32, cell.point.column.0 as i32, ), 1, convert_color(&bg, theme), )); } } None => { cur_alac_color = Some(bg); cur_rect = Some(LayoutRect::new( AlacPoint::new(line_index as i32, cell.point.column.0 as i32), 1, convert_color(&bg, theme), )); } } } } //Layout current cell text { if !is_blank(&cell) { let cell_text = cell.c.to_string(); let cell_style = TerminalElement::cell_style(&cell, fg, theme, text_style, hyperlink); let layout_cell = text_system .shape_line( cell_text.into(), text_style.font_size.to_pixels(window.rem_size()), &[cell_style], ) .unwrap(); cells.push(LayoutCell::new( AlacPoint::new(line_index as i32, cell.point.column.0 as i32), layout_cell, )) }; } } if cur_rect.is_some() { rects.push(cur_rect.take().unwrap()); } } (cells, rects) } /// Computes the cursor position and expected block width, may return a zero width if x_for_index returns /// the same position for sequential indexes. Use em_width instead fn shape_cursor( cursor_point: DisplayCursor, size: TerminalBounds, text_fragment: &ShapedLine, ) -> Option<(Point<Pixels>, Pixels)> { if cursor_point.line() < size.total_lines() as i32 { let cursor_width = if text_fragment.width == Pixels::ZERO { size.cell_width() } else { text_fragment.width }; // Cursor should always surround as much of the text as possible, // hence when on pixel boundaries round the origin down and the width up Some(( point( (cursor_point.col() as f32 * size.cell_width()).floor(), (cursor_point.line() as f32 * size.line_height()).floor(), ), cursor_width.ceil(), )) } else { None } } /// Converts the Alacritty cell styles to GPUI text styles and background color. fn cell_style( indexed: &IndexedCell, fg: terminal::alacritty_terminal::vte::ansi::Color, // bg: terminal::alacritty_terminal::ansi::Color, colors: &Theme, text_style: &TextStyle, hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>, ) -> TextRun { let flags = indexed.cell.flags; let mut fg = convert_color(&fg, colors); // Ghostty uses (175/255) as the multiplier (~0.69), Alacritty uses 0.66, Kitty // uses 0.75. We're using 0.7 because it's pretty well in the middle of that. if flags.intersects(Flags::DIM) { fg.a *= 0.7; } let underline = (flags.intersects(Flags::ALL_UNDERLINES) || indexed.cell.hyperlink().is_some()) .then(|| UnderlineStyle { color: Some(fg), thickness: Pixels::from(1.0), wavy: flags.contains(Flags::UNDERCURL), }); let strikethrough = flags .intersects(Flags::STRIKEOUT) .then(|| StrikethroughStyle { color: Some(fg), thickness: Pixels::from(1.0), }); let weight = if flags.intersects(Flags::BOLD) { FontWeight::BOLD } else { text_style.font_weight }; let style = if flags.intersects(Flags::ITALIC) { FontStyle::Italic } else { FontStyle::Normal }; let mut result = TextRun { len: indexed.c.len_utf8(), color: fg, background_color: None, font: Font { weight, style, ..text_style.font() }, underline, strikethrough, }; if let Some((style, range)) = hyperlink { if range.contains(&indexed.point) { if let Some(underline) = style.underline { result.underline = Some(underline); } if let Some(color) = style.color { result.color = color; } } } result } fn generic_button_handler<E>( connection: Entity<Terminal>, focus_handle: FocusHandle, f: impl Fn(&mut Terminal, &E, &mut Context<Terminal>), ) -> impl Fn(&E, &mut Window, &mut App) { move |event, window, cx| { window.focus(&focus_handle); connection.update(cx, |terminal, cx| { f(terminal, event, cx); cx.notify(); }) } } fn register_mouse_listeners(&mut self, mode: TermMode, hitbox: &Hitbox, window: &mut Window) { let focus = self.focus.clone(); let terminal = self.terminal.clone(); self.interactivity.on_mouse_down(MouseButton::Left, { let terminal = terminal.clone(); let focus = focus.clone(); move |e, window, cx| { window.focus(&focus); terminal.update(cx, |terminal, cx| { terminal.mouse_down(e, cx); cx.notify(); }) } }); window.on_mouse_event({ let terminal = self.terminal.clone(); let hitbox = hitbox.clone(); let focus = focus.clone(); move |e: &MouseMoveEvent, phase, window, cx| { if phase != DispatchPhase::Bubble { return; } if e.pressed_button.is_some() && !cx.has_active_drag() && focus.is_focused(window) { let hovered = hitbox.is_hovered(window); terminal.update(cx, |terminal, cx| { if terminal.selection_started() || hovered { terminal.mouse_drag(e, hitbox.bounds, cx); cx.notify(); } }) } if hitbox.is_hovered(window) { terminal.update(cx, |terminal, cx| { terminal.mouse_move(e, cx); }) } } }); self.interactivity.on_mouse_up( MouseButton::Left, TerminalElement::generic_button_handler( terminal.clone(), focus.clone(), move |terminal, e, cx| { terminal.mouse_up(e, cx); }, ), ); self.interactivity.on_mouse_down( MouseButton::Middle, TerminalElement::generic_button_handler( terminal.clone(), focus.clone(), move |terminal, e, cx| { terminal.mouse_down(e, cx); }, ), ); self.interactivity.on_scroll_wheel({ let terminal_view = self.terminal_view.downgrade(); move |e, _, cx| { terminal_view .update(cx, |terminal_view, cx| { terminal_view.scroll_wheel(e, cx); cx.notify(); }) .ok();Lines 1–500 of 1204. Load more
use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine}; use gpui::{ div, fill, point, px, relative, size, AnyElement, App, AvailableSpace, Bounds, ContentMask, Context, DispatchPhase, Element, ElementId, Entity, FocusHandle, Font, FontStyle, FontWeight, GlobalElementId, HighlightStyle, Hitbox, Hsla, InputHandler, InteractiveElement, Interactivity, IntoElement, LayoutId, ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle, UTF16Selection, UnderlineStyle, WeakEntity, WhiteSpace, Window, WindowTextSystem, }; use itertools::Itertools; use language::CursorShape; use settings::Settings; use terminal::{ alacritty_terminal::{ grid::Dimensions, index::Point as AlacPoint, term::{cell::Flags, TermMode}, vte::ansi::{ Color::{self as AnsiColor, Named}, CursorShape as AlacCursorShape, NamedColor, }, }, terminal_settings::TerminalSettings, HoveredWord, IndexedCell, Terminal, TerminalBounds, TerminalContent, }; use theme::{ActiveTheme, Theme, ThemeSettings}; use ui::{ParentElement, Tooltip}; use workspace::Workspace; use std::mem; use std::{fmt::Debug, ops::RangeInclusive, rc::Rc}; use crate::{BlockContext, BlockProperties, TerminalView}; /// The information generated during layout that is necessary for painting. pub struct LayoutState { hitbox: Hitbox, cells: Vec<LayoutCell>, rects: Vec<LayoutRect>, relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>, cursor: Option<CursorLayout>, background_color: Hsla, dimensions: TerminalBounds, mode: TermMode, display_offset: usize, hyperlink_tooltip: Option<AnyElement>, gutter: Pixels, last_hovered_word: Option<HoveredWord>, block_below_cursor_element: Option<AnyElement>, } /// Helper struct for converting data between Alacritty's cursor points, and displayed cursor points. struct DisplayCursor { line: i32, col: usize, } impl DisplayCursor { fn from(cursor_point: AlacPoint, display_offset: usize) -> Self { Self { line: cursor_point.line.0 + display_offset as i32, col: cursor_point.column.0, } } pub fn line(&self) -> i32 { self.line } pub fn col(&self) -> usize { self.col } } #[derive(Debug, Default)] pub struct LayoutCell { pub point: AlacPoint<i32, i32>, text: gpui::ShapedLine, } impl LayoutCell { fn new(point: AlacPoint<i32, i32>, text: gpui::ShapedLine) -> LayoutCell { LayoutCell { point, text } } pub fn paint( &self, origin: Point<Pixels>, dimensions: &TerminalBounds, _visible_bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App, ) { let pos = { let point = self.point; Point::new( (origin.x + point.column as f32 * dimensions.cell_width).floor(), origin.y + point.line as f32 * dimensions.line_height, ) }; self.text .paint(pos, dimensions.line_height, window, cx) .ok(); } } #[derive(Clone, Debug, Default)] pub struct LayoutRect { point: AlacPoint<i32, i32>, num_of_cells: usize, color: Hsla, } impl LayoutRect { fn new(point: AlacPoint<i32, i32>, num_of_cells: usize, color: Hsla) -> LayoutRect { LayoutRect { point, num_of_cells, color, } } fn extend(&self) -> Self { LayoutRect { point: self.point, num_of_cells: self.num_of_cells + 1, color: self.color, } } pub fn paint(&self, origin: Point<Pixels>, dimensions: &TerminalBounds, window: &mut Window) { let position = { let alac_point = self.point; point( (origin.x + alac_point.column as f32 * dimensions.cell_width).floor(), origin.y + alac_point.line as f32 * dimensions.line_height, ) }; let size = point( (dimensions.cell_width * self.num_of_cells as f32).ceil(), dimensions.line_height, ) .into(); window.paint_quad(fill(Bounds::new(position, size), self.color)); } } /// The GPUI element that paints the terminal. /// We need to keep a reference to the model for mouse events, do we need it for any other terminal stuff, or can we move that to connection? pub struct TerminalElement { terminal: Entity<Terminal>, terminal_view: Entity<TerminalView>, workspace: WeakEntity<Workspace>, focus: FocusHandle, focused: bool, cursor_visible: bool, can_navigate_to_selected_word: bool, interactivity: Interactivity, block_below_cursor: Option<Rc<BlockProperties>>, } impl InteractiveElement for TerminalElement { fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity } } impl StatefulInteractiveElement for TerminalElement {} impl TerminalElement { #[allow(clippy::too_many_arguments)] pub fn new( terminal: Entity<Terminal>, terminal_view: Entity<TerminalView>, workspace: WeakEntity<Workspace>, focus: FocusHandle, focused: bool, cursor_visible: bool, can_navigate_to_selected_word: bool, block_below_cursor: Option<Rc<BlockProperties>>, ) -> TerminalElement { TerminalElement { terminal, terminal_view, workspace, focused, focus: focus.clone(), cursor_visible, can_navigate_to_selected_word, block_below_cursor, interactivity: Default::default(), } .track_focus(&focus) .element } //Vec<Range<AlacPoint>> -> Clip out the parts of the ranges pub fn layout_grid( grid: impl Iterator<Item = IndexedCell>, text_style: &TextStyle, // terminal_theme: &TerminalStyle, text_system: &WindowTextSystem, hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>, window: &Window, cx: &App, ) -> (Vec<LayoutCell>, Vec<LayoutRect>) { let theme = cx.theme(); let mut cells = vec![]; let mut rects = vec![]; let mut cur_rect: Option<LayoutRect> = None; let mut cur_alac_color = None; let linegroups = grid.into_iter().chunk_by(|i| i.point.line); for (line_index, (_, line)) in linegroups.into_iter().enumerate() { for cell in line { let mut fg = cell.fg; let mut bg = cell.bg; if cell.flags.contains(Flags::INVERSE) { mem::swap(&mut fg, &mut bg); } //Expand background rect range { if matches!(bg, Named(NamedColor::Background)) { //Continue to next cell, resetting variables if necessary cur_alac_color = None; if let Some(rect) = cur_rect { rects.push(rect); cur_rect = None } } else { match cur_alac_color { Some(cur_color) => { if bg == cur_color { // `cur_rect` can be None if it was moved to the `rects` vec after wrapping around // from one line to the next. The variables are all set correctly but there is no current // rect, so we create one if necessary. cur_rect = cur_rect.map_or_else( || { Some(LayoutRect::new( AlacPoint::new( line_index as i32, cell.point.column.0 as i32, ), 1, convert_color(&bg, theme), )) }, |rect| Some(rect.extend()), ); } else { cur_alac_color = Some(bg); if cur_rect.is_some() { rects.push(cur_rect.take().unwrap()); } cur_rect = Some(LayoutRect::new( AlacPoint::new( line_index as i32, cell.point.column.0 as i32, ), 1, convert_color(&bg, theme), )); } } None => { cur_alac_color = Some(bg); cur_rect = Some(LayoutRect::new( AlacPoint::new(line_index as i32, cell.point.column.0 as i32), 1, convert_color(&bg, theme), )); } } } } //Layout current cell text { if !is_blank(&cell) { let cell_text = cell.c.to_string(); let cell_style = TerminalElement::cell_style(&cell, fg, theme, text_style, hyperlink); let layout_cell = text_system .shape_line( cell_text.into(), text_style.font_size.to_pixels(window.rem_size()), &[cell_style], ) .unwrap(); cells.push(LayoutCell::new( AlacPoint::new(line_index as i32, cell.point.column.0 as i32), layout_cell, )) }; } } if cur_rect.is_some() { rects.push(cur_rect.take().unwrap()); } } (cells, rects) } /// Computes the cursor position and expected block width, may return a zero width if x_for_index returns /// the same position for sequential indexes. Use em_width instead fn shape_cursor( cursor_point: DisplayCursor, size: TerminalBounds, text_fragment: &ShapedLine, ) -> Option<(Point<Pixels>, Pixels)> { if cursor_point.line() < size.total_lines() as i32 { let cursor_width = if text_fragment.width == Pixels::ZERO { size.cell_width() } else { text_fragment.width }; // Cursor should always surround as much of the text as possible, // hence when on pixel boundaries round the origin down and the width up Some(( point( (cursor_point.col() as f32 * size.cell_width()).floor(), (cursor_point.line() as f32 * size.line_height()).floor(), ), cursor_width.ceil(), )) } else { None } } /// Converts the Alacritty cell styles to GPUI text styles and background color. fn cell_style( indexed: &IndexedCell, fg: terminal::alacritty_terminal::vte::ansi::Color, // bg: terminal::alacritty_terminal::ansi::Color, colors: &Theme, text_style: &TextStyle, hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>, ) -> TextRun { let flags = indexed.cell.flags; let mut fg = convert_color(&fg, colors); // Ghostty uses (175/255) as the multiplier (~0.69), Alacritty uses 0.66, Kitty // uses 0.75. We're using 0.7 because it's pretty well in the middle of that. if flags.intersects(Flags::DIM) { fg.a *= 0.7; } let underline = (flags.intersects(Flags::ALL_UNDERLINES) || indexed.cell.hyperlink().is_some()) .then(|| UnderlineStyle { color: Some(fg), thickness: Pixels::from(1.0), wavy: flags.contains(Flags::UNDERCURL), }); let strikethrough = flags .intersects(Flags::STRIKEOUT) .then(|| StrikethroughStyle { color: Some(fg), thickness: Pixels::from(1.0), }); let weight = if flags.intersects(Flags::BOLD) { FontWeight::BOLD } else { text_style.font_weight }; let style = if flags.intersects(Flags::ITALIC) { FontStyle::Italic } else { FontStyle::Normal }; let mut result = TextRun { len: indexed.c.len_utf8(), color: fg, background_color: None, font: Font { weight, style, ..text_style.font() }, underline, strikethrough, }; if let Some((style, range)) = hyperlink { if range.contains(&indexed.point) { if let Some(underline) = style.underline { result.underline = Some(underline); } if let Some(color) = style.color { result.color = color; } } } result } fn generic_button_handler<E>( connection: Entity<Terminal>, focus_handle: FocusHandle, f: impl Fn(&mut Terminal, &E, &mut Context<Terminal>), ) -> impl Fn(&E, &mut Window, &mut App) { move |event, window, cx| { window.focus(&focus_handle); connection.update(cx, |terminal, cx| { f(terminal, event, cx); cx.notify(); }) } } fn register_mouse_listeners(&mut self, mode: TermMode, hitbox: &Hitbox, window: &mut Window) { let focus = self.focus.clone(); let terminal = self.terminal.clone(); self.interactivity.on_mouse_down(MouseButton::Left, { let terminal = terminal.clone(); let focus = focus.clone(); move |e, window, cx| { window.focus(&focus); terminal.update(cx, |terminal, cx| { terminal.mouse_down(e, cx); cx.notify(); }) } }); window.on_mouse_event({ let terminal = self.terminal.clone(); let hitbox = hitbox.clone(); let focus = focus.clone(); move |e: &MouseMoveEvent, phase, window, cx| { if phase != DispatchPhase::Bubble { return; } if e.pressed_button.is_some() && !cx.has_active_drag() && focus.is_focused(window) { let hovered = hitbox.is_hovered(window); terminal.update(cx, |terminal, cx| { if terminal.selection_started() || hovered { terminal.mouse_drag(e, hitbox.bounds, cx); cx.notify(); } }) } if hitbox.is_hovered(window) { terminal.update(cx, |terminal, cx| { terminal.mouse_move(e, cx); }) } } }); self.interactivity.on_mouse_up( MouseButton::Left, TerminalElement::generic_button_handler( terminal.clone(), focus.clone(), move |terminal, e, cx| { terminal.mouse_up(e, cx); }, ), ); self.interactivity.on_mouse_down( MouseButton::Middle, TerminalElement::generic_button_handler( terminal.clone(), focus.clone(), move |terminal, e, cx| { terminal.mouse_down(e, cx); }, ), ); self.interactivity.on_scroll_wheel({ let terminal_view = self.terminal_view.downgrade(); move |e, _, cx| { terminal_view .update(cx, |terminal_view, cx| { terminal_view.scroll_wheel(e, cx); cx.notify(); }) .ok();