diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 80dd120f24..9447c3370b 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -3461,7 +3461,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/editor2/src/items.rs b/crates/editor2/src/items.rs index 12feb31696..2b46c29e6b 100644 --- a/crates/editor2/src/items.rs +++ b/crates/editor2/src/items.rs @@ -939,7 +939,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/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 c95a7f890f..10fd7dda0a 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -559,6 +559,8 @@ pub type KeyDownListener = Box; +pub type DragEventListener = Box; + pub type ActionListener = Box; pub fn div() -> Div { @@ -751,7 +753,7 @@ pub struct Interactivity { pub tooltip_builder: Option, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct InteractiveBounds { pub bounds: Bounds, pub stacking_order: StackingOrder, @@ -899,11 +901,14 @@ impl Interactivity { .active_drag .take() .expect("checked for type drag state type above"); + listener(drag.view.clone(), cx); cx.notify(); cx.stop_propagation(); } } + } else { + cx.active_drag = None; } } }); @@ -1324,7 +1329,7 @@ impl GroupBounds { } pub struct Focusable { - element: E, + pub element: E, } impl FocusableElement for Focusable {} 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 245b36da56..9b7ad72472 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 { @@ -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, @@ -99,7 +103,7 @@ impl PlatformWindow for TestWindow { } fn activate(&self) { - todo!() + unimplemented!() } fn set_title(&mut self, title: &str) { @@ -107,23 +111,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 +143,7 @@ impl PlatformWindow for TestWindow { } fn on_fullscreen(&self, _callback: Box) { - todo!() + unimplemented!() } fn on_moved(&self, callback: Box) { @@ -147,19 +151,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/gpui2/src/window.rs b/crates/gpui2/src/window.rs index c3960c032b..455472a349 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1192,8 +1192,8 @@ impl<'a> WindowContext<'a> { 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); }); + 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); @@ -1228,6 +1228,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/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/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/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index 9f94339504..6036d65d6e 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/crates/terminal2/src/terminal2.rs @@ -1103,7 +1103,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); @@ -1129,7 +1134,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.); @@ -1229,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/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..4a37ca91b7 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -1,970 +1,1168 @@ -// #![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::{ + 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, 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; +use settings::Settings; +use terminal::{ + alacritty_terminal::ansi::NamedColor, + alacritty_terminal::{ + ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape}, + grid::Dimensions, + index::Point as AlacPoint, + term::{cell::Flags, TermMode}, + }, + terminal_settings::TerminalSettings, + 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, + 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, + 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).ok(); + } +} + +#[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, cx: &mut WindowContext) { + 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: Model, + terminal_view: View, + 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: Model, + terminal_view: View, + focus: FocusHandle, + focused: bool, + cursor_visible: bool, + can_navigate_to_selected_word: bool, + ) -> TerminalElement { + TerminalElement { + terminal, + terminal_view, + focused, + 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 + + fn layout_grid( + grid: &Vec, + text_style: &TextStyle, + // terminal_theme: &TerminalStyle, + text_system: &TextSystem, + hyperlink: Option<(HighlightStyle, &RangeInclusive)>, + cx: &WindowContext<'_>, + ) -> (Vec, Vec) { + let theme = cx.theme(); + 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), + )); + } + } + None => { + cur_alac_color = Some(bg); + cur_rect = Some(LayoutRect::new( + AlacPoint::new(line_index as i32, cell.point.column.0 as i32), + 1, + convert_color(&bg, &theme), + )); + } + } + } + } + + //Layout current cell text + { + let cell_text = cell.c.to_string(); + if !is_blank(&cell) { + let cell_style = TerminalElement::cell_style( + &cell, + fg, + theme, + 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], + ) + .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: &Theme, + 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 underline = (flags.intersects(Flags::ALL_UNDERLINES) + || indexed.cell.hyperlink().is_some()) + .then(|| UnderlineStyle { + color: Some(fg), + thickness: Pixels::from(1.0), + wavy: flags.contains(Flags::UNDERCURL), + }); + + let 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: None, + font: Font { + weight, + style, + ..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).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 font_features = terminal_settings + .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().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, + font_size: font_size.into(), + font_style: FontStyle::Normal, + line_height: line_height.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 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 * 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?) + 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 = self.terminal.read(cx).matches.clone(); + + let background_color = theme.colors().background; + + 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() { + terminal.last_content.last_hovered_word.clone() + } else { + None + } + }); + + 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, + mode, + display_offset, + cursor_char, + selection, + cursor, + .. + } = &self.terminal.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, + &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 { + theme.players().local().background + } else { + theme.players().local().cursor + }; + + let len = str_trxt.len(); + cx.text_system() + .shape_line( + str_trxt.into(), + text_style.font_size.to_pixels(cx.rem_size()), + &[TextRun { + 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, + theme.players().local().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, + } + } + + fn generic_button_handler( + 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); + connection.update(cx, |terminal, cx| { + f(terminal, origin, event, cx); + + cx.notify(); + }) + } + } + + 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)); + + if handled { + cx.notify(); + } + } + }); + } + + fn register_mouse_listeners( + self, + origin: Point, + mode: TermMode, + bounds: Bounds, + cx: &mut WindowContext, + ) -> Self { + let focus = self.focus.clone(); + let connection = self.terminal.clone(); + + let mut this = self + .on_mouse_down(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()); + connection.update(cx, |terminal, cx| { + terminal.mouse_down(&e, origin); + + cx.notify(); + }) + } + }) + .on_mouse_move({ + let connection = connection.clone(); + let focus = focus.clone(); + move |e, cx| { + if e.pressed_button.is_some() { + if focus.is_focused(cx) { + connection.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| { + if e.down.button == MouseButton::Right { + 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); + } + } + } + }) + .on_mouse_move({ + let connection = connection.clone(); + let focus = focus.clone(); + move |e, cx| { + if focus.is_focused(cx) { + connection.update(cx, |terminal, cx| { + terminal.mouse_move(&e, origin); + cx.notify(); + }) + } + } + }) + .on_scroll_wheel({ + let connection = connection.clone(); + move |e, cx| { + connection.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 + } +} + +impl Element for TerminalElement { + type State = InteractiveElementState; + + fn layout( + &mut self, + element_state: Option, + cx: &mut WindowContext<'_>, + ) -> (LayoutId, Self::State) { + 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, interactive_state) + } + + 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 = Some(dispatch_context); + cx.paint_quad( + bounds, + Default::default(), + layout.background_color, + Default::default(), + Hsla::default(), + ); + 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| { + cx.handle_input(&this.focus, terminal_input_handler); + + this.register_key_listeners(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() + { + 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(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) + } + }); + } +} + +impl IntoElement for TerminalElement { + type Element = Self; + + fn element_id(&self) -> Option { + Some("terminal".into()) + } + + fn into_element(self) -> Self::Element { + self + } +} + +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; + } + + 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)) +} + +///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_panel.rs b/crates/terminal_view2/src/terminal_panel.rs index caf339a8c6..b8db12381b 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()) } } diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index 49da703add..75b019f2ef 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -10,8 +10,8 @@ pub mod terminal_panel; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ actions, div, Action, AnyElement, AppContext, Div, EventEmitter, FocusEvent, FocusHandle, - Focusable, FocusableElement, FocusableView, InputHandler, KeyDownEvent, Keystroke, Model, - MouseButton, MouseDownEvent, Pixels, Render, Task, View, VisualContext, WeakView, + Focusable, FocusableElement, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, + MouseButton, MouseDownEvent, Pixels, Render, Subscription, Task, View, VisualContext, WeakView, }; use language::Bias; use persistence::TERMINAL_DB; @@ -24,6 +24,7 @@ use terminal::{ terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory}, Event, MaybeNavigationTarget, Terminal, }; +use terminal_element::TerminalElement; use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label}; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ @@ -89,6 +90,7 @@ pub struct TerminalView { blink_epoch: usize, can_navigate_to_selected_word: bool, workspace_id: WorkspaceId, + _subscriptions: Vec, } impl EventEmitter for TerminalView {} @@ -260,6 +262,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, @@ -272,6 +288,7 @@ impl TerminalView { blink_epoch: 0, can_navigate_to_selected_word: false, workspace_id, + _subscriptions: vec![/*focus_in, focus_out*/], } } @@ -301,8 +318,7 @@ impl TerminalView { menu.action("Clear", Box::new(Clear)) .action("Close", Box::new(CloseActiveItem { save_intent: None })) })); - dbg!(&position); - // todo!() + // todo!(context menus) // self.context_menu // .show(position, AnchorCorner::TopLeft, menu_entries, cx); // cx.notify(); @@ -447,6 +463,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( @@ -531,17 +622,20 @@ 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(); 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)) @@ -550,15 +644,14 @@ 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, + this_view, + self.focus_handle.clone(), + focused, + self.should_show_cursor(focused, cx), + self.can_navigate_to_selected_word, + )) .on_mouse_down( MouseButton::Right, cx.listener(|this, event: &MouseDownEvent, cx| { @@ -578,162 +671,6 @@ 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); -// } -// } - -impl InputHandler for TerminalView { - fn text_for_range( - &mut self, - range: std::ops::Range, - cx: &mut ViewContext, - ) -> Option { - todo!() - } - - 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> { - todo!() - } - - fn unmark_text(&mut self, cx: &mut ViewContext) { - todo!() - } - - 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!() - } - - fn bounds_for_range( - &mut self, - range_utf16: std::ops::Range, - element_bounds: gpui::Bounds, - cx: &mut ViewContext, - ) -> Option> { - todo!() - } -} - impl Item for TerminalView { type Event = ItemEvent; @@ -778,7 +715,7 @@ impl Item for TerminalView { false } - // todo!() + // todo!(search) // fn as_searchable(&self, handle: &View) -> Option> { // Some(Box::new(handle.clone())) // } @@ -808,22 +745,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) @@ -835,14 +773,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(); } diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index b61e4792a4..c01e0d8c80 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 fbcabc0ff3..29b01dbbf9 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.