From 39887be7d062324b7a81ba3734cc1e18e102e909 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 29 Nov 2023 14:38:27 -0800 Subject: [PATCH 01/10] WIP: layout compiling --- crates/editor2/src/items.rs | 2 +- crates/editor2/src/selections_collection.rs | 3 - crates/terminal2/src/terminal_settings.rs | 9 +- crates/terminal_view2/src/terminal_element.rs | 1946 +++++++++-------- crates/theme2/src/default_colors.rs | 4 + crates/theme2/src/one_themes.rs | 3 + crates/theme2/src/styles/colors.rs | 1 + 7 files changed, 990 insertions(+), 978 deletions(-) diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs index eca3b99d78..179aa8cd6f 100644 --- a/crates/editor2/src/items.rs +++ b/crates/editor2/src/items.rs @@ -913,7 +913,7 @@ impl SearchableItem for Editor { fn update_matches(&mut self, matches: Vec>, cx: &mut ViewContext) { self.highlight_background::( matches, - |theme| theme.title_bar_background, // todo: update theme + |theme| theme.search_match_background, cx, ); } diff --git a/crates/editor2/src/selections_collection.rs b/crates/editor2/src/selections_collection.rs index bcf41f135b..d9f0ec3764 100644 --- a/crates/editor2/src/selections_collection.rs +++ b/crates/editor2/src/selections_collection.rs @@ -315,14 +315,11 @@ impl SelectionsCollection { let line = display_map.layout_row(row, &text_layout_details); - dbg!("****START COL****"); let start_col = line.closest_index_for_x(positions.start) as u32; if start_col < line_len || (is_empty && positions.start == line.width) { let start = DisplayPoint::new(row, start_col); - dbg!("****END COL****"); let end_col = line.closest_index_for_x(positions.end) as u32; let end = DisplayPoint::new(row, end_col); - dbg!(start_col, end_col); Some(Selection { id: post_inc(&mut self.next_selection_id), diff --git a/crates/terminal2/src/terminal_settings.rs b/crates/terminal2/src/terminal_settings.rs index 16ec286922..1038c6d061 100644 --- a/crates/terminal2/src/terminal_settings.rs +++ b/crates/terminal2/src/terminal_settings.rs @@ -1,4 +1,4 @@ -use gpui::{AppContext, FontFeatures, Pixels}; +use gpui::{px, AbsoluteLength, AppContext, FontFeatures, Pixels}; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use std::{collections::HashMap, path::PathBuf}; @@ -114,12 +114,13 @@ pub enum TerminalLineHeight { } impl TerminalLineHeight { - pub fn value(&self) -> f32 { - match self { + pub fn value(&self) -> AbsoluteLength { + let value = match self { TerminalLineHeight::Comfortable => 1.618, TerminalLineHeight::Standard => 1.3, TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.), - } + }; + px(value).into() } } diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 363dd90287..5c4635bac5 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -1,970 +1,976 @@ -// #![allow(unused)] // todo!() - -// use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; -// use gpui::{ -// point, transparent_black, AnyElement, AppContext, Bounds, Component, CursorStyle, Element, -// ElementId, FontStyle, FontWeight, HighlightStyle, Hsla, IntoElement, IsZero, LayoutId, -// ModelContext, Overlay, Pixels, Point, Quad, TextRun, TextStyle, TextSystem, Underline, -// ViewContext, WeakModel, WindowContext, -// }; -// use itertools::Itertools; -// use language::CursorShape; -// use ordered_float::OrderedFloat; -// use settings::Settings; -// use terminal::{ -// alacritty_terminal::{ -// ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, -// grid::Dimensions, -// index::Point as AlacPoint, -// term::{cell::Flags, TermMode}, -// }, -// terminal_settings::TerminalSettings, -// IndexedCell, Terminal, TerminalContent, TerminalSize, -// }; -// use theme::ThemeSettings; - -// use std::mem; -// use std::{fmt::Debug, ops::RangeInclusive}; - -// use crate::TerminalView; - -// ///The information generated during layout that is necessary for painting -// pub struct LayoutState { -// cells: Vec, -// rects: Vec, -// relative_highlighted_ranges: Vec<(RangeInclusive, Hsla)>, -// cursor: Option, -// background_color: Hsla, -// size: TerminalSize, -// mode: TermMode, -// display_offset: usize, -// hyperlink_tooltip: Option, -// gutter: f32, -// } - -// ///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(Clone, Debug, Default)] -// struct LayoutCell { -// point: AlacPoint, -// text: Line, -// } - -// impl LayoutCell { -// fn new(point: AlacPoint, text: Line) -> LayoutCell { -// LayoutCell { point, text } -// } - -// fn paint( -// &self, -// origin: Point, -// layout: &LayoutState, -// _visible_bounds: Bounds, -// _view: &mut TerminalView, -// cx: &mut WindowContext, -// ) { -// let pos = { -// let point = self.point; - -// Point::new( -// (origin.x + point.column as f32 * layout.size.cell_width).floor(), -// origin.y + point.line as f32 * layout.size.line_height, -// ) -// }; - -// self.text.paint(pos, layout.size.line_height, cx); -// } -// } - -// #[derive(Clone, Debug, Default)] -// struct LayoutRect { -// point: AlacPoint, -// num_of_cells: usize, -// color: Hsla, -// } - -// impl LayoutRect { -// fn new(point: AlacPoint, 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, -// } -// } - -// fn paint( -// &self, -// origin: Point, -// layout: &LayoutState, -// _view: &mut TerminalView, -// cx: &mut ViewContext, -// ) { -// let position = { -// let alac_point = self.point; -// point( -// (origin.x + alac_point.column as f32 * layout.size.cell_width).floor(), -// origin.y + alac_point.line as f32 * layout.size.line_height, -// ) -// }; -// let size = point( -// (layout.size.cell_width * self.num_of_cells as f32).ceil(), -// layout.size.line_height, -// ) -// .into(); - -// cx.paint_quad( -// Bounds::new(position, size), -// Default::default(), -// self.color, -// Default::default(), -// transparent_black(), -// ); -// } -// } - -// ///The GPUI element that paints the terminal. -// ///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? -// pub struct TerminalElement { -// terminal: WeakModel, -// focused: bool, -// cursor_visible: bool, -// can_navigate_to_selected_word: bool, -// } - -// impl TerminalElement { -// pub fn new( -// terminal: WeakModel, -// focused: bool, -// cursor_visible: bool, -// can_navigate_to_selected_word: bool, -// ) -> TerminalElement { -// TerminalElement { -// terminal, -// focused, -// cursor_visible, -// can_navigate_to_selected_word, -// } -// } - -// //Vec> -> Clip out the parts of the ranges - -// fn layout_grid( -// grid: &Vec, -// text_style: &TextStyle, -// terminal_theme: &TerminalStyle, -// text_system: &TextSystem, -// hyperlink: Option<(HighlightStyle, &RangeInclusive)>, -// cx: &mut WindowContext<'_>, -// ) -> (Vec, Vec) { -// let mut cells = vec![]; -// let mut rects = vec![]; - -// let mut cur_rect: Option = None; -// let mut cur_alac_color = None; - -// let linegroups = grid.into_iter().group_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 = cur_rect.take().map(|rect| 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, &terminal_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, &terminal_theme), -// )); -// } -// } -// } -// } - -// //Layout current cell text -// { -// let cell_text = &cell.c.to_string(); -// if !is_blank(&cell) { -// let cell_style = TerminalElement::cell_style( -// &cell, -// fg, -// terminal_theme, -// text_style, -// text_system, -// hyperlink, -// ); - -// let layout_cell = text_system.layout_line( -// cell_text, -// text_style.font_size.to_pixels(cx.rem_size()), -// &[(cell_text.len(), cell_style)], -// )?; - -// 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) -// } - -// // Compute 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: TerminalSize, -// text_fragment: &Line, -// ) -> Option<(Point, 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 -// } -// } - -// ///Convert the Alacritty cell styles to GPUI text styles and background color -// fn cell_style( -// indexed: &IndexedCell, -// fg: terminal::alacritty_terminal::ansi::Color, -// style: &TerminalStyle, -// text_style: &TextStyle, -// text_system: &TextSystem, -// hyperlink: Option<(HighlightStyle, &RangeInclusive)>, -// ) -> TextRun { -// let flags = indexed.cell.flags; -// let fg = convert_color(&fg, &style); - -// let mut underline = flags -// .intersects(Flags::ALL_UNDERLINES) -// .then(|| Underline { -// color: fg, -// thickness: Pixels::from(1.0).scale(1.0), -// order: todo!(), -// bounds: todo!(), -// content_mask: todo!(), -// wavy: flags.contains(Flags::UNDERCURL), -// }) -// .unwrap_or_default(); - -// if indexed.cell.hyperlink().is_some() { -// if underline.thickness.is_zero() { -// underline.thickness = Pixels::from(1.0).scale(1.0); -// } -// } - -// let mut properties = Properties::new(); -// if indexed.flags.intersects(Flags::BOLD | Flags::DIM_BOLD) { -// properties = *properties.weight(FontWeight::BOLD); -// } -// if indexed.flags.intersects(Flags::ITALIC) { -// properties = *properties.style(FontStyle::Italic); -// } - -// let font_id = text_system -// .select_font(text_style.font_family, &properties) -// .unwrap_or(text_style.font_id); - -// let mut result = TextRun { -// color: fg, -// font_id, -// underline, -// }; - -// 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 -// } - -// // todo!() -// // fn generic_button_handler( -// // connection: WeakModel, -// // origin: Point, -// // f: impl Fn(&mut Terminal, Point, E, &mut ModelContext), -// // ) -> impl Fn(E, &mut TerminalView, &mut EventContext) { -// // move |event, _: &mut TerminalView, cx| { -// // cx.focus_parent(); -// // if let Some(conn_handle) = connection.upgrade() { -// // conn_handle.update(cx, |terminal, cx| { -// // f(terminal, origin, event, cx); - -// // cx.notify(); -// // }) -// // } -// // } -// // } - -// fn attach_mouse_handlers( -// &self, -// origin: Point, -// visible_bounds: Bounds, -// mode: TermMode, -// cx: &mut ViewContext, -// ) { -// // todo!() -// // let connection = self.terminal; - -// // let mut region = MouseRegion::new::(cx.view_id(), 0, visible_bounds); - -// // // Terminal Emulator controlled behavior: -// // region = region -// // // Start selections -// // .on_down(MouseButton::Left, move |event, v: &mut TerminalView, cx| { -// // let terminal_view = cx.handle(); -// // cx.focus(&terminal_view); -// // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); -// // if let Some(conn_handle) = connection.upgrade() { -// // conn_handle.update(cx, |terminal, cx| { -// // terminal.mouse_down(&event, origin); - -// // cx.notify(); -// // }) -// // } -// // }) -// // // Update drag selections -// // .on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| { -// // if event.end { -// // return; -// // } - -// // if cx.is_self_focused() { -// // if let Some(conn_handle) = connection.upgrade() { -// // conn_handle.update(cx, |terminal, cx| { -// // terminal.mouse_drag(event, origin); -// // cx.notify(); -// // }) -// // } -// // } -// // }) -// // // Copy on up behavior -// // .on_up( -// // MouseButton::Left, -// // TerminalElement::generic_button_handler( -// // connection, -// // origin, -// // move |terminal, origin, e, cx| { -// // terminal.mouse_up(&e, origin, cx); -// // }, -// // ), -// // ) -// // // Context menu -// // .on_click( -// // MouseButton::Right, -// // move |event, view: &mut TerminalView, cx| { -// // let mouse_mode = if let Some(conn_handle) = connection.upgrade() { -// // conn_handle.update(cx, |terminal, _cx| terminal.mouse_mode(event.shift)) -// // } else { -// // // If we can't get the model handle, probably can't deploy the context menu -// // true -// // }; -// // if !mouse_mode { -// // view.deploy_context_menu(event.position, cx); -// // } -// // }, -// // ) -// // .on_move(move |event, _: &mut TerminalView, cx| { -// // if cx.is_self_focused() { -// // if let Some(conn_handle) = connection.upgrade() { -// // conn_handle.update(cx, |terminal, cx| { -// // terminal.mouse_move(&event, origin); -// // cx.notify(); -// // }) -// // } -// // } -// // }) -// // .on_scroll(move |event, _: &mut TerminalView, cx| { -// // if let Some(conn_handle) = connection.upgrade() { -// // conn_handle.update(cx, |terminal, cx| { -// // terminal.scroll_wheel(event, origin); -// // cx.notify(); -// // }) -// // } -// // }); - -// // // Mouse mode handlers: -// // // All mouse modes need the extra click handlers -// // if mode.intersects(TermMode::MOUSE_MODE) { -// // region = region -// // .on_down( -// // MouseButton::Right, -// // TerminalElement::generic_button_handler( -// // connection, -// // origin, -// // move |terminal, origin, e, _cx| { -// // terminal.mouse_down(&e, origin); -// // }, -// // ), -// // ) -// // .on_down( -// // MouseButton::Middle, -// // TerminalElement::generic_button_handler( -// // connection, -// // origin, -// // move |terminal, origin, e, _cx| { -// // terminal.mouse_down(&e, origin); -// // }, -// // ), -// // ) -// // .on_up( -// // MouseButton::Right, -// // TerminalElement::generic_button_handler( -// // connection, -// // origin, -// // move |terminal, origin, e, cx| { -// // terminal.mouse_up(&e, origin, cx); -// // }, -// // ), -// // ) -// // .on_up( -// // MouseButton::Middle, -// // TerminalElement::generic_button_handler( -// // connection, -// // origin, -// // move |terminal, origin, e, cx| { -// // terminal.mouse_up(&e, origin, cx); -// // }, -// // ), -// // ) -// // } - -// // cx.scene().push_mouse_region(region); -// } -// } - -// impl Element for TerminalElement { -// type State = LayoutState; - -// fn layout( -// &mut self, -// element_state: Option, -// cx: &mut WindowContext<'_>, -// ) -> (LayoutId, Self::State) { -// let settings = ThemeSettings::get_global(cx); -// let terminal_settings = TerminalSettings::get_global(cx); - -// //Setup layout information -// let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone. -// let link_style = settings.theme.editor.link_definition; -// let tooltip_style = settings.theme.tooltip.clone(); - -// let text_system = cx.text_system(); -// let font_size = font_size(&terminal_settings, cx).unwrap_or(settings.buffer_font_size(cx)); -// let font_family_name = terminal_settings -// .font_family -// .as_ref() -// .unwrap_or(&settings.buffer_font_family_name); -// let font_features = terminal_settings -// .font_features -// .as_ref() -// .unwrap_or(&settings.buffer_font_features); -// let family_id = text_system -// .load_family(&[font_family_name], &font_features) -// .log_err() -// .unwrap_or(settings.buffer_font_family); -// let font_id = text_system -// .select_font(family_id, &Default::default()) -// .unwrap(); - -// let text_style = TextStyle { -// color: settings.theme.editor.text_color, -// font_family_id: family_id, -// font_family_name: text_system.family_name(family_id).unwrap(), -// font_id, -// font_size, -// font_properties: Default::default(), -// underline: Default::default(), -// soft_wrap: false, -// font_family: todo!(), -// font_features: todo!(), -// line_height: todo!(), -// font_weight: todo!(), -// font_style: todo!(), -// background_color: todo!(), -// white_space: todo!(), -// }; -// let selection_color = settings.theme.editor.selection.selection; -// let match_color = settings.theme.search.match_background; -// let gutter; -// let dimensions = { -// let line_height = text_style.font_size * terminal_settings.line_height.value(); -// let cell_width = text_system.em_advance(text_style.font_id, text_style.font_size); -// gutter = cell_width; - -// let size = constraint.max - point(gutter, 0.); -// TerminalSize::new(line_height, cell_width, size) -// }; - -// let search_matches = if let Some(terminal_model) = self.terminal.upgrade() { -// terminal_model.read(cx).matches.clone() -// } else { -// Default::default() -// }; - -// let background_color = terminal_theme.background; -// let terminal_handle = self.terminal.upgrade().unwrap(); - -// let last_hovered_word = terminal_handle.update(cx, |terminal, cx| { -// terminal.set_size(dimensions); -// terminal.try_sync(cx); -// if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() { -// terminal.last_content.last_hovered_word.clone() -// } else { -// None -// } -// }); - -// let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| { -// let mut tooltip = Overlay::new( -// Empty::new() -// .contained() -// .constrained() -// .with_width(dimensions.width()) -// .with_height(dimensions.height()) -// .with_tooltip::( -// hovered_word.id, -// hovered_word.word, -// None, -// tooltip_style, -// cx, -// ), -// ) -// .with_position_mode(gpui::OverlayPositionMode::Local) -// .into_any(); - -// tooltip.layout( -// SizeConstraint::new(Point::zero(), cx.window_size()), -// view_state, -// cx, -// ); -// tooltip -// }); - -// let TerminalContent { -// cells, -// mode, -// display_offset, -// cursor_char, -// selection, -// cursor, -// .. -// } = { &terminal_handle.read(cx).last_content }; - -// // searches, highlights to a single range representations -// let mut relative_highlighted_ranges = Vec::new(); -// for search_match in search_matches { -// relative_highlighted_ranges.push((search_match, match_color)) -// } -// if let Some(selection) = selection { -// relative_highlighted_ranges.push((selection.start..=selection.end, selection_color)); -// } - -// // then have that representation be converted to the appropriate highlight data structure - -// let (cells, rects) = TerminalElement::layout_grid( -// cells, -// &text_style, -// &terminal_theme, -// &cx.text_system(), -// last_hovered_word -// .as_ref() -// .map(|last_hovered_word| (link_style, &last_hovered_word.word_match)), -// cx, -// ); - -// //Layout cursor. Rectangle is used for IME, so we should lay it out even -// //if we don't end up showing it. -// let cursor = if let AlacCursorShape::Hidden = cursor.shape { -// None -// } else { -// let cursor_point = DisplayCursor::from(cursor.point, *display_offset); -// let cursor_text = { -// let str_trxt = cursor_char.to_string(); - -// let color = if self.focused { -// terminal_theme.background -// } else { -// terminal_theme.foreground -// }; - -// cx.text_system().layout_line( -// &str_trxt, -// text_style.font_size, -// &[( -// str_trxt.len(), -// TextRun { -// font_id: text_style.font_id, -// color, -// underline: Default::default(), -// }, -// )], -// )? -// }; - -// let focused = self.focused; -// TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map( -// move |(cursor_position, block_width)| { -// let (shape, text) = match cursor.shape { -// AlacCursorShape::Block if !focused => (CursorShape::Hollow, None), -// AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)), -// AlacCursorShape::Underline => (CursorShape::Underscore, None), -// AlacCursorShape::Beam => (CursorShape::Bar, None), -// AlacCursorShape::HollowBlock => (CursorShape::Hollow, None), -// //This case is handled in the if wrapping the whole cursor layout -// AlacCursorShape::Hidden => unreachable!(), -// }; - -// Cursor::new( -// cursor_position, -// block_width, -// dimensions.line_height, -// terminal_theme.cursor, -// shape, -// text, -// ) -// }, -// ) -// }; - -// //Done! -// ( -// constraint.max, -// Self::State { -// cells, -// cursor, -// background_color, -// size: dimensions, -// rects, -// relative_highlighted_ranges, -// mode: *mode, -// display_offset: *display_offset, -// hyperlink_tooltip, -// gutter, -// }, -// ) -// } - -// fn paint( -// self, -// bounds: Bounds, -// element_state: &mut Self::State, -// cx: &mut WindowContext<'_>, -// ) { -// // todo!() -// // let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - -// // //Setup element stuff -// // let clip_bounds = Some(visible_bounds); - -// // cx.paint_layer(clip_bounds, |cx| { -// // let origin = bounds.origin + point(element_state.gutter, 0.); - -// // // Elements are ephemeral, only at paint time do we know what could be clicked by a mouse -// // self.attach_mouse_handlers(origin, visible_bounds, element_state.mode, cx); - -// // cx.scene().push_cursor_region(gpui::CursorRegion { -// // bounds, -// // style: if element_state.hyperlink_tooltip.is_some() { -// // CursorStyle::AlacPointingHand -// // } else { -// // CursorStyle::IBeam -// // }, -// // }); - -// // cx.paint_layer(clip_bounds, |cx| { -// // //Start with a background color -// // cx.scene().push_quad(Quad { -// // bounds, -// // background: Some(element_state.background_color), -// // border: Default::default(), -// // corner_radii: Default::default(), -// // }); - -// // for rect in &element_state.rects { -// // rect.paint(origin, element_state, view_state, cx); -// // } -// // }); - -// // //Draw Highlighted Backgrounds -// // cx.paint_layer(clip_bounds, |cx| { -// // for (relative_highlighted_range, color) in -// // element_state.relative_highlighted_ranges.iter() -// // { -// // if let Some((start_y, highlighted_range_lines)) = to_highlighted_range_lines( -// // relative_highlighted_range, -// // element_state, -// // origin, -// // ) { -// // let hr = HighlightedRange { -// // start_y, //Need to change this -// // line_height: element_state.size.line_height, -// // lines: highlighted_range_lines, -// // color: color.clone(), -// // //Copied from editor. TODO: move to theme or something -// // corner_radius: 0.15 * element_state.size.line_height, -// // }; -// // hr.paint(bounds, cx); -// // } -// // } -// // }); - -// // //Draw the text cells -// // cx.paint_layer(clip_bounds, |cx| { -// // for cell in &element_state.cells { -// // cell.paint(origin, element_state, visible_bounds, view_state, cx); -// // } -// // }); - -// // //Draw cursor -// // if self.cursor_visible { -// // if let Some(cursor) = &element_state.cursor { -// // cx.paint_layer(clip_bounds, |cx| { -// // cursor.paint(origin, cx); -// // }) -// // } -// // } - -// // if let Some(element) = &mut element_state.hyperlink_tooltip { -// // element.paint(origin, visible_bounds, view_state, cx) -// // } -// // }); -// } - -// // todo!() remove? -// // fn metadata(&self) -> Option<&dyn std::any::Any> { -// // None -// // } - -// // fn debug( -// // &self, -// // _: Bounds, -// // _: &Self::State, -// // _: &Self::PaintState, -// // _: &TerminalView, -// // _: &gpui::ViewContext, -// // ) -> gpui::serde_json::Value { -// // json!({ -// // "type": "TerminalElement", -// // }) -// // } - -// // fn rect_for_text_range( -// // &self, -// // _: Range, -// // bounds: Bounds, -// // _: Bounds, -// // layout: &Self::State, -// // _: &Self::PaintState, -// // _: &TerminalView, -// // _: &gpui::ViewContext, -// // ) -> Option> { -// // // Use the same origin that's passed to `Cursor::paint` in the paint -// // // method bove. -// // let mut origin = bounds.origin() + point(layout.size.cell_width, 0.); - -// // // TODO - Why is it necessary to move downward one line to get correct -// // // positioning? I would think that we'd want the same rect that is -// // // painted for the cursor. -// // origin += point(0., layout.size.line_height); - -// // Some(layout.cursor.as_ref()?.bounding_rect(origin)) -// // } -// } - -// impl IntoElement for TerminalElement { -// type Element = Self; - -// fn element_id(&self) -> Option { -// todo!() -// } - -// fn into_element(self) -> Self::Element { -// self -// } -// } - -// fn is_blank(cell: &IndexedCell) -> bool { -// if cell.c != ' ' { -// return false; -// } - -// if cell.bg != AnsiColor::Named(NamedColor::Background) { -// return false; -// } - -// if cell.hyperlink().is_some() { -// return false; -// } - -// if cell -// .flags -// .intersects(Flags::ALL_UNDERLINES | Flags::INVERSE | Flags::STRIKEOUT) -// { -// return false; -// } - -// return true; -// } - -// fn to_highlighted_range_lines( -// range: &RangeInclusive, -// layout: &LayoutState, -// origin: Point, -// ) -> Option<(Pixels, Vec)> { -// // Step 1. Normalize the points to be viewport relative. -// // When display_offset = 1, here's how the grid is arranged: -// //-2,0 -2,1... -// //--- Viewport top -// //-1,0 -1,1... -// //--------- Terminal Top -// // 0,0 0,1... -// // 1,0 1,1... -// //--- Viewport Bottom -// // 2,0 2,1... -// //--------- Terminal Bottom - -// // Normalize to viewport relative, from terminal relative. -// // lines are i32s, which are negative above the top left corner of the terminal -// // If the user has scrolled, we use the display_offset to tell us which offset -// // of the grid data we should be looking at. But for the rendering step, we don't -// // want negatives. We want things relative to the 'viewport' (the area of the grid -// // which is currently shown according to the display offset) -// let unclamped_start = AlacPoint::new( -// range.start().line + layout.display_offset, -// range.start().column, -// ); -// let unclamped_end = -// AlacPoint::new(range.end().line + layout.display_offset, range.end().column); - -// // Step 2. Clamp range to viewport, and return None if it doesn't overlap -// if unclamped_end.line.0 < 0 || unclamped_start.line.0 > layout.size.num_lines() as i32 { -// return None; -// } - -// let clamped_start_line = unclamped_start.line.0.max(0) as usize; -// let clamped_end_line = unclamped_end.line.0.min(layout.size.num_lines() as i32) as usize; -// //Convert the start of the range to pixels -// let start_y = origin.y + clamped_start_line as f32 * layout.size.line_height; - -// // Step 3. Expand ranges that cross lines into a collection of single-line ranges. -// // (also convert to pixels) -// let mut highlighted_range_lines = Vec::new(); -// for line in clamped_start_line..=clamped_end_line { -// let mut line_start = 0; -// let mut line_end = layout.size.columns(); - -// if line == clamped_start_line { -// line_start = unclamped_start.column.0 as usize; -// } -// if line == clamped_end_line { -// line_end = unclamped_end.column.0 as usize + 1; //+1 for inclusive -// } - -// highlighted_range_lines.push(HighlightedRangeLine { -// start_x: origin.x + line_start as f32 * layout.size.cell_width, -// end_x: origin.x + line_end as f32 * layout.size.cell_width, -// }); -// } - -// Some((start_y, highlighted_range_lines)) -// } - -// fn font_size(terminal_settings: &TerminalSettings, cx: &mut AppContext) -> Option { -// terminal_settings -// .font_size -// .map(|size| theme::adjusted_font_size(size, cx)) -// } - -// // mappings::colors::convert_color -// fn convert_color(fg: &terminal::alacritty_terminal::ansi::Color, style: &TerminalStyle) -> Hsla { -// todo!() -// } +use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; +use gpui::{ + point, px, relative, rems, transparent_black, AnyElement, AppContext, Bounds, Component, + CursorStyle, Element, ElementId, FontStyle, FontWeight, HighlightStyle, Hsla, IntoElement, + IsZero, LayoutId, ModelContext, Overlay, Pixels, Point, Quad, ShapedLine, SharedString, Style, + Styled, TextRun, TextStyle, TextSystem, Underline, UnderlineStyle, ViewContext, WeakModel, + WhiteSpace, WindowContext, +}; +use itertools::Itertools; +use language::CursorShape; +use ordered_float::OrderedFloat; +use settings::Settings; +use terminal::{ + alacritty_terminal::{ + ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, + grid::Dimensions, + index::Point as AlacPoint, + term::{cell::Flags, TermMode}, + }, + terminal_settings::TerminalSettings, + IndexedCell, Terminal, TerminalContent, TerminalSize, +}; +use theme::{ActiveTheme, ThemeColors, ThemeSettings}; + +use std::mem; +use std::{fmt::Debug, ops::RangeInclusive}; + +use crate::TerminalView; + +///The information generated during layout that is necessary for painting +pub struct LayoutState { + cells: Vec, + rects: Vec, + relative_highlighted_ranges: Vec<(RangeInclusive, Hsla)>, + cursor: Option, + background_color: Hsla, + size: TerminalSize, + mode: TermMode, + display_offset: usize, + hyperlink_tooltip: Option, + gutter: Pixels, +} + +///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)] +struct LayoutCell { + point: AlacPoint, + text: gpui::ShapedLine, +} + +impl LayoutCell { + fn new(point: AlacPoint, text: gpui::ShapedLine) -> LayoutCell { + LayoutCell { point, text } + } + + fn paint( + &self, + origin: Point, + layout: &LayoutState, + _visible_bounds: Bounds, + _view: &mut TerminalView, + cx: &mut WindowContext, + ) { + let pos = { + let point = self.point; + + Point::new( + (origin.x + point.column as f32 * layout.size.cell_width).floor(), + origin.y + point.line as f32 * layout.size.line_height, + ) + }; + + self.text.paint(pos, layout.size.line_height, cx); + } +} + +#[derive(Clone, Debug, Default)] +struct LayoutRect { + point: AlacPoint, + num_of_cells: usize, + color: Hsla, +} + +impl LayoutRect { + fn new(point: AlacPoint, 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, + } + } + + fn paint( + &self, + origin: Point, + layout: &LayoutState, + _view: &mut TerminalView, + cx: &mut ViewContext, + ) { + let position = { + let alac_point = self.point; + point( + (origin.x + alac_point.column as f32 * layout.size.cell_width).floor(), + origin.y + alac_point.line as f32 * layout.size.line_height, + ) + }; + let size = point( + (layout.size.cell_width * self.num_of_cells as f32).ceil(), + layout.size.line_height, + ) + .into(); + + cx.paint_quad( + Bounds::new(position, size), + Default::default(), + self.color, + Default::default(), + transparent_black(), + ); + } +} + +///The GPUI element that paints the terminal. +///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? +pub struct TerminalElement { + terminal: WeakModel, + focused: bool, + cursor_visible: bool, + can_navigate_to_selected_word: bool, +} + +impl TerminalElement { + pub fn new( + terminal: WeakModel, + focused: bool, + cursor_visible: bool, + can_navigate_to_selected_word: bool, + ) -> TerminalElement { + TerminalElement { + terminal, + focused, + cursor_visible, + can_navigate_to_selected_word, + } + } + + //Vec> -> Clip out the parts of the ranges + + fn layout_grid( + grid: &Vec, + text_style: &TextStyle, + // terminal_theme: &TerminalStyle, + text_system: &TextSystem, + hyperlink: Option<(HighlightStyle, &RangeInclusive)>, + cx: &mut WindowContext<'_>, + ) -> (Vec, Vec) { + let theme_colors = cx.theme().colors(); + let mut cells = vec![]; + let mut rects = vec![]; + + let mut cur_rect: Option = None; + let mut cur_alac_color = None; + + let linegroups = grid.into_iter().group_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 = cur_rect.take().map(|rect| 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_colors), + )); + } + } + 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_colors), + )); + } + } + } + } + + //Layout current cell text + { + let cell_text = cell.c.to_string(); + if !is_blank(&cell) { + let cell_style = TerminalElement::cell_style( + &cell, + fg, + bg, + theme_colors, + text_style, + text_system, + hyperlink, + ); + + let layout_cell = text_system + .shape_line( + cell_text.into(), + text_style.font_size.to_pixels(cx.rem_size()), + &[cell_style], + ) + //todo!() Can we remove this unwrap? + .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) + } + + // Compute 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: TerminalSize, + text_fragment: &ShapedLine, + ) -> Option<(Point, 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 + } + } + + ///Convert the Alacritty cell styles to GPUI text styles and background color + fn cell_style( + indexed: &IndexedCell, + fg: terminal::alacritty_terminal::ansi::Color, + bg: terminal::alacritty_terminal::ansi::Color, + colors: &ThemeColors, + text_style: &TextStyle, + text_system: &TextSystem, + hyperlink: Option<(HighlightStyle, &RangeInclusive)>, + ) -> TextRun { + let flags = indexed.cell.flags; + let fg = convert_color(&fg, &colors); + let bg = convert_color(&bg, &colors); + + let mut 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), + }); + + //todo!(support bold and italic) + // let mut properties = Properties::new(); + // if indexed.flags.intersects(Flags::BOLD | Flags::DIM_BOLD) { + // properties = *properties.weight(FontWeight::BOLD); + // } + // if indexed.flags.intersects(Flags::ITALIC) { + // properties = *properties.style(FontStyle::Italic); + // } + + let mut result = TextRun { + len: indexed.c.len_utf8() as usize, + color: fg, + background_color: Some(bg), + font: text_style.font(), + underline, + }; + + 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 compute_layout(&self, bounds: Bounds, cx: &mut WindowContext) -> LayoutState { + let settings = ThemeSettings::get_global(cx); + let terminal_settings = TerminalSettings::get_global(cx); + + //Setup layout information + // todo!(Terminal tooltips) + // let link_style = settings.theme.editor.link_definition; + // let tooltip_style = settings.theme.tooltip.clone(); + + let text_system = cx.text_system(); + let font_size = font_size(&terminal_settings, cx).unwrap_or(settings.buffer_font_size(cx)); + let font_family: SharedString = terminal_settings + .font_family + .as_ref() + .map(|string| SharedString::from(*string)) + .unwrap_or(settings.buffer_font.family); + + let font_features = terminal_settings + .font_features + .as_ref() + .unwrap_or(&settings.buffer_font.features); + + let settings = ThemeSettings::get_global(cx); + let theme = cx.theme(); + let text_style = TextStyle { + font_family, + font_features: *font_features, + font_size: font_size.into(), + font_style: FontStyle::Normal, + line_height: terminal_settings.line_height.value().into(), + background_color: None, + white_space: WhiteSpace::Normal, + // These are going to be overridden per-cell + underline: None, + color: theme.colors().text, + font_weight: FontWeight::NORMAL, + }; + + let selection_color = theme.players().local(); + let match_color = theme.colors().search_match_background; + let gutter; + let dimensions = { + let rem_size = cx.rem_size(); + let font_pixels = text_style.font_size.to_pixels(rem_size); + let line_height = + font_pixels * terminal_settings.line_height.value().to_pixels(rem_size); + let font_id = cx.text_system().font_id(&text_style.font()).unwrap(); + + // todo!(do we need to keep this unwrap?) + let cell_width = text_system + .advance(font_id, font_pixels, 'm') + .unwrap() + .width; + gutter = cell_width; + + let mut size = bounds.size.clone(); + size.width -= gutter; + + TerminalSize::new(line_height, cell_width, size) + }; + + let search_matches = if let Some(terminal_model) = self.terminal.upgrade() { + terminal_model.read(cx).matches.clone() + } else { + Default::default() + }; + + let background_color = theme.colors().background; + let terminal_handle = self.terminal.upgrade().unwrap(); + + let last_hovered_word = terminal_handle.update(cx, |terminal, cx| { + terminal.set_size(dimensions); + terminal.try_sync(cx); + // if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() { + // terminal.last_content.last_hovered_word.clone() + // } else { + None + // } + }); + + // let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| { + // let mut tooltip = Overlay::new( + // Empty::new() + // .contained() + // .constrained() + // .with_width(dimensions.width()) + // .with_height(dimensions.height()) + // .with_tooltip::( + // hovered_word.id, + // hovered_word.word, + // None, + // tooltip_style, + // cx, + // ), + // ) + // .with_position_mode(gpui::OverlayPositionMode::Local) + // .into_any(); + + // tooltip.layout( + // SizeConstraint::new(Point::zero(), cx.window_size()), + // view_state, + // cx, + // ); + // tooltip + // }); + + let TerminalContent { + cells, + mode, + display_offset, + cursor_char, + selection, + cursor, + .. + } = { &terminal_handle.read(cx).last_content }; + + // searches, highlights to a single range representations + let mut relative_highlighted_ranges = Vec::new(); + for search_match in search_matches { + relative_highlighted_ranges.push((search_match, match_color)) + } + if let Some(selection) = selection { + relative_highlighted_ranges + .push((selection.start..=selection.end, selection_color.cursor)); + } + + // then have that representation be converted to the appropriate highlight data structure + + let (cells, rects) = TerminalElement::layout_grid( + cells, + &text_style, + // &terminal_theme, + &cx.text_system(), + // todo!(Terminal tooltips) + last_hovered_word, + // .as_ref() + // .map(|last_hovered_word| (link_style, &last_hovered_word.word_match)), + cx, + ); + + //Layout cursor. Rectangle is used for IME, so we should lay it out even + //if we don't end up showing it. + let cursor = if let AlacCursorShape::Hidden = cursor.shape { + None + } else { + let cursor_point = DisplayCursor::from(cursor.point, *display_offset); + let cursor_text = { + let str_trxt = cursor_char.to_string(); + + let color = if self.focused { + theme.players().local().background + } else { + theme.players().local().cursor + }; + + cx.text_system() + .layout_line( + &str_trxt, + text_style.font_size.to_pixels(cx.rem_size()), + &[TextRun { + len: str_trxt.len(), + font: text_style.font(), + color, + background_color: None, + underline: Default::default(), + }], + ) + //todo!(do we need to keep this unwrap?) + .unwrap() + }; + + let focused = self.focused; + TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map( + move |(cursor_position, block_width)| { + let (shape, text) = match cursor.shape { + AlacCursorShape::Block if !focused => (CursorShape::Hollow, None), + AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)), + AlacCursorShape::Underline => (CursorShape::Underscore, None), + AlacCursorShape::Beam => (CursorShape::Bar, None), + AlacCursorShape::HollowBlock => (CursorShape::Hollow, None), + //This case is handled in the if wrapping the whole cursor layout + AlacCursorShape::Hidden => unreachable!(), + }; + + Cursor::new( + cursor_position, + block_width, + dimensions.line_height, + terminal_theme.cursor, + shape, + text, + ) + }, + ) + }; + + //Done! + LayoutState { + cells, + cursor, + background_color, + size: dimensions, + rects, + relative_highlighted_ranges, + mode: *mode, + display_offset: *display_offset, + hyperlink_tooltip: None, // todo!(tooltips) + gutter, + } + } + + // todo!() + // fn generic_button_handler( + // connection: WeakModel, + // origin: Point, + // f: impl Fn(&mut Terminal, Point, E, &mut ModelContext), + // ) -> impl Fn(E, &mut TerminalView, &mut EventContext) { + // move |event, _: &mut TerminalView, cx| { + // cx.focus_parent(); + // if let Some(conn_handle) = connection.upgrade() { + // conn_handle.update(cx, |terminal, cx| { + // f(terminal, origin, event, cx); + + // cx.notify(); + // }) + // } + // } + // } + + fn attach_mouse_handlers( + &self, + origin: Point, + visible_bounds: Bounds, + mode: TermMode, + cx: &mut ViewContext, + ) { + // todo!() + // let connection = self.terminal; + + // let mut region = MouseRegion::new::(cx.view_id(), 0, visible_bounds); + + // // Terminal Emulator controlled behavior: + // region = region + // // Start selections + // .on_down(MouseButton::Left, move |event, v: &mut TerminalView, cx| { + // let terminal_view = cx.handle(); + // cx.focus(&terminal_view); + // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); + // if let Some(conn_handle) = connection.upgrade() { + // conn_handle.update(cx, |terminal, cx| { + // terminal.mouse_down(&event, origin); + + // cx.notify(); + // }) + // } + // }) + // // Update drag selections + // .on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| { + // if event.end { + // return; + // } + + // if cx.is_self_focused() { + // if let Some(conn_handle) = connection.upgrade() { + // conn_handle.update(cx, |terminal, cx| { + // terminal.mouse_drag(event, origin); + // cx.notify(); + // }) + // } + // } + // }) + // // Copy on up behavior + // .on_up( + // MouseButton::Left, + // TerminalElement::generic_button_handler( + // connection, + // origin, + // move |terminal, origin, e, cx| { + // terminal.mouse_up(&e, origin, cx); + // }, + // ), + // ) + // // Context menu + // .on_click( + // MouseButton::Right, + // move |event, view: &mut TerminalView, cx| { + // let mouse_mode = if let Some(conn_handle) = connection.upgrade() { + // conn_handle.update(cx, |terminal, _cx| terminal.mouse_mode(event.shift)) + // } else { + // // If we can't get the model handle, probably can't deploy the context menu + // true + // }; + // if !mouse_mode { + // view.deploy_context_menu(event.position, cx); + // } + // }, + // ) + // .on_move(move |event, _: &mut TerminalView, cx| { + // if cx.is_self_focused() { + // if let Some(conn_handle) = connection.upgrade() { + // conn_handle.update(cx, |terminal, cx| { + // terminal.mouse_move(&event, origin); + // cx.notify(); + // }) + // } + // } + // }) + // .on_scroll(move |event, _: &mut TerminalView, cx| { + // if let Some(conn_handle) = connection.upgrade() { + // conn_handle.update(cx, |terminal, cx| { + // terminal.scroll_wheel(event, origin); + // cx.notify(); + // }) + // } + // }); + + // // Mouse mode handlers: + // // All mouse modes need the extra click handlers + // if mode.intersects(TermMode::MOUSE_MODE) { + // region = region + // .on_down( + // MouseButton::Right, + // TerminalElement::generic_button_handler( + // connection, + // origin, + // move |terminal, origin, e, _cx| { + // terminal.mouse_down(&e, origin); + // }, + // ), + // ) + // .on_down( + // MouseButton::Middle, + // TerminalElement::generic_button_handler( + // connection, + // origin, + // move |terminal, origin, e, _cx| { + // terminal.mouse_down(&e, origin); + // }, + // ), + // ) + // .on_up( + // MouseButton::Right, + // TerminalElement::generic_button_handler( + // connection, + // origin, + // move |terminal, origin, e, cx| { + // terminal.mouse_up(&e, origin, cx); + // }, + // ), + // ) + // .on_up( + // MouseButton::Middle, + // TerminalElement::generic_button_handler( + // connection, + // origin, + // move |terminal, origin, e, cx| { + // terminal.mouse_up(&e, origin, cx); + // }, + // ), + // ) + // } + + // cx.scene().push_mouse_region(region); + } +} + +impl Element for TerminalElement { + type State = (); + + fn layout( + &mut self, + element_state: Option, + cx: &mut WindowContext<'_>, + ) -> (LayoutId, Self::State) { + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + let layout_id = cx.request_layout(&style, None); + + (layout_id, ()) + } + + fn paint(self, bounds: Bounds, _: &mut Self::State, cx: &mut WindowContext<'_>) { + let layout = self.compute_layout(bounds, cx); + // todo!() + // let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + // //Setup element stuff + // let clip_bounds = Some(visible_bounds); + + // cx.paint_layer(clip_bounds, |cx| { + // let origin = bounds.origin + point(element_state.gutter, 0.); + + // // Elements are ephemeral, only at paint time do we know what could be clicked by a mouse + // self.attach_mouse_handlers(origin, visible_bounds, element_state.mode, cx); + + // cx.scene().push_cursor_region(gpui::CursorRegion { + // bounds, + // style: if element_state.hyperlink_tooltip.is_some() { + // CursorStyle::AlacPointingHand + // } else { + // CursorStyle::IBeam + // }, + // }); + + // cx.paint_layer(clip_bounds, |cx| { + // //Start with a background color + // cx.scene().push_quad(Quad { + // bounds, + // background: Some(element_state.background_color), + // border: Default::default(), + // corner_radii: Default::default(), + // }); + + // for rect in &element_state.rects { + // rect.paint(origin, element_state, view_state, cx); + // } + // }); + + // //Draw Highlighted Backgrounds + // cx.paint_layer(clip_bounds, |cx| { + // for (relative_highlighted_range, color) in + // element_state.relative_highlighted_ranges.iter() + // { + // if let Some((start_y, highlighted_range_lines)) = to_highlighted_range_lines( + // relative_highlighted_range, + // element_state, + // origin, + // ) { + // let hr = HighlightedRange { + // start_y, //Need to change this + // line_height: element_state.size.line_height, + // lines: highlighted_range_lines, + // color: color.clone(), + // //Copied from editor. TODO: move to theme or something + // corner_radius: 0.15 * element_state.size.line_height, + // }; + // hr.paint(bounds, cx); + // } + // } + // }); + + // //Draw the text cells + // cx.paint_layer(clip_bounds, |cx| { + // for cell in &element_state.cells { + // cell.paint(origin, element_state, visible_bounds, view_state, cx); + // } + // }); + + // //Draw cursor + // if self.cursor_visible { + // if let Some(cursor) = &element_state.cursor { + // cx.paint_layer(clip_bounds, |cx| { + // cursor.paint(origin, cx); + // }) + // } + // } + + // if let Some(element) = &mut element_state.hyperlink_tooltip { + // element.paint(origin, visible_bounds, view_state, cx) + // } + // }); + } + + // todo!() remove? + // fn metadata(&self) -> Option<&dyn std::any::Any> { + // None + // } + + // fn debug( + // &self, + // _: Bounds, + // _: &Self::State, + // _: &Self::PaintState, + // _: &TerminalView, + // _: &gpui::ViewContext, + // ) -> gpui::serde_json::Value { + // json!({ + // "type": "TerminalElement", + // }) + // } + + // fn rect_for_text_range( + // &self, + // _: Range, + // bounds: Bounds, + // _: Bounds, + // layout: &Self::State, + // _: &Self::PaintState, + // _: &TerminalView, + // _: &gpui::ViewContext, + // ) -> Option> { + // // Use the same origin that's passed to `Cursor::paint` in the paint + // // method bove. + // let mut origin = bounds.origin() + point(layout.size.cell_width, 0.); + + // // TODO - Why is it necessary to move downward one line to get correct + // // positioning? I would think that we'd want the same rect that is + // // painted for the cursor. + // origin += point(0., layout.size.line_height); + + // Some(layout.cursor.as_ref()?.bounding_rect(origin)) + // } +} + +impl IntoElement for TerminalElement { + type Element = Self; + + fn element_id(&self) -> Option { + todo!() + } + + fn into_element(self) -> Self::Element { + self + } +} + +fn is_blank(cell: &IndexedCell) -> bool { + if cell.c != ' ' { + return false; + } + + if cell.bg != AnsiColor::Named(NamedColor::Background) { + return false; + } + + if cell.hyperlink().is_some() { + return false; + } + + if cell + .flags + .intersects(Flags::ALL_UNDERLINES | Flags::INVERSE | Flags::STRIKEOUT) + { + return false; + } + + return true; +} + +fn to_highlighted_range_lines( + range: &RangeInclusive, + layout: &LayoutState, + origin: Point, +) -> Option<(Pixels, Vec)> { + // Step 1. Normalize the points to be viewport relative. + // When display_offset = 1, here's how the grid is arranged: + //-2,0 -2,1... + //--- Viewport top + //-1,0 -1,1... + //--------- Terminal Top + // 0,0 0,1... + // 1,0 1,1... + //--- Viewport Bottom + // 2,0 2,1... + //--------- Terminal Bottom + + // Normalize to viewport relative, from terminal relative. + // lines are i32s, which are negative above the top left corner of the terminal + // If the user has scrolled, we use the display_offset to tell us which offset + // of the grid data we should be looking at. But for the rendering step, we don't + // want negatives. We want things relative to the 'viewport' (the area of the grid + // which is currently shown according to the display offset) + let unclamped_start = AlacPoint::new( + range.start().line + layout.display_offset, + range.start().column, + ); + let unclamped_end = + AlacPoint::new(range.end().line + layout.display_offset, range.end().column); + + // Step 2. Clamp range to viewport, and return None if it doesn't overlap + if unclamped_end.line.0 < 0 || unclamped_start.line.0 > layout.size.num_lines() as i32 { + return None; + } + + let clamped_start_line = unclamped_start.line.0.max(0) as usize; + let clamped_end_line = unclamped_end.line.0.min(layout.size.num_lines() as i32) as usize; + //Convert the start of the range to pixels + let start_y = origin.y + clamped_start_line as f32 * layout.size.line_height; + + // Step 3. Expand ranges that cross lines into a collection of single-line ranges. + // (also convert to pixels) + let mut highlighted_range_lines = Vec::new(); + for line in clamped_start_line..=clamped_end_line { + let mut line_start = 0; + let mut line_end = layout.size.columns(); + + if line == clamped_start_line { + line_start = unclamped_start.column.0 as usize; + } + if line == clamped_end_line { + line_end = unclamped_end.column.0 as usize + 1; //+1 for inclusive + } + + highlighted_range_lines.push(HighlightedRangeLine { + start_x: origin.x + line_start as f32 * layout.size.cell_width, + end_x: origin.x + line_end as f32 * layout.size.cell_width, + }); + } + + Some((start_y, highlighted_range_lines)) +} + +fn font_size(terminal_settings: &TerminalSettings, cx: &mut AppContext) -> Option { + terminal_settings + .font_size + .map(|size| theme::adjusted_font_size(size, cx)) +} + +// mappings::colors::convert_color +fn convert_color(fg: &terminal::alacritty_terminal::ansi::Color, colors: &ThemeColors) -> Hsla { + todo!() +} diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 4a47bc0536..aa07019301 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -49,6 +49,8 @@ impl ThemeColors { tab_bar_background: neutral().light().step_2(), tab_active_background: neutral().light().step_1(), tab_inactive_background: neutral().light().step_2(), + search_match_background: neutral().light().step_2(), // todo!(this was inserted by Mikayla) + editor_background: neutral().light().step_1(), editor_gutter_background: neutral().light().step_1(), // todo!("pick the right colors") editor_subheader_background: neutral().light().step_2(), @@ -121,6 +123,8 @@ impl ThemeColors { tab_bar_background: neutral().dark().step_2(), tab_active_background: neutral().dark().step_1(), tab_inactive_background: neutral().dark().step_2(), + search_match_background: neutral().dark().step_2(), // todo!(this was inserted by Mikayla) + editor_background: neutral().dark().step_1(), editor_gutter_background: neutral().dark().step_1(), editor_subheader_background: neutral().dark().step_3(), diff --git a/crates/theme2/src/one_themes.rs b/crates/theme2/src/one_themes.rs index 2f663618a6..839f5e13fe 100644 --- a/crates/theme2/src/one_themes.rs +++ b/crates/theme2/src/one_themes.rs @@ -75,6 +75,8 @@ pub(crate) fn one_dark() -> Theme { tab_bar_background: bg, tab_inactive_background: bg, tab_active_background: editor, + search_match_background: bg, // todo!(this was inserted by Mikayla) + editor_background: editor, editor_gutter_background: editor, editor_subheader_background: bg, @@ -92,6 +94,7 @@ pub(crate) fn one_dark() -> Theme { 0.2, ), editor_document_highlight_write_background: gpui::red(), + terminal_background: bg, // todo!("Use one colors for terminal") terminal_ansi_black: crate::black().dark().step_12(), diff --git a/crates/theme2/src/styles/colors.rs b/crates/theme2/src/styles/colors.rs index 1d4917ac00..27d891ce94 100644 --- a/crates/theme2/src/styles/colors.rs +++ b/crates/theme2/src/styles/colors.rs @@ -114,6 +114,7 @@ pub struct ThemeColors { pub tab_bar_background: Hsla, pub tab_inactive_background: Hsla, pub tab_active_background: Hsla, + pub search_match_background: Hsla, // pub panel_background: Hsla, // pub pane_focused_border: Hsla, // /// The color of the scrollbar thumb. From b0f91441679bfec0faa2101f4dacbeea7aea97bc Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 1 Dec 2023 16:21:12 -0800 Subject: [PATCH 02/10] Compiling layout! co-authored-by: Max --- crates/terminal_view2/src/terminal_element.rs | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 5c4635bac5..c2be4f62af 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -182,7 +182,7 @@ impl TerminalElement { // terminal_theme: &TerminalStyle, text_system: &TextSystem, hyperlink: Option<(HighlightStyle, &RangeInclusive)>, - cx: &mut WindowContext<'_>, + cx: &WindowContext<'_>, ) -> (Vec, Vec) { let theme_colors = cx.theme().colors(); let mut cells = vec![]; @@ -362,35 +362,41 @@ impl TerminalElement { } fn compute_layout(&self, bounds: Bounds, cx: &mut WindowContext) -> LayoutState { - let settings = ThemeSettings::get_global(cx); - let terminal_settings = TerminalSettings::get_global(cx); + let settings = ThemeSettings::get_global(cx).clone(); //Setup layout information // todo!(Terminal tooltips) // let link_style = settings.theme.editor.link_definition; // let tooltip_style = settings.theme.tooltip.clone(); - let text_system = cx.text_system(); - let font_size = font_size(&terminal_settings, cx).unwrap_or(settings.buffer_font_size(cx)); - let font_family: SharedString = terminal_settings + let buffer_font_size = settings.buffer_font_size(cx); + + let terminal_settings = TerminalSettings::get_global(cx); + let font_family = terminal_settings .font_family .as_ref() - .map(|string| SharedString::from(*string)) + .map(|string| string.clone().into()) .unwrap_or(settings.buffer_font.family); let font_features = terminal_settings .font_features - .as_ref() - .unwrap_or(&settings.buffer_font.features); + .clone() + .unwrap_or(settings.buffer_font.features.clone()); + + let line_height = terminal_settings.line_height.value(); + let font_size = terminal_settings.font_size.clone(); + + let font_size = + font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx)); let settings = ThemeSettings::get_global(cx); - let theme = cx.theme(); + let theme = cx.theme().clone(); let text_style = TextStyle { font_family, - font_features: *font_features, + font_features, font_size: font_size.into(), font_style: FontStyle::Normal, - line_height: terminal_settings.line_height.value().into(), + line_height: line_height.into(), background_color: None, white_space: WhiteSpace::Normal, // These are going to be overridden per-cell @@ -399,14 +405,14 @@ impl TerminalElement { font_weight: FontWeight::NORMAL, }; + let text_system = cx.text_system(); let selection_color = theme.players().local(); let match_color = theme.colors().search_match_background; let gutter; let dimensions = { let rem_size = cx.rem_size(); let font_pixels = text_style.font_size.to_pixels(rem_size); - let line_height = - font_pixels * terminal_settings.line_height.value().to_pixels(rem_size); + let line_height = font_pixels * line_height.to_pixels(rem_size); let font_id = cx.text_system().font_id(&text_style.font()).unwrap(); // todo!(do we need to keep this unwrap?) @@ -475,7 +481,7 @@ impl TerminalElement { selection, cursor, .. - } = { &terminal_handle.read(cx).last_content }; + } = &terminal_handle.read(cx).last_content; // searches, highlights to a single range representations let mut relative_highlighted_ranges = Vec::new(); @@ -516,12 +522,13 @@ impl TerminalElement { theme.players().local().cursor }; + let len = str_trxt.len(); cx.text_system() - .layout_line( - &str_trxt, + .shape_line( + str_trxt.into(), text_style.font_size.to_pixels(cx.rem_size()), &[TextRun { - len: str_trxt.len(), + len, font: text_style.font(), color, background_color: None, @@ -549,7 +556,7 @@ impl TerminalElement { cursor_position, block_width, dimensions.line_height, - terminal_theme.cursor, + theme.players().local().cursor, shape, text, ) @@ -964,12 +971,6 @@ fn to_highlighted_range_lines( Some((start_y, highlighted_range_lines)) } -fn font_size(terminal_settings: &TerminalSettings, cx: &mut AppContext) -> Option { - terminal_settings - .font_size - .map(|size| theme::adjusted_font_size(size, cx)) -} - // mappings::colors::convert_color fn convert_color(fg: &terminal::alacritty_terminal::ansi::Color, colors: &ThemeColors) -> Hsla { todo!() From fe839197464361b5aefa168df849c572e4deb56a Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 1 Dec 2023 17:01:57 -0800 Subject: [PATCH 03/10] Get terminal turning on --- crates/client/src/telemetry.rs | 1 - crates/terminal2/src/mappings/colors.rs | 125 ------- crates/terminal2/src/terminal2.rs | 1 + crates/terminal_view2/src/terminal_element.rs | 318 ++++++++++++------ crates/terminal_view2/src/terminal_view.rs | 19 +- 5 files changed, 223 insertions(+), 241 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index e2fc8ad3ad..a3e7449cf8 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -350,7 +350,6 @@ impl Telemetry { milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - dbg!(telemetry_settings); self.report_clickhouse_event(event, telemetry_settings, true) } diff --git a/crates/terminal2/src/mappings/colors.rs b/crates/terminal2/src/mappings/colors.rs index d3c8443cbf..adcc0d8080 100644 --- a/crates/terminal2/src/mappings/colors.rs +++ b/crates/terminal2/src/mappings/colors.rs @@ -1,118 +1,5 @@ -// todo!() use alacritty_terminal::term::color::Rgb as AlacRgb; -// use gpui::color::Color; -// use theme2::TerminalStyle; -///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent -// pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color { -// match alac_color { -// //Named and theme defined colors -// alacritty_terminal::ansi::Color::Named(n) => match n { -// alacritty_terminal::ansi::NamedColor::Black => style.black, -// alacritty_terminal::ansi::NamedColor::Red => style.red, -// alacritty_terminal::ansi::NamedColor::Green => style.green, -// alacritty_terminal::ansi::NamedColor::Yellow => style.yellow, -// alacritty_terminal::ansi::NamedColor::Blue => style.blue, -// alacritty_terminal::ansi::NamedColor::Magenta => style.magenta, -// alacritty_terminal::ansi::NamedColor::Cyan => style.cyan, -// alacritty_terminal::ansi::NamedColor::White => style.white, -// alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black, -// alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red, -// alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green, -// alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow, -// alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue, -// alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta, -// alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan, -// alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white, -// alacritty_terminal::ansi::NamedColor::Foreground => style.foreground, -// alacritty_terminal::ansi::NamedColor::Background => style.background, -// alacritty_terminal::ansi::NamedColor::Cursor => style.cursor, -// alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black, -// alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red, -// alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green, -// alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow, -// alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue, -// alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta, -// alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan, -// alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white, -// alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground, -// alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground, -// }, -// //'True' colors -// alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX), -// //8 bit, indexed colors -// alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(&(*i as usize), style), -// } -// } - -/// TODO: Move this -///Converts an 8 bit ANSI color to it's GPUI equivalent. -///Accepts usize for compatibility with the alacritty::Colors interface, -///Other than that use case, should only be called with values in the [0,255] range -// pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color { -// match index { -// //0-15 are the same as the named colors above -// 0 => style.black, -// 1 => style.red, -// 2 => style.green, -// 3 => style.yellow, -// 4 => style.blue, -// 5 => style.magenta, -// 6 => style.cyan, -// 7 => style.white, -// 8 => style.bright_black, -// 9 => style.bright_red, -// 10 => style.bright_green, -// 11 => style.bright_yellow, -// 12 => style.bright_blue, -// 13 => style.bright_magenta, -// 14 => style.bright_cyan, -// 15 => style.bright_white, -// //16-231 are mapped to their RGB colors on a 0-5 range per channel -// 16..=231 => { -// let (r, g, b) = rgb_for_index(&(*index as u8)); //Split the index into it's ANSI-RGB components -// let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow -// Color::new(r * step, g * step, b * step, u8::MAX) //Map the ANSI-RGB components to an RGB color -// } -// //232-255 are a 24 step grayscale from black to white -// 232..=255 => { -// let i = *index as u8 - 232; //Align index to 0..24 -// let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks -// Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale -// } -// //For compatibility with the alacritty::Colors interface -// 256 => style.foreground, -// 257 => style.background, -// 258 => style.cursor, -// 259 => style.dim_black, -// 260 => style.dim_red, -// 261 => style.dim_green, -// 262 => style.dim_yellow, -// 263 => style.dim_blue, -// 264 => style.dim_magenta, -// 265 => style.dim_cyan, -// 266 => style.dim_white, -// 267 => style.bright_foreground, -// 268 => style.black, //'Dim Background', non-standard color -// _ => Color::new(0, 0, 0, 255), -// } -// } -///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube -///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit). -/// -///Wikipedia gives a formula for calculating the index for a given color: -/// -///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) -/// -///This function does the reverse, calculating the r, g, and b components from a given index. -// fn rgb_for_index(i: &u8) -> (u8, u8, u8) { -// debug_assert!((&16..=&231).contains(&i)); -// let i = i - 16; -// let r = (i - (i % 36)) / 36; -// let g = ((i % 36) - (i % 6)) / 6; -// let b = (i % 36) % 6; -// (r, g, b) -// } use gpui::Rgba; //Convenience method to convert from a GPUI color to an alacritty Rgb @@ -123,15 +10,3 @@ pub fn to_alac_rgb(color: impl Into) -> AlacRgb { let b = ((color.b * color.a) * 255.) as u8; AlacRgb::new(r, g, b) } - -// #[cfg(test)] -// mod tests { -// #[test] -// fn test_rgb_for_index() { -// //Test every possible value in the color cube -// for i in 16..=231 { -// let (r, g, b) = crate::mappings::colors::rgb_for_index(&(i as u8)); -// assert_eq!(i, 16 + 36 * r + 6 * g + b); -// } -// } -// } diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index 9f94339504..761bee26b2 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/crates/terminal2/src/terminal2.rs @@ -976,6 +976,7 @@ impl Terminal { } pub fn try_sync(&mut self, cx: &mut ModelContext) { + println!("trying to sync"); let term = self.term.clone(); let mut terminal = if let Some(term) = term.try_lock_unfair() { diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index c2be4f62af..7b7c2a3041 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -1,18 +1,17 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ - point, px, relative, rems, transparent_black, AnyElement, AppContext, Bounds, Component, - CursorStyle, Element, ElementId, FontStyle, FontWeight, HighlightStyle, Hsla, IntoElement, - IsZero, LayoutId, ModelContext, Overlay, Pixels, Point, Quad, ShapedLine, SharedString, Style, - Styled, TextRun, TextStyle, TextSystem, Underline, UnderlineStyle, ViewContext, WeakModel, + black, point, px, red, relative, transparent_black, AnyElement, Bounds, Element, ElementId, + Font, FontStyle, FontWeight, HighlightStyle, Hsla, IntoElement, LayoutId, Pixels, Point, Rgba, + ShapedLine, Style, TextRun, TextStyle, TextSystem, UnderlineStyle, ViewContext, WeakModel, WhiteSpace, WindowContext, }; use itertools::Itertools; use language::CursorShape; -use ordered_float::OrderedFloat; use settings::Settings; use terminal::{ + alacritty_terminal::ansi::NamedColor, alacritty_terminal::{ - ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, + ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape}, grid::Dimensions, index::Point as AlacPoint, term::{cell::Flags, TermMode}, @@ -20,7 +19,7 @@ use terminal::{ terminal_settings::TerminalSettings, IndexedCell, Terminal, TerminalContent, TerminalSize, }; -use theme::{ActiveTheme, ThemeColors, ThemeSettings}; +use theme::{ActiveTheme, Theme, ThemeSettings}; use std::mem; use std::{fmt::Debug, ops::RangeInclusive}; @@ -80,7 +79,6 @@ impl LayoutCell { origin: Point, layout: &LayoutState, _visible_bounds: Bounds, - _view: &mut TerminalView, cx: &mut WindowContext, ) { let pos = { @@ -92,7 +90,7 @@ impl LayoutCell { ) }; - self.text.paint(pos, layout.size.line_height, cx); + self.text.paint(pos, layout.size.line_height, cx).ok(); } } @@ -120,13 +118,7 @@ impl LayoutRect { } } - fn paint( - &self, - origin: Point, - layout: &LayoutState, - _view: &mut TerminalView, - cx: &mut ViewContext, - ) { + fn paint(&self, origin: Point, layout: &LayoutState, cx: &mut WindowContext) { let position = { let alac_point = self.point; point( @@ -184,7 +176,7 @@ impl TerminalElement { hyperlink: Option<(HighlightStyle, &RangeInclusive)>, cx: &WindowContext<'_>, ) -> (Vec, Vec) { - let theme_colors = cx.theme().colors(); + let theme = cx.theme(); let mut cells = vec![]; let mut rects = vec![]; @@ -225,7 +217,7 @@ impl TerminalElement { cell.point.column.0 as i32, ), 1, - convert_color(&bg, theme_colors), + convert_color(&bg, theme), )); } } @@ -234,7 +226,7 @@ impl TerminalElement { cur_rect = Some(LayoutRect::new( AlacPoint::new(line_index as i32, cell.point.column.0 as i32), 1, - convert_color(&bg, &theme_colors), + convert_color(&bg, &theme), )); } } @@ -249,7 +241,7 @@ impl TerminalElement { &cell, fg, bg, - theme_colors, + theme, text_style, text_system, hyperlink, @@ -312,7 +304,7 @@ impl TerminalElement { indexed: &IndexedCell, fg: terminal::alacritty_terminal::ansi::Color, bg: terminal::alacritty_terminal::ansi::Color, - colors: &ThemeColors, + colors: &Theme, text_style: &TextStyle, text_system: &TextSystem, hyperlink: Option<(HighlightStyle, &RangeInclusive)>, @@ -321,7 +313,7 @@ impl TerminalElement { let fg = convert_color(&fg, &colors); let bg = convert_color(&bg, &colors); - let mut underline = (flags.intersects(Flags::ALL_UNDERLINES) + let underline = (flags.intersects(Flags::ALL_UNDERLINES) || indexed.cell.hyperlink().is_some()) .then(|| UnderlineStyle { color: Some(fg), @@ -329,20 +321,27 @@ impl TerminalElement { wavy: flags.contains(Flags::UNDERCURL), }); - //todo!(support bold and italic) - // let mut properties = Properties::new(); - // if indexed.flags.intersects(Flags::BOLD | Flags::DIM_BOLD) { - // properties = *properties.weight(FontWeight::BOLD); - // } - // if indexed.flags.intersects(Flags::ITALIC) { - // properties = *properties.style(FontStyle::Italic); - // } + let weight = if flags.intersects(Flags::BOLD | Flags::DIM_BOLD) { + FontWeight::BOLD + } else { + FontWeight::NORMAL + }; + + let style = if flags.intersects(Flags::ITALIC) { + FontStyle::Italic + } else { + FontStyle::Normal + }; let mut result = TextRun { len: indexed.c.len_utf8() as usize, color: fg, background_color: Some(bg), - font: text_style.font(), + font: Font { + weight, + style, + ..text_style.font() + }, underline, }; @@ -498,7 +497,6 @@ impl TerminalElement { let (cells, rects) = TerminalElement::layout_grid( cells, &text_style, - // &terminal_theme, &cx.text_system(), // todo!(Terminal tooltips) last_hovered_word, @@ -752,84 +750,54 @@ impl Element for TerminalElement { fn paint(self, bounds: Bounds, _: &mut Self::State, cx: &mut WindowContext<'_>) { let layout = self.compute_layout(bounds, cx); - // todo!() - // let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - // //Setup element stuff - // let clip_bounds = Some(visible_bounds); + let theme = cx.theme(); + cx.paint_quad( + bounds, + Default::default(), + theme.colors().editor_background, + Default::default(), + Hsla::default(), + ); + let origin = bounds.origin + Point::new(layout.gutter, px(0.)); - // cx.paint_layer(clip_bounds, |cx| { - // let origin = bounds.origin + point(element_state.gutter, 0.); + for rect in &layout.rects { + rect.paint(origin, &layout, cx); + } - // // Elements are ephemeral, only at paint time do we know what could be clicked by a mouse - // self.attach_mouse_handlers(origin, visible_bounds, element_state.mode, cx); + cx.with_z_index(1, |cx| { + for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter() { + if let Some((start_y, highlighted_range_lines)) = + to_highlighted_range_lines(relative_highlighted_range, &layout, origin) + { + let hr = HighlightedRange { + start_y, //Need to change this + line_height: layout.size.line_height, + lines: highlighted_range_lines, + color: color.clone(), + //Copied from editor. TODO: move to theme or something + corner_radius: 0.15 * layout.size.line_height, + }; + hr.paint(bounds, cx); + } + } + }); - // cx.scene().push_cursor_region(gpui::CursorRegion { - // bounds, - // style: if element_state.hyperlink_tooltip.is_some() { - // CursorStyle::AlacPointingHand - // } else { - // CursorStyle::IBeam - // }, - // }); + cx.with_z_index(2, |cx| { + for cell in &layout.cells { + cell.paint(origin, &layout, bounds, cx); + } + }); - // cx.paint_layer(clip_bounds, |cx| { - // //Start with a background color - // cx.scene().push_quad(Quad { - // bounds, - // background: Some(element_state.background_color), - // border: Default::default(), - // corner_radii: Default::default(), - // }); - - // for rect in &element_state.rects { - // rect.paint(origin, element_state, view_state, cx); - // } - // }); - - // //Draw Highlighted Backgrounds - // cx.paint_layer(clip_bounds, |cx| { - // for (relative_highlighted_range, color) in - // element_state.relative_highlighted_ranges.iter() - // { - // if let Some((start_y, highlighted_range_lines)) = to_highlighted_range_lines( - // relative_highlighted_range, - // element_state, - // origin, - // ) { - // let hr = HighlightedRange { - // start_y, //Need to change this - // line_height: element_state.size.line_height, - // lines: highlighted_range_lines, - // color: color.clone(), - // //Copied from editor. TODO: move to theme or something - // corner_radius: 0.15 * element_state.size.line_height, - // }; - // hr.paint(bounds, cx); - // } - // } - // }); - - // //Draw the text cells - // cx.paint_layer(clip_bounds, |cx| { - // for cell in &element_state.cells { - // cell.paint(origin, element_state, visible_bounds, view_state, cx); - // } - // }); - - // //Draw cursor - // if self.cursor_visible { - // if let Some(cursor) = &element_state.cursor { - // cx.paint_layer(clip_bounds, |cx| { - // cursor.paint(origin, cx); - // }) - // } - // } + cx.with_z_index(3, |cx| { + if let Some(cursor) = &layout.cursor { + cursor.paint(origin, cx); + } + }); // if let Some(element) = &mut element_state.hyperlink_tooltip { // element.paint(origin, visible_bounds, view_state, cx) // } - // }); } // todo!() remove? @@ -877,7 +845,7 @@ impl IntoElement for TerminalElement { type Element = Self; fn element_id(&self) -> Option { - todo!() + Some("terminal".into()) } fn into_element(self) -> Self::Element { @@ -971,7 +939,147 @@ fn to_highlighted_range_lines( Some((start_y, highlighted_range_lines)) } -// mappings::colors::convert_color -fn convert_color(fg: &terminal::alacritty_terminal::ansi::Color, colors: &ThemeColors) -> Hsla { - todo!() +///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent +fn convert_color(fg: &terminal::alacritty_terminal::ansi::Color, theme: &Theme) -> Hsla { + let colors = theme.colors(); + match fg { + //Named and theme defined colors + terminal::alacritty_terminal::ansi::Color::Named(n) => match n { + NamedColor::Black => colors.terminal_ansi_black, + NamedColor::Red => colors.terminal_ansi_red, + NamedColor::Green => colors.terminal_ansi_green, + NamedColor::Yellow => colors.terminal_ansi_yellow, + NamedColor::Blue => colors.terminal_ansi_blue, + NamedColor::Magenta => colors.terminal_ansi_magenta, + NamedColor::Cyan => colors.terminal_ansi_cyan, + NamedColor::White => colors.terminal_ansi_white, + NamedColor::BrightBlack => colors.terminal_ansi_bright_black, + NamedColor::BrightRed => colors.terminal_ansi_bright_red, + NamedColor::BrightGreen => colors.terminal_ansi_bright_green, + NamedColor::BrightYellow => colors.terminal_ansi_bright_yellow, + NamedColor::BrightBlue => colors.terminal_ansi_bright_blue, + NamedColor::BrightMagenta => colors.terminal_ansi_bright_magenta, + NamedColor::BrightCyan => colors.terminal_ansi_bright_cyan, + NamedColor::BrightWhite => colors.terminal_ansi_bright_white, + NamedColor::Foreground => colors.text, + NamedColor::Background => colors.background, + NamedColor::Cursor => theme.players().local().cursor, + + // todo!(more colors) + NamedColor::DimBlack => red(), + NamedColor::DimRed => red(), + NamedColor::DimGreen => red(), + NamedColor::DimYellow => red(), + NamedColor::DimBlue => red(), + NamedColor::DimMagenta => red(), + NamedColor::DimCyan => red(), + NamedColor::DimWhite => red(), + NamedColor::BrightForeground => red(), + NamedColor::DimForeground => red(), + }, + //'True' colors + terminal::alacritty_terminal::ansi::Color::Spec(rgb) => rgba_color(rgb.r, rgb.g, rgb.b), + //8 bit, indexed colors + terminal::alacritty_terminal::ansi::Color::Indexed(i) => { + get_color_at_index(&(*i as usize), theme) + } + } +} + +///Converts an 8 bit ANSI color to it's GPUI equivalent. +///Accepts usize for compatibility with the alacritty::Colors interface, +///Other than that use case, should only be called with values in the [0,255] range +pub fn get_color_at_index(index: &usize, theme: &Theme) -> Hsla { + let colors = theme.colors(); + + match index { + //0-15 are the same as the named colors above + 0 => colors.terminal_ansi_black, + 1 => colors.terminal_ansi_red, + 2 => colors.terminal_ansi_green, + 3 => colors.terminal_ansi_yellow, + 4 => colors.terminal_ansi_blue, + 5 => colors.terminal_ansi_magenta, + 6 => colors.terminal_ansi_cyan, + 7 => colors.terminal_ansi_white, + 8 => colors.terminal_ansi_bright_black, + 9 => colors.terminal_ansi_bright_red, + 10 => colors.terminal_ansi_bright_green, + 11 => colors.terminal_ansi_bright_yellow, + 12 => colors.terminal_ansi_bright_blue, + 13 => colors.terminal_ansi_bright_magenta, + 14 => colors.terminal_ansi_bright_cyan, + 15 => colors.terminal_ansi_bright_white, + //16-231 are mapped to their RGB colors on a 0-5 range per channel + 16..=231 => { + let (r, g, b) = rgb_for_index(&(*index as u8)); //Split the index into it's ANSI-RGB components + let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow + rgba_color(r * step, g * step, b * step) //Map the ANSI-RGB components to an RGB color + } + //232-255 are a 24 step grayscale from black to white + 232..=255 => { + let i = *index as u8 - 232; //Align index to 0..24 + let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks + rgba_color(i * step, i * step, i * step) //Map the ANSI-grayscale components to the RGB-grayscale + } + //For compatibility with the alacritty::Colors interface + 256 => colors.text, + 257 => colors.background, + 258 => theme.players().local().cursor, + + // todo!(more colors) + 259 => red(), //style.dim_black, + 260 => red(), //style.dim_red, + 261 => red(), //style.dim_green, + 262 => red(), //style.dim_yellow, + 263 => red(), //style.dim_blue, + 264 => red(), //style.dim_magenta, + 265 => red(), //style.dim_cyan, + 266 => red(), //style.dim_white, + 267 => red(), //style.bright_foreground, + 268 => colors.terminal_ansi_black, //'Dim Background', non-standard color + + _ => black(), + } +} + +///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube +///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit). +/// +///Wikipedia gives a formula for calculating the index for a given color: +/// +///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) +/// +///This function does the reverse, calculating the r, g, and b components from a given index. +fn rgb_for_index(i: &u8) -> (u8, u8, u8) { + debug_assert!((&16..=&231).contains(&i)); + let i = i - 16; + let r = (i - (i % 36)) / 36; + let g = ((i % 36) - (i % 6)) / 6; + let b = (i % 36) % 6; + (r, g, b) +} + +fn rgba_color(r: u8, g: u8, b: u8) -> Hsla { + Rgba { + r: (r as f32 / 255.) as f32, + g: (g as f32 / 255.) as f32, + b: (b as f32 / 255.) as f32, + a: 1., + } + .into() +} + +#[cfg(test)] +mod tests { + use crate::terminal_element::rgb_for_index; + + #[test] + fn test_rgb_for_index() { + //Test every possible value in the color cube + for i in 16..=231 { + let (r, g, b) = rgb_for_index(&(i as u8)); + assert_eq!(i, 16 + 36 * r + 6 * g + b); + } + } } diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index b007d58c34..62306a8644 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -25,6 +25,7 @@ use terminal::{ terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory}, Event, MaybeNavigationTarget, Terminal, }; +use terminal_element::TerminalElement; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent}, @@ -305,7 +306,6 @@ impl TerminalView { cx, ) })); - dbg!(&position); // todo!() // self.context_menu // .show(position, AnchorCorner::TopLeft, menu_entries, cx); @@ -541,11 +541,13 @@ impl Render for TerminalView { let focused = self.focus_handle.is_focused(cx); div() + .size_full() .relative() .child( div() .z_index(0) .absolute() + .size_full() .on_key_down(cx.listener(Self::key_down)) .on_action(cx.listener(TerminalView::send_text)) .on_action(cx.listener(TerminalView::send_keystroke)) @@ -554,15 +556,12 @@ impl Render for TerminalView { .on_action(cx.listener(TerminalView::clear)) .on_action(cx.listener(TerminalView::show_character_palette)) .on_action(cx.listener(TerminalView::select_all)) - // todo!() - .child( - "TERMINAL HERE", // TerminalElement::new( - // terminal_handle, - // focused, - // self.should_show_cursor(focused, cx), - // self.can_navigate_to_selected_word, - // ) - ) + .child(TerminalElement::new( + terminal_handle, + focused, + self.should_show_cursor(focused, cx), + self.can_navigate_to_selected_word, + )) .on_mouse_down( MouseButton::Right, cx.listener(|this, event: &MouseDownEvent, cx| { From ab140ee4c271c33be31aa3b8e60892aff40c80c7 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 5 Dec 2023 12:07:17 -0800 Subject: [PATCH 04/10] Add event based drag API to GPUI, continue binding mouse handlers to terminal --- Cargo.lock | 1 + crates/gpui2/src/app.rs | 30 +- crates/gpui2/src/element.rs | 33 -- crates/gpui2/src/elements/div.rs | 73 +++- crates/gpui2/src/window.rs | 19 +- crates/terminal2/src/terminal2.rs | 9 +- crates/terminal_view2/Cargo.toml | 1 + crates/terminal_view2/src/terminal_element.rs | 352 ++++++++++-------- crates/terminal_view2/src/terminal_view.rs | 1 + 9 files changed, 295 insertions(+), 224 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39683c9fc1..349ec8f175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9478,6 +9478,7 @@ dependencies = [ "terminal2", "theme2", "thiserror", + "ui2", "util", "workspace2", ] diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index fec6f150f6..a639660c8d 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -185,7 +185,7 @@ pub struct AppContext { flushing_effects: bool, pending_updates: usize, pub(crate) actions: Rc, - pub(crate) active_drag: Option, + pub(crate) active_drag: Option, pub(crate) active_tooltip: Option, pub(crate) next_frame_callbacks: HashMap>, pub(crate) frame_consumers: HashMap>, @@ -1172,6 +1172,34 @@ pub struct AnyDrag { pub cursor_offset: Point, } +pub enum AnyDragState { + EventListener, + AnyDrag(AnyDrag), +} + +impl AnyDragState { + pub fn any_drag(&self) -> Option<&AnyDrag> { + match self { + AnyDragState::EventListener => None, + AnyDragState::AnyDrag(any_drag) => Some(any_drag), + } + } + + pub fn entity_id(&self) -> Option { + match self { + AnyDragState::EventListener => None, + AnyDragState::AnyDrag(any_drag) => Some(any_drag.view.entity_id()), + } + } + + pub fn entity_type(&self) -> Option { + match self { + AnyDragState::EventListener => None, + AnyDragState::AnyDrag(any_drag) => Some(any_drag.view.entity_type()), + } + } +} + #[derive(Clone)] pub(crate) struct AnyTooltip { pub view: AnyView, diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 3c8f678b89..226a477012 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -370,39 +370,6 @@ impl DrawableElement { } } -// impl Element for DrawableElement { -// type State = ::State; - -// fn layout( -// &mut self, -// element_state: Option, -// cx: &mut WindowContext, -// ) -> (LayoutId, Self::State) { - -// } - -// fn paint( -// self, -// bounds: Bounds, -// element_state: &mut Self::State, -// cx: &mut WindowContext, -// ) { -// todo!() -// } -// } - -// impl RenderOnce for DrawableElement { -// type Element = Self; - -// fn element_id(&self) -> Option { -// self.element.as_ref()?.element_id() -// } - -// fn render_once(self) -> Self::Element { -// self -// } -// } - impl ElementObject for Option> where E: Element, diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index ce457fc693..e653710c0f 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,10 +1,10 @@ use crate::{ - point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, - BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle, - IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, - SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, - WindowContext, + point, px, Action, AnyDrag, AnyDragState, AnyElement, AnyTooltip, AnyView, AppContext, + BorrowAppContext, BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, + FocusEvent, FocusHandle, IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, + Render, ScrollWheelEvent, SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, + Task, View, Visibility, WindowContext, }; use collections::HashMap; use refineable::Refineable; @@ -415,6 +415,19 @@ pub trait StatefulInteractiveElement: InteractiveElement { self } + fn on_drag_event( + mut self, + listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static, + ) -> Self + where + Self: Sized, + { + self.interactivity() + .drag_event_listeners + .push(Box::new(listener)); + self + } + fn on_hover(mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) -> Self where Self: Sized, @@ -559,6 +572,8 @@ pub type KeyDownListener = Box; +pub type DragEventListener = Box; + pub type ActionListener = Box; pub fn div() -> Div { @@ -746,6 +761,7 @@ pub struct Interactivity { pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>, pub drop_listeners: SmallVec<[(TypeId, Box); 2]>, pub click_listeners: SmallVec<[ClickListener; 2]>, + pub drag_event_listeners: SmallVec<[DragEventListener; 1]>, pub drag_listener: Option, pub hover_listener: Option>, pub tooltip_builder: Option, @@ -890,8 +906,11 @@ impl Interactivity { if phase == DispatchPhase::Bubble && interactive_bounds.visibly_contains(&event.position, &cx) { - if let Some(drag_state_type) = - cx.active_drag.as_ref().map(|drag| drag.view.entity_type()) + if let Some(drag_state_type) = cx + .active_drag + .as_ref() + .and_then(|drag| drag.any_drag()) + .map(|drag| drag.view.entity_type()) { for (drop_state_type, listener) in &drop_listeners { if *drop_state_type == drag_state_type { @@ -899,11 +918,14 @@ impl Interactivity { .active_drag .take() .expect("checked for type drag state type above"); + let drag = drag.any_drag().expect("checked for any drag above"); listener(drag.view.clone(), cx); cx.notify(); cx.stop_propagation(); } } + } else { + cx.active_drag = None; } } }); @@ -911,12 +933,16 @@ impl Interactivity { let click_listeners = mem::take(&mut self.click_listeners); let drag_listener = mem::take(&mut self.drag_listener); + let drag_event_listeners = mem::take(&mut self.drag_event_listeners); - if !click_listeners.is_empty() || drag_listener.is_some() { + if !click_listeners.is_empty() + || drag_listener.is_some() + || !drag_event_listeners.is_empty() + { let pending_mouse_down = element_state.pending_mouse_down.clone(); let mouse_down = pending_mouse_down.borrow().clone(); if let Some(mouse_down) = mouse_down { - if let Some(drag_listener) = drag_listener { + if !drag_event_listeners.is_empty() || drag_listener.is_some() { let active_state = element_state.clicked_state.clone(); let interactive_bounds = interactive_bounds.clone(); @@ -924,17 +950,29 @@ impl Interactivity { if cx.active_drag.is_some() { if phase == DispatchPhase::Capture { cx.notify(); + } else if interactive_bounds.visibly_contains(&event.position, cx) + && (event.position - mouse_down.position).magnitude() + > DRAG_THRESHOLD + { + for listener in &drag_event_listeners { + listener(event, cx); + } } } else if phase == DispatchPhase::Bubble && interactive_bounds.visibly_contains(&event.position, cx) && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD { *active_state.borrow_mut() = ElementClickedState::default(); - let cursor_offset = event.position - bounds.origin; - let drag = drag_listener(cursor_offset, cx); - cx.active_drag = Some(drag); - cx.notify(); - cx.stop_propagation(); + if let Some(drag_listener) = &drag_listener { + let cursor_offset = event.position - bounds.origin; + let drag = drag_listener(cursor_offset, cx); + cx.active_drag = Some(AnyDragState::AnyDrag(drag)); + cx.notify(); + cx.stop_propagation(); + } + for listener in &drag_event_listeners { + listener(event, cx); + } } }); } @@ -1197,7 +1235,7 @@ impl Interactivity { if let Some(drag) = cx.active_drag.take() { for (state_type, group_drag_style) in &self.group_drag_over_styles { if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { - if *state_type == drag.view.entity_type() + if Some(*state_type) == drag.entity_type() && group_bounds.contains_point(&mouse_position) { style.refine(&group_drag_style.style); @@ -1206,7 +1244,7 @@ impl Interactivity { } for (state_type, drag_over_style) in &self.drag_over_styles { - if *state_type == drag.view.entity_type() + if Some(*state_type) == drag.entity_type() && bounds .intersect(&cx.content_mask().bounds) .contains_point(&mouse_position) @@ -1263,6 +1301,7 @@ impl Default for Interactivity { action_listeners: SmallVec::new(), drop_listeners: SmallVec::new(), click_listeners: SmallVec::new(), + drag_event_listeners: SmallVec::new(), drag_listener: None, hover_listener: None, tooltip_builder: None, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 8eb14769bf..e83f3012a5 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1159,12 +1159,15 @@ impl<'a> WindowContext<'a> { }); if let Some(active_drag) = self.app.active_drag.take() { - self.with_z_index(1, |cx| { - let offset = cx.mouse_position() - active_drag.cursor_offset; - let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); - active_drag.view.draw(offset, available_space, cx); - cx.active_drag = Some(active_drag); - }); + if let Some(active_drag) = active_drag.any_drag() { + self.with_z_index(1, |cx| { + let offset = cx.mouse_position() - active_drag.cursor_offset; + let available_space = + size(AvailableSpace::MinContent, AvailableSpace::MinContent); + active_drag.view.draw(offset, available_space, cx); + }); + } + self.active_drag = Some(active_drag); } else if let Some(active_tooltip) = self.app.active_tooltip.take() { self.with_z_index(1, |cx| { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); @@ -1240,10 +1243,10 @@ impl<'a> WindowContext<'a> { FileDropEvent::Entered { position, files } => { self.window.mouse_position = position; if self.active_drag.is_none() { - self.active_drag = Some(AnyDrag { + self.active_drag = Some(crate::AnyDragState::AnyDrag(AnyDrag { view: self.build_view(|_| files).into(), cursor_offset: position, - }); + })); } InputEvent::MouseDown(MouseDownEvent { position, diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index 761bee26b2..197b912b1b 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/crates/terminal2/src/terminal2.rs @@ -1104,7 +1104,12 @@ impl Terminal { } } - pub fn mouse_drag(&mut self, e: MouseMoveEvent, origin: Point, region: Bounds) { + pub fn mouse_drag( + &mut self, + e: &MouseMoveEvent, + origin: Point, + region: Bounds, + ) { let position = e.position - origin; self.last_mouse_position = Some(position); @@ -1130,7 +1135,7 @@ impl Terminal { } } - fn drag_line_delta(&mut self, e: MouseMoveEvent, region: Bounds) -> Option { + fn drag_line_delta(&mut self, e: &MouseMoveEvent, region: Bounds) -> Option { //TODO: Why do these need to be doubled? Probably the same problem that the IME has let top = region.origin.y + (self.last_content.size.line_height * 2.); let bottom = region.lower_left().y - (self.last_content.size.line_height * 2.); diff --git a/crates/terminal_view2/Cargo.toml b/crates/terminal_view2/Cargo.toml index 12e2c06504..9654bed7f5 100644 --- a/crates/terminal_view2/Cargo.toml +++ b/crates/terminal_view2/Cargo.toml @@ -21,6 +21,7 @@ workspace = { package = "workspace2", path = "../workspace2" } db = { package = "db2", path = "../db2" } procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false } terminal = { package = "terminal2", path = "../terminal2" } +ui = { package = "ui2", path = "../ui2" } smallvec.workspace = true smol.workspace = true mio-extras = "2.0.6" diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 7b7c2a3041..96ffbb1ffb 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -1,9 +1,10 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ - black, point, px, red, relative, transparent_black, AnyElement, Bounds, Element, ElementId, - Font, FontStyle, FontWeight, HighlightStyle, Hsla, IntoElement, LayoutId, Pixels, Point, Rgba, - ShapedLine, Style, TextRun, TextStyle, TextSystem, UnderlineStyle, ViewContext, WeakModel, - WhiteSpace, WindowContext, + black, div, point, px, red, relative, transparent_black, AnyElement, AvailableSpace, Bounds, + Element, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hsla, + InteractiveElement, InteractiveElementState, IntoElement, LayoutId, ModelContext, Pixels, + Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled, TextRun, TextStyle, + TextSystem, UnderlineStyle, WeakModel, WhiteSpace, WindowContext, }; use itertools::Itertools; use language::CursorShape; @@ -20,12 +21,11 @@ use terminal::{ IndexedCell, Terminal, TerminalContent, TerminalSize, }; use theme::{ActiveTheme, Theme, ThemeSettings}; +use ui::Tooltip; use std::mem; use std::{fmt::Debug, ops::RangeInclusive}; -use crate::TerminalView; - ///The information generated during layout that is necessary for painting pub struct LayoutState { cells: Vec, @@ -146,14 +146,25 @@ impl LayoutRect { ///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? pub struct TerminalElement { terminal: WeakModel, + focus: FocusHandle, focused: bool, cursor_visible: bool, can_navigate_to_selected_word: bool, + interactivity: gpui::Interactivity, } +impl InteractiveElement for TerminalElement { + fn interactivity(&mut self) -> &mut gpui::Interactivity { + &mut self.interactivity + } +} + +impl StatefulInteractiveElement for TerminalElement {} + impl TerminalElement { pub fn new( terminal: WeakModel, + focus: FocusHandle, focused: bool, cursor_visible: bool, can_navigate_to_selected_word: bool, @@ -161,8 +172,10 @@ impl TerminalElement { TerminalElement { terminal, focused, + focus, cursor_visible, can_navigate_to_selected_word, + interactivity: Default::default(), } } @@ -365,7 +378,6 @@ impl TerminalElement { //Setup layout information // todo!(Terminal tooltips) - // let link_style = settings.theme.editor.link_definition; // let tooltip_style = settings.theme.tooltip.clone(); let buffer_font_size = settings.buffer_font_size(cx); @@ -390,6 +402,20 @@ impl TerminalElement { let settings = ThemeSettings::get_global(cx); let theme = cx.theme().clone(); + + let link_style = HighlightStyle { + color: Some(gpui::blue()), + font_weight: None, + font_style: None, + background_color: None, + underline: Some(UnderlineStyle { + thickness: px(1.0), + color: Some(gpui::red()), + wavy: false, + }), + fade_out: None, + }; + let text_style = TextStyle { font_family, font_features, @@ -439,38 +465,19 @@ impl TerminalElement { let last_hovered_word = terminal_handle.update(cx, |terminal, cx| { terminal.set_size(dimensions); terminal.try_sync(cx); - // if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() { - // terminal.last_content.last_hovered_word.clone() - // } else { - None - // } + if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() { + terminal.last_content.last_hovered_word.clone() + } else { + None + } }); - // let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| { - // let mut tooltip = Overlay::new( - // Empty::new() - // .contained() - // .constrained() - // .with_width(dimensions.width()) - // .with_height(dimensions.height()) - // .with_tooltip::( - // hovered_word.id, - // hovered_word.word, - // None, - // tooltip_style, - // cx, - // ), - // ) - // .with_position_mode(gpui::OverlayPositionMode::Local) - // .into_any(); - - // tooltip.layout( - // SizeConstraint::new(Point::zero(), cx.window_size()), - // view_state, - // cx, - // ); - // tooltip - // }); + let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| { + div() + .size_full() + .id("terminal-element") + .tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx)) + }); let TerminalContent { cells, @@ -498,10 +505,9 @@ impl TerminalElement { cells, &text_style, &cx.text_system(), - // todo!(Terminal tooltips) - last_hovered_word, - // .as_ref() - // .map(|last_hovered_word| (link_style, &last_hovered_word.word_match)), + last_hovered_word + .as_ref() + .map(|last_hovered_word| (link_style, &last_hovered_word.word_match)), cx, ); @@ -577,92 +583,95 @@ impl TerminalElement { } } - // todo!() - // fn generic_button_handler( - // connection: WeakModel, - // origin: Point, - // f: impl Fn(&mut Terminal, Point, E, &mut ModelContext), - // ) -> impl Fn(E, &mut TerminalView, &mut EventContext) { - // move |event, _: &mut TerminalView, cx| { - // cx.focus_parent(); - // if let Some(conn_handle) = connection.upgrade() { - // conn_handle.update(cx, |terminal, cx| { - // f(terminal, origin, event, cx); - - // cx.notify(); - // }) - // } - // } - // } - - fn attach_mouse_handlers( - &self, + fn generic_button_handler( + connection: WeakModel, + origin: Point, + focus_handle: FocusHandle, + f: impl Fn(&mut Terminal, Point, &E, &mut ModelContext), + ) -> impl Fn(&E, &mut WindowContext) { + move |event, cx| { + cx.focus(&focus_handle); + if let Some(conn_handle) = connection.upgrade() { + conn_handle.update(cx, |terminal, cx| { + f(terminal, origin, event, cx); + + cx.notify(); + }) + } + } + } + + fn paint_mouse_listeners( + self, origin: Point, - visible_bounds: Bounds, mode: TermMode, - cx: &mut ViewContext, - ) { - // todo!() - // let connection = self.terminal; + bounds: Bounds, + cx: &mut WindowContext, + ) -> Self { + let focus = self.focus.clone(); + let connection = self.terminal.clone(); - // let mut region = MouseRegion::new::(cx.view_id(), 0, visible_bounds); + self.on_mouse_down(gpui::MouseButton::Left, { + let connection = connection.clone(); + let focus = focus.clone(); + move |e, cx| { + cx.focus(&focus); + //todo!(context menu) + // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); + if let Some(conn_handle) = connection.upgrade() { + conn_handle.update(cx, |terminal, cx| { + terminal.mouse_down(&e, origin); - // // Terminal Emulator controlled behavior: - // region = region - // // Start selections - // .on_down(MouseButton::Left, move |event, v: &mut TerminalView, cx| { - // let terminal_view = cx.handle(); - // cx.focus(&terminal_view); - // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); - // if let Some(conn_handle) = connection.upgrade() { - // conn_handle.update(cx, |terminal, cx| { - // terminal.mouse_down(&event, origin); + cx.notify(); + }) + } + } + }) + .on_drag_event({ + let connection = connection.clone(); + let focus = focus.clone(); + move |e, cx| { + if focus.is_focused(cx) { + if let Some(conn_handle) = connection.upgrade() { + conn_handle.update(cx, |terminal, cx| { + terminal.mouse_drag(e, origin, bounds); + cx.notify(); + }) + } + } + } + }) + .on_mouse_up( + gpui::MouseButton::Left, + TerminalElement::generic_button_handler( + connection.clone(), + origin, + focus.clone(), + move |terminal, origin, e, cx| { + terminal.mouse_up(&e, origin, cx); + }, + ), + ) + .on_click({ + let connection = connection.clone(); + move |e, cx| { + if e.down.button == gpui::MouseButton::Right { + let mouse_mode = if let Some(conn_handle) = connection.upgrade() { + conn_handle.update(cx, |terminal, _cx| { + terminal.mouse_mode(e.down.modifiers.shift) + }) + } else { + // If we can't get the model handle, probably can't deploy the context menu + true + }; + if !mouse_mode { + //todo!(context menu) + // view.deploy_context_menu(e.position, cx); + } + } + } + }) - // cx.notify(); - // }) - // } - // }) - // // Update drag selections - // .on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| { - // if event.end { - // return; - // } - - // if cx.is_self_focused() { - // if let Some(conn_handle) = connection.upgrade() { - // conn_handle.update(cx, |terminal, cx| { - // terminal.mouse_drag(event, origin); - // cx.notify(); - // }) - // } - // } - // }) - // // Copy on up behavior - // .on_up( - // MouseButton::Left, - // TerminalElement::generic_button_handler( - // connection, - // origin, - // move |terminal, origin, e, cx| { - // terminal.mouse_up(&e, origin, cx); - // }, - // ), - // ) - // // Context menu - // .on_click( - // MouseButton::Right, - // move |event, view: &mut TerminalView, cx| { - // let mouse_mode = if let Some(conn_handle) = connection.upgrade() { - // conn_handle.update(cx, |terminal, _cx| terminal.mouse_mode(event.shift)) - // } else { - // // If we can't get the model handle, probably can't deploy the context menu - // true - // }; - // if !mouse_mode { - // view.deploy_context_menu(event.position, cx); - // } - // }, - // ) // .on_move(move |event, _: &mut TerminalView, cx| { // if cx.is_self_focused() { // if let Some(conn_handle) = connection.upgrade() { @@ -733,71 +742,88 @@ impl TerminalElement { } impl Element for TerminalElement { - type State = (); + type State = InteractiveElementState; fn layout( &mut self, element_state: Option, cx: &mut WindowContext<'_>, ) -> (LayoutId, Self::State) { - let mut style = Style::default(); - style.size.width = relative(1.).into(); - style.size.height = relative(1.).into(); - let layout_id = cx.request_layout(&style, None); + let (layout_id, interactive_state) = + self.interactivity + .layout(element_state, cx, |mut style, cx| { + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + let layout_id = cx.request_layout(&style, None); - (layout_id, ()) + layout_id + }); + + (layout_id, interactive_state) } - fn paint(self, bounds: Bounds, _: &mut Self::State, cx: &mut WindowContext<'_>) { - let layout = self.compute_layout(bounds, cx); + fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext<'_>) { + let mut layout = self.compute_layout(bounds, cx); let theme = cx.theme(); + cx.paint_quad( bounds, Default::default(), - theme.colors().editor_background, + layout.background_color, Default::default(), Hsla::default(), ); let origin = bounds.origin + Point::new(layout.gutter, px(0.)); - for rect in &layout.rects { - rect.paint(origin, &layout, cx); - } + let this = self.paint_mouse_listeners(origin, layout.mode, bounds, cx); - cx.with_z_index(1, |cx| { - for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter() { - if let Some((start_y, highlighted_range_lines)) = - to_highlighted_range_lines(relative_highlighted_range, &layout, origin) - { - let hr = HighlightedRange { - start_y, //Need to change this - line_height: layout.size.line_height, - lines: highlighted_range_lines, - color: color.clone(), - //Copied from editor. TODO: move to theme or something - corner_radius: 0.15 * layout.size.line_height, - }; - hr.paint(bounds, cx); + this.interactivity + .paint(bounds, bounds.size, state, cx, |_, _, cx| { + for rect in &layout.rects { + rect.paint(origin, &layout, cx); } - } - }); - cx.with_z_index(2, |cx| { - for cell in &layout.cells { - cell.paint(origin, &layout, bounds, cx); - } - }); + cx.with_z_index(1, |cx| { + for (relative_highlighted_range, color) in + layout.relative_highlighted_ranges.iter() + { + if let Some((start_y, highlighted_range_lines)) = + to_highlighted_range_lines(relative_highlighted_range, &layout, origin) + { + let hr = HighlightedRange { + start_y, //Need to change this + line_height: layout.size.line_height, + lines: highlighted_range_lines, + color: color.clone(), + //Copied from editor. TODO: move to theme or something + corner_radius: 0.15 * layout.size.line_height, + }; + hr.paint(bounds, cx); + } + } + }); - cx.with_z_index(3, |cx| { - if let Some(cursor) = &layout.cursor { - cursor.paint(origin, cx); - } - }); + cx.with_z_index(2, |cx| { + for cell in &layout.cells { + cell.paint(origin, &layout, bounds, cx); + } + }); - // if let Some(element) = &mut element_state.hyperlink_tooltip { - // element.paint(origin, visible_bounds, view_state, cx) - // } + if this.cursor_visible { + cx.with_z_index(3, |cx| { + if let Some(cursor) = &layout.cursor { + cursor.paint(origin, cx); + } + }); + } + + if let Some(element) = layout.hyperlink_tooltip.take() { + let width: AvailableSpace = bounds.size.width.into(); + let height: AvailableSpace = bounds.size.height.into(); + element.draw(origin, Size { width, height }, cx) + } + }); } // todo!() remove? diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index 63ed101f50..5b864f4a5e 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -555,6 +555,7 @@ impl Render for TerminalView { .on_action(cx.listener(TerminalView::select_all)) .child(TerminalElement::new( terminal_handle, + self.focus_handle.clone(), focused, self.should_show_cursor(focused, cx), self.can_navigate_to_selected_word, From 2ee0ecb6774f9831f0f07cbddd5d841a5803e834 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 5 Dec 2023 16:52:29 -0800 Subject: [PATCH 05/10] Add back the main structure --- crates/gpui2/src/elements/div.rs | 14 +- crates/terminal2/src/terminal2.rs | 3 +- crates/terminal_view2/src/terminal_element.rs | 349 +++++++++--------- crates/terminal_view2/src/terminal_view.rs | 327 +++++++++------- 4 files changed, 383 insertions(+), 310 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index e653710c0f..aa6a5e32ec 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,10 +1,10 @@ use crate::{ - point, px, Action, AnyDrag, AnyDragState, AnyElement, AnyTooltip, AnyView, AppContext, - BorrowAppContext, BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, - FocusEvent, FocusHandle, IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, - MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, - Render, ScrollWheelEvent, SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, - Task, View, Visibility, WindowContext, + green, point, px, red, Action, AnyDrag, AnyDragState, AnyElement, AnyTooltip, AnyView, + AppContext, BorrowAppContext, BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, + ElementId, FocusEvent, FocusHandle, IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, + LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, + Point, Render, ScrollWheelEvent, SharedString, Size, StackingOrder, Style, StyleRefinement, + Styled, Task, View, Visibility, WindowContext, }; use collections::HashMap; use refineable::Refineable; @@ -1363,7 +1363,7 @@ impl GroupBounds { } pub struct Focusable { - element: E, + pub element: E, } impl FocusableElement for Focusable {} diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index 197b912b1b..6036d65d6e 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/crates/terminal2/src/terminal2.rs @@ -976,7 +976,6 @@ impl Terminal { } pub fn try_sync(&mut self, cx: &mut ModelContext) { - println!("trying to sync"); let term = self.term.clone(); let mut terminal = if let Some(term) = term.try_lock_unfair() { @@ -1235,7 +1234,7 @@ impl Terminal { } ///Scroll the terminal - pub fn scroll_wheel(&mut self, e: ScrollWheelEvent, origin: Point) { + pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent, origin: Point) { let mouse_mode = self.mouse_mode(e.shift); if let Some(scroll_lines) = self.determine_scroll_lines(&e, mouse_mode) { diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 96ffbb1ffb..03e98e831d 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -1,10 +1,11 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ black, div, point, px, red, relative, transparent_black, AnyElement, AvailableSpace, Bounds, - Element, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hsla, - InteractiveElement, InteractiveElementState, IntoElement, LayoutId, ModelContext, Pixels, - Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled, TextRun, TextStyle, - TextSystem, UnderlineStyle, WeakModel, WhiteSpace, WindowContext, + DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, + Hsla, InteractiveElement, InteractiveElementState, IntoElement, LayoutId, ModelContext, + ModifiersChangedEvent, MouseButton, Pixels, Point, Rgba, ShapedLine, Size, + StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, View, + WeakModel, WhiteSpace, WindowContext, }; use itertools::Itertools; use language::CursorShape; @@ -26,6 +27,8 @@ use ui::Tooltip; use std::mem; use std::{fmt::Debug, ops::RangeInclusive}; +use crate::TerminalView; + ///The information generated during layout that is necessary for painting pub struct LayoutState { cells: Vec, @@ -146,6 +149,7 @@ impl LayoutRect { ///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? pub struct TerminalElement { terminal: WeakModel, + terminal_view: View, focus: FocusHandle, focused: bool, cursor_visible: bool, @@ -164,6 +168,7 @@ impl StatefulInteractiveElement for TerminalElement {} impl TerminalElement { pub fn new( terminal: WeakModel, + terminal_view: View, focus: FocusHandle, focused: bool, cursor_visible: bool, @@ -171,12 +176,15 @@ impl TerminalElement { ) -> TerminalElement { TerminalElement { terminal, + terminal_view, focused, - focus, + focus: focus.clone(), cursor_visible, can_navigate_to_selected_word, interactivity: Default::default(), } + .track_focus(&focus) + .element } //Vec> -> Clip out the parts of the ranges @@ -601,7 +609,25 @@ impl TerminalElement { } } - fn paint_mouse_listeners( + fn register_key_listeners(&self, cx: &mut WindowContext) { + cx.on_key_event({ + let this = self.terminal.clone(); + move |event: &ModifiersChangedEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + + let handled = this + .update(cx, |term, _| term.try_modifiers_change(&event.modifiers)) + .ok(); + if handled == Some(true) { + cx.notify(); + } + } + }); + } + + fn register_mouse_listeners( self, origin: Point, mode: TermMode, @@ -611,133 +637,153 @@ impl TerminalElement { let focus = self.focus.clone(); let connection = self.terminal.clone(); - self.on_mouse_down(gpui::MouseButton::Left, { - let connection = connection.clone(); - let focus = focus.clone(); - move |e, cx| { - cx.focus(&focus); - //todo!(context menu) - // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); - if let Some(conn_handle) = connection.upgrade() { - conn_handle.update(cx, |terminal, cx| { - terminal.mouse_down(&e, origin); - - cx.notify(); - }) - } - } - }) - .on_drag_event({ - let connection = connection.clone(); - let focus = focus.clone(); - move |e, cx| { - if focus.is_focused(cx) { + let mut this = self + .on_mouse_down(MouseButton::Left, { + let connection = connection.clone(); + let focus = focus.clone(); + move |e, cx| { + dbg!("here"); + cx.focus(&focus); + //todo!(context menu) + // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); if let Some(conn_handle) = connection.upgrade() { conn_handle.update(cx, |terminal, cx| { - terminal.mouse_drag(e, origin, bounds); + terminal.mouse_down(&e, origin); + cx.notify(); }) } } - } - }) - .on_mouse_up( - gpui::MouseButton::Left, - TerminalElement::generic_button_handler( - connection.clone(), - origin, - focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(&e, origin, cx); - }, - ), - ) - .on_click({ - let connection = connection.clone(); - move |e, cx| { - if e.down.button == gpui::MouseButton::Right { - let mouse_mode = if let Some(conn_handle) = connection.upgrade() { - conn_handle.update(cx, |terminal, _cx| { - terminal.mouse_mode(e.down.modifiers.shift) - }) - } else { - // If we can't get the model handle, probably can't deploy the context menu - true - }; - if !mouse_mode { - //todo!(context menu) - // view.deploy_context_menu(e.position, cx); + }) + .on_drag_event({ + let connection = connection.clone(); + let focus = focus.clone(); + move |e, cx| { + dbg!("here"); + + if focus.is_focused(cx) { + if let Some(conn_handle) = connection.upgrade() { + conn_handle.update(cx, |terminal, cx| { + terminal.mouse_drag(e, origin, bounds); + cx.notify(); + }) + } } } - } - }) + }) + .on_mouse_up( + MouseButton::Left, + TerminalElement::generic_button_handler( + connection.clone(), + origin, + focus.clone(), + move |terminal, origin, e, cx| { + terminal.mouse_up(&e, origin, cx); + }, + ), + ) + .on_click({ + let connection = connection.clone(); + move |e, cx| { + dbg!("here"); - // .on_move(move |event, _: &mut TerminalView, cx| { - // if cx.is_self_focused() { - // if let Some(conn_handle) = connection.upgrade() { - // conn_handle.update(cx, |terminal, cx| { - // terminal.mouse_move(&event, origin); - // cx.notify(); - // }) - // } - // } - // }) - // .on_scroll(move |event, _: &mut TerminalView, cx| { - // if let Some(conn_handle) = connection.upgrade() { - // conn_handle.update(cx, |terminal, cx| { - // terminal.scroll_wheel(event, origin); - // cx.notify(); - // }) - // } - // }); + if e.down.button == MouseButton::Right { + let mouse_mode = if let Some(conn_handle) = connection.upgrade() { + conn_handle.update(cx, |terminal, _cx| { + terminal.mouse_mode(e.down.modifiers.shift) + }) + } else { + // If we can't get the model handle, probably can't deploy the context menu + true + }; + if !mouse_mode { + //todo!(context menu) + // view.deploy_context_menu(e.position, cx); + } + } + } + }) + .on_mouse_move({ + let connection = connection.clone(); + let focus = focus.clone(); + move |e, cx| { + dbg!("here"); - // // Mouse mode handlers: - // // All mouse modes need the extra click handlers - // if mode.intersects(TermMode::MOUSE_MODE) { - // region = region - // .on_down( - // MouseButton::Right, - // TerminalElement::generic_button_handler( - // connection, - // origin, - // move |terminal, origin, e, _cx| { - // terminal.mouse_down(&e, origin); - // }, - // ), - // ) - // .on_down( - // MouseButton::Middle, - // TerminalElement::generic_button_handler( - // connection, - // origin, - // move |terminal, origin, e, _cx| { - // terminal.mouse_down(&e, origin); - // }, - // ), - // ) - // .on_up( - // MouseButton::Right, - // TerminalElement::generic_button_handler( - // connection, - // origin, - // move |terminal, origin, e, cx| { - // terminal.mouse_up(&e, origin, cx); - // }, - // ), - // ) - // .on_up( - // MouseButton::Middle, - // TerminalElement::generic_button_handler( - // connection, - // origin, - // move |terminal, origin, e, cx| { - // terminal.mouse_up(&e, origin, cx); - // }, - // ), - // ) - // } + if focus.is_focused(cx) { + if let Some(conn_handle) = connection.upgrade() { + conn_handle.update(cx, |terminal, cx| { + terminal.mouse_move(&e, origin); + cx.notify(); + }) + } + } + } + }) + .on_scroll_wheel({ + let connection = connection.clone(); + move |e, cx| { + dbg!("here"); - // cx.scene().push_mouse_region(region); + if let Some(conn_handle) = connection.upgrade() { + conn_handle.update(cx, |terminal, cx| { + terminal.scroll_wheel(e, origin); + cx.notify(); + }) + } + } + }); + + // Mouse mode handlers: + // All mouse modes need the extra click handlers + if mode.intersects(TermMode::MOUSE_MODE) { + this = this + .on_mouse_down( + MouseButton::Right, + TerminalElement::generic_button_handler( + connection.clone(), + origin, + focus.clone(), + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); + }, + ), + ) + .on_mouse_down( + MouseButton::Middle, + TerminalElement::generic_button_handler( + connection.clone(), + origin, + focus.clone(), + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); + }, + ), + ) + .on_mouse_up( + MouseButton::Right, + TerminalElement::generic_button_handler( + connection.clone(), + origin, + focus.clone(), + move |terminal, origin, e, cx| { + terminal.mouse_up(&e, origin, cx); + }, + ), + ) + .on_mouse_up( + MouseButton::Middle, + TerminalElement::generic_button_handler( + connection, + origin, + focus, + move |terminal, origin, e, cx| { + terminal.mouse_up(&e, origin, cx); + }, + ), + ) + } + + this } } @@ -762,11 +808,18 @@ impl Element for TerminalElement { (layout_id, interactive_state) } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext<'_>) { + fn paint( + mut self, + bounds: Bounds, + state: &mut Self::State, + cx: &mut WindowContext<'_>, + ) { let mut layout = self.compute_layout(bounds, cx); let theme = cx.theme(); + let dispatch_context = self.terminal_view.read(cx).dispatch_context(cx); + self.interactivity().key_context = dispatch_context; cx.paint_quad( bounds, Default::default(), @@ -776,10 +829,13 @@ impl Element for TerminalElement { ); let origin = bounds.origin + Point::new(layout.gutter, px(0.)); - let this = self.paint_mouse_listeners(origin, layout.mode, bounds, cx); + let mut this = self.register_mouse_listeners(origin, layout.mode, bounds, cx); + let interactivity = mem::take(&mut this.interactivity); + + cx.with_z_index(0, |cx| { + interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| { + this.register_key_listeners(cx); - this.interactivity - .paint(bounds, bounds.size, state, cx, |_, _, cx| { for rect in &layout.rects { rect.paint(origin, &layout, cx); } @@ -824,47 +880,8 @@ impl Element for TerminalElement { element.draw(origin, Size { width, height }, cx) } }); + }); } - - // todo!() remove? - // fn metadata(&self) -> Option<&dyn std::any::Any> { - // None - // } - - // fn debug( - // &self, - // _: Bounds, - // _: &Self::State, - // _: &Self::PaintState, - // _: &TerminalView, - // _: &gpui::ViewContext, - // ) -> gpui::serde_json::Value { - // json!({ - // "type": "TerminalElement", - // }) - // } - - // fn rect_for_text_range( - // &self, - // _: Range, - // bounds: Bounds, - // _: Bounds, - // layout: &Self::State, - // _: &Self::PaintState, - // _: &TerminalView, - // _: &gpui::ViewContext, - // ) -> Option> { - // // Use the same origin that's passed to `Cursor::paint` in the paint - // // method bove. - // let mut origin = bounds.origin() + point(layout.size.cell_width, 0.); - - // // TODO - Why is it necessary to move downward one line to get correct - // // positioning? I would think that we'd want the same rect that is - // // painted for the cursor. - // origin += point(0., layout.size.line_height); - - // Some(layout.cursor.as_ref()?.bounding_rect(origin)) - // } } impl IntoElement for TerminalElement { diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index 5b864f4a5e..e9e4cd5167 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -9,10 +9,11 @@ pub mod terminal_panel; // use crate::terminal_element::TerminalElement; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ - actions, div, Action, AnyElement, AppContext, Div, Element, EventEmitter, FocusEvent, - FocusHandle, Focusable, FocusableElement, FocusableView, InputHandler, InteractiveElement, - KeyDownEvent, Keystroke, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Render, - SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView, WindowContext, + actions, div, point, px, size, Action, AnyElement, AppContext, Bounds, Div, Element, + EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableElement, FocusableView, Font, + FontStyle, FontWeight, InputHandler, InteractiveElement, KeyContext, KeyDownEvent, Keystroke, + Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Render, SharedString, Styled, + Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; use language::Bias; use persistence::TERMINAL_DB; @@ -26,6 +27,7 @@ use terminal::{ Event, MaybeNavigationTarget, Terminal, }; use terminal_element::TerminalElement; +use theme::ThemeSettings; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent}, @@ -91,6 +93,7 @@ pub struct TerminalView { blink_epoch: usize, can_navigate_to_selected_word: bool, workspace_id: WorkspaceId, + _subscriptions: Vec, } impl EventEmitter for TerminalView {} @@ -262,6 +265,20 @@ impl TerminalView { }) .detach(); + let focus = cx.focus_handle(); + let focus_in = cx.on_focus_in(&focus, |this, cx| { + this.has_new_content = false; + this.terminal.read(cx).focus_in(); + this.blink_cursors(this.blink_epoch, cx); + cx.notify(); + }); + let focus_out = cx.on_focus_out(&focus, |this, cx| { + this.terminal.update(cx, |terminal, _| { + terminal.focus_out(); + }); + cx.notify(); + }); + Self { terminal, has_new_content: true, @@ -274,6 +291,7 @@ impl TerminalView { blink_epoch: 0, can_navigate_to_selected_word: false, workspace_id, + _subscriptions: vec![focus_in, focus_out], } } @@ -303,7 +321,7 @@ impl TerminalView { menu.action("Clear", Box::new(Clear)) .action("Close", Box::new(CloseActiveItem { save_intent: None })) })); - // todo!() + // todo!(context menus) // self.context_menu // .show(position, AnchorCorner::TopLeft, menu_entries, cx); // cx.notify(); @@ -448,6 +466,81 @@ impl TerminalView { }); } } + + fn dispatch_context(&self, cx: &AppContext) -> KeyContext { + let mut dispatch_context = KeyContext::default(); + dispatch_context.add("Terminal"); + + let mode = self.terminal.read(cx).last_content.mode; + dispatch_context.set( + "screen", + if mode.contains(TermMode::ALT_SCREEN) { + "alt" + } else { + "normal" + }, + ); + + if mode.contains(TermMode::APP_CURSOR) { + dispatch_context.add("DECCKM"); + } + if mode.contains(TermMode::APP_KEYPAD) { + dispatch_context.add("DECPAM"); + } else { + dispatch_context.add("DECPNM"); + } + if mode.contains(TermMode::SHOW_CURSOR) { + dispatch_context.add("DECTCEM"); + } + if mode.contains(TermMode::LINE_WRAP) { + dispatch_context.add("DECAWM"); + } + if mode.contains(TermMode::ORIGIN) { + dispatch_context.add("DECOM"); + } + if mode.contains(TermMode::INSERT) { + dispatch_context.add("IRM"); + } + //LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html + if mode.contains(TermMode::LINE_FEED_NEW_LINE) { + dispatch_context.add("LNM"); + } + if mode.contains(TermMode::FOCUS_IN_OUT) { + dispatch_context.add("report_focus"); + } + if mode.contains(TermMode::ALTERNATE_SCROLL) { + dispatch_context.add("alternate_scroll"); + } + if mode.contains(TermMode::BRACKETED_PASTE) { + dispatch_context.add("bracketed_paste"); + } + if mode.intersects(TermMode::MOUSE_MODE) { + dispatch_context.add("any_mouse_reporting"); + } + { + let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) { + "click" + } else if mode.contains(TermMode::MOUSE_DRAG) { + "drag" + } else if mode.contains(TermMode::MOUSE_MOTION) { + "motion" + } else { + "off" + }; + dispatch_context.set("mouse_reporting", mouse_reporting); + } + { + let format = if mode.contains(TermMode::SGR_MOUSE) { + "sgr" + } else if mode.contains(TermMode::UTF8_MOUSE) { + "utf8" + } else { + "normal" + }; + dispatch_context.set("mouse_format", format); + }; + dispatch_context + } } fn possible_open_targets( @@ -533,6 +626,7 @@ impl Render for TerminalView { fn render(&mut self, cx: &mut ViewContext) -> Self::Element { let terminal_handle = self.terminal.clone().downgrade(); + let this_view = cx.view().clone(); let self_id = cx.entity_id(); let focused = self.focus_handle.is_focused(cx); @@ -555,6 +649,7 @@ impl Render for TerminalView { .on_action(cx.listener(TerminalView::select_all)) .child(TerminalElement::new( terminal_handle, + this_view, self.focus_handle.clone(), focused, self.should_show_cursor(focused, cx), @@ -579,104 +674,14 @@ impl Render for TerminalView { } } -// impl View for TerminalView { -//todo!() -// fn modifiers_changed( -// &mut self, -// event: &ModifiersChangedEvent, -// cx: &mut ViewContext, -// ) -> bool { -// let handled = self -// .terminal() -// .update(cx, |term, _| term.try_modifiers_change(&event.modifiers)); -// if handled { -// cx.notify(); -// } -// handled -// } -// } - -// todo!() -// fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &gpui::AppContext) { -// Self::reset_to_default_keymap_context(keymap); - -// let mode = self.terminal.read(cx).last_content.mode; -// keymap.add_key( -// "screen", -// if mode.contains(TermMode::ALT_SCREEN) { -// "alt" -// } else { -// "normal" -// }, -// ); - -// if mode.contains(TermMode::APP_CURSOR) { -// keymap.add_identifier("DECCKM"); -// } -// if mode.contains(TermMode::APP_KEYPAD) { -// keymap.add_identifier("DECPAM"); -// } else { -// keymap.add_identifier("DECPNM"); -// } -// if mode.contains(TermMode::SHOW_CURSOR) { -// keymap.add_identifier("DECTCEM"); -// } -// if mode.contains(TermMode::LINE_WRAP) { -// keymap.add_identifier("DECAWM"); -// } -// if mode.contains(TermMode::ORIGIN) { -// keymap.add_identifier("DECOM"); -// } -// if mode.contains(TermMode::INSERT) { -// keymap.add_identifier("IRM"); -// } -// //LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html -// if mode.contains(TermMode::LINE_FEED_NEW_LINE) { -// keymap.add_identifier("LNM"); -// } -// if mode.contains(TermMode::FOCUS_IN_OUT) { -// keymap.add_identifier("report_focus"); -// } -// if mode.contains(TermMode::ALTERNATE_SCROLL) { -// keymap.add_identifier("alternate_scroll"); -// } -// if mode.contains(TermMode::BRACKETED_PASTE) { -// keymap.add_identifier("bracketed_paste"); -// } -// if mode.intersects(TermMode::MOUSE_MODE) { -// keymap.add_identifier("any_mouse_reporting"); -// } -// { -// let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) { -// "click" -// } else if mode.contains(TermMode::MOUSE_DRAG) { -// "drag" -// } else if mode.contains(TermMode::MOUSE_MOTION) { -// "motion" -// } else { -// "off" -// }; -// keymap.add_key("mouse_reporting", mouse_reporting); -// } -// { -// let format = if mode.contains(TermMode::SGR_MOUSE) { -// "sgr" -// } else if mode.contains(TermMode::UTF8_MOUSE) { -// "utf8" -// } else { -// "normal" -// }; -// keymap.add_key("mouse_format", format); -// } -// } - +//todo!(Implement IME) impl InputHandler for TerminalView { fn text_for_range( &mut self, range: std::ops::Range, cx: &mut ViewContext, ) -> Option { - todo!() + None } fn selected_text_range( @@ -696,13 +701,11 @@ impl InputHandler for TerminalView { } } - fn marked_text_range(&self, cx: &mut ViewContext) -> Option> { - todo!() + fn marked_text_range(&self, _cx: &mut ViewContext) -> Option> { + None } - fn unmark_text(&mut self, cx: &mut ViewContext) { - todo!() - } + fn unmark_text(&mut self, _cx: &mut ViewContext) {} fn replace_text_in_range( &mut self, @@ -717,21 +720,75 @@ impl InputHandler for TerminalView { fn replace_and_mark_text_in_range( &mut self, - range: Option>, - new_text: &str, - new_selected_range: Option>, - cx: &mut ViewContext, + _range: Option>, + _new_text: &str, + _new_selected_range: Option>, + _cx: &mut ViewContext, ) { - todo!() } + // todo!(Check that this works correctly, why aren't we reading the range?) fn bounds_for_range( &mut self, - range_utf16: std::ops::Range, - element_bounds: gpui::Bounds, + _range_utf16: std::ops::Range, + bounds: gpui::Bounds, cx: &mut ViewContext, ) -> Option> { - todo!() + let settings = ThemeSettings::get_global(cx).clone(); + + let buffer_font_size = settings.buffer_font_size(cx); + + let terminal_settings = TerminalSettings::get_global(cx); + let font_family = terminal_settings + .font_family + .as_ref() + .map(|string| string.clone().into()) + .unwrap_or(settings.buffer_font.family); + + let line_height = terminal_settings + .line_height + .value() + .to_pixels(cx.rem_size()); + + let font_size = terminal_settings.font_size.clone(); + let features = terminal_settings + .font_features + .clone() + .unwrap_or(settings.buffer_font.features.clone()); + + let font_size = + font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx)); + + let font_id = cx + .text_system() + .font_id(&Font { + family: font_family, + style: FontStyle::Normal, + weight: FontWeight::NORMAL, + features, + }) + .unwrap(); + + let cell_width = cx + .text_system() + .advance(font_id, font_size, 'm') + .unwrap() + .width; + + let mut origin = bounds.origin + point(cell_width, px(0.)); + + // TODO - Why is it necessary to move downward one line to get correct + // positioning? I would think that we'd want the same rect that is + // painted for the cursor. + origin += point(px(0.), line_height); + + let cursor = Bounds { + origin, + //todo!(correctly calculate this width and height based on the text the line is over) + size: size(cell_width, line_height), + }; + + Some(cursor) } } @@ -776,7 +833,7 @@ impl Item for TerminalView { false } - // todo!() + // todo!(search) // fn as_searchable(&self, handle: &View) -> Option> { // Some(Box::new(handle.clone())) // } @@ -806,22 +863,23 @@ impl Item for TerminalView { let window = cx.window_handle(); cx.spawn(|pane, mut cx| async move { let cwd = None; - // todo!() - // TERMINAL_DB - // .get_working_directory(item_id, workspace_id) - // .log_err() - // .flatten() - // .or_else(|| { - // cx.read(|cx| { - // let strategy = TerminalSettings::get_global(cx).working_directory.clone(); - // workspace - // .upgrade() - // .map(|workspace| { - // get_working_directory(workspace.read(cx), cx, strategy) - // }) - // .flatten() - // }) - // }); + TERMINAL_DB + .get_working_directory(item_id, workspace_id) + .log_err() + .flatten() + .or_else(|| { + cx.update(|_, cx| { + let strategy = TerminalSettings::get_global(cx).working_directory.clone(); + workspace + .upgrade() + .map(|workspace| { + get_working_directory(workspace.read(cx), cx, strategy) + }) + .flatten() + }) + .ok() + .flatten() + }); let terminal = project.update(&mut cx, |project, cx| { project.create_terminal(cwd, window, cx) @@ -833,14 +891,13 @@ impl Item for TerminalView { } fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext) { - // todo!() - // cx.background() - // .spawn(TERMINAL_DB.update_workspace_id( - // workspace.database_id(), - // self.workspace_id, - // cx.view_id(), - // )) - // .detach(); + cx.background_executor() + .spawn(TERMINAL_DB.update_workspace_id( + workspace.database_id(), + self.workspace_id, + cx.entity_id().as_u64(), + )) + .detach(); self.workspace_id = workspace.database_id(); } } From 735f2029e9bbb4edad1bba4a3709da37529ade9c Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 5 Dec 2023 17:31:33 -0800 Subject: [PATCH 06/10] Add more debugging --- crates/gpui2/src/elements/div.rs | 40 +++++++-- crates/terminal_view2/src/terminal_element.rs | 84 +++++++++---------- crates/terminal_view2/src/terminal_view.rs | 26 +++--- 3 files changed, 87 insertions(+), 63 deletions(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index aa6a5e32ec..168be4d094 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,10 +1,10 @@ use crate::{ - green, point, px, red, Action, AnyDrag, AnyDragState, AnyElement, AnyTooltip, AnyView, - AppContext, BorrowAppContext, BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, - ElementId, FocusEvent, FocusHandle, IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, - LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, - Point, Render, ScrollWheelEvent, SharedString, Size, StackingOrder, Style, StyleRefinement, - Styled, Task, View, Visibility, WindowContext, + point, px, Action, AnyDrag, AnyDragState, AnyElement, AnyTooltip, AnyView, AppContext, + BorrowAppContext, BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, + FocusEvent, FocusHandle, IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, + Render, ScrollWheelEvent, SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, + Task, View, Visibility, WindowContext, }; use collections::HashMap; use refineable::Refineable; @@ -95,6 +95,32 @@ pub trait InteractiveElement: Sized + Element { self } + fn on_mouse_down_weird( + mut self, + button: MouseButton, + listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) -> Self { + self.interactivity().mouse_down_listeners.push(Box::new( + move |event, bounds, phase, cx| { + dbg!("HEREEEE"); + + let contains = dbg!(dbg!(&bounds.bounds).contains_point(dbg!(&event.position))) + && dbg!(cx.was_top_layer(&event.position, &bounds.stacking_order)); + dbg!(contains); + + if phase == DispatchPhase::Bubble + && event.button == button + && bounds.visibly_contains(&event.position, cx) + { + dbg!("HEREEEE2"); + + (listener)(event, cx) + } + }, + )); + self + } + fn on_any_mouse_down( mut self, listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, @@ -767,7 +793,7 @@ pub struct Interactivity { pub tooltip_builder: Option, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct InteractiveBounds { pub bounds: Bounds, pub stacking_order: StackingOrder, diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 03e98e831d..211d74ea3a 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -638,7 +638,7 @@ impl TerminalElement { let connection = self.terminal.clone(); let mut this = self - .on_mouse_down(MouseButton::Left, { + .on_mouse_down_weird(MouseButton::Left, { let connection = connection.clone(); let focus = focus.clone(); move |e, cx| { @@ -814,6 +814,7 @@ impl Element for TerminalElement { state: &mut Self::State, cx: &mut WindowContext<'_>, ) { + dbg!(bounds); let mut layout = self.compute_layout(bounds, cx); let theme = cx.theme(); @@ -832,54 +833,51 @@ impl Element for TerminalElement { let mut this = self.register_mouse_listeners(origin, layout.mode, bounds, cx); let interactivity = mem::take(&mut this.interactivity); - cx.with_z_index(0, |cx| { - interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| { - this.register_key_listeners(cx); + interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| { + this.register_key_listeners(cx); - for rect in &layout.rects { - rect.paint(origin, &layout, cx); - } + for rect in &layout.rects { + rect.paint(origin, &layout, cx); + } - cx.with_z_index(1, |cx| { - for (relative_highlighted_range, color) in - layout.relative_highlighted_ranges.iter() + cx.with_z_index(1, |cx| { + for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter() + { + if let Some((start_y, highlighted_range_lines)) = + to_highlighted_range_lines(relative_highlighted_range, &layout, origin) { - if let Some((start_y, highlighted_range_lines)) = - to_highlighted_range_lines(relative_highlighted_range, &layout, origin) - { - let hr = HighlightedRange { - start_y, //Need to change this - line_height: layout.size.line_height, - lines: highlighted_range_lines, - color: color.clone(), - //Copied from editor. TODO: move to theme or something - corner_radius: 0.15 * layout.size.line_height, - }; - hr.paint(bounds, cx); - } + let hr = HighlightedRange { + start_y, //Need to change this + line_height: layout.size.line_height, + lines: highlighted_range_lines, + color: color.clone(), + //Copied from editor. TODO: move to theme or something + corner_radius: 0.15 * layout.size.line_height, + }; + hr.paint(bounds, cx); } - }); - - cx.with_z_index(2, |cx| { - for cell in &layout.cells { - cell.paint(origin, &layout, bounds, cx); - } - }); - - if this.cursor_visible { - cx.with_z_index(3, |cx| { - if let Some(cursor) = &layout.cursor { - cursor.paint(origin, cx); - } - }); - } - - if let Some(element) = layout.hyperlink_tooltip.take() { - let width: AvailableSpace = bounds.size.width.into(); - let height: AvailableSpace = bounds.size.height.into(); - element.draw(origin, Size { width, height }, cx) } }); + + cx.with_z_index(2, |cx| { + for cell in &layout.cells { + cell.paint(origin, &layout, bounds, cx); + } + }); + + if this.cursor_visible { + cx.with_z_index(3, |cx| { + if let Some(cursor) = &layout.cursor { + cursor.paint(origin, cx); + } + }); + } + + if let Some(element) = layout.hyperlink_tooltip.take() { + let width: AvailableSpace = bounds.size.width.into(); + let height: AvailableSpace = bounds.size.height.into(); + element.draw(origin, Size { width, height }, cx) + } }); } } diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index e9e4cd5167..17d4829e7c 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -266,18 +266,18 @@ impl TerminalView { .detach(); let focus = cx.focus_handle(); - let focus_in = cx.on_focus_in(&focus, |this, cx| { - this.has_new_content = false; - this.terminal.read(cx).focus_in(); - this.blink_cursors(this.blink_epoch, cx); - cx.notify(); - }); - let focus_out = cx.on_focus_out(&focus, |this, cx| { - this.terminal.update(cx, |terminal, _| { - terminal.focus_out(); - }); - cx.notify(); - }); + // let focus_in = cx.on_focus_in(&focus, |this, cx| { + // this.has_new_content = false; + // this.terminal.read(cx).focus_in(); + // this.blink_cursors(this.blink_epoch, cx); + // cx.notify(); + // }); + // let focus_out = cx.on_focus_out(&focus, |this, cx| { + // this.terminal.update(cx, |terminal, _| { + // terminal.focus_out(); + // }); + // cx.notify(); + // }); Self { terminal, @@ -291,7 +291,7 @@ impl TerminalView { blink_epoch: 0, can_navigate_to_selected_word: false, workspace_id, - _subscriptions: vec![focus_in, focus_out], + _subscriptions: vec![/*focus_in, focus_out*/], } } From 12e7f61f625f6f9690d88b05fa082dab075b6291 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 6 Dec 2023 15:07:09 -0800 Subject: [PATCH 07/10] Fix a porting bugs for terminal2 co-authored-by: Nathan --- crates/editor2/src/element.rs | 1 - crates/gpui2/src/elements/div.rs | 26 ------------ crates/gpui2/src/platform/test/window.rs | 26 ++++++------ crates/terminal2/src/mappings/mouse.rs | 4 +- crates/terminal_view2/src/terminal_element.rs | 40 +++++++++---------- crates/terminal_view2/src/terminal_panel.rs | 6 +-- 6 files changed, 36 insertions(+), 67 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index d7b9d0bb40..c9746f1808 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -3448,7 +3448,6 @@ mod tests { DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0) ); assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0)); - dbg!("Hi"); // moves cursor on buffer boundary back two lines // and doesn't allow selection to bleed through assert_eq!( diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 168be4d094..e4a6daf390 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -95,32 +95,6 @@ pub trait InteractiveElement: Sized + Element { self } - fn on_mouse_down_weird( - mut self, - button: MouseButton, - listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, - ) -> Self { - self.interactivity().mouse_down_listeners.push(Box::new( - move |event, bounds, phase, cx| { - dbg!("HEREEEE"); - - let contains = dbg!(dbg!(&bounds.bounds).contains_point(dbg!(&event.position))) - && dbg!(cx.was_top_layer(&event.position, &bounds.stacking_order)); - dbg!(contains); - - if phase == DispatchPhase::Bubble - && event.button == button - && bounds.visibly_contains(&event.position, cx) - { - dbg!("HEREEEE2"); - - (listener)(event, cx) - } - }, - )); - self - } - fn on_any_mouse_down( mut self, listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, diff --git a/crates/gpui2/src/platform/test/window.rs b/crates/gpui2/src/platform/test/window.rs index b1bfebad06..f18722d88d 100644 --- a/crates/gpui2/src/platform/test/window.rs +++ b/crates/gpui2/src/platform/test/window.rs @@ -66,11 +66,11 @@ impl PlatformWindow for TestWindow { } fn titlebar_height(&self) -> Pixels { - todo!() + unimplemented!() } fn appearance(&self) -> WindowAppearance { - todo!() + unimplemented!() } fn display(&self) -> std::rc::Rc { @@ -99,7 +99,7 @@ impl PlatformWindow for TestWindow { } fn activate(&self) { - todo!() + unimplemented!() } fn set_title(&mut self, title: &str) { @@ -107,23 +107,23 @@ impl PlatformWindow for TestWindow { } fn set_edited(&mut self, _edited: bool) { - todo!() + unimplemented!() } fn show_character_palette(&self) { - todo!() + unimplemented!() } fn minimize(&self) { - todo!() + unimplemented!() } fn zoom(&self) { - todo!() + unimplemented!() } fn toggle_full_screen(&self) { - todo!() + unimplemented!() } fn on_input(&self, callback: Box bool>) { @@ -139,7 +139,7 @@ impl PlatformWindow for TestWindow { } fn on_fullscreen(&self, _callback: Box) { - todo!() + unimplemented!() } fn on_moved(&self, callback: Box) { @@ -147,19 +147,19 @@ impl PlatformWindow for TestWindow { } fn on_should_close(&self, _callback: Box bool>) { - todo!() + unimplemented!() } fn on_close(&self, _callback: Box) { - todo!() + unimplemented!() } fn on_appearance_changed(&self, _callback: Box) { - todo!() + unimplemented!() } fn is_topmost_for_position(&self, _position: crate::Point) -> bool { - todo!() + unimplemented!() } fn draw(&self, scene: crate::Scene) { diff --git a/crates/terminal2/src/mappings/mouse.rs b/crates/terminal2/src/mappings/mouse.rs index edced3156f..a32d83d28d 100644 --- a/crates/terminal2/src/mappings/mouse.rs +++ b/crates/terminal2/src/mappings/mouse.rs @@ -186,9 +186,9 @@ pub fn mouse_side( } pub fn grid_point(pos: Point, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { - let col = GridCol((cur_size.cell_width / pos.x) as usize); + let col = GridCol((pos.x / cur_size.cell_width) as usize); let col = min(col, cur_size.last_column()); - let line = (cur_size.line_height / pos.y) as i32; + let line = (pos.y / cur_size.line_height) as i32; let line = min(line, cur_size.bottommost_line().0); AlacPoint::new(GridLine(line - display_offset as i32), col) } diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 211d74ea3a..fbd961492c 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -1,10 +1,10 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ black, div, point, px, red, relative, transparent_black, AnyElement, AvailableSpace, Bounds, - DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, - Hsla, InteractiveElement, InteractiveElementState, IntoElement, LayoutId, ModelContext, - ModifiersChangedEvent, MouseButton, Pixels, Point, Rgba, ShapedLine, Size, - StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, View, + DispatchPhase, Element, ElementId, ElementInputHandler, FocusHandle, Font, FontStyle, + FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, IntoElement, + LayoutId, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, Point, Rgba, ShapedLine, + Size, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, View, WeakModel, WhiteSpace, WindowContext, }; use itertools::Itertools; @@ -638,11 +638,10 @@ impl TerminalElement { let connection = self.terminal.clone(); let mut this = self - .on_mouse_down_weird(MouseButton::Left, { + .on_mouse_down(MouseButton::Left, { let connection = connection.clone(); let focus = focus.clone(); move |e, cx| { - dbg!("here"); cx.focus(&focus); //todo!(context menu) // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); @@ -655,18 +654,18 @@ impl TerminalElement { } } }) - .on_drag_event({ + .on_mouse_move({ let connection = connection.clone(); let focus = focus.clone(); move |e, cx| { - dbg!("here"); - - if focus.is_focused(cx) { - if let Some(conn_handle) = connection.upgrade() { - conn_handle.update(cx, |terminal, cx| { - terminal.mouse_drag(e, origin, bounds); - cx.notify(); - }) + if e.pressed_button.is_some() { + if focus.is_focused(cx) { + if let Some(conn_handle) = connection.upgrade() { + conn_handle.update(cx, |terminal, cx| { + terminal.mouse_drag(e, origin, bounds); + cx.notify(); + }) + } } } } @@ -685,8 +684,6 @@ impl TerminalElement { .on_click({ let connection = connection.clone(); move |e, cx| { - dbg!("here"); - if e.down.button == MouseButton::Right { let mouse_mode = if let Some(conn_handle) = connection.upgrade() { conn_handle.update(cx, |terminal, _cx| { @@ -707,8 +704,6 @@ impl TerminalElement { let connection = connection.clone(); let focus = focus.clone(); move |e, cx| { - dbg!("here"); - if focus.is_focused(cx) { if let Some(conn_handle) = connection.upgrade() { conn_handle.update(cx, |terminal, cx| { @@ -722,8 +717,6 @@ impl TerminalElement { .on_scroll_wheel({ let connection = connection.clone(); move |e, cx| { - dbg!("here"); - if let Some(conn_handle) = connection.upgrade() { conn_handle.update(cx, |terminal, cx| { terminal.scroll_wheel(e, origin); @@ -814,7 +807,6 @@ impl Element for TerminalElement { state: &mut Self::State, cx: &mut WindowContext<'_>, ) { - dbg!(bounds); let mut layout = self.compute_layout(bounds, cx); let theme = cx.theme(); @@ -831,9 +823,13 @@ impl Element for TerminalElement { let origin = bounds.origin + Point::new(layout.gutter, px(0.)); let mut this = self.register_mouse_listeners(origin, layout.mode, bounds, cx); + let interactivity = mem::take(&mut this.interactivity); interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| { + let input_handler = ElementInputHandler::new(bounds, this.terminal_view.clone(), cx); + cx.handle_input(&this.focus, input_handler); + this.register_key_listeners(cx); for rect in &layout.rects { diff --git a/crates/terminal_view2/src/terminal_panel.rs b/crates/terminal_view2/src/terminal_panel.rs index b6582b07b1..4a169836fc 100644 --- a/crates/terminal_view2/src/terminal_panel.rs +++ b/crates/terminal_view2/src/terminal_panel.rs @@ -4,8 +4,8 @@ use crate::TerminalView; use db::kvp::KEY_VALUE_STORE; use gpui::{ actions, div, serde_json, AppContext, AsyncWindowContext, Div, Entity, EventEmitter, - FocusHandle, FocusableView, ParentElement, Render, Subscription, Task, View, ViewContext, - VisualContext, WeakView, WindowContext, + FocusHandle, FocusableView, ParentElement, Render, Styled, Subscription, Task, View, + ViewContext, VisualContext, WeakView, WindowContext, }; use project::Fs; use serde::{Deserialize, Serialize}; @@ -339,7 +339,7 @@ impl Render for TerminalPanel { type Element = Div; fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { - div().child(self.pane.clone()) + div().size_full().child(self.pane.clone()) } } From c092cfbfb3ae8fcccf2203a7ad595ca81e089f0b Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 6 Dec 2023 15:54:08 -0800 Subject: [PATCH 08/10] Fix bug with IME Adjust how IME works in the terminal co-authored-by: nathan --- crates/gpui2/src/platform.rs | 1 + crates/gpui2/src/platform/mac/window.rs | 4 + crates/gpui2/src/platform/test/window.rs | 4 + crates/gpui2/src/window.rs | 1 + crates/terminal_view2/src/terminal_element.rs | 172 ++++++++++++------ crates/terminal_view2/src/terminal_view.rs | 128 +------------ 6 files changed, 126 insertions(+), 184 deletions(-) diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 66cf7c14ef..8a10173ca8 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -145,6 +145,7 @@ pub trait PlatformWindow { fn mouse_position(&self) -> Point; fn as_any_mut(&mut self) -> &mut dyn Any; fn set_input_handler(&mut self, input_handler: Box); + fn clear_input_handler(&mut self); fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; fn activate(&self); fn set_title(&mut self, title: &str); diff --git a/crates/gpui2/src/platform/mac/window.rs b/crates/gpui2/src/platform/mac/window.rs index ba9a67e158..03ba635327 100644 --- a/crates/gpui2/src/platform/mac/window.rs +++ b/crates/gpui2/src/platform/mac/window.rs @@ -750,6 +750,10 @@ impl PlatformWindow for MacWindow { self.0.as_ref().lock().input_handler = Some(input_handler); } + fn clear_input_handler(&mut self) { + self.0.as_ref().lock().input_handler = None; + } + fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver { // macOs applies overrides to modal window buttons after they are added. // Two most important for this logic are: diff --git a/crates/gpui2/src/platform/test/window.rs b/crates/gpui2/src/platform/test/window.rs index 2b9ee7798e..9b7ad72472 100644 --- a/crates/gpui2/src/platform/test/window.rs +++ b/crates/gpui2/src/platform/test/window.rs @@ -89,6 +89,10 @@ impl PlatformWindow for TestWindow { self.input_handler = Some(Arc::new(Mutex::new(input_handler))); } + fn clear_input_handler(&mut self) { + self.input_handler = None; + } + fn prompt( &self, _level: crate::PromptLevel, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 6343f67988..3a966871fc 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1231,6 +1231,7 @@ impl<'a> WindowContext<'a> { /// Rotate the current frame and the previous frame, then clear the current frame. /// We repopulate all state in the current frame during each paint. fn start_frame(&mut self) { + self.window.platform_window.clear_input_handler(); self.text_system().start_frame(); let window = &mut *self.window; diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 24d9527b46..4d088ff63b 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -1,11 +1,11 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ - black, div, point, px, red, relative, transparent_black, AnyElement, AvailableSpace, Bounds, - DispatchPhase, Element, ElementId, ElementInputHandler, FocusHandle, Font, FontStyle, + black, div, point, px, red, relative, transparent_black, AnyElement, AsyncWindowContext, + AvailableSpace, Bounds, DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, IntoElement, - LayoutId, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, Point, Rgba, ShapedLine, - Size, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, View, - WeakModel, WhiteSpace, WindowContext, + LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, + PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled, + TextRun, TextStyle, TextSystem, UnderlineStyle, View, WhiteSpace, WindowContext, }; use itertools::Itertools; use language::CursorShape; @@ -148,7 +148,7 @@ impl LayoutRect { ///The GPUI element that paints the terminal. ///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? pub struct TerminalElement { - terminal: WeakModel, + terminal: Model, terminal_view: View, focus: FocusHandle, focused: bool, @@ -167,7 +167,7 @@ impl StatefulInteractiveElement for TerminalElement {} impl TerminalElement { pub fn new( - terminal: WeakModel, + terminal: Model, terminal_view: View, focus: FocusHandle, focused: bool, @@ -461,16 +461,11 @@ impl TerminalElement { TerminalSize::new(line_height, cell_width, size) }; - let search_matches = if let Some(terminal_model) = self.terminal.upgrade() { - terminal_model.read(cx).matches.clone() - } else { - Default::default() - }; + let search_matches = self.terminal.read(cx).matches.clone(); let background_color = theme.colors().background; - let terminal_handle = self.terminal.upgrade().unwrap(); - let last_hovered_word = terminal_handle.update(cx, |terminal, cx| { + let last_hovered_word = self.terminal.update(cx, |terminal, cx| { terminal.set_size(dimensions); terminal.try_sync(cx); if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() { @@ -495,7 +490,7 @@ impl TerminalElement { selection, cursor, .. - } = &terminal_handle.read(cx).last_content; + } = &self.terminal.read(cx).last_content; // searches, highlights to a single range representations let mut relative_highlighted_ranges = Vec::new(); @@ -592,20 +587,18 @@ impl TerminalElement { } fn generic_button_handler( - connection: WeakModel, + connection: Model, origin: Point, focus_handle: FocusHandle, f: impl Fn(&mut Terminal, Point, &E, &mut ModelContext), ) -> impl Fn(&E, &mut WindowContext) { move |event, cx| { cx.focus(&focus_handle); - if let Some(conn_handle) = connection.upgrade() { - conn_handle.update(cx, |terminal, cx| { - f(terminal, origin, event, cx); + connection.update(cx, |terminal, cx| { + f(terminal, origin, event, cx); - cx.notify(); - }) - } + cx.notify(); + }) } } @@ -617,10 +610,10 @@ impl TerminalElement { return; } - let handled = this - .update(cx, |term, _| term.try_modifiers_change(&event.modifiers)) - .ok(); - if handled == Some(true) { + let handled = + this.update(cx, |term, _| term.try_modifiers_change(&event.modifiers)); + + if handled { cx.notify(); } } @@ -645,13 +638,11 @@ impl TerminalElement { cx.focus(&focus); //todo!(context menu) // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); - if let Some(conn_handle) = connection.upgrade() { - conn_handle.update(cx, |terminal, cx| { - terminal.mouse_down(&e, origin); + connection.update(cx, |terminal, cx| { + terminal.mouse_down(&e, origin); - cx.notify(); - }) - } + cx.notify(); + }) } }) .on_mouse_move({ @@ -660,12 +651,10 @@ impl TerminalElement { move |e, cx| { if e.pressed_button.is_some() { if focus.is_focused(cx) { - if let Some(conn_handle) = connection.upgrade() { - conn_handle.update(cx, |terminal, cx| { - terminal.mouse_drag(e, origin, bounds); - cx.notify(); - }) - } + connection.update(cx, |terminal, cx| { + terminal.mouse_drag(e, origin, bounds); + cx.notify(); + }) } } } @@ -685,14 +674,10 @@ impl TerminalElement { let connection = connection.clone(); move |e, cx| { if e.down.button == MouseButton::Right { - let mouse_mode = if let Some(conn_handle) = connection.upgrade() { - conn_handle.update(cx, |terminal, _cx| { - terminal.mouse_mode(e.down.modifiers.shift) - }) - } else { - // If we can't get the model handle, probably can't deploy the context menu - true - }; + let mouse_mode = connection.update(cx, |terminal, _cx| { + terminal.mouse_mode(e.down.modifiers.shift) + }); + if !mouse_mode { //todo!(context menu) // view.deploy_context_menu(e.position, cx); @@ -705,24 +690,20 @@ impl TerminalElement { let focus = focus.clone(); move |e, cx| { if focus.is_focused(cx) { - if let Some(conn_handle) = connection.upgrade() { - conn_handle.update(cx, |terminal, cx| { - terminal.mouse_move(&e, origin); - cx.notify(); - }) - } + connection.update(cx, |terminal, cx| { + terminal.mouse_move(&e, origin); + cx.notify(); + }) } } }) .on_scroll_wheel({ let connection = connection.clone(); move |e, cx| { - if let Some(conn_handle) = connection.upgrade() { - conn_handle.update(cx, |terminal, cx| { - terminal.scroll_wheel(e, origin); - cx.notify(); - }) - } + connection.update(cx, |terminal, cx| { + terminal.scroll_wheel(e, origin); + cx.notify(); + }) } }); @@ -822,13 +803,21 @@ impl Element for TerminalElement { ); let origin = bounds.origin + Point::new(layout.gutter, px(0.)); + let terminal_input_handler = TerminalInputHandler { + cx: cx.to_async(), + terminal: self.terminal.clone(), + cursor_bounds: layout + .cursor + .as_ref() + .map(|cursor| cursor.bounding_rect(origin)), + }; + let mut this = self.register_mouse_listeners(origin, layout.mode, bounds, cx); let interactivity = mem::take(&mut this.interactivity); interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| { - let input_handler = ElementInputHandler::new(bounds, this.terminal_view.clone(), cx); - cx.handle_input(&this.focus, input_handler); + cx.handle_input(&this.focus, terminal_input_handler); this.register_key_listeners(cx); @@ -890,6 +879,69 @@ impl IntoElement for TerminalElement { } } +struct TerminalInputHandler { + cx: AsyncWindowContext, + terminal: Model, + cursor_bounds: Option>, +} + +impl PlatformInputHandler for TerminalInputHandler { + fn selected_text_range(&mut self) -> Option> { + self.cx + .update(|_, cx| { + if self + .terminal + .read(cx) + .last_content + .mode + .contains(TermMode::ALT_SCREEN) + { + None + } else { + Some(0..0) + } + }) + .ok() + .flatten() + } + + fn marked_text_range(&mut self) -> Option> { + None + } + + fn text_for_range(&mut self, range_utf16: std::ops::Range) -> Option { + None + } + + fn replace_text_in_range( + &mut self, + _replacement_range: Option>, + text: &str, + ) { + self.cx + .update(|_, cx| { + self.terminal.update(cx, |terminal, _| { + terminal.input(text.into()); + }) + }) + .ok(); + } + + fn replace_and_mark_text_in_range( + &mut self, + _range_utf16: Option>, + _new_text: &str, + _new_selected_range: Option>, + ) { + } + + fn unmark_text(&mut self) {} + + fn bounds_for_range(&mut self, _range_utf16: std::ops::Range) -> Option> { + self.cursor_bounds + } +} + fn is_blank(cell: &IndexedCell) -> bool { if cell.c != ' ' { return false; diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index 1562ba38e7..75b019f2ef 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -9,10 +9,9 @@ pub mod terminal_panel; // use crate::terminal_element::TerminalElement; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ - actions, div, point, px, size, Action, AnyElement, AppContext, Bounds, Div, EventEmitter, - FocusEvent, FocusHandle, Focusable, FocusableElement, FocusableView, Font, FontStyle, - FontWeight, InputHandler, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton, - MouseDownEvent, Pixels, Render, Task, View, VisualContext, WeakView, Subscription + actions, div, Action, AnyElement, AppContext, Div, EventEmitter, FocusEvent, FocusHandle, + Focusable, FocusableElement, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, + MouseButton, MouseDownEvent, Pixels, Render, Subscription, Task, View, VisualContext, WeakView, }; use language::Bias; use persistence::TERMINAL_DB; @@ -26,7 +25,6 @@ use terminal::{ Event, MaybeNavigationTarget, Terminal, }; use terminal_element::TerminalElement; -use theme::ThemeSettings; use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label}; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ @@ -624,7 +622,7 @@ impl Render for TerminalView { type Element = Focusable
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let terminal_handle = self.terminal.clone().downgrade(); + let terminal_handle = self.terminal.clone(); let this_view = cx.view().clone(); let self_id = cx.entity_id(); @@ -673,124 +671,6 @@ impl Render for TerminalView { } } -//todo!(Implement IME) -impl InputHandler for TerminalView { - fn text_for_range( - &mut self, - range: std::ops::Range, - cx: &mut ViewContext, - ) -> Option { - None - } - - fn selected_text_range( - &mut self, - cx: &mut ViewContext, - ) -> Option> { - if self - .terminal - .read(cx) - .last_content - .mode - .contains(TermMode::ALT_SCREEN) - { - None - } else { - Some(0..0) - } - } - - fn marked_text_range(&self, _cx: &mut ViewContext) -> Option> { - None - } - - fn unmark_text(&mut self, _cx: &mut ViewContext) {} - - fn replace_text_in_range( - &mut self, - _: Option>, - text: &str, - cx: &mut ViewContext, - ) { - self.terminal.update(cx, |terminal, _| { - terminal.input(text.into()); - }); - } - - fn replace_and_mark_text_in_range( - &mut self, - _range: Option>, - _new_text: &str, - _new_selected_range: Option>, - _cx: &mut ViewContext, - ) { - } - - // todo!(Check that this works correctly, why aren't we reading the range?) - fn bounds_for_range( - &mut self, - _range_utf16: std::ops::Range, - bounds: gpui::Bounds, - cx: &mut ViewContext, - ) -> Option> { - let settings = ThemeSettings::get_global(cx).clone(); - - let buffer_font_size = settings.buffer_font_size(cx); - - let terminal_settings = TerminalSettings::get_global(cx); - let font_family = terminal_settings - .font_family - .as_ref() - .map(|string| string.clone().into()) - .unwrap_or(settings.buffer_font.family); - - let line_height = terminal_settings - .line_height - .value() - .to_pixels(cx.rem_size()); - - let font_size = terminal_settings.font_size.clone(); - let features = terminal_settings - .font_features - .clone() - .unwrap_or(settings.buffer_font.features.clone()); - - let font_size = - font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx)); - - let font_id = cx - .text_system() - .font_id(&Font { - family: font_family, - style: FontStyle::Normal, - weight: FontWeight::NORMAL, - features, - }) - .unwrap(); - - let cell_width = cx - .text_system() - .advance(font_id, font_size, 'm') - .unwrap() - .width; - - let mut origin = bounds.origin + point(cell_width, px(0.)); - - // TODO - Why is it necessary to move downward one line to get correct - // positioning? I would think that we'd want the same rect that is - // painted for the cursor. - origin += point(px(0.), line_height); - - let cursor = Bounds { - origin, - //todo!(correctly calculate this width and height based on the text the line is over) - size: size(cell_width, line_height), - }; - - Some(cursor) - } -} - impl Item for TerminalView { type Event = ItemEvent; From 22cd62213b550f5e2bf7102a67e8cad6b75c4011 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 6 Dec 2023 16:22:25 -0800 Subject: [PATCH 09/10] Tear out drag event listener --- crates/gpui2/src/app.rs | 30 +------------- crates/gpui2/src/elements/div.rs | 70 ++++++++------------------------ crates/gpui2/src/window.rs | 17 ++++---- 3 files changed, 26 insertions(+), 91 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 800c86d569..9293302938 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -188,7 +188,7 @@ pub struct AppContext { flushing_effects: bool, pending_updates: usize, pub(crate) actions: Rc, - pub(crate) active_drag: Option, + pub(crate) active_drag: Option, pub(crate) active_tooltip: Option, pub(crate) next_frame_callbacks: HashMap>, pub(crate) frame_consumers: HashMap>, @@ -1264,34 +1264,6 @@ pub struct AnyDrag { pub cursor_offset: Point, } -pub enum AnyDragState { - EventListener, - AnyDrag(AnyDrag), -} - -impl AnyDragState { - pub fn any_drag(&self) -> Option<&AnyDrag> { - match self { - AnyDragState::EventListener => None, - AnyDragState::AnyDrag(any_drag) => Some(any_drag), - } - } - - pub fn entity_id(&self) -> Option { - match self { - AnyDragState::EventListener => None, - AnyDragState::AnyDrag(any_drag) => Some(any_drag.view.entity_id()), - } - } - - pub fn entity_type(&self) -> Option { - match self { - AnyDragState::EventListener => None, - AnyDragState::AnyDrag(any_drag) => Some(any_drag.view.entity_type()), - } - } -} - #[derive(Clone)] pub(crate) struct AnyTooltip { pub view: AnyView, diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index ab21243143..10fd7dda0a 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,10 +1,10 @@ use crate::{ - point, px, Action, AnyDrag, AnyDragState, AnyElement, AnyTooltip, AnyView, AppContext, - BorrowAppContext, BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, - FocusEvent, FocusHandle, IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, - MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, - Render, ScrollWheelEvent, SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, - Task, View, Visibility, WindowContext, + point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, + BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle, + IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, + SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, + WindowContext, }; use collections::HashMap; use refineable::Refineable; @@ -415,19 +415,6 @@ pub trait StatefulInteractiveElement: InteractiveElement { self } - fn on_drag_event( - mut self, - listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static, - ) -> Self - where - Self: Sized, - { - self.interactivity() - .drag_event_listeners - .push(Box::new(listener)); - self - } - fn on_hover(mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) -> Self where Self: Sized, @@ -761,7 +748,6 @@ pub struct Interactivity { pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>, pub drop_listeners: SmallVec<[(TypeId, Box); 2]>, pub click_listeners: SmallVec<[ClickListener; 2]>, - pub drag_event_listeners: SmallVec<[DragEventListener; 1]>, pub drag_listener: Option, pub hover_listener: Option>, pub tooltip_builder: Option, @@ -906,11 +892,8 @@ impl Interactivity { if phase == DispatchPhase::Bubble && interactive_bounds.visibly_contains(&event.position, &cx) { - if let Some(drag_state_type) = cx - .active_drag - .as_ref() - .and_then(|drag| drag.any_drag()) - .map(|drag| drag.view.entity_type()) + if let Some(drag_state_type) = + cx.active_drag.as_ref().map(|drag| drag.view.entity_type()) { for (drop_state_type, listener) in &drop_listeners { if *drop_state_type == drag_state_type { @@ -918,7 +901,7 @@ impl Interactivity { .active_drag .take() .expect("checked for type drag state type above"); - let drag = drag.any_drag().expect("checked for any drag above"); + listener(drag.view.clone(), cx); cx.notify(); cx.stop_propagation(); @@ -933,16 +916,12 @@ impl Interactivity { let click_listeners = mem::take(&mut self.click_listeners); let drag_listener = mem::take(&mut self.drag_listener); - let drag_event_listeners = mem::take(&mut self.drag_event_listeners); - if !click_listeners.is_empty() - || drag_listener.is_some() - || !drag_event_listeners.is_empty() - { + if !click_listeners.is_empty() || drag_listener.is_some() { let pending_mouse_down = element_state.pending_mouse_down.clone(); let mouse_down = pending_mouse_down.borrow().clone(); if let Some(mouse_down) = mouse_down { - if !drag_event_listeners.is_empty() || drag_listener.is_some() { + if let Some(drag_listener) = drag_listener { let active_state = element_state.clicked_state.clone(); let interactive_bounds = interactive_bounds.clone(); @@ -950,29 +929,17 @@ impl Interactivity { if cx.active_drag.is_some() { if phase == DispatchPhase::Capture { cx.notify(); - } else if interactive_bounds.visibly_contains(&event.position, cx) - && (event.position - mouse_down.position).magnitude() - > DRAG_THRESHOLD - { - for listener in &drag_event_listeners { - listener(event, cx); - } } } else if phase == DispatchPhase::Bubble && interactive_bounds.visibly_contains(&event.position, cx) && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD { *active_state.borrow_mut() = ElementClickedState::default(); - if let Some(drag_listener) = &drag_listener { - let cursor_offset = event.position - bounds.origin; - let drag = drag_listener(cursor_offset, cx); - cx.active_drag = Some(AnyDragState::AnyDrag(drag)); - cx.notify(); - cx.stop_propagation(); - } - for listener in &drag_event_listeners { - listener(event, cx); - } + let cursor_offset = event.position - bounds.origin; + let drag = drag_listener(cursor_offset, cx); + cx.active_drag = Some(drag); + cx.notify(); + cx.stop_propagation(); } }); } @@ -1235,7 +1202,7 @@ impl Interactivity { if let Some(drag) = cx.active_drag.take() { for (state_type, group_drag_style) in &self.group_drag_over_styles { if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { - if Some(*state_type) == drag.entity_type() + if *state_type == drag.view.entity_type() && group_bounds.contains_point(&mouse_position) { style.refine(&group_drag_style.style); @@ -1244,7 +1211,7 @@ impl Interactivity { } for (state_type, drag_over_style) in &self.drag_over_styles { - if Some(*state_type) == drag.entity_type() + if *state_type == drag.view.entity_type() && bounds .intersect(&cx.content_mask().bounds) .contains_point(&mouse_position) @@ -1301,7 +1268,6 @@ impl Default for Interactivity { action_listeners: SmallVec::new(), drop_listeners: SmallVec::new(), click_listeners: SmallVec::new(), - drag_event_listeners: SmallVec::new(), drag_listener: None, hover_listener: None, tooltip_builder: None, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 3a966871fc..455472a349 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1188,14 +1188,11 @@ impl<'a> WindowContext<'a> { }); if let Some(active_drag) = self.app.active_drag.take() { - if let Some(active_drag) = active_drag.any_drag() { - self.with_z_index(1, |cx| { - let offset = cx.mouse_position() - active_drag.cursor_offset; - let available_space = - size(AvailableSpace::MinContent, AvailableSpace::MinContent); - active_drag.view.draw(offset, available_space, cx); - }); - } + self.with_z_index(1, |cx| { + let offset = cx.mouse_position() - active_drag.cursor_offset; + let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); + active_drag.view.draw(offset, available_space, cx); + }); self.active_drag = Some(active_drag); } else if let Some(active_tooltip) = self.app.active_tooltip.take() { self.with_z_index(1, |cx| { @@ -1273,10 +1270,10 @@ impl<'a> WindowContext<'a> { FileDropEvent::Entered { position, files } => { self.window.mouse_position = position; if self.active_drag.is_none() { - self.active_drag = Some(crate::AnyDragState::AnyDrag(AnyDrag { + self.active_drag = Some(AnyDrag { view: self.build_view(|_| files).into(), cursor_offset: position, - })); + }); } InputEvent::MouseDown(MouseDownEvent { position, From 66b93212fe9257d49769df25a581ae50d25d7a63 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 6 Dec 2023 16:25:50 -0800 Subject: [PATCH 10/10] Fix selection backgrounds --- crates/terminal_view2/src/terminal_element.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 4d088ff63b..4a37ca91b7 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -261,7 +261,6 @@ impl TerminalElement { let cell_style = TerminalElement::cell_style( &cell, fg, - bg, theme, text_style, text_system, @@ -274,7 +273,6 @@ impl TerminalElement { text_style.font_size.to_pixels(cx.rem_size()), &[cell_style], ) - //todo!() Can we remove this unwrap? .unwrap(); cells.push(LayoutCell::new( @@ -324,7 +322,7 @@ impl TerminalElement { fn cell_style( indexed: &IndexedCell, fg: terminal::alacritty_terminal::ansi::Color, - bg: terminal::alacritty_terminal::ansi::Color, + // bg: terminal::alacritty_terminal::ansi::Color, colors: &Theme, text_style: &TextStyle, text_system: &TextSystem, @@ -332,7 +330,7 @@ impl TerminalElement { ) -> TextRun { let flags = indexed.cell.flags; let fg = convert_color(&fg, &colors); - let bg = convert_color(&bg, &colors); + // let bg = convert_color(&bg, &colors); let underline = (flags.intersects(Flags::ALL_UNDERLINES) || indexed.cell.hyperlink().is_some()) @@ -357,7 +355,7 @@ impl TerminalElement { let mut result = TextRun { len: indexed.c.len_utf8() as usize, color: fg, - background_color: Some(bg), + background_color: None, font: Font { weight, style, @@ -384,10 +382,6 @@ impl TerminalElement { fn compute_layout(&self, bounds: Bounds, cx: &mut WindowContext) -> LayoutState { let settings = ThemeSettings::get_global(cx).clone(); - //Setup layout information - // todo!(Terminal tooltips) - // let tooltip_style = settings.theme.tooltip.clone(); - let buffer_font_size = settings.buffer_font_size(cx); let terminal_settings = TerminalSettings::get_global(cx);