From a858b3fda9134361c46b6dcbbb590b4710cbfbf7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 6 Jul 2022 11:20:29 -0700 Subject: [PATCH 01/47] Treat window deactivation as a focus change for the purpose of autosave --- crates/workspace/src/workspace.rs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 53318d943fe1b5854823e88b4bcbd1edd474692a..f7d3032eabd33b327523af15603f642d8f3234d0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2360,7 +2360,12 @@ impl Workspace { } fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext) { - if !active && cx.global::().autosave == Autosave::OnWindowChange { + if !active + && matches!( + cx.global::().autosave, + Autosave::OnWindowChange | Autosave::OnFocusChange + ) + { for pane in &self.panes { pane.update(cx, |pane, cx| { for item in pane.items() { @@ -3073,6 +3078,17 @@ mod tests { deterministic.run_until_parked(); item.read_with(cx, |item, _| assert_eq!(item.save_count, 2)); + // Deactivating the window still saves the file. + cx.simulate_window_activation(Some(window_id)); + item.update(cx, |item, cx| { + cx.focus_self(); + item.is_dirty = true; + }); + cx.simulate_window_activation(None); + + deterministic.run_until_parked(); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 3)); + // Autosave after delay. item.update(cx, |item, cx| { cx.update_global(|settings: &mut Settings, _| { @@ -3084,11 +3100,11 @@ mod tests { // Delay hasn't fully expired, so the file is still dirty and unsaved. deterministic.advance_clock(Duration::from_millis(250)); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 2)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 3)); // After delay expires, the file is saved. deterministic.advance_clock(Duration::from_millis(250)); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 3)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 4)); // Autosave on focus change, ensuring closing the tab counts as such. item.update(cx, |item, cx| { @@ -3106,7 +3122,7 @@ mod tests { .await .unwrap(); assert!(!cx.has_pending_prompt(window_id)); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 4)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // Add the item again, ensuring autosave is prevented if the underlying file has been deleted. workspace.update(cx, |workspace, cx| { @@ -3118,7 +3134,7 @@ mod tests { cx.blur(); }); deterministic.run_until_parked(); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 4)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // Ensure autosave is prevented for deleted files also when closing the buffer. let _close_items = workspace.update(cx, |workspace, cx| { @@ -3127,7 +3143,7 @@ mod tests { }); deterministic.run_until_parked(); assert!(cx.has_pending_prompt(window_id)); - item.read_with(cx, |item, _| assert_eq!(item.save_count, 4)); + item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); } #[derive(Clone)] From bbe325930f78e478775022c9df86be75ca9d847d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 6 Jul 2022 11:32:21 -0700 Subject: [PATCH 02/47] 0.45 --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7dcecd009390c92a0602ae3fc70c421be27c6f1d..8624921d976cae526e121a843be3eeec68e5c740 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6159,7 +6159,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.44.1" +version = "0.45.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 6d488e83e8ce64729791d96b2a6b9c28119a69e9..88acfdb14dd2f3796c5715abfe56a4c8da8fd829 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.44.1" +version = "0.45.0" [lib] name = "zed" From cba5b4ac113bba836be5a9c4d5c5bfce152fda50 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 6 Jul 2022 13:44:30 -0700 Subject: [PATCH 03/47] Began working on selections, refactored colors --- crates/terminal/src/color_translation.rs | 134 ++++++++++++++++ crates/terminal/src/terminal.rs | 41 +---- crates/terminal/src/terminal_element.rs | 190 ++++++++++------------- 3 files changed, 227 insertions(+), 138 deletions(-) create mode 100644 crates/terminal/src/color_translation.rs diff --git a/crates/terminal/src/color_translation.rs b/crates/terminal/src/color_translation.rs new file mode 100644 index 0000000000000000000000000000000000000000..78c2a569dbbeac5806e6535a2c87b7c0b6b57c03 --- /dev/null +++ b/crates/terminal/src/color_translation.rs @@ -0,0 +1,134 @@ +use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb}; +use gpui::color::Color; +use theme::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), + } +} + +///Converts an 8 bit ANSI color to it's GPUI equivalent. +///Accepts usize for compatability 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 compatability 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!(i >= &16 && i <= &231); + 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) +} + +//Convenience method to convert from a GPUI color to an alacritty Rgb +pub fn to_alac_rgb(color: Color) -> AlacRgb { + AlacRgb { + r: color.r, + g: color.g, + b: color.g, + } +} + +#[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::color_translation::rgb_for_index(&(i as u8)); + assert_eq!(i, 16 + 36 * r + 6 * g + b); + } + } +} diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index eb7f2a0a90b0d6d4b1204bdc13396634145c4371..dc10f181e7543bc3fd97f455fcd9561f216b7025 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -4,18 +4,19 @@ use alacritty_terminal::{ event_loop::{EventLoop, Msg, Notifier}, grid::Scroll, sync::FairMutex, - term::{color::Rgb as AlacRgb, SizeInfo}, + term::SizeInfo, tty::{self, setup_env}, Term, }; +use color_translation::{get_color_at_index, to_alac_rgb}; use futures::{ channel::mpsc::{unbounded, UnboundedSender}, StreamExt, }; use gpui::{ - actions, color::Color, elements::*, impl_internal_actions, platform::CursorStyle, - ClipboardItem, Entity, MutableAppContext, View, ViewContext, + actions, elements::*, impl_internal_actions, platform::CursorStyle, ClipboardItem, Entity, + MutableAppContext, View, ViewContext, }; use project::{Project, ProjectPath}; use settings::Settings; @@ -23,7 +24,7 @@ use smallvec::SmallVec; use std::{collections::HashMap, path::PathBuf, sync::Arc}; use workspace::{Item, Workspace}; -use crate::terminal_element::{get_color_at_index, TerminalEl}; +use crate::terminal_element::TerminalEl; //ASCII Control characters on a keyboard const ETX_CHAR: char = 3_u8 as char; //'End of text', the control code for 'ctrl-c' @@ -37,6 +38,7 @@ const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; const DEFAULT_TITLE: &str = "Terminal"; +pub mod color_translation; pub mod gpui_func_tools; pub mod terminal_element; @@ -62,7 +64,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::escape); cx.add_action(Terminal::quit); cx.add_action(Terminal::del); - cx.add_action(Terminal::carriage_return); //TODO figure out how to do this properly. Should we be checking the terminal mode? + cx.add_action(Terminal::carriage_return); cx.add_action(Terminal::left); cx.add_action(Terminal::right); cx.add_action(Terminal::up); @@ -129,7 +131,6 @@ impl Terminal { hold: false, }; - //Does this mangle the zed Env? I'm guessing it does... do child processes have a seperate ENV? let mut env: HashMap = HashMap::new(); //TODO: Properly set the current locale, env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string()); @@ -219,24 +220,7 @@ impl Terminal { AlacTermEvent::ColorRequest(index, format) => { let color = self.term.lock().colors()[index].unwrap_or_else(|| { let term_style = &cx.global::().theme.terminal; - match index { - 0..=255 => to_alac_rgb(get_color_at_index(&(index as u8), term_style)), - //These additional values are required to match the Alacritty Colors object's behavior - 256 => to_alac_rgb(term_style.foreground), - 257 => to_alac_rgb(term_style.background), - 258 => to_alac_rgb(term_style.cursor), - 259 => to_alac_rgb(term_style.dim_black), - 260 => to_alac_rgb(term_style.dim_red), - 261 => to_alac_rgb(term_style.dim_green), - 262 => to_alac_rgb(term_style.dim_yellow), - 263 => to_alac_rgb(term_style.dim_blue), - 264 => to_alac_rgb(term_style.dim_magenta), - 265 => to_alac_rgb(term_style.dim_cyan), - 266 => to_alac_rgb(term_style.dim_white), - 267 => to_alac_rgb(term_style.bright_foreground), - 268 => to_alac_rgb(term_style.black), //Dim Background, non-standard - _ => AlacRgb { r: 0, g: 0, b: 0 }, - } + to_alac_rgb(get_color_at_index(&index, term_style)) }); self.write_to_pty(&Input(format(color)), cx) } @@ -468,15 +452,6 @@ impl Item for Terminal { } } -//Convenience method for less lines -fn to_alac_rgb(color: Color) -> AlacRgb { - AlacRgb { - r: color.r, - g: color.g, - b: color.g, - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 408fb0dcecc35c844e78ad4240e48bdbd657662f..ad017c011fa250ec44125daa37b7d9b5be7eea7a 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -1,7 +1,6 @@ use alacritty_terminal::{ - ansi::Color as AnsiColor, grid::{Dimensions, GridIterator, Indexed}, - index::Point, + index::{Column as GridCol, Line as GridLine, Point, Side}, term::{ cell::{Cell, Flags}, SizeInfo, @@ -24,10 +23,12 @@ use gpui::{ use itertools::Itertools; use ordered_float::OrderedFloat; use settings::Settings; -use std::rc::Rc; +use std::{cmp::min, rc::Rc}; use theme::TerminalStyle; -use crate::{gpui_func_tools::paint_layer, Input, ScrollTerminal, Terminal}; +use crate::{ + color_translation::convert_color, gpui_func_tools::paint_layer, Input, ScrollTerminal, Terminal, +}; ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable ///Scroll multiplier that is set to 3 by default. This will be removed when I @@ -74,6 +75,7 @@ pub struct LayoutState { cursor: Option, background_color: Color, cur_size: SizeInfo, + display_offset: usize, } impl TerminalEl { @@ -194,6 +196,7 @@ impl Element for TerminalEl { cur_size, background_rects, background_color: terminal_theme.background, + display_offset: content.display_offset, }, ) } @@ -209,10 +212,50 @@ impl Element for TerminalEl { let clip_bounds = Some(visible_bounds); paint_layer(cx, clip_bounds, |cx| { //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse + + /* + To set a selection, + set the selection variable on the terminal + + CLICK: + Get the grid point associated with this mouse click + And the side????? - TODO - algorithm for calculating this in Processor::cell_side + On single left click -> Clear selection, start empty selection + On double left click -> start semantic selection + On double triple click -> start line selection + + MOUSE MOVED: + Find the new cell the mouse is over + Update the selection by calling terminal.selection.update() + */ + let cur_size = layout.cur_size.clone(); + let display_offset = layout.display_offset.clone(); cx.scene.push_mouse_region(MouseRegion { view_id: self.view.id(), - mouse_down: Some(Rc::new(|_, cx| cx.focus_parent_view())), + mouse_down: Some(Rc::new(move |pos, cx| { + let point = grid_cell(pos, cur_size, display_offset); + let side = cell_side(cur_size, pos.x() as usize); + + //One problem is we need a terminal + //Second problem is that we need # of clicks + //Third problem is that dragging reports deltas, and we need locations. + //Fourth (minor) is need to render the selection + + // if single_click { + // terminal.selection = Some(Selection::new(SelectionType::Simple, point, side)) + // } else if double_click { + // terminal.selection = Some(Selection::new(SelectionType::Semantic, point, side)) + // } else if triple_click { + // terminal.selection = Some(Selection::new(SelectionType::Lines, point, side)) + // } + + cx.focus_parent_view() + })), bounds: visible_bounds, + drag: Some(Rc::new(|delta, cx| { + //Calculate new point from delta + //terminal.selection.update(point, side) + })), ..Default::default() }); @@ -311,6 +354,19 @@ impl Element for TerminalEl { } } +/* +Mouse moved -> WindowEvent::CursorMoved +mouse press -> WindowEvent::MouseInput +update_selection_scrolling + + +copy_selection +start_selection +toggle_selection +update_selection +clear_selection + */ + ///Configures a text style from the current settings. fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle { TextStyle { @@ -430,98 +486,34 @@ fn cell_style(indexed: &Indexed<&Cell>, style: &TerminalStyle, text_style: &Text } } -///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent -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, style), - } -} +///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side() +fn cell_side(cur_size: SizeInfo, x: usize) -> Side { + let cell_x = x.saturating_sub(cur_size.cell_width() as usize) % cur_size.cell_width() as usize; + let half_cell_width = (cur_size.cell_width() / 2.0) as usize; -///Converts an 8 bit ANSI color to it's GPUI equivalent. -pub fn get_color_at_index(index: &u8, 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); //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 - 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 - } + let additional_padding = + (cur_size.width() - cur_size.cell_width() * 2.) % cur_size.cell_width(); + let end_of_grid = cur_size.width() - cur_size.cell_width() - additional_padding; + + if cell_x > half_cell_width + // Edge case when mouse leaves the window. + || x as f32 >= end_of_grid + { + Side::Right + } else { + Side::Left } } -///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!(i >= &16 && i <= &231); - 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) +///Copied (with modifications) from alacritty/src/event.rs > Mouse::point() +fn grid_cell(pos: Vector2F, cur_size: SizeInfo, display_offset: usize) -> Point { + let col = pos.x() - cur_size.cell_width() / cur_size.cell_width(); //TODO: underflow... + let col = min(GridCol(col as usize), cur_size.last_column()); + + let line = pos.y() - cur_size.padding_y() / cur_size.cell_height(); + let line = min(line as usize, cur_size.bottommost_line().0 as usize); + + Point::new(GridLine((line - display_offset) as i32), col) } ///Draws the grid as Alacritty sees it. Useful for checking if there is an inconsistency between @@ -554,15 +546,3 @@ fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContex }); } } - -#[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::terminal_element::rgb_for_index(&(i as u8)); - assert_eq!(i, 16 + 36 * r + 6 * g + b); - } - } -} From 7e5cf6669ff985c96f1824a58e3e7cb2cd237021 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 6 Jul 2022 14:05:24 -0700 Subject: [PATCH 04/47] Add forward and backward navigation buttons to toolbar --- assets/icons/arrow-left.svg | 3 ++ assets/icons/arrow-right.svg | 3 ++ crates/theme/src/theme.rs | 1 + crates/workspace/src/pane.rs | 3 +- crates/workspace/src/toolbar.rs | 70 +++++++++++++++++++++++++++---- styles/src/styleTree/workspace.ts | 9 ++++ 6 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 assets/icons/arrow-left.svg create mode 100644 assets/icons/arrow-right.svg diff --git a/assets/icons/arrow-left.svg b/assets/icons/arrow-left.svg new file mode 100644 index 0000000000000000000000000000000000000000..904fdaa1a73221efda6946614db7c707dc74c2ed --- /dev/null +++ b/assets/icons/arrow-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/arrow-right.svg b/assets/icons/arrow-right.svg new file mode 100644 index 0000000000000000000000000000000000000000..b7e1bec6d8a0830b8ebfcf677cd1a5b3480ce5b0 --- /dev/null +++ b/assets/icons/arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 184b1880f0c0a05cc581059b80766b3d282cc9cc..c3052ff6c5afc0a7a5acde8135f555e7113ab6c1 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -108,6 +108,7 @@ pub struct Toolbar { pub container: ContainerStyle, pub height: f32, pub item_spacing: f32, + pub nav_button: Interactive, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index bbc086395b12a4fd09d7c8ce07e6c52c6491cb53..0fb5225cc533266753c4b84acf225373ce0c6cf0 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -168,12 +168,13 @@ pub struct NavigationEntry { impl Pane { pub fn new(cx: &mut ViewContext) -> Self { + let handle = cx.weak_handle(); Self { items: Vec::new(), active_item_index: 0, autoscroll: false, nav_history: Default::default(), - toolbar: cx.add_view(|_| Toolbar::new()), + toolbar: cx.add_view(|_| Toolbar::new(handle)), } } diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index e9b20bf3a04e5876a7a6e202f06b3b78c8727a68..9d8274288be5b6e5a4d8a8a26287da028f347768 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -1,7 +1,7 @@ -use crate::ItemHandle; +use crate::{ItemHandle, Pane}; use gpui::{ - elements::*, AnyViewHandle, AppContext, ElementBox, Entity, MutableAppContext, RenderContext, - View, ViewContext, ViewHandle, + elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox, Entity, + MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle, }; use settings::Settings; @@ -42,6 +42,7 @@ pub enum ToolbarItemLocation { pub struct Toolbar { active_pane_item: Option>, + pane: WeakViewHandle, items: Vec<(Box, ToolbarItemLocation)>, } @@ -60,6 +61,7 @@ impl View for Toolbar { let mut primary_left_items = Vec::new(); let mut primary_right_items = Vec::new(); let mut secondary_item = None; + let spacing = theme.item_spacing; for (item, position) in &self.items { match *position { @@ -68,7 +70,7 @@ impl View for Toolbar { let left_item = ChildView::new(item.as_ref()) .aligned() .contained() - .with_margin_right(theme.item_spacing); + .with_margin_right(spacing); if let Some((flex, expanded)) = flex { primary_left_items.push(left_item.flex(flex, expanded).boxed()); } else { @@ -79,7 +81,7 @@ impl View for Toolbar { let right_item = ChildView::new(item.as_ref()) .aligned() .contained() - .with_margin_left(theme.item_spacing) + .with_margin_left(spacing) .flex_float(); if let Some((flex, expanded)) = flex { primary_right_items.push(right_item.flex(flex, expanded).boxed()); @@ -98,26 +100,78 @@ impl View for Toolbar { } } + let pane = self.pane.clone(); + let container_style = theme.container; + let height = theme.height; + let button_style = theme.nav_button; + Flex::column() .with_child( Flex::row() + .with_child(nav_button( + "icons/arrow-left.svg", + button_style, + spacing, + super::GoBack { + pane: Some(pane.clone()), + }, + cx, + )) + .with_child(nav_button( + "icons/arrow-right.svg", + button_style, + spacing, + super::GoForward { + pane: Some(pane.clone()), + }, + cx, + )) .with_children(primary_left_items) .with_children(primary_right_items) .constrained() - .with_height(theme.height) + .with_height(height) .boxed(), ) .with_children(secondary_item) .contained() - .with_style(theme.container) + .with_style(container_style) .boxed() } } +fn nav_button( + svg_path: &'static str, + style: theme::Interactive, + spacing: f32, + action: A, + cx: &mut RenderContext, +) -> ElementBox { + MouseEventHandler::new::(0, cx, |state, _| { + let style = style.style_for(state, false); + Svg::new(svg_path) + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .contained() + .with_style(style.container) + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_mouse_down(move |_, cx| cx.dispatch_action(action.clone())) + .contained() + .with_margin_right(spacing) + .boxed() +} + impl Toolbar { - pub fn new() -> Self { + pub fn new(pane: WeakViewHandle) -> Self { Self { active_pane_item: None, + pane, items: Default::default(), } } diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 2deadc02e7730041c2801d5fb87cf95c7bfb3ec8..ba3978fc74e1ef93d956cd30beb8d64c9f89df8b 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -139,6 +139,15 @@ export default function workspace(theme: Theme) { background: backgroundColor(theme, 500), border: border(theme, "secondary", { bottom: true }), itemSpacing: 8, + navButton: { + color: iconColor(theme, "secondary"), + iconWidth: 8, + buttonWidth: 12, + margin: { left: 8, right: 8 }, + hover: { + color: iconColor(theme, "active"), + }, + }, padding: { left: 16, right: 8, top: 4, bottom: 4 }, }, breadcrumbs: { From 686e57373b08557610ff62ba5e3158a5a51111c7 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Wed, 6 Jul 2022 15:36:42 -0700 Subject: [PATCH 05/47] pull event data out into individual Event structs --- crates/editor/src/element.rs | 27 +-- crates/gpui/src/app.rs | 14 +- crates/gpui/src/elements/event_handler.rs | 11 +- crates/gpui/src/elements/flex.rs | 9 +- crates/gpui/src/elements/list.rs | 6 +- crates/gpui/src/elements/uniform_list.rs | 6 +- crates/gpui/src/gpui.rs | 3 +- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/event.rs | 198 +++++++++++++--------- crates/gpui/src/platform/mac/event.rs | 139 ++++++++------- crates/gpui/src/platform/mac/window.rs | 24 +-- crates/gpui/src/presenter.rs | 31 ++-- crates/terminal/src/terminal_element.rs | 12 +- 13 files changed, 270 insertions(+), 212 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1169df3fd1d72f725a72beaa81154c58e9bd4884..49ee4e4b72275a4a6e9ea0db124b8426b7837844 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -23,9 +23,10 @@ use gpui::{ json::{self, ToJson}, platform::CursorStyle, text_layout::{self, Line, RunStyle, TextLayoutCache}, - AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext, - LayoutContext, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, - WeakViewHandle, + AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext, KeyDownEvent, + LayoutContext, LeftMouseDownEvent, LeftMouseDraggedEvent, LeftMouseUpEvent, + ModifiersChangedEvent, MouseMovedEvent, MutableAppContext, PaintContext, Quad, Scene, + ScrollWheelEvent, SizeConstraint, ViewContext, WeakViewHandle, }; use json::json; use language::{Bias, DiagnosticSeverity, Selection}; @@ -1463,14 +1464,14 @@ impl Element for EditorElement { } match event { - Event::LeftMouseDown { + Event::LeftMouseDown(LeftMouseDownEvent { position, cmd, alt, shift, click_count, .. - } => self.mouse_down( + }) => self.mouse_down( *position, *cmd, *alt, @@ -1480,18 +1481,20 @@ impl Element for EditorElement { paint, cx, ), - Event::LeftMouseUp { position, .. } => self.mouse_up(*position, cx), - Event::LeftMouseDragged { position, .. } => { + Event::LeftMouseUp(LeftMouseUpEvent { position, .. }) => self.mouse_up(*position, cx), + Event::LeftMouseDragged(LeftMouseDraggedEvent { position, .. }) => { self.mouse_dragged(*position, layout, paint, cx) } - Event::ScrollWheel { + Event::ScrollWheel(ScrollWheelEvent { position, delta, precise, - } => self.scroll(*position, *delta, *precise, layout, paint, cx), - Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx), - Event::ModifiersChanged { cmd, .. } => self.modifiers_changed(*cmd, cx), - Event::MouseMoved { position, cmd, .. } => { + }) => self.scroll(*position, *delta, *precise, layout, paint, cx), + Event::KeyDown(KeyDownEvent { input, .. }) => self.key_down(input.as_deref(), cx), + Event::ModifiersChanged(ModifiersChangedEvent { cmd, .. }) => { + self.modifiers_changed(*cmd, cx) + } + Event::MouseMoved(MouseMovedEvent { position, cmd, .. }) => { self.mouse_moved(*position, *cmd, layout, paint, cx) } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index fd447e2469fc24c538bc7a05ab77fe64460a0a36..8ce980e95f6c83a979cec872849e44f70a16ed85 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -4,7 +4,7 @@ use crate::{ elements::ElementBox, executor::{self, Task}, keymap::{self, Binding, Keystroke}, - platform::{self, Platform, PromptLevel, WindowOptions}, + platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions}, presenter::Presenter, util::post_inc, AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId, PathPromptOptions, @@ -377,11 +377,11 @@ impl TestAppContext { if !cx.dispatch_keystroke(window_id, dispatch_path, &keystroke) { presenter.borrow_mut().dispatch_event( - Event::KeyDown { + Event::KeyDown(KeyDownEvent { keystroke, input, is_held, - }, + }), cx, ); } @@ -1820,7 +1820,7 @@ impl MutableAppContext { window.on_event(Box::new(move |event| { app.update(|cx| { if let Some(presenter) = presenter.upgrade() { - if let Event::KeyDown { keystroke, .. } = &event { + if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event { if cx.dispatch_keystroke( window_id, presenter.borrow().dispatch_path(cx.as_ref()), @@ -5381,7 +5381,7 @@ impl RefCounts { #[cfg(test)] mod tests { use super::*; - use crate::{actions, elements::*, impl_actions}; + use crate::{actions, elements::*, impl_actions, LeftMouseDownEvent}; use serde::Deserialize; use smol::future::poll_once; use std::{ @@ -5734,14 +5734,14 @@ mod tests { let presenter = cx.presenters_and_platform_windows[&window_id].0.clone(); // Ensure window's root element is in a valid lifecycle state. presenter.borrow_mut().dispatch_event( - Event::LeftMouseDown { + Event::LeftMouseDown(LeftMouseDownEvent { position: Default::default(), ctrl: false, alt: false, shift: false, cmd: false, click_count: 1, - }, + }), cx, ); assert_eq!(mouse_down_count.load(SeqCst), 1); diff --git a/crates/gpui/src/elements/event_handler.rs b/crates/gpui/src/elements/event_handler.rs index 7144b21dd0f19ed42dd9403e40173fdec4cf7506..93ff357ee6ef1c999244a2dd965d44ae866309d0 100644 --- a/crates/gpui/src/elements/event_handler.rs +++ b/crates/gpui/src/elements/event_handler.rs @@ -1,6 +1,7 @@ use crate::{ geometry::vector::Vector2F, CursorRegion, DebugContext, Element, ElementBox, Event, - EventContext, LayoutContext, MouseRegion, NavigationDirection, PaintContext, SizeConstraint, + EventContext, LayoutContext, LeftMouseDownEvent, MouseRegion, NavigateMouseDownEvent, + NavigationDirection, PaintContext, RightMouseDownEvent, SizeConstraint, }; use pathfinder_geometry::rect::RectF; use serde_json::json; @@ -116,7 +117,7 @@ impl Element for EventHandler { true } else { match event { - Event::LeftMouseDown { position, .. } => { + Event::LeftMouseDown(LeftMouseDownEvent { position, .. }) => { if let Some(callback) = self.mouse_down.as_mut() { if visible_bounds.contains_point(*position) { return callback(cx); @@ -124,7 +125,7 @@ impl Element for EventHandler { } false } - Event::RightMouseDown { position, .. } => { + Event::RightMouseDown(RightMouseDownEvent { position, .. }) => { if let Some(callback) = self.right_mouse_down.as_mut() { if visible_bounds.contains_point(*position) { return callback(cx); @@ -132,11 +133,11 @@ impl Element for EventHandler { } false } - Event::NavigateMouseDown { + Event::NavigateMouseDown(NavigateMouseDownEvent { position, direction, .. - } => { + }) => { if let Some(callback) = self.navigate_mouse_down.as_mut() { if visible_bounds.contains_point(*position) { return callback(*direction, cx); diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index 82a6299d10d07537cca26e179902e2b5b9af4f84..cb43c1db68e87112b7f991171c00e72119c757bd 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -3,7 +3,8 @@ use std::{any::Any, f32::INFINITY}; use crate::{ json::{self, ToJson, Value}, Axis, DebugContext, Element, ElementBox, ElementStateHandle, Event, EventContext, - LayoutContext, PaintContext, RenderContext, SizeConstraint, Vector2FExt, View, + LayoutContext, MouseMovedEvent, PaintContext, RenderContext, ScrollWheelEvent, SizeConstraint, + Vector2FExt, View, }; use pathfinder_geometry::{ rect::RectF, @@ -287,11 +288,11 @@ impl Element for Flex { handled = child.dispatch_event(event, cx) || handled; } if !handled { - if let &Event::ScrollWheel { + if let &Event::ScrollWheel(ScrollWheelEvent { position, delta, precise, - } = event + }) = event { if *remaining_space < 0. && bounds.contains_point(position) { if let Some(scroll_state) = self.scroll_state.as_ref() { @@ -321,7 +322,7 @@ impl Element for Flex { } if !handled { - if let &Event::MouseMoved { position, .. } = event { + if let &Event::MouseMoved(MouseMovedEvent { position, .. }) = event { // If this is a scrollable flex, and the mouse is over it, eat the scroll event to prevent // propogating it to the element below. if self.scroll_state.is_some() && bounds.contains_point(position) { diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 6479f2ee283dac64c6b815f47b16131f05ec8796..e368b45288ce5490bb16ad121943b3d3ea5c4ab2 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -5,7 +5,7 @@ use crate::{ }, json::json, DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext, - RenderContext, SizeConstraint, View, ViewContext, + RenderContext, ScrollWheelEvent, SizeConstraint, View, ViewContext, }; use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc}; use sum_tree::{Bias, SumTree}; @@ -311,11 +311,11 @@ impl Element for List { state.items = new_items; match event { - Event::ScrollWheel { + Event::ScrollWheel(ScrollWheelEvent { position, delta, precise, - } => { + }) => { if bounds.contains_point(*position) { if state.scroll(scroll_top, bounds.height(), *delta, *precise, cx) { handled = true; diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index de217a017c7699bddad0d991fadca9cf72ba02a9..9b2d966a7d6cc44b39fee390747fe7d28f4265b8 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -5,7 +5,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{self, json}, - ElementBox, RenderContext, View, + ElementBox, RenderContext, ScrollWheelEvent, View, }; use json::ToJson; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -310,11 +310,11 @@ impl Element for UniformList { } match event { - Event::ScrollWheel { + Event::ScrollWheel(ScrollWheelEvent { position, delta, precise, - } => { + }) => { if bounds.contains_point(*position) { if self.scroll(*position, *delta, *precise, layout.scroll_max, cx) { handled = true; diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 5a1fc2fe143a32a15c13e1bea92389ef9b0a7898..723e25c55d62a55951a013255992167bbe6676bf 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -28,8 +28,7 @@ pub mod json; pub mod keymap; pub mod platform; pub use gpui_macros::test; -pub use platform::FontSystem; -pub use platform::{Event, NavigationDirection, PathPromptOptions, Platform, PromptLevel}; +pub use platform::*; pub use presenter::{ Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt, }; diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index b4875df3f5708010172fc0447865fca7116faef5..cf508a5634a4341ee65573f6613fb93087d2bb6d 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -20,7 +20,7 @@ use crate::{ }; use anyhow::{anyhow, Result}; use async_task::Runnable; -pub use event::{Event, NavigationDirection}; +pub use event::*; use postage::oneshot; use serde::Deserialize; use std::{ diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index f43d5bea49796b7dad579af8a0e4c137b9ddc6fe..983468343379c1bbae9db40add0ad154266fe70e 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -6,80 +6,116 @@ pub enum NavigationDirection { Forward, } +#[derive(Clone, Debug)] +pub struct KeyDownEvent { + pub keystroke: Keystroke, + pub input: Option, + pub is_held: bool, +} + +#[derive(Clone, Debug)] +pub struct KeyUpEvent { + pub keystroke: Keystroke, + pub input: Option, +} + +#[derive(Clone, Debug)] +pub struct ModifiersChangedEvent { + pub ctrl: bool, + pub alt: bool, + pub shift: bool, + pub cmd: bool, +} + +#[derive(Clone, Debug)] +pub struct ScrollWheelEvent { + pub position: Vector2F, + pub delta: Vector2F, + pub precise: bool, +} + +#[derive(Clone, Debug)] +pub struct LeftMouseDownEvent { + pub position: Vector2F, + pub ctrl: bool, + pub alt: bool, + pub shift: bool, + pub cmd: bool, + pub click_count: usize, +} + +#[derive(Clone, Debug)] +pub struct LeftMouseUpEvent { + pub position: Vector2F, + pub click_count: usize, +} + +#[derive(Clone, Debug)] +pub struct LeftMouseDraggedEvent { + pub position: Vector2F, + pub ctrl: bool, + pub alt: bool, + pub shift: bool, + pub cmd: bool, +} + +#[derive(Clone, Debug)] +pub struct RightMouseDownEvent { + pub position: Vector2F, + pub ctrl: bool, + pub alt: bool, + pub shift: bool, + pub cmd: bool, + pub click_count: usize, +} + +#[derive(Clone, Debug)] +pub struct RightMouseUpEvent { + pub position: Vector2F, + pub click_count: usize, +} + +#[derive(Clone, Debug)] +pub struct NavigateMouseDownEvent { + pub position: Vector2F, + pub direction: NavigationDirection, + pub ctrl: bool, + pub alt: bool, + pub shift: bool, + pub cmd: bool, + pub click_count: usize, +} + +#[derive(Clone, Debug)] +pub struct NavigateMouseUpEvent { + pub position: Vector2F, + pub direction: NavigationDirection, +} + +#[derive(Clone, Debug)] +pub struct MouseMovedEvent { + pub position: Vector2F, + pub left_mouse_down: bool, + pub ctrl: bool, + pub cmd: bool, + pub alt: bool, + pub shift: bool, +} + #[derive(Clone, Debug)] pub enum Event { - KeyDown { - keystroke: Keystroke, - input: Option, - is_held: bool, - }, - KeyUp { - keystroke: Keystroke, - input: Option, - }, - ModifiersChanged { - ctrl: bool, - alt: bool, - shift: bool, - cmd: bool, - }, - ScrollWheel { - position: Vector2F, - delta: Vector2F, - precise: bool, - }, - LeftMouseDown { - position: Vector2F, - ctrl: bool, - alt: bool, - shift: bool, - cmd: bool, - click_count: usize, - }, - LeftMouseUp { - position: Vector2F, - click_count: usize, - }, - LeftMouseDragged { - position: Vector2F, - ctrl: bool, - alt: bool, - shift: bool, - cmd: bool, - }, - RightMouseDown { - position: Vector2F, - ctrl: bool, - alt: bool, - shift: bool, - cmd: bool, - click_count: usize, - }, - RightMouseUp { - position: Vector2F, - click_count: usize, - }, - NavigateMouseDown { - position: Vector2F, - direction: NavigationDirection, - ctrl: bool, - alt: bool, - shift: bool, - cmd: bool, - click_count: usize, - }, - NavigateMouseUp { - position: Vector2F, - direction: NavigationDirection, - }, - MouseMoved { - position: Vector2F, - left_mouse_down: bool, - ctrl: bool, - cmd: bool, - alt: bool, - shift: bool, - }, + KeyDown(KeyDownEvent), + KeyUp(KeyUpEvent), + ModifiersChanged(ModifiersChangedEvent), + ScrollWheel(ScrollWheelEvent), + LeftMouseDown(LeftMouseDownEvent), + LeftMouseUp(LeftMouseUpEvent), + LeftMouseDragged(LeftMouseDraggedEvent), + RightMouseDown(RightMouseDownEvent), + RightMouseUp(RightMouseUpEvent), + NavigateMouseDown(NavigateMouseDownEvent), + NavigateMouseUp(NavigateMouseUpEvent), + MouseMoved(MouseMovedEvent), } impl Event { @@ -88,15 +124,15 @@ impl Event { Event::KeyDown { .. } => None, Event::KeyUp { .. } => None, Event::ModifiersChanged { .. } => None, - Event::ScrollWheel { position, .. } - | Event::LeftMouseDown { position, .. } - | Event::LeftMouseUp { position, .. } - | Event::LeftMouseDragged { position, .. } - | Event::RightMouseDown { position, .. } - | Event::RightMouseUp { position, .. } - | Event::NavigateMouseDown { position, .. } - | Event::NavigateMouseUp { position, .. } - | Event::MouseMoved { position, .. } => Some(*position), + Event::ScrollWheel(ScrollWheelEvent { position, .. }) + | Event::LeftMouseDown(LeftMouseDownEvent { position, .. }) + | Event::LeftMouseUp(LeftMouseUpEvent { position, .. }) + | Event::LeftMouseDragged(LeftMouseDraggedEvent { position, .. }) + | Event::RightMouseDown(RightMouseDownEvent { position, .. }) + | Event::RightMouseUp(RightMouseUpEvent { position, .. }) + | Event::NavigateMouseDown(NavigateMouseDownEvent { position, .. }) + | Event::NavigateMouseUp(NavigateMouseUpEvent { position, .. }) + | Event::MouseMoved(MouseMovedEvent { position, .. }) => Some(*position), } } } diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index ed880513e823a291e81b7d5cbf8a33418ed99c89..2af0922de0720101e7638f263869615c47f8feb4 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -2,6 +2,9 @@ use crate::{ geometry::vector::vec2f, keymap::Keystroke, platform::{Event, NavigationDirection}, + KeyDownEvent, KeyUpEvent, LeftMouseDownEvent, LeftMouseDraggedEvent, LeftMouseUpEvent, + ModifiersChangedEvent, MouseMovedEvent, NavigateMouseDownEvent, NavigateMouseUpEvent, + RightMouseDownEvent, RightMouseUpEvent, ScrollWheelEvent, }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventType}, @@ -59,12 +62,12 @@ impl Event { let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask); let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask); - Some(Self::ModifiersChanged { + Some(Self::ModifiersChanged(ModifiersChangedEvent { ctrl, alt, shift, cmd, - }) + })) } NSEventType::NSKeyDown => { let modifiers = native_event.modifierFlags(); @@ -76,7 +79,7 @@ impl Event { let (unmodified_chars, input) = get_key_text(native_event, cmd, ctrl, function)?; - Some(Self::KeyDown { + Some(Self::KeyDown(KeyDownEvent { keystroke: Keystroke { ctrl, alt, @@ -86,7 +89,7 @@ impl Event { }, input, is_held: native_event.isARepeat() == YES, - }) + })) } NSEventType::NSKeyUp => { let modifiers = native_event.modifierFlags(); @@ -98,7 +101,7 @@ impl Event { let (unmodified_chars, input) = get_key_text(native_event, cmd, ctrl, function)?; - Some(Self::KeyUp { + Some(Self::KeyUp(KeyUpEvent { keystroke: Keystroke { ctrl, alt, @@ -107,49 +110,57 @@ impl Event { key: unmodified_chars.into(), }, input, - }) + })) } NSEventType::NSLeftMouseDown => { let modifiers = native_event.modifierFlags(); - window_height.map(|window_height| Self::LeftMouseDown { + window_height.map(|window_height| { + Self::LeftMouseDown(LeftMouseDownEvent { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), + click_count: native_event.clickCount() as usize, + }) + }) + } + NSEventType::NSLeftMouseUp => window_height.map(|window_height| { + Self::LeftMouseUp(LeftMouseUpEvent { position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), - ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), - alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), - shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), - cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), click_count: native_event.clickCount() as usize, }) - } - NSEventType::NSLeftMouseUp => window_height.map(|window_height| Self::LeftMouseUp { - position: vec2f( - native_event.locationInWindow().x as f32, - window_height - native_event.locationInWindow().y as f32, - ), - click_count: native_event.clickCount() as usize, }), NSEventType::NSRightMouseDown => { let modifiers = native_event.modifierFlags(); - window_height.map(|window_height| Self::RightMouseDown { + window_height.map(|window_height| { + Self::RightMouseDown(RightMouseDownEvent { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), + click_count: native_event.clickCount() as usize, + }) + }) + } + NSEventType::NSRightMouseUp => window_height.map(|window_height| { + Self::RightMouseUp(RightMouseUpEvent { position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), - ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), - alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), - shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), - cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), click_count: native_event.clickCount() as usize, }) - } - NSEventType::NSRightMouseUp => window_height.map(|window_height| Self::RightMouseUp { - position: vec2f( - native_event.locationInWindow().x as f32, - window_height - native_event.locationInWindow().y as f32, - ), - click_count: native_event.clickCount() as usize, }), NSEventType::NSOtherMouseDown => { let direction = match native_event.buttonNumber() { @@ -160,17 +171,19 @@ impl Event { }; let modifiers = native_event.modifierFlags(); - window_height.map(|window_height| Self::NavigateMouseDown { - position: vec2f( - native_event.locationInWindow().x as f32, - window_height - native_event.locationInWindow().y as f32, - ), - direction, - ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), - alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), - shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), - cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), - click_count: native_event.clickCount() as usize, + window_height.map(|window_height| { + Self::NavigateMouseDown(NavigateMouseDownEvent { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + direction, + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), + click_count: native_event.clickCount() as usize, + }) }) } NSEventType::NSOtherMouseUp => { @@ -181,17 +194,19 @@ impl Event { _ => return None, }; - window_height.map(|window_height| Self::NavigateMouseUp { - position: vec2f( - native_event.locationInWindow().x as f32, - window_height - native_event.locationInWindow().y as f32, - ), - direction, + window_height.map(|window_height| { + Self::NavigateMouseUp(NavigateMouseUpEvent { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + direction, + }) }) } NSEventType::NSLeftMouseDragged => window_height.map(|window_height| { let modifiers = native_event.modifierFlags(); - Self::LeftMouseDragged { + Self::LeftMouseDragged(LeftMouseDraggedEvent { position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, @@ -200,22 +215,24 @@ impl Event { alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), - } + }) }), - NSEventType::NSScrollWheel => window_height.map(|window_height| Self::ScrollWheel { - position: vec2f( - native_event.locationInWindow().x as f32, - window_height - native_event.locationInWindow().y as f32, - ), - delta: vec2f( - native_event.scrollingDeltaX() as f32, - native_event.scrollingDeltaY() as f32, - ), - precise: native_event.hasPreciseScrollingDeltas() == YES, + NSEventType::NSScrollWheel => window_height.map(|window_height| { + Self::ScrollWheel(ScrollWheelEvent { + position: vec2f( + native_event.locationInWindow().x as f32, + window_height - native_event.locationInWindow().y as f32, + ), + delta: vec2f( + native_event.scrollingDeltaX() as f32, + native_event.scrollingDeltaY() as f32, + ), + precise: native_event.hasPreciseScrollingDeltas() == YES, + }) }), NSEventType::NSMouseMoved => window_height.map(|window_height| { let modifiers = native_event.modifierFlags(); - Self::MouseMoved { + Self::MouseMoved(MouseMovedEvent { position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, @@ -225,7 +242,7 @@ impl Event { alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), - } + }) }), _ => None, } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index c845693ba996af356a04e5de77c93cbe4639944d..3d9b887c672081f36b22d687db0d6a36dfb3538c 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -6,7 +6,7 @@ use crate::{ }, keymap::Keystroke, platform::{self, Event, WindowBounds, WindowContext}, - Scene, + KeyDownEvent, LeftMouseDraggedEvent, ModifiersChangedEvent, Scene, }; use block::ConcreteBlock; use cocoa::{ @@ -562,11 +562,11 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) }; if let Some(event) = event { match &event { - Event::KeyDown { + Event::KeyDown(KeyDownEvent { keystroke, input, is_held, - } => { + }) => { let keydown = (keystroke.clone(), input.clone()); // Ignore events from held-down keys after some of the initially-pressed keys // were released. @@ -603,7 +603,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { if let Some(event) = event { match &event { - Event::LeftMouseDragged { position, .. } => { + Event::LeftMouseDragged(LeftMouseDraggedEvent { position, .. }) => { window_state_borrow.synthetic_drag_counter += 1; window_state_borrow .executor @@ -617,19 +617,19 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { Event::LeftMouseUp { .. } => { window_state_borrow.synthetic_drag_counter += 1; } - Event::ModifiersChanged { + Event::ModifiersChanged(ModifiersChangedEvent { ctrl, alt, shift, cmd, - } => { + }) => { // Only raise modifiers changed event when they have actually changed - if let Some(Event::ModifiersChanged { + if let Some(Event::ModifiersChanged(ModifiersChangedEvent { ctrl: prev_ctrl, alt: prev_alt, shift: prev_shift, cmd: prev_cmd, - }) = &window_state_borrow.previous_modifiers_changed_event + })) = &window_state_borrow.previous_modifiers_changed_event { if prev_ctrl == ctrl && prev_alt == alt @@ -667,11 +667,11 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { shift: false, key: chars.clone(), }; - let event = Event::KeyDown { + let event = Event::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), input: Some(chars.clone()), is_held: false, - }; + }); window_state_borrow.last_fresh_keydown = Some((keystroke, Some(chars))); if let Some(mut callback) = window_state_borrow.event_callback.take() { @@ -844,14 +844,14 @@ async fn synthetic_drag( if window_state_borrow.synthetic_drag_counter == drag_id { if let Some(mut callback) = window_state_borrow.event_callback.take() { drop(window_state_borrow); - callback(Event::LeftMouseDragged { + callback(Event::LeftMouseDragged(LeftMouseDraggedEvent { // TODO: Make sure empty modifiers is correct for this position, shift: false, ctrl: false, alt: false, cmd: false, - }); + })); window_state.borrow_mut().event_callback = Some(callback); } } else { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 6285b1be9931b30ada4d4673681ce335398bf252..015b44b97b3dbe77d73d8b2534dd4ad671171ab6 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -9,9 +9,10 @@ use crate::{ scene::CursorRegion, text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity, - FontSystem, ModelHandle, MouseRegion, MouseRegionId, ReadModel, ReadView, RenderContext, - RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle, - WeakViewHandle, + FontSystem, LeftMouseDownEvent, LeftMouseDraggedEvent, LeftMouseUpEvent, ModelHandle, + MouseMovedEvent, MouseRegion, MouseRegionId, ReadModel, ReadView, RenderContext, RenderParams, + RightMouseDownEvent, RightMouseUpEvent, Scene, UpgradeModelHandle, UpgradeViewHandle, View, + ViewHandle, WeakModelHandle, WeakViewHandle, }; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; @@ -235,7 +236,7 @@ impl Presenter { let mut dragged_region = None; match event { - Event::LeftMouseDown { position, .. } => { + Event::LeftMouseDown(LeftMouseDownEvent { position, .. }) => { let mut hit = false; for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(position) { @@ -251,11 +252,11 @@ impl Presenter { } } } - Event::LeftMouseUp { + Event::LeftMouseUp(LeftMouseUpEvent { position, click_count, .. - } => { + }) => { self.prev_drag_position.take(); if let Some(region) = self.clicked_region.take() { invalidated_views.push(region.view_id); @@ -264,7 +265,7 @@ impl Presenter { } } } - Event::RightMouseDown { position, .. } => { + Event::RightMouseDown(RightMouseDownEvent { position, .. }) => { let mut hit = false; for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(position) { @@ -279,11 +280,11 @@ impl Presenter { } } } - Event::RightMouseUp { + Event::RightMouseUp(RightMouseUpEvent { position, click_count, .. - } => { + }) => { if let Some(region) = self.right_clicked_region.take() { invalidated_views.push(region.view_id); if region.bounds.contains_point(position) { @@ -294,13 +295,13 @@ impl Presenter { Event::MouseMoved { .. } => { self.last_mouse_moved_event = Some(event.clone()); } - Event::LeftMouseDragged { + Event::LeftMouseDragged(LeftMouseDraggedEvent { position, shift, ctrl, alt, cmd, - } => { + }) => { if let Some((clicked_region, prev_drag_position)) = self .clicked_region .as_ref() @@ -311,14 +312,14 @@ impl Presenter { *prev_drag_position = position; } - self.last_mouse_moved_event = Some(Event::MouseMoved { + self.last_mouse_moved_event = Some(Event::MouseMoved(MouseMovedEvent { position, left_mouse_down: true, shift, ctrl, alt, cmd, - }); + })); } _ => {} } @@ -410,11 +411,11 @@ impl Presenter { let mut unhovered_regions = Vec::new(); let mut hovered_regions = Vec::new(); - if let Event::MouseMoved { + if let Event::MouseMoved(MouseMovedEvent { position, left_mouse_down, .. - } = event + }) = event { if !left_mouse_down { let mut style_to_assign = CursorStyle::Arrow; diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 408fb0dcecc35c844e78ad4240e48bdbd657662f..ecbd94f640d08bc9555a97ddae5b0e511cde726f 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -18,8 +18,8 @@ use gpui::{ }, json::json, text_layout::{Line, RunStyle}, - Event, FontCache, MouseRegion, PaintContext, Quad, SizeConstraint, TextLayoutCache, - WeakViewHandle, + Event, FontCache, KeyDownEvent, MouseRegion, PaintContext, Quad, ScrollWheelEvent, + SizeConstraint, TextLayoutCache, WeakViewHandle, }; use itertools::Itertools; use ordered_float::OrderedFloat; @@ -276,9 +276,9 @@ impl Element for TerminalEl { cx: &mut gpui::EventContext, ) -> bool { match event { - Event::ScrollWheel { + Event::ScrollWheel(ScrollWheelEvent { delta, position, .. - } => visible_bounds + }) => visible_bounds .contains_point(*position) .then(|| { let vertical_scroll = @@ -286,9 +286,9 @@ impl Element for TerminalEl { cx.dispatch_action(ScrollTerminal(vertical_scroll.round() as i32)); }) .is_some(), - Event::KeyDown { + Event::KeyDown(KeyDownEvent { input: Some(input), .. - } => cx + }) => cx .is_parent_view_focused() .then(|| { cx.dispatch_action(Input(input.to_string())); From a378ec49ec8e4bd0a8ed08f32a35d4f830f16730 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 6 Jul 2022 15:43:42 -0700 Subject: [PATCH 06/47] Enable and disable nav buttons based on pane's navigation stack Also, make the `NavHistory` type private to the `workspace` crate. Expose only the `ItemNavHistory` type, via a method on Pane called `nav_history_for_item`. --- crates/editor/src/editor.rs | 57 +++++++------ crates/theme/src/theme.rs | 29 +++---- crates/workspace/src/pane.rs | 128 ++++++++++++++++++++---------- crates/workspace/src/toolbar.rs | 23 +++++- crates/workspace/src/workspace.rs | 10 +-- styles/src/styleTree/workspace.ts | 6 +- 6 files changed, 158 insertions(+), 95 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9dd40413fd254e7ef852643acfdb5cbe464ee415..c1e25575557bef78921841f1c2a9eb8e589085a2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4065,13 +4065,16 @@ impl Editor { } } - nav_history.push(Some(NavigationData { - cursor_anchor: position, - cursor_position: point, - scroll_position: self.scroll_position, - scroll_top_anchor: self.scroll_top_anchor.clone(), - scroll_top_row, - })); + nav_history.push( + Some(NavigationData { + cursor_anchor: position, + cursor_position: point, + scroll_position: self.scroll_position, + scroll_top_anchor: self.scroll_top_anchor.clone(), + scroll_top_row, + }), + cx, + ); } } @@ -4669,7 +4672,7 @@ impl Editor { definitions: Vec, cx: &mut ViewContext, ) { - let nav_history = workspace.active_pane().read(cx).nav_history().clone(); + let pane = workspace.active_pane().clone(); for definition in definitions { let range = definition .target @@ -4681,13 +4684,13 @@ impl Editor { // When selecting a definition in a different buffer, disable the nav history // to avoid creating a history entry at the previous cursor location. if editor_handle != target_editor_handle { - nav_history.borrow_mut().disable(); + pane.update(cx, |pane, _| pane.disable_history()); } target_editor.change_selections(Some(Autoscroll::Center), cx, |s| { s.select_ranges([range]); }); - nav_history.borrow_mut().enable(); + pane.update(cx, |pane, _| pane.enable_history()); }); } } @@ -5641,8 +5644,8 @@ impl Editor { editor_handle.update(cx, |editor, cx| { editor.push_to_nav_history(editor.selections.newest_anchor().head(), None, cx); }); - let nav_history = workspace.active_pane().read(cx).nav_history().clone(); - nav_history.borrow_mut().disable(); + let pane = workspace.active_pane().clone(); + pane.update(cx, |pane, _| pane.disable_history()); // We defer the pane interaction because we ourselves are a workspace item // and activating a new item causes the pane to call a method on us reentrantly, @@ -5657,7 +5660,7 @@ impl Editor { }); } - nav_history.borrow_mut().enable(); + pane.update(cx, |pane, _| pane.enable_history()); }); } @@ -6241,7 +6244,7 @@ mod tests { assert_set_eq, test::{marked_text_by, marked_text_ranges, marked_text_ranges_by, sample_text}, }; - use workspace::{FollowableItem, ItemHandle}; + use workspace::{FollowableItem, ItemHandle, NavigationEntry, Pane}; #[gpui::test] fn test_edit_events(cx: &mut MutableAppContext) { @@ -6589,12 +6592,20 @@ mod tests { fn test_navigation_history(cx: &mut gpui::MutableAppContext) { cx.set_global(Settings::test(cx)); use workspace::Item; - let nav_history = Rc::new(RefCell::new(workspace::NavHistory::default())); + let pane = cx.add_view(Default::default(), |cx| Pane::new(cx)); let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx); cx.add_window(Default::default(), |cx| { let mut editor = build_editor(buffer.clone(), cx); - editor.nav_history = Some(ItemNavHistory::new(nav_history.clone(), &cx.handle())); + let handle = cx.handle(); + editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle))); + + fn pop_history( + editor: &mut Editor, + cx: &mut MutableAppContext, + ) -> Option { + editor.nav_history.as_mut().unwrap().pop_backward(cx) + } // Move the cursor a small distance. // Nothing is added to the navigation history. @@ -6604,21 +6615,21 @@ mod tests { editor.change_selections(None, cx, |s| { s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]) }); - assert!(nav_history.borrow_mut().pop_backward().is_none()); + assert!(pop_history(&mut editor, cx).is_none()); // Move the cursor a large distance. // The history can jump back to the previous position. editor.change_selections(None, cx, |s| { s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)]) }); - let nav_entry = nav_history.borrow_mut().pop_backward().unwrap(); + let nav_entry = pop_history(&mut editor, cx).unwrap(); editor.navigate(nav_entry.data.unwrap(), cx); assert_eq!(nav_entry.item.id(), cx.view_id()); assert_eq!( editor.selections.display_ranges(cx), &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)] ); - assert!(nav_history.borrow_mut().pop_backward().is_none()); + assert!(pop_history(&mut editor, cx).is_none()); // Move the cursor a small distance via the mouse. // Nothing is added to the navigation history. @@ -6628,7 +6639,7 @@ mod tests { editor.selections.display_ranges(cx), &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)] ); - assert!(nav_history.borrow_mut().pop_backward().is_none()); + assert!(pop_history(&mut editor, cx).is_none()); // Move the cursor a large distance via the mouse. // The history can jump back to the previous position. @@ -6638,14 +6649,14 @@ mod tests { editor.selections.display_ranges(cx), &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)] ); - let nav_entry = nav_history.borrow_mut().pop_backward().unwrap(); + let nav_entry = pop_history(&mut editor, cx).unwrap(); editor.navigate(nav_entry.data.unwrap(), cx); assert_eq!(nav_entry.item.id(), cx.view_id()); assert_eq!( editor.selections.display_ranges(cx), &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)] ); - assert!(nav_history.borrow_mut().pop_backward().is_none()); + assert!(pop_history(&mut editor, cx).is_none()); // Set scroll position to check later editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx); @@ -6658,7 +6669,7 @@ mod tests { assert_ne!(editor.scroll_position, original_scroll_position); assert_ne!(editor.scroll_top_anchor, original_scroll_top_anchor); - let nav_entry = nav_history.borrow_mut().pop_backward().unwrap(); + let nav_entry = pop_history(&mut editor, cx).unwrap(); editor.navigate(nav_entry.data.unwrap(), cx); assert_eq!(editor.scroll_position, original_scroll_position); assert_eq!(editor.scroll_top_anchor, original_scroll_top_anchor); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c3052ff6c5afc0a7a5acde8135f555e7113ab6c1..058dd5a331729c2ab84dcdd5d41f7349768ce48c 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -510,28 +510,23 @@ pub struct Interactive { pub default: T, pub hover: Option, pub active: Option, - pub active_hover: Option, + pub disabled: Option, } impl Interactive { pub fn style_for(&self, state: MouseState, active: bool) -> &T { if active { - if state.hovered { - self.active_hover - .as_ref() - .or(self.active.as_ref()) - .unwrap_or(&self.default) - } else { - self.active.as_ref().unwrap_or(&self.default) - } + self.active.as_ref().unwrap_or(&self.default) + } else if state.hovered { + self.hover.as_ref().unwrap_or(&self.default) } else { - if state.hovered { - self.hover.as_ref().unwrap_or(&self.default) - } else { - &self.default - } + &self.default } } + + pub fn disabled_style(&self) -> &T { + self.disabled.as_ref().unwrap_or(&self.default) + } } impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive { @@ -545,7 +540,7 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive { default: Value, hover: Option, active: Option, - active_hover: Option, + disabled: Option, } let json = Helper::deserialize(deserializer)?; @@ -571,14 +566,14 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive { let hover = deserialize_state(json.hover)?; let active = deserialize_state(json.active)?; - let active_hover = deserialize_state(json.active_hover)?; + let disabled = deserialize_state(json.disabled)?; let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?; Ok(Interactive { default, hover, active, - active_hover, + disabled, }) } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 0fb5225cc533266753c4b84acf225373ce0c6cf0..391ddfc1f7a6d0c9e6a727622d925b3d40ead78b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -136,13 +136,13 @@ pub struct ItemNavHistory { item: Rc, } -#[derive(Default)] -pub struct NavHistory { +struct NavHistory { mode: NavigationMode, backward_stack: VecDeque, forward_stack: VecDeque, closed_stack: VecDeque, paths_by_item: HashMap, + pane: WeakViewHandle, } #[derive(Copy, Clone)] @@ -173,13 +173,23 @@ impl Pane { items: Vec::new(), active_item_index: 0, autoscroll: false, - nav_history: Default::default(), + nav_history: Rc::new(RefCell::new(NavHistory { + mode: NavigationMode::Normal, + backward_stack: Default::default(), + forward_stack: Default::default(), + closed_stack: Default::default(), + paths_by_item: Default::default(), + pane: handle.clone(), + })), toolbar: cx.add_view(|_| Toolbar::new(handle)), } } - pub fn nav_history(&self) -> &Rc> { - &self.nav_history + pub fn nav_history_for_item(&self, item: &ViewHandle) -> ItemNavHistory { + ItemNavHistory { + history: self.nav_history.clone(), + item: Rc::new(item.downgrade()), + } } pub fn activate(&self, cx: &mut ViewContext) { @@ -224,6 +234,26 @@ impl Pane { ) } + pub fn disable_history(&mut self) { + self.nav_history.borrow_mut().disable(); + } + + pub fn enable_history(&mut self) { + self.nav_history.borrow_mut().enable(); + } + + pub fn can_navigate_backward(&self) -> bool { + !self.nav_history.borrow().backward_stack.is_empty() + } + + pub fn can_navigate_forward(&self) -> bool { + !self.nav_history.borrow().forward_stack.is_empty() + } + + fn history_updated(&mut self, cx: &mut ViewContext) { + self.toolbar.update(cx, |_, cx| cx.notify()); + } + fn navigate_history( workspace: &mut Workspace, pane: ViewHandle, @@ -235,7 +265,7 @@ impl Pane { let to_load = pane.update(cx, |pane, cx| { loop { // Retrieve the weak item handle from the history. - let entry = pane.nav_history.borrow_mut().pop(mode)?; + let entry = pane.nav_history.borrow_mut().pop(mode, cx)?; // If the item is still present in this pane, then activate it. if let Some(index) = entry @@ -368,7 +398,6 @@ impl Pane { return; } - item.set_nav_history(pane.read(cx).nav_history.clone(), cx); item.added_to_pane(workspace, pane.clone(), cx); pane.update(cx, |pane, cx| { // If there is already an active item, then insert the new item @@ -626,11 +655,16 @@ impl Pane { .borrow_mut() .set_mode(NavigationMode::Normal); - let mut nav_history = pane.nav_history().borrow_mut(); if let Some(path) = item.project_path(cx) { - nav_history.paths_by_item.insert(item.id(), path); + pane.nav_history + .borrow_mut() + .paths_by_item + .insert(item.id(), path); } else { - nav_history.paths_by_item.remove(&item.id()); + pane.nav_history + .borrow_mut() + .paths_by_item + .remove(&item.id()); } } }); @@ -954,57 +988,56 @@ impl View for Pane { } impl ItemNavHistory { - pub fn new(history: Rc>, item: &ViewHandle) -> Self { - Self { - history, - item: Rc::new(item.downgrade()), - } + pub fn push(&self, data: Option, cx: &mut MutableAppContext) { + self.history.borrow_mut().push(data, self.item.clone(), cx); } - pub fn history(&self) -> Rc> { - self.history.clone() + pub fn pop_backward(&self, cx: &mut MutableAppContext) -> Option { + self.history.borrow_mut().pop(NavigationMode::GoingBack, cx) } - pub fn push(&self, data: Option) { - self.history.borrow_mut().push(data, self.item.clone()); + pub fn pop_forward(&self, cx: &mut MutableAppContext) -> Option { + self.history + .borrow_mut() + .pop(NavigationMode::GoingForward, cx) } } impl NavHistory { - pub fn disable(&mut self) { - self.mode = NavigationMode::Disabled; - } - - pub fn enable(&mut self) { - self.mode = NavigationMode::Normal; - } - - pub fn pop_backward(&mut self) -> Option { - self.backward_stack.pop_back() + fn set_mode(&mut self, mode: NavigationMode) { + self.mode = mode; } - pub fn pop_forward(&mut self) -> Option { - self.forward_stack.pop_back() + fn disable(&mut self) { + self.mode = NavigationMode::Disabled; } - pub fn pop_closed(&mut self) -> Option { - self.closed_stack.pop_back() + fn enable(&mut self) { + self.mode = NavigationMode::Normal; } - fn pop(&mut self, mode: NavigationMode) -> Option { - match mode { - NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => None, - NavigationMode::GoingBack => self.pop_backward(), - NavigationMode::GoingForward => self.pop_forward(), - NavigationMode::ReopeningClosedItem => self.pop_closed(), + fn pop(&mut self, mode: NavigationMode, cx: &mut MutableAppContext) -> Option { + let entry = match mode { + NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => { + return None + } + NavigationMode::GoingBack => &mut self.backward_stack, + NavigationMode::GoingForward => &mut self.forward_stack, + NavigationMode::ReopeningClosedItem => &mut self.closed_stack, } + .pop_back(); + if entry.is_some() { + self.did_update(cx); + } + entry } - fn set_mode(&mut self, mode: NavigationMode) { - self.mode = mode; - } - - pub fn push(&mut self, data: Option, item: Rc) { + fn push( + &mut self, + data: Option, + item: Rc, + cx: &mut MutableAppContext, + ) { match self.mode { NavigationMode::Disabled => {} NavigationMode::Normal | NavigationMode::ReopeningClosedItem => { @@ -1045,5 +1078,12 @@ impl NavHistory { }); } } + self.did_update(cx); + } + + fn did_update(&self, cx: &mut MutableAppContext) { + if let Some(pane) = self.pane.upgrade(cx) { + cx.defer(move |cx| pane.update(cx, |pane, cx| pane.history_updated(cx))); + } } } diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index 9d8274288be5b6e5a4d8a8a26287da028f347768..7525c0413d7097d7b4e2e1f338835737ba2eab25 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -101,6 +101,14 @@ impl View for Toolbar { } let pane = self.pane.clone(); + let mut enable_go_backward = false; + let mut enable_go_forward = false; + if let Some(pane) = pane.upgrade(cx) { + let pane = pane.read(cx); + enable_go_backward = pane.can_navigate_backward(); + enable_go_forward = pane.can_navigate_forward(); + } + let container_style = theme.container; let height = theme.height; let button_style = theme.nav_button; @@ -111,6 +119,7 @@ impl View for Toolbar { .with_child(nav_button( "icons/arrow-left.svg", button_style, + enable_go_backward, spacing, super::GoBack { pane: Some(pane.clone()), @@ -120,6 +129,7 @@ impl View for Toolbar { .with_child(nav_button( "icons/arrow-right.svg", button_style, + enable_go_forward, spacing, super::GoForward { pane: Some(pane.clone()), @@ -142,12 +152,17 @@ impl View for Toolbar { fn nav_button( svg_path: &'static str, style: theme::Interactive, + enabled: bool, spacing: f32, action: A, cx: &mut RenderContext, ) -> ElementBox { MouseEventHandler::new::(0, cx, |state, _| { - let style = style.style_for(state, false); + let style = if enabled { + style.style_for(state, false) + } else { + style.disabled_style() + }; Svg::new(svg_path) .with_color(style.color) .constrained() @@ -160,7 +175,11 @@ fn nav_button( .with_height(style.button_width) .boxed() }) - .with_cursor_style(CursorStyle::PointingHand) + .with_cursor_style(if enabled { + CursorStyle::PointingHand + } else { + CursorStyle::default() + }) .on_mouse_down(move |_, cx| cx.dispatch_action(action.clone())) .contained() .with_margin_right(spacing) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f7d3032eabd33b327523af15603f642d8f3234d0..2ba1b4d008400d2429afac0874c164e296c673ef 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -414,7 +414,6 @@ pub trait ItemHandle: 'static + fmt::Debug { fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; fn is_singleton(&self, cx: &AppContext) -> bool; fn boxed_clone(&self) -> Box; - fn set_nav_history(&self, nav_history: Rc>, cx: &mut MutableAppContext); fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; fn added_to_pane( &self, @@ -484,12 +483,6 @@ impl ItemHandle for ViewHandle { Box::new(self.clone()) } - fn set_nav_history(&self, nav_history: Rc>, cx: &mut MutableAppContext) { - self.update(cx, |item, cx| { - item.set_nav_history(ItemNavHistory::new(nav_history, &cx.handle()), cx); - }) - } - fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option> { self.update(cx, |item, cx| { cx.add_option_view(|cx| item.clone_on_split(cx)) @@ -503,6 +496,9 @@ impl ItemHandle for ViewHandle { pane: ViewHandle, cx: &mut ViewContext, ) { + let history = pane.read(cx).nav_history_for_item(self); + self.update(cx, |this, cx| this.set_nav_history(history, cx)); + if let Some(followed_item) = self.to_followable_item_handle(cx) { if let Some(message) = followed_item.to_state_proto(cx) { workspace.update_followers( diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index ba3978fc74e1ef93d956cd30beb8d64c9f89df8b..0b71453e7148c71a4664c135c0b0f6926759d1db 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -140,13 +140,15 @@ export default function workspace(theme: Theme) { border: border(theme, "secondary", { bottom: true }), itemSpacing: 8, navButton: { - color: iconColor(theme, "secondary"), + color: iconColor(theme, "primary"), iconWidth: 8, buttonWidth: 12, - margin: { left: 8, right: 8 }, hover: { color: iconColor(theme, "active"), }, + disabled: { + color: iconColor(theme, "muted") + } }, padding: { left: 16, right: 8, top: 4, bottom: 4 }, }, From 4e8dbbfd4b637fa2ad114f42298584643cc31637 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 6 Jul 2022 16:29:11 -0700 Subject: [PATCH 07/47] Add test for pane nav history covering notification of pane's toolbar --- crates/workspace/src/workspace.rs | 113 +++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2ba1b4d008400d2429afac0874c164e296c673ef..fdfa640718fdd066f801a81142c54e7a99c9c9b3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3142,25 +3142,104 @@ mod tests { item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); } - #[derive(Clone)] + #[gpui::test] + async fn test_pane_navigation( + deterministic: Arc, + cx: &mut gpui::TestAppContext, + ) { + deterministic.forbid_parking(); + Settings::test_async(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, [], cx).await; + let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + + let item = cx.add_view(window_id, |_| { + let mut item = TestItem::new(); + item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; + item + }); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone()); + let toolbar_notify_count = Rc::new(RefCell::new(0)); + + workspace.update(cx, |workspace, cx| { + workspace.add_item(Box::new(item.clone()), cx); + let toolbar_notification_count = toolbar_notify_count.clone(); + cx.observe(&toolbar, move |_, _, _| { + *toolbar_notification_count.borrow_mut() += 1 + }) + .detach(); + }); + + pane.read_with(cx, |pane, _| { + assert!(!pane.can_navigate_backward()); + assert!(!pane.can_navigate_forward()); + }); + + item.update(cx, |item, cx| { + item.set_state("one".to_string(), cx); + }); + + // Toolbar must be notified to re-render the navigation buttons + assert_eq!(*toolbar_notify_count.borrow(), 1); + + pane.read_with(cx, |pane, _| { + assert!(pane.can_navigate_backward()); + assert!(!pane.can_navigate_forward()); + }); + + workspace + .update(cx, |workspace, cx| { + Pane::go_back(workspace, Some(pane.clone()), cx) + }) + .await; + + assert_eq!(*toolbar_notify_count.borrow(), 3); + pane.read_with(cx, |pane, _| { + assert!(!pane.can_navigate_backward()); + assert!(pane.can_navigate_forward()); + }); + } + struct TestItem { + state: String, save_count: usize, save_as_count: usize, reload_count: usize, is_dirty: bool, + is_singleton: bool, has_conflict: bool, project_entry_ids: Vec, project_path: Option, - is_singleton: bool, + nav_history: Option, } enum TestItemEvent { Edit, } + impl Clone for TestItem { + fn clone(&self) -> Self { + Self { + state: self.state.clone(), + save_count: self.save_count, + save_as_count: self.save_as_count, + reload_count: self.reload_count, + is_dirty: self.is_dirty, + is_singleton: self.is_singleton, + has_conflict: self.has_conflict, + project_entry_ids: self.project_entry_ids.clone(), + project_path: self.project_path.clone(), + nav_history: None, + } + } + } + impl TestItem { fn new() -> Self { Self { + state: String::new(), save_count: 0, save_as_count: 0, reload_count: 0, @@ -3169,6 +3248,18 @@ mod tests { project_entry_ids: Vec::new(), project_path: None, is_singleton: true, + nav_history: None, + } + } + + fn set_state(&mut self, state: String, cx: &mut ViewContext) { + self.push_to_nav_history(cx); + self.state = state; + } + + fn push_to_nav_history(&mut self, cx: &mut ViewContext) { + if let Some(history) = &mut self.nav_history { + history.push(Some(Box::new(self.state.clone())), cx); } } } @@ -3204,7 +3295,23 @@ mod tests { self.is_singleton } - fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext) {} + fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { + self.nav_history = Some(history); + } + + fn navigate(&mut self, state: Box, _: &mut ViewContext) -> bool { + let state = *state.downcast::().unwrap_or_default(); + if state != self.state { + self.state = state; + true + } else { + false + } + } + + fn deactivated(&mut self, cx: &mut ViewContext) { + self.push_to_nav_history(cx); + } fn clone_on_split(&self, _: &mut ViewContext) -> Option where From 70cf6b4041f979bf3a5e0056f3bd0337ff89acc4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 6 Jul 2022 16:33:44 -0700 Subject: [PATCH 08/47] Give nav buttons a background on hover --- crates/workspace/src/toolbar.rs | 1 + styles/src/styleTree/workspace.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index 7525c0413d7097d7b4e2e1f338835737ba2eab25..9e0c085b1f45dbfc85816b7dae3591d6f21909c6 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -173,6 +173,7 @@ fn nav_button( .constrained() .with_width(style.button_width) .with_height(style.button_width) + .aligned() .boxed() }) .with_cursor_style(if enabled { diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 0b71453e7148c71a4664c135c0b0f6926759d1db..e23e047eda29cc5240b420728297f5bf1100309d 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -142,13 +142,15 @@ export default function workspace(theme: Theme) { navButton: { color: iconColor(theme, "primary"), iconWidth: 8, - buttonWidth: 12, + buttonWidth: 18, + cornerRadius: 6, hover: { color: iconColor(theme, "active"), + background: backgroundColor(theme, 300), }, disabled: { color: iconColor(theme, "muted") - } + }, }, padding: { left: 16, right: 8, top: 4, bottom: 4 }, }, From bcb553f2338093d0be78287a89593fef1bd8e48a Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Wed, 6 Jul 2022 16:37:56 -0700 Subject: [PATCH 09/47] Combine platform mouse events to use common MouseEvent struct and remove MouseDragged event in favor of MouseMoved --- crates/editor/src/element.rs | 23 ++++-- crates/gpui/src/app.rs | 5 +- crates/gpui/src/elements/event_handler.rs | 20 +++-- crates/gpui/src/platform/event.rs | 90 +++++------------------ crates/gpui/src/platform/mac/event.rs | 73 +++++++++++------- crates/gpui/src/platform/mac/window.rs | 31 ++++---- crates/gpui/src/presenter.rs | 72 ++++++++++-------- crates/gpui/src/scene.rs | 13 +++- 8 files changed, 168 insertions(+), 159 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 49ee4e4b72275a4a6e9ea0db124b8426b7837844..9c16ca5cd1d12042181ab86eaf5dca8b7714c9d9 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -24,9 +24,9 @@ use gpui::{ platform::CursorStyle, text_layout::{self, Line, RunStyle, TextLayoutCache}, AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext, KeyDownEvent, - LayoutContext, LeftMouseDownEvent, LeftMouseDraggedEvent, LeftMouseUpEvent, - ModifiersChangedEvent, MouseMovedEvent, MutableAppContext, PaintContext, Quad, Scene, - ScrollWheelEvent, SizeConstraint, ViewContext, WeakViewHandle, + LayoutContext, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent, + MutableAppContext, PaintContext, Quad, Scene, ScrollWheelEvent, SizeConstraint, ViewContext, + WeakViewHandle, }; use json::json; use language::{Bias, DiagnosticSeverity, Selection}; @@ -1464,7 +1464,8 @@ impl Element for EditorElement { } match event { - Event::LeftMouseDown(LeftMouseDownEvent { + Event::MouseDown(MouseEvent { + button: MouseButton::Left, position, cmd, alt, @@ -1481,10 +1482,16 @@ impl Element for EditorElement { paint, cx, ), - Event::LeftMouseUp(LeftMouseUpEvent { position, .. }) => self.mouse_up(*position, cx), - Event::LeftMouseDragged(LeftMouseDraggedEvent { position, .. }) => { - self.mouse_dragged(*position, layout, paint, cx) - } + Event::MouseUp(MouseEvent { + button: MouseButton::Left, + position, + .. + }) => self.mouse_up(*position, cx), + Event::MouseMoved(MouseMovedEvent { + pressed_button: Some(MouseButton::Left), + position, + .. + }) => self.mouse_dragged(*position, layout, paint, cx), Event::ScrollWheel(ScrollWheelEvent { position, delta, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 8ce980e95f6c83a979cec872849e44f70a16ed85..0124fe9fcf592f762945b664c635b89362ab4a39 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -5381,7 +5381,7 @@ impl RefCounts { #[cfg(test)] mod tests { use super::*; - use crate::{actions, elements::*, impl_actions, LeftMouseDownEvent}; + use crate::{actions, elements::*, impl_actions, MouseButton, MouseEvent}; use serde::Deserialize; use smol::future::poll_once; use std::{ @@ -5734,8 +5734,9 @@ mod tests { let presenter = cx.presenters_and_platform_windows[&window_id].0.clone(); // Ensure window's root element is in a valid lifecycle state. presenter.borrow_mut().dispatch_event( - Event::LeftMouseDown(LeftMouseDownEvent { + Event::MouseDown(MouseEvent { position: Default::default(), + button: MouseButton::Left, ctrl: false, alt: false, shift: false, diff --git a/crates/gpui/src/elements/event_handler.rs b/crates/gpui/src/elements/event_handler.rs index 93ff357ee6ef1c999244a2dd965d44ae866309d0..60fa0c564da5cf01a65709827fb66a321052ff4d 100644 --- a/crates/gpui/src/elements/event_handler.rs +++ b/crates/gpui/src/elements/event_handler.rs @@ -1,7 +1,7 @@ use crate::{ geometry::vector::Vector2F, CursorRegion, DebugContext, Element, ElementBox, Event, - EventContext, LayoutContext, LeftMouseDownEvent, MouseRegion, NavigateMouseDownEvent, - NavigationDirection, PaintContext, RightMouseDownEvent, SizeConstraint, + EventContext, LayoutContext, MouseButton, MouseEvent, MouseRegion, NavigationDirection, + PaintContext, SizeConstraint, }; use pathfinder_geometry::rect::RectF; use serde_json::json; @@ -117,7 +117,11 @@ impl Element for EventHandler { true } else { match event { - Event::LeftMouseDown(LeftMouseDownEvent { position, .. }) => { + Event::MouseDown(MouseEvent { + button: MouseButton::Left, + position, + .. + }) => { if let Some(callback) = self.mouse_down.as_mut() { if visible_bounds.contains_point(*position) { return callback(cx); @@ -125,7 +129,11 @@ impl Element for EventHandler { } false } - Event::RightMouseDown(RightMouseDownEvent { position, .. }) => { + Event::MouseDown(MouseEvent { + button: MouseButton::Right, + position, + .. + }) => { if let Some(callback) = self.right_mouse_down.as_mut() { if visible_bounds.contains_point(*position) { return callback(cx); @@ -133,9 +141,9 @@ impl Element for EventHandler { } false } - Event::NavigateMouseDown(NavigateMouseDownEvent { + Event::MouseDown(MouseEvent { + button: MouseButton::Navigate(direction), position, - direction, .. }) => { if let Some(callback) = self.navigate_mouse_down.as_mut() { diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 983468343379c1bbae9db40add0ad154266fe70e..90b5d21fc2f7e1f8ec9092c598ca4a4c58cde658 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -1,11 +1,5 @@ use crate::{geometry::vector::Vector2F, keymap::Keystroke}; -#[derive(Copy, Clone, Debug)] -pub enum NavigationDirection { - Back, - Forward, -} - #[derive(Clone, Debug)] pub struct KeyDownEvent { pub keystroke: Keystroke, @@ -34,51 +28,24 @@ pub struct ScrollWheelEvent { pub precise: bool, } -#[derive(Clone, Debug)] -pub struct LeftMouseDownEvent { - pub position: Vector2F, - pub ctrl: bool, - pub alt: bool, - pub shift: bool, - pub cmd: bool, - pub click_count: usize, -} - -#[derive(Clone, Debug)] -pub struct LeftMouseUpEvent { - pub position: Vector2F, - pub click_count: usize, -} - -#[derive(Clone, Debug)] -pub struct LeftMouseDraggedEvent { - pub position: Vector2F, - pub ctrl: bool, - pub alt: bool, - pub shift: bool, - pub cmd: bool, -} - -#[derive(Clone, Debug)] -pub struct RightMouseDownEvent { - pub position: Vector2F, - pub ctrl: bool, - pub alt: bool, - pub shift: bool, - pub cmd: bool, - pub click_count: usize, +#[derive(Copy, Clone, Debug)] +pub enum NavigationDirection { + Back, + Forward, } -#[derive(Clone, Debug)] -pub struct RightMouseUpEvent { - pub position: Vector2F, - pub click_count: usize, +#[derive(Copy, Clone, Debug)] +pub enum MouseButton { + Left, + Right, + Middle, + Navigate(NavigationDirection), } #[derive(Clone, Debug)] -pub struct NavigateMouseDownEvent { +pub struct MouseEvent { + pub button: MouseButton, pub position: Vector2F, - pub direction: NavigationDirection, pub ctrl: bool, pub alt: bool, pub shift: bool, @@ -86,16 +53,10 @@ pub struct NavigateMouseDownEvent { pub click_count: usize, } -#[derive(Clone, Debug)] -pub struct NavigateMouseUpEvent { - pub position: Vector2F, - pub direction: NavigationDirection, -} - -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct MouseMovedEvent { pub position: Vector2F, - pub left_mouse_down: bool, + pub pressed_button: Option, pub ctrl: bool, pub cmd: bool, pub alt: bool, @@ -107,15 +68,10 @@ pub enum Event { KeyDown(KeyDownEvent), KeyUp(KeyUpEvent), ModifiersChanged(ModifiersChangedEvent), - ScrollWheel(ScrollWheelEvent), - LeftMouseDown(LeftMouseDownEvent), - LeftMouseUp(LeftMouseUpEvent), - LeftMouseDragged(LeftMouseDraggedEvent), - RightMouseDown(RightMouseDownEvent), - RightMouseUp(RightMouseUpEvent), - NavigateMouseDown(NavigateMouseDownEvent), - NavigateMouseUp(NavigateMouseUpEvent), + MouseDown(MouseEvent), + MouseUp(MouseEvent), MouseMoved(MouseMovedEvent), + ScrollWheel(ScrollWheelEvent), } impl Event { @@ -124,15 +80,9 @@ impl Event { Event::KeyDown { .. } => None, Event::KeyUp { .. } => None, Event::ModifiersChanged { .. } => None, - Event::ScrollWheel(ScrollWheelEvent { position, .. }) - | Event::LeftMouseDown(LeftMouseDownEvent { position, .. }) - | Event::LeftMouseUp(LeftMouseUpEvent { position, .. }) - | Event::LeftMouseDragged(LeftMouseDraggedEvent { position, .. }) - | Event::RightMouseDown(RightMouseDownEvent { position, .. }) - | Event::RightMouseUp(RightMouseUpEvent { position, .. }) - | Event::NavigateMouseDown(NavigateMouseDownEvent { position, .. }) - | Event::NavigateMouseUp(NavigateMouseUpEvent { position, .. }) - | Event::MouseMoved(MouseMovedEvent { position, .. }) => Some(*position), + Event::MouseDown(event) | Event::MouseUp(event) => Some(event.position), + Event::MouseMoved(event) => Some(event.position), + Event::ScrollWheel(event) => Some(event.position), } } } diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 2af0922de0720101e7638f263869615c47f8feb4..417d4d1afabea1edd98bb4f8ff0e744e68ed519d 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -2,9 +2,8 @@ use crate::{ geometry::vector::vec2f, keymap::Keystroke, platform::{Event, NavigationDirection}, - KeyDownEvent, KeyUpEvent, LeftMouseDownEvent, LeftMouseDraggedEvent, LeftMouseUpEvent, - ModifiersChangedEvent, MouseMovedEvent, NavigateMouseDownEvent, NavigateMouseUpEvent, - RightMouseDownEvent, RightMouseUpEvent, ScrollWheelEvent, + KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent, + ScrollWheelEvent, }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventType}, @@ -115,7 +114,8 @@ impl Event { NSEventType::NSLeftMouseDown => { let modifiers = native_event.modifierFlags(); window_height.map(|window_height| { - Self::LeftMouseDown(LeftMouseDownEvent { + Self::MouseDown(MouseEvent { + button: MouseButton::Left, position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, @@ -129,18 +129,25 @@ impl Event { }) } NSEventType::NSLeftMouseUp => window_height.map(|window_height| { - Self::LeftMouseUp(LeftMouseUpEvent { + let modifiers = native_event.modifierFlags(); + Self::MouseUp(MouseEvent { + button: MouseButton::Left, position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), click_count: native_event.clickCount() as usize, }) }), NSEventType::NSRightMouseDown => { let modifiers = native_event.modifierFlags(); window_height.map(|window_height| { - Self::RightMouseDown(RightMouseDownEvent { + Self::MouseDown(MouseEvent { + button: MouseButton::Right, position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, @@ -154,11 +161,17 @@ impl Event { }) } NSEventType::NSRightMouseUp => window_height.map(|window_height| { - Self::RightMouseUp(RightMouseUpEvent { + let modifiers = native_event.modifierFlags(); + Self::MouseUp(MouseEvent { + button: MouseButton::Right, position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), click_count: native_event.clickCount() as usize, }) }), @@ -172,12 +185,12 @@ impl Event { let modifiers = native_event.modifierFlags(); window_height.map(|window_height| { - Self::NavigateMouseDown(NavigateMouseDownEvent { + Self::MouseDown(MouseEvent { + button: MouseButton::Navigate(direction), position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), - direction, ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), @@ -194,29 +207,22 @@ impl Event { _ => return None, }; + let modifiers = native_event.modifierFlags(); window_height.map(|window_height| { - Self::NavigateMouseUp(NavigateMouseUpEvent { + Self::MouseUp(MouseEvent { + button: MouseButton::Navigate(direction), position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), - direction, + ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), + alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), + shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), + cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), + click_count: native_event.clickCount() as usize, }) }) } - NSEventType::NSLeftMouseDragged => window_height.map(|window_height| { - let modifiers = native_event.modifierFlags(); - Self::LeftMouseDragged(LeftMouseDraggedEvent { - position: vec2f( - native_event.locationInWindow().x as f32, - window_height - native_event.locationInWindow().y as f32, - ), - ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), - alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), - shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), - cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), - }) - }), NSEventType::NSScrollWheel => window_height.map(|window_height| { Self::ScrollWheel(ScrollWheelEvent { position: vec2f( @@ -232,12 +238,29 @@ impl Event { }), NSEventType::NSMouseMoved => window_height.map(|window_height| { let modifiers = native_event.modifierFlags(); + let pressed_button_flags = NSEvent::pressedMouseButtons(nil); + + // Pick the "strongest" button to report in mouse dragged events + let pressed_button = if pressed_button_flags & (1 << 0) != 0 { + Some(MouseButton::Left) + } else if pressed_button_flags & (1 << 1) != 0 { + Some(MouseButton::Right) + } else if pressed_button_flags & (1 << 2) != 0 { + Some(MouseButton::Middle) + } else if pressed_button_flags & (1 << 3) != 0 { + Some(MouseButton::Navigate(NavigationDirection::Back)) + } else if pressed_button_flags & (1 << 4) != 0 { + Some(MouseButton::Navigate(NavigationDirection::Forward)) + } else { + None + }; + Self::MouseMoved(MouseMovedEvent { position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), - left_mouse_down: NSEvent::pressedMouseButtons(nil) & 1 != 0, + pressed_button, ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 3d9b887c672081f36b22d687db0d6a36dfb3538c..02549d82a99ab3b070d6228f0181393f9d16e7d6 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -6,7 +6,7 @@ use crate::{ }, keymap::Keystroke, platform::{self, Event, WindowBounds, WindowContext}, - KeyDownEvent, LeftMouseDraggedEvent, ModifiersChangedEvent, Scene, + KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent, Scene, }; use block::ConcreteBlock; use cocoa::{ @@ -127,10 +127,6 @@ unsafe fn build_classes() { sel!(mouseMoved:), handle_view_event as extern "C" fn(&Object, Sel, id), ); - decl.add_method( - sel!(mouseDragged:), - handle_view_event as extern "C" fn(&Object, Sel, id), - ); decl.add_method( sel!(scrollWheel:), handle_view_event as extern "C" fn(&Object, Sel, id), @@ -603,18 +599,26 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { if let Some(event) = event { match &event { - Event::LeftMouseDragged(LeftMouseDraggedEvent { position, .. }) => { + Event::MouseMoved( + event @ MouseMovedEvent { + pressed_button: Some(_), + .. + }, + ) => { window_state_borrow.synthetic_drag_counter += 1; window_state_borrow .executor .spawn(synthetic_drag( weak_window_state, window_state_borrow.synthetic_drag_counter, - *position, + *event, )) .detach(); } - Event::LeftMouseUp { .. } => { + Event::MouseUp(MouseEvent { + button: MouseButton::Left, + .. + }) => { window_state_borrow.synthetic_drag_counter += 1; } Event::ModifiersChanged(ModifiersChangedEvent { @@ -835,7 +839,7 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) { async fn synthetic_drag( window_state: Weak>, drag_id: usize, - position: Vector2F, + event: MouseMovedEvent, ) { loop { Timer::after(Duration::from_millis(16)).await; @@ -844,14 +848,7 @@ async fn synthetic_drag( if window_state_borrow.synthetic_drag_counter == drag_id { if let Some(mut callback) = window_state_borrow.event_callback.take() { drop(window_state_borrow); - callback(Event::LeftMouseDragged(LeftMouseDraggedEvent { - // TODO: Make sure empty modifiers is correct for this - position, - shift: false, - ctrl: false, - alt: false, - cmd: false, - })); + callback(Event::MouseMoved(event)); window_state.borrow_mut().event_callback = Some(callback); } } else { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 015b44b97b3dbe77d73d8b2534dd4ad671171ab6..0d7deec8031003e6ce71c29ec67fc9200778eca6 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -9,10 +9,9 @@ use crate::{ scene::CursorRegion, text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity, - FontSystem, LeftMouseDownEvent, LeftMouseDraggedEvent, LeftMouseUpEvent, ModelHandle, - MouseMovedEvent, MouseRegion, MouseRegionId, ReadModel, ReadView, RenderContext, RenderParams, - RightMouseDownEvent, RightMouseUpEvent, Scene, UpgradeModelHandle, UpgradeViewHandle, View, - ViewHandle, WeakModelHandle, WeakViewHandle, + FontSystem, ModelHandle, MouseButton, MouseEvent, MouseMovedEvent, MouseRegion, MouseRegionId, + ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, + View, ViewHandle, WeakModelHandle, WeakViewHandle, }; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; @@ -236,7 +235,11 @@ impl Presenter { let mut dragged_region = None; match event { - Event::LeftMouseDown(LeftMouseDownEvent { position, .. }) => { + Event::MouseDown(MouseEvent { + position, + button: MouseButton::Left, + .. + }) => { let mut hit = false; for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(position) { @@ -252,9 +255,10 @@ impl Presenter { } } } - Event::LeftMouseUp(LeftMouseUpEvent { + Event::MouseUp(MouseEvent { position, click_count, + button: MouseButton::Left, .. }) => { self.prev_drag_position.take(); @@ -265,7 +269,11 @@ impl Presenter { } } } - Event::RightMouseDown(RightMouseDownEvent { position, .. }) => { + Event::MouseDown(MouseEvent { + position, + button: MouseButton::Right, + .. + }) => { let mut hit = false; for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(position) { @@ -280,9 +288,10 @@ impl Presenter { } } } - Event::RightMouseUp(RightMouseUpEvent { + Event::MouseUp(MouseEvent { position, click_count, + button: MouseButton::Right, .. }) => { if let Some(region) = self.right_clicked_region.take() { @@ -292,34 +301,37 @@ impl Presenter { } } } - Event::MouseMoved { .. } => { - self.last_mouse_moved_event = Some(event.clone()); - } - Event::LeftMouseDragged(LeftMouseDraggedEvent { + Event::MouseMoved(MouseMovedEvent { + pressed_button, position, shift, ctrl, alt, cmd, + .. }) => { - if let Some((clicked_region, prev_drag_position)) = self - .clicked_region - .as_ref() - .zip(self.prev_drag_position.as_mut()) - { - dragged_region = - Some((clicked_region.clone(), position - *prev_drag_position)); - *prev_drag_position = position; + if let Some(MouseButton::Left) = pressed_button { + if let Some((clicked_region, prev_drag_position)) = self + .clicked_region + .as_ref() + .zip(self.prev_drag_position.as_mut()) + { + dragged_region = + Some((clicked_region.clone(), position - *prev_drag_position)); + *prev_drag_position = position; + } + + self.last_mouse_moved_event = Some(Event::MouseMoved(MouseMovedEvent { + position, + pressed_button: Some(MouseButton::Left), + shift, + ctrl, + alt, + cmd, + })); } - self.last_mouse_moved_event = Some(Event::MouseMoved(MouseMovedEvent { - position, - left_mouse_down: true, - shift, - ctrl, - alt, - cmd, - })); + self.last_mouse_moved_event = Some(event.clone()); } _ => {} } @@ -413,11 +425,11 @@ impl Presenter { if let Event::MouseMoved(MouseMovedEvent { position, - left_mouse_down, + pressed_button, .. }) = event { - if !left_mouse_down { + if let None = pressed_button { let mut style_to_assign = CursorStyle::Arrow; for region in self.cursor_regions.iter().rev() { if region.bounds.contains_point(*position) { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 1f0e2c0ecc0e3921f7a37625a5b32457e923fef9..66f4fb0783b1db3847f48336c71f923f8ac47160 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -8,7 +8,7 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::ToJson, platform::CursorStyle, - EventContext, ImageData, + EventContext, ImageData, MouseEvent, MouseMovedEvent, ScrollWheelEvent, }; pub struct Scene { @@ -44,11 +44,22 @@ pub struct CursorRegion { pub style: CursorStyle, } +pub enum MouseRegionEvent { + Moved(MouseMovedEvent), + Hover(MouseEvent), + Down(MouseEvent), + Up(MouseEvent), + Click(MouseEvent), + DownOut(MouseEvent), + ScrollWheel(ScrollWheelEvent), +} + #[derive(Clone, Default)] pub struct MouseRegion { pub view_id: usize, pub discriminant: Option<(TypeId, usize)>, pub bounds: RectF, + pub hover: Option>, pub mouse_down: Option>, pub click: Option>, From 4ec2d6e50d9542ce86248b2c67b37249cc255fda Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 6 Jul 2022 16:45:38 -0700 Subject: [PATCH 10/47] Tweak navigation bar colors in theme I meant to include this in #1297 --- styles/src/styleTree/workspace.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index e23e047eda29cc5240b420728297f5bf1100309d..fbd7b05a224f8272126631ad5107e1a36215c623 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -140,7 +140,7 @@ export default function workspace(theme: Theme) { border: border(theme, "secondary", { bottom: true }), itemSpacing: 8, navButton: { - color: iconColor(theme, "primary"), + color: iconColor(theme, "secondary"), iconWidth: 8, buttonWidth: 18, cornerRadius: 6, @@ -149,7 +149,7 @@ export default function workspace(theme: Theme) { background: backgroundColor(theme, 300), }, disabled: { - color: iconColor(theme, "muted") + color: withOpacity(iconColor(theme, "muted"), 0.6), }, }, padding: { left: 16, right: 8, top: 4, bottom: 4 }, From c139f1e6b68829f5b6f78892cddce27ce0a0ae1e Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Wed, 6 Jul 2022 16:59:58 -0700 Subject: [PATCH 11/47] combine branches of events in from_native --- crates/gpui/src/platform/mac/event.rs | 145 +++++++++---------------- crates/gpui/src/platform/mac/window.rs | 4 + 2 files changed, 55 insertions(+), 94 deletions(-) diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 417d4d1afabea1edd98bb4f8ff0e744e68ed519d..5e23859675cbc173254428f376f832207a2b3c00 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -7,7 +7,7 @@ use crate::{ }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventType}, - base::{id, nil, YES}, + base::{id, YES}, foundation::NSString as _, }; use std::{borrow::Cow, ffi::CStr, os::raw::c_char}; @@ -111,11 +111,23 @@ impl Event { input, })) } - NSEventType::NSLeftMouseDown => { + NSEventType::NSLeftMouseDown + | NSEventType::NSRightMouseDown + | NSEventType::NSOtherMouseDown => { + let button = match native_event.buttonNumber() { + 0 => MouseButton::Left, + 1 => MouseButton::Right, + 2 => MouseButton::Middle, + 3 => MouseButton::Navigate(NavigationDirection::Back), + 4 => MouseButton::Navigate(NavigationDirection::Forward), + // Other mouse buttons aren't tracked currently + _ => return None, + }; let modifiers = native_event.modifierFlags(); + window_height.map(|window_height| { Self::MouseDown(MouseEvent { - button: MouseButton::Left, + button, position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, @@ -128,26 +140,23 @@ impl Event { }) }) } - NSEventType::NSLeftMouseUp => window_height.map(|window_height| { - let modifiers = native_event.modifierFlags(); - Self::MouseUp(MouseEvent { - button: MouseButton::Left, - position: vec2f( - native_event.locationInWindow().x as f32, - window_height - native_event.locationInWindow().y as f32, - ), - ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), - alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), - shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), - cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), - click_count: native_event.clickCount() as usize, - }) - }), - NSEventType::NSRightMouseDown => { - let modifiers = native_event.modifierFlags(); + NSEventType::NSLeftMouseUp + | NSEventType::NSRightMouseUp + | NSEventType::NSOtherMouseUp => { + let button = match native_event.buttonNumber() { + 0 => MouseButton::Left, + 1 => MouseButton::Right, + 2 => MouseButton::Middle, + 3 => MouseButton::Navigate(NavigationDirection::Back), + 4 => MouseButton::Navigate(NavigationDirection::Forward), + // Other mouse buttons aren't tracked currently + _ => return None, + }; + window_height.map(|window_height| { - Self::MouseDown(MouseEvent { - button: MouseButton::Right, + let modifiers = native_event.modifierFlags(); + Self::MouseUp(MouseEvent { + button, position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, @@ -160,57 +169,36 @@ impl Event { }) }) } - NSEventType::NSRightMouseUp => window_height.map(|window_height| { - let modifiers = native_event.modifierFlags(); - Self::MouseUp(MouseEvent { - button: MouseButton::Right, + NSEventType::NSScrollWheel => window_height.map(|window_height| { + Self::ScrollWheel(ScrollWheelEvent { position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), - ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), - alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), - shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), - cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), - click_count: native_event.clickCount() as usize, + delta: vec2f( + native_event.scrollingDeltaX() as f32, + native_event.scrollingDeltaY() as f32, + ), + precise: native_event.hasPreciseScrollingDeltas() == YES, }) }), - NSEventType::NSOtherMouseDown => { - let direction = match native_event.buttonNumber() { - 3 => NavigationDirection::Back, - 4 => NavigationDirection::Forward, - // Other mouse buttons aren't tracked currently - _ => return None, - }; - - let modifiers = native_event.modifierFlags(); - window_height.map(|window_height| { - Self::MouseDown(MouseEvent { - button: MouseButton::Navigate(direction), - position: vec2f( - native_event.locationInWindow().x as f32, - window_height - native_event.locationInWindow().y as f32, - ), - ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), - alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), - shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), - cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), - click_count: native_event.clickCount() as usize, - }) - }) - } - NSEventType::NSOtherMouseUp => { - let direction = match native_event.buttonNumber() { - 3 => NavigationDirection::Back, - 4 => NavigationDirection::Forward, + NSEventType::NSLeftMouseDragged + | NSEventType::NSRightMouseDragged + | NSEventType::NSOtherMouseDragged => { + let pressed_button = match native_event.buttonNumber() { + 0 => MouseButton::Left, + 1 => MouseButton::Right, + 2 => MouseButton::Middle, + 3 => MouseButton::Navigate(NavigationDirection::Back), + 4 => MouseButton::Navigate(NavigationDirection::Forward), // Other mouse buttons aren't tracked currently _ => return None, }; - let modifiers = native_event.modifierFlags(); window_height.map(|window_height| { - Self::MouseUp(MouseEvent { - button: MouseButton::Navigate(direction), + let modifiers = native_event.modifierFlags(); + Self::MouseMoved(MouseMovedEvent { + pressed_button: Some(pressed_button), position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, @@ -219,48 +207,17 @@ impl Event { alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask), - click_count: native_event.clickCount() as usize, }) }) } - NSEventType::NSScrollWheel => window_height.map(|window_height| { - Self::ScrollWheel(ScrollWheelEvent { - position: vec2f( - native_event.locationInWindow().x as f32, - window_height - native_event.locationInWindow().y as f32, - ), - delta: vec2f( - native_event.scrollingDeltaX() as f32, - native_event.scrollingDeltaY() as f32, - ), - precise: native_event.hasPreciseScrollingDeltas() == YES, - }) - }), NSEventType::NSMouseMoved => window_height.map(|window_height| { let modifiers = native_event.modifierFlags(); - let pressed_button_flags = NSEvent::pressedMouseButtons(nil); - - // Pick the "strongest" button to report in mouse dragged events - let pressed_button = if pressed_button_flags & (1 << 0) != 0 { - Some(MouseButton::Left) - } else if pressed_button_flags & (1 << 1) != 0 { - Some(MouseButton::Right) - } else if pressed_button_flags & (1 << 2) != 0 { - Some(MouseButton::Middle) - } else if pressed_button_flags & (1 << 3) != 0 { - Some(MouseButton::Navigate(NavigationDirection::Back)) - } else if pressed_button_flags & (1 << 4) != 0 { - Some(MouseButton::Navigate(NavigationDirection::Forward)) - } else { - None - }; - Self::MouseMoved(MouseMovedEvent { position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), - pressed_button, + pressed_button: None, ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask), alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask), shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask), diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 02549d82a99ab3b070d6228f0181393f9d16e7d6..5e6b3b9c190fa6ff16a2cfee0c245a35fea0b444 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -127,6 +127,10 @@ unsafe fn build_classes() { sel!(mouseMoved:), handle_view_event as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(mouseDragged:), + handle_view_event as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(scrollWheel:), handle_view_event as extern "C" fn(&Object, Sel, id), From 778cfd94d873e3750f29a19e296cc482f95b1b9c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 6 Jul 2022 17:37:12 -0700 Subject: [PATCH 12/47] Added basic selections --- crates/editor/src/element.rs | 20 +- crates/terminal/src/terminal_element.rs | 317 ++++++++++++++++-------- 2 files changed, 220 insertions(+), 117 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1169df3fd1d72f725a72beaa81154c58e9bd4884..976f932e580769c667886c2cb80f46d3521029b7 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1685,22 +1685,22 @@ impl Cursor { } #[derive(Debug)] -struct HighlightedRange { - start_y: f32, - line_height: f32, - lines: Vec, - color: Color, - corner_radius: f32, +pub struct HighlightedRange { + pub start_y: f32, + pub line_height: f32, + pub lines: Vec, + pub color: Color, + pub corner_radius: f32, } #[derive(Debug)] -struct HighlightedRangeLine { - start_x: f32, - end_x: f32, +pub struct HighlightedRangeLine { + pub start_x: f32, + pub end_x: f32, } impl HighlightedRange { - fn paint(&self, bounds: RectF, scene: &mut Scene) { + pub fn paint(&self, bounds: RectF, scene: &mut Scene) { if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x { self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene); self.paint_lines( diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index ad017c011fa250ec44125daa37b7d9b5be7eea7a..a339c2c6f942c18f1fcb8384ffe6a9471d1140d4 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -1,12 +1,15 @@ use alacritty_terminal::{ grid::{Dimensions, GridIterator, Indexed}, index::{Column as GridCol, Line as GridLine, Point, Side}, + selection::{Selection, SelectionRange, SelectionType}, + sync::FairMutex, term::{ cell::{Cell, Flags}, SizeInfo, }, + Term, }; -use editor::{Cursor, CursorShape}; +use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ color::Color, elements::*, @@ -23,11 +26,13 @@ use gpui::{ use itertools::Itertools; use ordered_float::OrderedFloat; use settings::Settings; -use std::{cmp::min, rc::Rc}; +use std::{cmp::min, ops::Range, rc::Rc, sync::Arc}; +use std::{fmt::Debug, ops::Sub}; use theme::TerminalStyle; use crate::{ - color_translation::convert_color, gpui_func_tools::paint_layer, Input, ScrollTerminal, Terminal, + color_translation::convert_color, gpui_func_tools::paint_layer, Input, ScrollTerminal, + Terminal, ZedListener, }; ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable @@ -45,14 +50,27 @@ pub struct TerminalEl { view: WeakViewHandle, } -///Helper types so I don't mix these two up +///New type pattern so I don't mix these two up struct CellWidth(f32); struct LineHeight(f32); +struct LayoutLine { + cells: Vec, + highlighted_range: Option>, +} + +///New type pattern to ensure that we use adjusted mouse positions throughout the code base, rather than +struct PaneRelativePos(Vector2F); + +///Functionally the constructor for the PaneRelativePos type, mutates the mouse_position +fn relative_pos(mouse_position: Vector2F, origin: Vector2F) -> PaneRelativePos { + PaneRelativePos(mouse_position.sub(origin)) //Avoid the extra allocation by mutating +} + #[derive(Clone, Debug, Default)] struct LayoutCell { point: Point, - text: Line, + text: Line, //NOTE TO SELF THIS IS BAD PERFORMANCE RN! background_color: Color, } @@ -68,14 +86,15 @@ impl LayoutCell { ///The information generated during layout that is nescessary for painting pub struct LayoutState { - cells: Vec<(Point, Line)>, - background_rects: Vec<(RectF, Color)>, //Vec index == Line index for the LineSpan + layout_lines: Vec, line_height: LineHeight, em_width: CellWidth, cursor: Option, background_color: Color, cur_size: SizeInfo, display_offset: usize, + terminal: Arc>>, + selection_color: Color, } impl TerminalEl { @@ -111,42 +130,31 @@ impl Element for TerminalEl { view_handle.update(cx.app, |view, _cx| view.set_size(cur_size)); //Now that we're done with the mutable portion, grab the immutable settings and view again - let terminal_theme = &(cx.global::()).theme.terminal; - let term = view_handle.read(cx).term.lock(); + let (selection_color, terminal_theme) = { + let theme = &(cx.global::()).theme; + (theme.editor.selection.selection, &theme.terminal) + }; + let terminal_mutex = view_handle.read(cx).term.clone(); + let term = terminal_mutex.lock(); let grid = term.grid(); let cursor_point = grid.cursor.point; let cursor_text = grid[cursor_point.line][cursor_point.column].c.to_string(); let content = term.renderable_content(); - let layout_cells = layout_cells( + //We have a 'SelectionRange' struct to work with, + //Allows us to query start, end, and contains + //content.selection.unwrap() + + let layout_lines = layout_lines( content.display_iter, &text_style, terminal_theme, cx.text_layout_cache, + content.selection, ); - let cells = layout_cells - .iter() - .map(|c| (c.point, c.text.clone())) - .collect::, Line)>>(); - let background_rects = layout_cells - .iter() - .map(|cell| { - ( - RectF::new( - vec2f( - cell.point.column as f32 * cell_width.0, - cell.point.line as f32 * line_height.0, - ), - vec2f(cell_width.0, line_height.0), - ), - cell.background_color, - ) - }) - .collect::>(); - let block_text = cx.text_layout_cache.layout_str( &cursor_text, text_style.font_size, @@ -185,18 +193,21 @@ impl Element for TerminalEl { Some(block_text.clone()), ) }); + let display_offset = content.display_offset; + drop(term); ( constraint.max, LayoutState { - cells, + layout_lines, line_height, em_width: cell_width, cursor, cur_size, - background_rects, background_color: terminal_theme.background, - display_offset: content.display_offset, + display_offset, + terminal: terminal_mutex, + selection_color, }, ) } @@ -210,6 +221,7 @@ impl Element for TerminalEl { ) -> Self::PaintState { //Setup element stuff let clip_bounds = Some(visible_bounds); + paint_layer(cx, clip_bounds, |cx| { //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse @@ -230,37 +242,45 @@ impl Element for TerminalEl { */ let cur_size = layout.cur_size.clone(); let display_offset = layout.display_offset.clone(); + let terminal_mutex = layout.terminal.clone(); + let origin = bounds.origin() + vec2f(layout.em_width.0, 0.); + + //TODO: Better way of doing this? + let mutex1 = terminal_mutex.clone(); + let _mutex2 = terminal_mutex.clone(); + cx.scene.push_mouse_region(MouseRegion { view_id: self.view.id(), - mouse_down: Some(Rc::new(move |pos, cx| { - let point = grid_cell(pos, cur_size, display_offset); - let side = cell_side(cur_size, pos.x() as usize); - - //One problem is we need a terminal - //Second problem is that we need # of clicks - //Third problem is that dragging reports deltas, and we need locations. - //Fourth (minor) is need to render the selection - - // if single_click { - // terminal.selection = Some(Selection::new(SelectionType::Simple, point, side)) - // } else if double_click { - // terminal.selection = Some(Selection::new(SelectionType::Semantic, point, side)) - // } else if triple_click { - // terminal.selection = Some(Selection::new(SelectionType::Lines, point, side)) - // } + click: Some(Rc::new(move |pos, click_count, cx| { + let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); + + let selection_type = match click_count { + 1 => Some(SelectionType::Simple), + 2 => Some(SelectionType::Semantic), + 3 => Some(SelectionType::Lines), + _ => None, + }; + let selection = selection_type + .map(|selection_type| Selection::new(selection_type, point, side)); + + let mut term = mutex1.lock(); + term.selection = selection; cx.focus_parent_view() })), bounds: visible_bounds, - drag: Some(Rc::new(|delta, cx| { - //Calculate new point from delta - //terminal.selection.update(point, side) + drag: Some(Rc::new(move |_delta, _cx| { + // let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); + + // let mut term = mutex2.lock(); + // if let Some(mut selection) = term.selection.take() { + // selection.update(point, side); + // term.selection = Some(selection); + // } })), ..Default::default() }); - let origin = bounds.origin() + vec2f(layout.em_width.0, 0.); - paint_layer(cx, clip_bounds, |cx| { //Start with a background color cx.scene.push_quad(Quad { @@ -271,25 +291,84 @@ impl Element for TerminalEl { }); //Draw cell backgrounds - for background_rect in &layout.background_rects { - let new_origin = origin + background_rect.0.origin(); - cx.scene.push_quad(Quad { - bounds: RectF::new(new_origin, background_rect.0.size()), - background: Some(background_rect.1), - border: Default::default(), - corner_radius: 0., + for layout_line in &layout.layout_lines { + for layout_cell in &layout_line.cells { + let position = vec2f( + origin.x() + layout_cell.point.column as f32 * layout.em_width.0, + origin.y() + layout_cell.point.line as f32 * layout.line_height.0, + ); + let size = vec2f(layout.em_width.0, layout.line_height.0); + + cx.scene.push_quad(Quad { + bounds: RectF::new(position, size), + background: Some(layout_cell.background_color), + border: Default::default(), + corner_radius: 0., + }) + } + } + }); + + //Draw Selection + paint_layer(cx, clip_bounds, |cx| { + let mut highlight_y = None; + let highlight_lines = layout + .layout_lines + .iter() + .filter_map(|line| { + if let Some(range) = &line.highlighted_range { + if let None = highlight_y { + highlight_y = Some( + origin.y() + + line.cells[0].point.line as f32 * layout.line_height.0, + ); + } + let start_x = origin.x() + + line.cells[range.start].point.column as f32 * layout.em_width.0; + let end_x = origin.x() + //TODO: Why -1? I know switch from count to index... but where... + + line.cells[range.end - 1].point.column as f32 * layout.em_width.0 + + layout.em_width.0; + + return Some(HighlightedRangeLine { start_x, end_x }); + } else { + return None; + } }) + .collect::>(); + + if let Some(y) = highlight_y { + let hr = HighlightedRange { + start_y: y, //Need to change this + line_height: layout.line_height.0, + lines: highlight_lines, + color: layout.selection_color, + //Copied from editor. TODO: move to theme or something + corner_radius: 0.15 * layout.line_height.0, + }; + hr.paint(bounds, cx.scene); } }); //Draw text paint_layer(cx, clip_bounds, |cx| { - for (point, cell) in &layout.cells { - let cell_origin = vec2f( - origin.x() + point.column as f32 * layout.em_width.0, - origin.y() + point.line as f32 * layout.line_height.0, - ); - cell.paint(cell_origin, visible_bounds, layout.line_height.0, cx); + for layout_line in &layout.layout_lines { + for layout_cell in &layout_line.cells { + let point = layout_cell.point; + + //Don't actually know the start_x for a line, until here: + let cell_origin = vec2f( + origin.x() + point.column as f32 * layout.em_width.0, + origin.y() + point.line as f32 * layout.line_height.0, + ); + + layout_cell.text.paint( + cell_origin, + visible_bounds, + layout.line_height.0, + cx, + ); + } } }); @@ -354,18 +433,17 @@ impl Element for TerminalEl { } } -/* -Mouse moved -> WindowEvent::CursorMoved -mouse press -> WindowEvent::MouseInput -update_selection_scrolling - - -copy_selection -start_selection -toggle_selection -update_selection -clear_selection - */ +fn mouse_to_cell_data( + pos: Vector2F, + origin: Vector2F, + cur_size: SizeInfo, + display_offset: usize, +) -> (Point, alacritty_terminal::index::Direction) { + let relative_pos = relative_pos(pos, origin); + let point = grid_cell(&relative_pos, cur_size, display_offset); + let side = cell_side(&relative_pos, cur_size); + (point, side) +} ///Configures a text style from the current settings. fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle { @@ -399,38 +477,59 @@ fn make_new_size( ) } -fn layout_cells( +//Let's say that calculating the display is correct, that means that either calculating the highlight ranges is incorrect +//OR calculating the click ranges is incorrect + +fn layout_lines( grid: GridIterator, text_style: &TextStyle, terminal_theme: &TerminalStyle, text_layout_cache: &TextLayoutCache, -) -> Vec { - let mut line_count: i32 = 0; + selection_range: Option, +) -> Vec { let lines = grid.group_by(|i| i.point.line); lines .into_iter() - .map(|(_, line)| { - line_count += 1; - line.map(|indexed_cell| { - let cell_text = &indexed_cell.c.to_string(); - - let cell_style = cell_style(&indexed_cell, terminal_theme, text_style); - - let layout_cell = text_layout_cache.layout_str( - cell_text, - text_style.font_size, - &[(cell_text.len(), cell_style)], - ); - LayoutCell::new( - Point::new(line_count - 1, indexed_cell.point.column.0 as i32), - layout_cell, - convert_color(&indexed_cell.bg, terminal_theme), - ) - }) - .collect::>() + .enumerate() + .map(|(line_index, (_, line))| { + let mut highlighted_range = None; + let cells = line + .enumerate() + .map(|(x_index, indexed_cell)| { + if selection_range + .map(|range| range.contains(indexed_cell.point)) + .unwrap_or(false) + { + let mut range = highlighted_range.take().unwrap_or(x_index..x_index + 1); + range.end = range.end.max(x_index + 1); + highlighted_range = Some(range); + } + + let cell_text = &indexed_cell.c.to_string(); + + let cell_style = cell_style(&indexed_cell, terminal_theme, text_style); + + //This is where we might be able to get better performance + let layout_cell = text_layout_cache.layout_str( + cell_text, + text_style.font_size, + &[(cell_text.len(), cell_style)], + ); + + LayoutCell::new( + Point::new(line_index as i32, indexed_cell.point.column.0 as i32), + layout_cell, + convert_color(&indexed_cell.bg, terminal_theme), + ) + }) + .collect::>(); + + LayoutLine { + cells, + highlighted_range, + } }) - .flatten() - .collect::>() + .collect::>() } // Compute the cursor position and expected block width, may return a zero width if x_for_index returns @@ -487,7 +586,8 @@ fn cell_style(indexed: &Indexed<&Cell>, style: &TerminalStyle, text_style: &Text } ///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side() -fn cell_side(cur_size: SizeInfo, x: usize) -> Side { +fn cell_side(pos: &PaneRelativePos, cur_size: SizeInfo) -> Side { + let x = pos.0.x() as usize; let cell_x = x.saturating_sub(cur_size.cell_width() as usize) % cur_size.cell_width() as usize; let half_cell_width = (cur_size.cell_width() / 2.0) as usize; @@ -506,11 +606,14 @@ fn cell_side(cur_size: SizeInfo, x: usize) -> Side { } ///Copied (with modifications) from alacritty/src/event.rs > Mouse::point() -fn grid_cell(pos: Vector2F, cur_size: SizeInfo, display_offset: usize) -> Point { - let col = pos.x() - cur_size.cell_width() / cur_size.cell_width(); //TODO: underflow... +///Position is a pane-relative position. That means the top left corner of the mouse +///Region should be (0,0) +fn grid_cell(pos: &PaneRelativePos, cur_size: SizeInfo, display_offset: usize) -> Point { + let pos = pos.0; + let col = pos.x() / cur_size.cell_width(); //TODO: underflow... let col = min(GridCol(col as usize), cur_size.last_column()); - let line = pos.y() - cur_size.padding_y() / cur_size.cell_height(); + let line = pos.y() / cur_size.cell_height(); let line = min(line as usize, cur_size.bottommost_line().0 as usize); Point::new(GridLine((line - display_offset) as i32), col) From b2fa511acd7a57d29ec593ab671f10e13252e863 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 6 Jul 2022 17:52:20 -0700 Subject: [PATCH 13/47] GPUI change proposals --- crates/gpui/src/elements/event_handler.rs | 2 +- .../gpui/src/elements/mouse_event_handler.rs | 7 +++++-- crates/gpui/src/presenter.rs | 20 +++++++++++++++---- crates/gpui/src/scene.rs | 2 +- crates/workspace/src/sidebar.rs | 2 +- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/elements/event_handler.rs b/crates/gpui/src/elements/event_handler.rs index 7144b21dd0f19ed42dd9403e40173fdec4cf7506..1b025c3d3549bd8959c8040038bf9545a38a3de3 100644 --- a/crates/gpui/src/elements/event_handler.rs +++ b/crates/gpui/src/elements/event_handler.rs @@ -90,7 +90,7 @@ impl Element for EventHandler { click: Some(Rc::new(|_, _, _| {})), right_mouse_down: Some(Rc::new(|_, _| {})), right_click: Some(Rc::new(|_, _, _| {})), - drag: Some(Rc::new(|_, _| {})), + drag: Some(Rc::new(|_, _, _| {})), mouse_down_out: Some(Rc::new(|_, _| {})), right_mouse_down_out: Some(Rc::new(|_, _| {})), }); diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 8f70daf9e6d695a5c91bc9e1ecbbe508e5b878ae..832aafaa9e8b890cd613027c982cd2ef841c266e 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -24,7 +24,7 @@ pub struct MouseEventHandler { right_click: Option>, mouse_down_out: Option>, right_mouse_down_out: Option>, - drag: Option>, + drag: Option>, hover: Option>, padding: Padding, } @@ -106,7 +106,10 @@ impl MouseEventHandler { self } - pub fn on_drag(mut self, handler: impl Fn(Vector2F, &mut EventContext) + 'static) -> Self { + pub fn on_drag( + mut self, + handler: impl Fn(Vector2F, Vector2F, &mut EventContext) + 'static, + ) -> Self { self.drag = Some(Rc::new(handler)); self } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 6285b1be9931b30ada4d4673681ce335398bf252..fd3b70916afc89da4b25cf3202c53562167a2b95 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -306,8 +306,11 @@ impl Presenter { .as_ref() .zip(self.prev_drag_position.as_mut()) { - dragged_region = - Some((clicked_region.clone(), position - *prev_drag_position)); + dragged_region = Some(( + clicked_region.clone(), + position - *prev_drag_position, + position, + )); *prev_drag_position = position; } @@ -366,11 +369,11 @@ impl Presenter { } } - if let Some((dragged_region, delta)) = dragged_region { + if let Some((dragged_region, delta, position)) = dragged_region { handled = true; if let Some(drag_callback) = dragged_region.drag { event_cx.with_current_view(dragged_region.view_id, |event_cx| { - drag_callback(delta, event_cx); + drag_callback(delta, position, event_cx); }) } } @@ -648,6 +651,15 @@ impl<'a> PaintContext<'a> { } } + pub fn paint_layer(&mut self, clip_bounds: Option, f: F) + where + F: FnOnce(&mut Self) -> (), + { + self.scene.push_layer(clip_bounds); + f(self); + self.scene.pop_layer(); + } + pub fn current_view_id(&self) -> usize { *self.view_stack.last().unwrap() } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 1f0e2c0ecc0e3921f7a37625a5b32457e923fef9..a413d96981b5ededf133ae242f5eb351e8ce2c7f 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -54,7 +54,7 @@ pub struct MouseRegion { pub click: Option>, pub right_mouse_down: Option>, pub right_click: Option>, - pub drag: Option>, + pub drag: Option>, pub mouse_down_out: Option>, pub right_mouse_down_out: Option>, } diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 341026aecf3c064343e4d12fb9a0603a03c56ce7..c31998aa93995ff5f2f58efd8929bcfb4da76410 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -188,7 +188,7 @@ impl Sidebar { }) .with_cursor_style(CursorStyle::ResizeLeftRight) .on_mouse_down(|_, _| {}) // This prevents the mouse down event from being propagated elsewhere - .on_drag(move |delta, cx| { + .on_drag(move |delta, _, cx| { let prev_width = *actual_width.borrow(); *custom_width.borrow_mut() = 0f32 .max(match side { From baa011ccf451f88fee8e1a4da11834ee9512d308 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 6 Jul 2022 17:57:32 -0700 Subject: [PATCH 14/47] added inline hint --- crates/gpui/src/presenter.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index fd3b70916afc89da4b25cf3202c53562167a2b95..9810277a87a416f8d3845fba184dbe57a503fa37 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -651,6 +651,7 @@ impl<'a> PaintContext<'a> { } } + #[inline] pub fn paint_layer(&mut self, clip_bounds: Option, f: F) where F: FnOnce(&mut Self) -> (), From c6254247c3c5ddbf5b0bba84732312ec1a108b7b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Jul 2022 11:03:37 +0200 Subject: [PATCH 15/47] Allow providing an external format in `format_on_save` setting --- crates/editor/src/items.rs | 7 +- crates/language/src/buffer.rs | 10 +- crates/project/src/project.rs | 247 +++++++++++++++++++++++--------- crates/settings/src/settings.rs | 25 +++- 4 files changed, 200 insertions(+), 89 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index f7aa80beaa6aa9219d42e091d258306781aef62c..0e3aca1447043aaba3354fb925babcbaa324b35f 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -352,13 +352,8 @@ impl Item for Editor { project: ModelHandle, cx: &mut ViewContext, ) -> Task> { - let settings = cx.global::(); let buffer = self.buffer().clone(); - let mut buffers = buffer.read(cx).all_buffers(); - buffers.retain(|buffer| { - let language_name = buffer.read(cx).language().map(|l| l.name()); - settings.format_on_save(language_name.as_deref()) - }); + let buffers = buffer.read(cx).all_buffers(); let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse(); let format = project.update(cx, |project, cx| project.format(buffers, true, cx)); cx.spawn(|this, mut cx| async move { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index cebf5f504ea8c634a38155e5c5654362fb345b12..d5ed1c1620da0b514ba22f4c04e050f5043c67fb 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -273,7 +273,7 @@ pub struct Chunk<'a> { pub is_unnecessary: bool, } -pub(crate) struct Diff { +pub struct Diff { base_version: clock::Global, new_text: Arc, changes: Vec<(ChangeTag, usize)>, @@ -958,7 +958,7 @@ impl Buffer { } } - pub(crate) fn diff(&self, mut new_text: String, cx: &AppContext) -> Task { + pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task { let old_text = self.as_rope().clone(); let base_version = self.version(); cx.background().spawn(async move { @@ -979,11 +979,7 @@ impl Buffer { }) } - pub(crate) fn apply_diff( - &mut self, - diff: Diff, - cx: &mut ModelContext, - ) -> Option<&Transaction> { + pub fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext) -> Option<&Transaction> { if self.version == diff.base_version { self.finalize_last_transaction(); self.start_transaction(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e4425e341476dfb4a316bfdc9df5b010e97e0964..0ac3064e56b33840130d0e43a98d12d8908f4476 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -12,7 +12,7 @@ use anyhow::{anyhow, Context, Result}; use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; use collections::{hash_map, BTreeMap, HashMap, HashSet}; -use futures::{future::Shared, Future, FutureExt, StreamExt, TryFutureExt}; +use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt}; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, @@ -51,10 +51,12 @@ use std::{ ffi::OsString, hash::Hash, mem, + num::NonZeroU32, ops::Range, os::unix::{ffi::OsStrExt, prelude::OsStringExt}, path::{Component, Path, PathBuf}, rc::Rc, + str, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}, Arc, @@ -3025,78 +3027,50 @@ impl Project { } for (buffer, buffer_abs_path, language_server) in local_buffers { - let text_document = lsp::TextDocumentIdentifier::new( - lsp::Url::from_file_path(&buffer_abs_path).unwrap(), - ); - let capabilities = &language_server.capabilities(); - let tab_size = cx.update(|cx| { - let language_name = buffer.read(cx).language().map(|language| language.name()); - cx.global::().tab_size(language_name.as_deref()) + let (format_on_save, tab_size) = buffer.read_with(&cx, |buffer, cx| { + let settings = cx.global::(); + let language_name = buffer.language().map(|language| language.name()); + ( + settings.format_on_save(language_name.as_deref()), + settings.tab_size(language_name.as_deref()), + ) }); - let lsp_edits = if capabilities - .document_formatting_provider - .as_ref() - .map_or(false, |provider| *provider != lsp::OneOf::Left(false)) - { - language_server - .request::(lsp::DocumentFormattingParams { - text_document, - options: lsp::FormattingOptions { - tab_size: tab_size.into(), - insert_spaces: true, - insert_final_newline: Some(true), - ..Default::default() - }, - work_done_progress_params: Default::default(), - }) - .await? - } else if capabilities - .document_range_formatting_provider - .as_ref() - .map_or(false, |provider| *provider != lsp::OneOf::Left(false)) - { - let buffer_start = lsp::Position::new(0, 0); - let buffer_end = - buffer.read_with(&cx, |buffer, _| point_to_lsp(buffer.max_point_utf16())); - language_server - .request::( - lsp::DocumentRangeFormattingParams { - text_document, - range: lsp::Range::new(buffer_start, buffer_end), - options: lsp::FormattingOptions { - tab_size: tab_size.into(), - insert_spaces: true, - insert_final_newline: Some(true), - ..Default::default() - }, - work_done_progress_params: Default::default(), - }, + + let transaction = match format_on_save { + settings::FormatOnSave::Off => continue, + settings::FormatOnSave::LanguageServer => Self::format_via_lsp( + &this, + &buffer, + &buffer_abs_path, + &language_server, + tab_size, + &mut cx, + ) + .await + .context("failed to format via language server")?, + settings::FormatOnSave::External { command, arguments } => { + Self::format_via_external_command( + &buffer, + &buffer_abs_path, + &command, + &arguments, + &mut cx, ) - .await? - } else { - continue; + .await + .context(format!( + "failed to format via external command {:?}", + command + ))? + } }; - if let Some(lsp_edits) = lsp_edits { - let edits = this - .update(&mut cx, |this, cx| { - this.edits_from_lsp(&buffer, lsp_edits, None, cx) - }) - .await?; - buffer.update(&mut cx, |buffer, cx| { - buffer.finalize_last_transaction(); - buffer.start_transaction(); - for (range, text) in edits { - buffer.edit([(range, text)], cx); - } - if buffer.end_transaction(cx).is_some() { - let transaction = buffer.finalize_last_transaction().unwrap().clone(); - if !push_to_history { - buffer.forget_transaction(transaction.id); - } - project_transaction.0.insert(cx.handle(), transaction); - } - }); + if let Some(transaction) = transaction { + if !push_to_history { + buffer.update(&mut cx, |buffer, _| { + buffer.forget_transaction(transaction.id) + }); + } + project_transaction.0.insert(buffer, transaction); } } @@ -3104,6 +3078,141 @@ impl Project { }) } + async fn format_via_lsp( + this: &ModelHandle, + buffer: &ModelHandle, + abs_path: &Path, + language_server: &Arc, + tab_size: NonZeroU32, + cx: &mut AsyncAppContext, + ) -> Result> { + let text_document = + lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(abs_path).unwrap()); + let capabilities = &language_server.capabilities(); + let lsp_edits = if capabilities + .document_formatting_provider + .as_ref() + .map_or(false, |provider| *provider != lsp::OneOf::Left(false)) + { + language_server + .request::(lsp::DocumentFormattingParams { + text_document, + options: lsp::FormattingOptions { + tab_size: tab_size.into(), + insert_spaces: true, + insert_final_newline: Some(true), + ..Default::default() + }, + work_done_progress_params: Default::default(), + }) + .await? + } else if capabilities + .document_range_formatting_provider + .as_ref() + .map_or(false, |provider| *provider != lsp::OneOf::Left(false)) + { + let buffer_start = lsp::Position::new(0, 0); + let buffer_end = + buffer.read_with(cx, |buffer, _| point_to_lsp(buffer.max_point_utf16())); + language_server + .request::(lsp::DocumentRangeFormattingParams { + text_document, + range: lsp::Range::new(buffer_start, buffer_end), + options: lsp::FormattingOptions { + tab_size: tab_size.into(), + insert_spaces: true, + insert_final_newline: Some(true), + ..Default::default() + }, + work_done_progress_params: Default::default(), + }) + .await? + } else { + None + }; + + if let Some(lsp_edits) = lsp_edits { + let edits = this + .update(cx, |this, cx| { + this.edits_from_lsp(&buffer, lsp_edits, None, cx) + }) + .await?; + buffer.update(cx, |buffer, cx| { + buffer.finalize_last_transaction(); + buffer.start_transaction(); + for (range, text) in edits { + buffer.edit([(range, text)], cx); + } + if buffer.end_transaction(cx).is_some() { + let transaction = buffer.finalize_last_transaction().unwrap().clone(); + Ok(Some(transaction)) + } else { + Ok(None) + } + }) + } else { + Ok(None) + } + } + + async fn format_via_external_command( + buffer: &ModelHandle, + buffer_abs_path: &Path, + command: &str, + arguments: &[String], + cx: &mut AsyncAppContext, + ) -> Result> { + let working_dir_path = buffer.read_with(cx, |buffer, cx| { + let file = File::from_dyn(buffer.file())?; + let worktree = file.worktree.read(cx).as_local()?; + let mut worktree_path = worktree.abs_path().to_path_buf(); + if worktree.root_entry()?.is_file() { + worktree_path.pop(); + } + Some(worktree_path) + }); + + if let Some(working_dir_path) = working_dir_path { + let mut child = + smol::process::Command::new(command) + .args(arguments.iter().map(|arg| { + arg.replace("{buffer_path}", &buffer_abs_path.to_string_lossy()) + })) + .current_dir(&working_dir_path) + .stdin(smol::process::Stdio::piped()) + .stdout(smol::process::Stdio::piped()) + .stderr(smol::process::Stdio::piped()) + .spawn()?; + let stdin = child + .stdin + .as_mut() + .ok_or_else(|| anyhow!("failed to acquire stdin"))?; + let text = buffer.read_with(cx, |buffer, _| buffer.as_rope().clone()); + for chunk in text.chunks() { + stdin.write_all(chunk.as_bytes()).await?; + } + stdin.flush().await?; + + let output = child.output().await?; + if !output.status.success() { + return Err(anyhow!( + "command failed with exit code {:?}:\nstdout: {}\nstderr: {}", + output.status.code(), + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + )); + } + + let stdout = String::from_utf8(output.stdout)?; + let diff = buffer + .read_with(cx, |buffer, cx| buffer.diff(stdout, cx)) + .await; + Ok(buffer.update(cx, |buffer, cx| buffer.apply_diff(diff, cx).cloned())) + } else { + Ok(None) + } + } + pub fn definition( &self, buffer: &ModelHandle, diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 0a59d28cc9e845adb1ba4d3fd434245b101a5fbf..98df5e2f1f6be1d664fd4e63278ecac48b084454 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -38,7 +38,7 @@ pub struct LanguageSettings { pub hard_tabs: Option, pub soft_wrap: Option, pub preferred_line_length: Option, - pub format_on_save: Option, + pub format_on_save: Option, pub enable_language_server: Option, } @@ -50,6 +50,17 @@ pub enum SoftWrap { PreferredLineLength, } +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum FormatOnSave { + Off, + LanguageServer, + External { + command: String, + arguments: Vec, + }, +} + #[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Autosave { @@ -72,7 +83,7 @@ pub struct SettingsFileContent { #[serde(default)] pub vim_mode: Option, #[serde(default)] - pub format_on_save: Option, + pub format_on_save: Option, #[serde(default)] pub autosave: Option, #[serde(default)] @@ -136,9 +147,9 @@ impl Settings { .unwrap_or(80) } - pub fn format_on_save(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| settings.format_on_save) - .unwrap_or(true) + pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave { + self.language_setting(language, |settings| settings.format_on_save.clone()) + .unwrap_or(FormatOnSave::LanguageServer) } pub fn enable_language_server(&self, language: Option<&str>) -> bool { @@ -215,7 +226,7 @@ impl Settings { merge(&mut self.autosave, data.autosave); merge_option( &mut self.language_settings.format_on_save, - data.format_on_save, + data.format_on_save.clone(), ); merge_option( &mut self.language_settings.enable_language_server, @@ -339,7 +350,7 @@ fn merge(target: &mut T, value: Option) { } } -fn merge_option(target: &mut Option, value: Option) { +fn merge_option(target: &mut Option, value: Option) { if value.is_some() { *target = value; } From b91d44b448119562a7c88152848297c7494e6d09 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Jul 2022 11:52:56 +0200 Subject: [PATCH 16/47] Respond with a debug version of the error in rpc `Client` --- crates/client/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 538b0fa4b0101be73c64d366faf09238e5d8da16..0e9ec4076ad43754a53e11f833935c9b807f025c 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -549,7 +549,7 @@ impl Client { client.respond_with_error( receipt, proto::Error { - message: error.to_string(), + message: format!("{:?}", error), }, )?; Err(error) From 52b8efca1b5ccc77c063ea25972ebf00b4c70232 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Jul 2022 11:53:32 +0200 Subject: [PATCH 17/47] Add integration test to exercise formatting via external command --- crates/collab/src/integration_tests.rs | 34 ++++++++++++++++++++++---- crates/project/src/fs.rs | 26 ++------------------ crates/zed/src/zed.rs | 33 ++++++++++++++++--------- 3 files changed, 53 insertions(+), 40 deletions(-) diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index a4c8386b13fb58a88fd2a4a0f1d5664ed9226852..7767b361c1bbbd327ae765ab62a8308ca3cb6b61 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -35,7 +35,7 @@ use project::{ use rand::prelude::*; use rpc::PeerId; use serde_json::json; -use settings::Settings; +use settings::{FormatOnSave, Settings}; use sqlx::types::time::OffsetDateTime; use std::{ cell::RefCell, @@ -1912,7 +1912,6 @@ async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut Te #[gpui::test(iterations = 10)] async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { - cx_a.foreground().forbid_parking(); let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -1932,11 +1931,15 @@ async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppCon let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()); client_a.language_registry.add(Arc::new(language)); + // Here we insert a fake tree with a directory that exists on disk. This is needed + // because later we'll invoke a command, which requires passing a working directory + // that points to a valid location on disk. + let directory = env::current_dir().unwrap(); client_a .fs - .insert_tree("/a", json!({ "a.rs": "let one = two" })) + .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" })) .await; - let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await; let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await; let buffer_b = cx_b @@ -1967,7 +1970,28 @@ async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppCon .unwrap(); assert_eq!( buffer_b.read_with(cx_b, |buffer, _| buffer.text()), - "let honey = two" + "let honey = \"two\"" + ); + + // Ensure buffer can be formatted using an external command. Notice how the + // host's configuration is honored as opposed to using the guest's settings. + cx_a.update(|cx| { + cx.update_global(|settings: &mut Settings, _| { + settings.language_settings.format_on_save = Some(FormatOnSave::External { + command: "awk".to_string(), + arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()], + }); + }); + }); + project_b + .update(cx_b, |project, cx| { + project.format(HashSet::from_iter([buffer_b.clone()]), true, cx) + }) + .await + .unwrap(); + assert_eq!( + buffer_b.read_with(cx_b, |buffer, _| buffer.text()), + format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap()) ); } diff --git a/crates/project/src/fs.rs b/crates/project/src/fs.rs index 17d7264f1d80853a02707ccdff6308feeff178e9..5c528016118fdde2126f512170e5c46e6be9e171 100644 --- a/crates/project/src/fs.rs +++ b/crates/project/src/fs.rs @@ -334,28 +334,6 @@ impl FakeFs { }) } - pub async fn insert_dir(&self, path: impl AsRef) { - let mut state = self.state.lock().await; - let path = path.as_ref(); - state.validate_path(path).unwrap(); - - let inode = state.next_inode; - state.next_inode += 1; - state.entries.insert( - path.to_path_buf(), - FakeFsEntry { - metadata: Metadata { - inode, - mtime: SystemTime::now(), - is_dir: true, - is_symlink: false, - }, - content: None, - }, - ); - state.emit_event(&[path]).await; - } - pub async fn insert_file(&self, path: impl AsRef, content: String) { let mut state = self.state.lock().await; let path = path.as_ref(); @@ -392,7 +370,7 @@ impl FakeFs { match tree { Object(map) => { - self.insert_dir(path).await; + self.create_dir(path).await.unwrap(); for (name, contents) in map { let mut path = PathBuf::from(path); path.push(name); @@ -400,7 +378,7 @@ impl FakeFs { } } Null => { - self.insert_dir(&path).await; + self.create_dir(&path).await.unwrap(); } String(contents) => { self.insert_file(&path, contents).await; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f88aee3d7c6c06265e94e8c541e0c6ec44261e02..56ccfaf9fed86fbfddd5544d75565c808bdce24a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -554,7 +554,7 @@ mod tests { }); let save_task = workspace.update(cx, |workspace, cx| workspace.save_active_item(false, cx)); - app_state.fs.as_fake().insert_dir("/root").await; + app_state.fs.create_dir(Path::new("/root")).await.unwrap(); cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name"))); save_task.await.unwrap(); editor.read_with(cx, |editor, cx| { @@ -680,14 +680,25 @@ mod tests { async fn test_open_paths(cx: &mut TestAppContext) { let app_state = init(cx); - let fs = app_state.fs.as_fake(); - fs.insert_dir("/dir1").await; - fs.insert_dir("/dir2").await; - fs.insert_dir("/dir3").await; - fs.insert_file("/dir1/a.txt", "".into()).await; - fs.insert_file("/dir2/b.txt", "".into()).await; - fs.insert_file("/dir3/c.txt", "".into()).await; - fs.insert_file("/d.txt", "".into()).await; + app_state + .fs + .as_fake() + .insert_tree( + "/", + json!({ + "dir1": { + "a.txt": "" + }, + "dir2": { + "b.txt": "" + }, + "dir3": { + "c.txt": "" + }, + "d.txt": "" + }), + ) + .await; let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); @@ -891,7 +902,7 @@ mod tests { #[gpui::test] async fn test_open_and_save_new_file(cx: &mut TestAppContext) { let app_state = init(cx); - app_state.fs.as_fake().insert_dir("/root").await; + app_state.fs.create_dir(Path::new("/root")).await.unwrap(); let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; project.update(cx, |project, _| project.languages().add(rust_lang())); @@ -980,7 +991,7 @@ mod tests { #[gpui::test] async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) { let app_state = init(cx); - app_state.fs.as_fake().insert_dir("/root").await; + app_state.fs.create_dir(Path::new("/root")).await.unwrap(); let project = Project::test(app_state.fs.clone(), [], cx).await; project.update(cx, |project, _| project.languages().add(rust_lang())); From 9c518085aeed7b388035c494bc8c1b27421fa253 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 11:01:26 -0700 Subject: [PATCH 18/47] Fixed working directory issues, added tests. Working on regression --- Cargo.lock | 2 + crates/terminal/Cargo.toml | 4 ++ crates/terminal/src/terminal.rs | 99 ++++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5dbc470f597c8e3f17c9bfc3052115d51886227..634797507be3b71e9942be0cdee5e43f98924dc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4878,6 +4878,8 @@ name = "terminal" version = "0.1.0" dependencies = [ "alacritty_terminal", + "client", + "dirs 4.0.0", "editor", "futures", "gpui", diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 0bbc0569227198a45538016b63a24f35d079abd0..b44b93e745ccabe06da58d540ded3eec17f3a145 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -21,7 +21,11 @@ mio-extras = "2.0.6" futures = "0.3" ordered-float = "2.1.1" itertools = "0.10" +dirs = "4.0.0" [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } +client = { path = "../client", features = ["test-support"]} +project = { path = "../project", features = ["test-support"]} + diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index eb7f2a0a90b0d6d4b1204bdc13396634145c4371..846ed2680665264c0dfa4d4d0c0d6af74000fc52 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -9,6 +9,7 @@ use alacritty_terminal::{ Term, }; +use dirs::home_dir; use futures::{ channel::mpsc::{unbounded, UnboundedSender}, StreamExt, @@ -17,7 +18,7 @@ use gpui::{ actions, color::Color, elements::*, impl_internal_actions, platform::CursorStyle, ClipboardItem, Entity, MutableAppContext, View, ViewContext, }; -use project::{Project, ProjectPath}; +use project::{LocalWorktree, Project, ProjectPath}; use settings::Settings; use smallvec::SmallVec; use std::{collections::HashMap, path::PathBuf, sync::Arc}; @@ -268,11 +269,12 @@ impl Terminal { ///Create a new Terminal in the current working directory or the user's home directory fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { let project = workspace.project().read(cx); + let abs_path = project .active_entry() .and_then(|entry_id| project.worktree_for_entry(entry_id, cx)) .and_then(|worktree_handle| worktree_handle.read(cx).as_local()) - .map(|wt| wt.abs_path().to_path_buf()); + .and_then(get_working_directory); workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(cx, abs_path))), cx); } @@ -477,19 +479,28 @@ fn to_alac_rgb(color: Color) -> AlacRgb { } } +fn get_working_directory(wt: &LocalWorktree) -> Option { + Some(wt.abs_path().to_path_buf()) + .filter(|path| path.is_dir()) + .or_else(|| home_dir()) +} + #[cfg(test)] mod tests { + + use std::{path::Path, sync::atomic::AtomicUsize}; + use super::*; use alacritty_terminal::{grid::GridIterator, term::cell::Cell}; use gpui::TestAppContext; use itertools::Itertools; + use project::{FakeFs, Fs, RealFs, RemoveOptions, Worktree}; ///Basic integration test, can we get the terminal to show up, execute a command, //and produce noticable output? #[gpui::test] async fn test_terminal(cx: &mut TestAppContext) { let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None)); - terminal.update(cx, |terminal, cx| { terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx); terminal.carriage_return(&Return, cx); @@ -499,6 +510,7 @@ mod tests { .condition(cx, |terminal, _cx| { let term = terminal.term.clone(); let content = grid_as_str(term.lock().renderable_content().display_iter); + dbg!(&content); content.contains("7") }) .await; @@ -512,4 +524,85 @@ mod tests { .collect::>() .join("\n") } + + #[gpui::test] + async fn single_file_worktree(cx: &mut TestAppContext) { + let mut async_cx = cx.to_async(); + let http_client = client::test::FakeHttpClient::with_404_response(); + let client = client::Client::new(http_client.clone()); + let fake_fs = FakeFs::new(cx.background().clone()); + + let path = Path::new("/file/"); + fake_fs.insert_file(path, "a".to_string()).await; + + let worktree_handle = Worktree::local( + client, + path, + true, + fake_fs, + Arc::new(AtomicUsize::new(0)), + &mut async_cx, + ) + .await + .ok() + .unwrap(); + + async_cx.update(|cx| { + let wt = worktree_handle.read(cx).as_local().unwrap(); + let wd = get_working_directory(wt); + assert!(wd.is_some()); + let path = wd.unwrap(); + //This should be the system's working directory, so querying the real file system is probably ok. + assert!(path.is_dir()); + assert_eq!(path, home_dir().unwrap()); + }); + } + + #[gpui::test] + async fn test_worktree_directory(cx: &mut TestAppContext) { + let mut async_cx = cx.to_async(); + let http_client = client::test::FakeHttpClient::with_404_response(); + let client = client::Client::new(http_client.clone()); + + let fs = RealFs; + let mut test_wd = home_dir().unwrap(); + test_wd.push("dir"); + + fs.create_dir(test_wd.as_path()) + .await + .expect("File could not be created"); + + let worktree_handle = Worktree::local( + client, + test_wd.clone(), + true, + Arc::new(RealFs), + Arc::new(AtomicUsize::new(0)), + &mut async_cx, + ) + .await + .ok() + .unwrap(); + + async_cx.update(|cx| { + let wt = worktree_handle.read(cx).as_local().unwrap(); + let wd = get_working_directory(wt); + assert!(wd.is_some()); + let path = wd.unwrap(); + assert!(path.is_dir()); + assert_eq!(path, test_wd); + }); + + //Clean up after ourselves. + fs.remove_dir( + test_wd.as_path(), + RemoveOptions { + recursive: false, + ignore_if_not_exists: true, + }, + ) + .await + .ok() + .expect("Could not remove test directory"); + } } From 02525c5bbefe9ed28b228398042863eb7a20636f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 12:04:17 -0700 Subject: [PATCH 19/47] Added a way to change the timeout with state --- crates/gpui/src/app.rs | 23 +++++++++++++++++------ crates/terminal/src/terminal.rs | 5 +++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 505f609f5709f57b0c037ec5be2032729f736266..bcb8908629500c0916ea4110f28119b7e0189869 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -151,6 +151,7 @@ pub struct AsyncAppContext(Rc>); pub struct TestAppContext { cx: Rc>, foreground_platform: Rc, + condition_duration: Option, } impl App { @@ -337,6 +338,7 @@ impl TestAppContext { let cx = TestAppContext { cx: Rc::new(RefCell::new(cx)), foreground_platform, + condition_duration: None, }; cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx)); cx @@ -612,6 +614,19 @@ impl TestAppContext { test_window }) } + + pub fn set_condition_duration(&mut self, duration: Duration) { + self.condition_duration = Some(duration); + } + pub fn condition_duration(&self) -> Duration { + self.condition_duration.unwrap_or_else(|| { + if std::env::var("CI").is_ok() { + Duration::from_secs(2) + } else { + Duration::from_millis(500) + } + }) + } } impl AsyncAppContext { @@ -4398,6 +4413,7 @@ impl ViewHandle { use postage::prelude::{Sink as _, Stream as _}; let (tx, mut rx) = postage::mpsc::channel(1024); + let timeout_duration = cx.condition_duration(); let mut cx = cx.cx.borrow_mut(); let subscriptions = self.update(&mut *cx, |_, cx| { @@ -4419,14 +4435,9 @@ impl ViewHandle { let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap(); let handle = self.downgrade(); - let duration = if std::env::var("CI").is_ok() { - Duration::from_secs(2) - } else { - Duration::from_millis(500) - }; async move { - crate::util::timeout(duration, async move { + crate::util::timeout(timeout_duration, async move { loop { { let cx = cx.borrow(); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 846ed2680665264c0dfa4d4d0c0d6af74000fc52..a99e211dca24eb8f9de3d182b48abdc443462c26 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -488,7 +488,7 @@ fn get_working_directory(wt: &LocalWorktree) -> Option { #[cfg(test)] mod tests { - use std::{path::Path, sync::atomic::AtomicUsize}; + use std::{path::Path, sync::atomic::AtomicUsize, time::Duration}; use super::*; use alacritty_terminal::{grid::GridIterator, term::cell::Cell}; @@ -501,6 +501,8 @@ mod tests { #[gpui::test] async fn test_terminal(cx: &mut TestAppContext) { let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None)); + cx.set_condition_duration(Duration::from_secs(2)); + terminal.update(cx, |terminal, cx| { terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx); terminal.carriage_return(&Return, cx); @@ -510,7 +512,6 @@ mod tests { .condition(cx, |terminal, _cx| { let term = terminal.term.clone(); let content = grid_as_str(term.lock().renderable_content().display_iter); - dbg!(&content); content.contains("7") }) .await; From 240f3d8754c9a11c8499cafd7e7aa865932f215d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 12:29:49 -0700 Subject: [PATCH 20/47] Fixed default shell --- crates/terminal/src/terminal.rs | 4 ++-- crates/terminal/src/terminal_element.rs | 4 ---- styles/package-lock.json | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 59ff100a7fb2eb871332354987a4e8b7498c847b..1b929b69419b0ae97f53b527c99e9376b71624af 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,5 +1,5 @@ use alacritty_terminal::{ - config::{Config, Program, PtyConfig}, + config::{Config, PtyConfig}, event::{Event as AlacTermEvent, EventListener, Notify}, event_loop::{EventLoop, Msg, Notifier}, grid::Scroll, @@ -126,7 +126,7 @@ impl Terminal { .detach(); let pty_config = PtyConfig { - shell: Some(Program::Just("zsh".to_string())), + shell: None, //Use the users default shell working_directory, hold: false, }; diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index a339c2c6f942c18f1fcb8384ffe6a9471d1140d4..4a162cc99c27e709121acf4fad1ad7714bb6f3ec 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -143,10 +143,6 @@ impl Element for TerminalEl { let content = term.renderable_content(); - //We have a 'SelectionRange' struct to work with, - //Allows us to query start, end, and contains - //content.selection.unwrap() - let layout_lines = layout_lines( content.display_iter, &text_style, diff --git a/styles/package-lock.json b/styles/package-lock.json index 49304dc2fa98dfec942bf6687be56cefc13cdf80..2eb6d3a1bfaeaa206f0cc8a0a03efa0387d144c2 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From ec4082695b6f442745b3d0f3f80638bbdac10b3d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 12:31:21 -0700 Subject: [PATCH 21/47] Now defaults to using user's shell --- crates/terminal/src/terminal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index a99e211dca24eb8f9de3d182b48abdc443462c26..57a78fbe78db8a1b3b62c1b3506d3ef8449088cd 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,5 +1,5 @@ use alacritty_terminal::{ - config::{Config, Program, PtyConfig}, + config::{Config, PtyConfig}, event::{Event as AlacTermEvent, EventListener, Notify}, event_loop::{EventLoop, Msg, Notifier}, grid::Scroll, @@ -125,7 +125,7 @@ impl Terminal { .detach(); let pty_config = PtyConfig { - shell: Some(Program::Just("zsh".to_string())), + shell: None, working_directory, hold: false, }; From 98f6dccd43f80ca79306efd4cf5409f5ebd31c87 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:01:16 -0700 Subject: [PATCH 22/47] Fixed terminal clone on split --- crates/terminal/src/terminal.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 57a78fbe78db8a1b3b62c1b3506d3ef8449088cd..4542e283ee2fd5ee5c0db34bace104fdada1f861 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -91,6 +91,7 @@ pub struct Terminal { has_new_content: bool, has_bell: bool, //Currently using iTerm bell, show bell emoji in tab until input is received cur_size: SizeInfo, + associated_directory: Option, } ///Upward flowing events, for changing the title and such @@ -126,7 +127,7 @@ impl Terminal { let pty_config = PtyConfig { shell: None, - working_directory, + working_directory: working_directory.clone(), hold: false, }; @@ -173,6 +174,7 @@ impl Terminal { has_new_content: false, has_bell: false, cur_size: size_info, + associated_directory: working_directory, } } @@ -410,6 +412,13 @@ impl Item for Terminal { .boxed() } + fn clone_on_split(&self, cx: &mut ViewContext) -> Option { + //From what I can tell, there's no way to tell the current working + //Directory of the terminal from outside the terminal. There might be + //solutions to this, but they are non-trivial and require more IPC + Some(Terminal::new(cx, self.associated_directory.clone())) + } + fn project_path(&self, _cx: &gpui::AppContext) -> Option { None } From 4e3c32c2776cc8b77b0cb829fbe6408a577b337a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:19:38 -0700 Subject: [PATCH 23/47] Added copying --- assets/keymaps/default.json | 3 ++- crates/terminal/src/terminal.rs | 13 ++++++++++++- pbcpoy | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 pbcpoy diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index bea53ece45b2a672879453bb522fd64814e635f3..6cd3660bf5120d4b16f1f6988588a537b7b92a31 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -417,7 +417,8 @@ "up": "terminal::Up", "down": "terminal::Down", "tab": "terminal::Tab", - "cmd-v": "terminal::Paste" + "cmd-v": "terminal::Paste", + "cmd-c": "terminal::Copy" } } ] \ No newline at end of file diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 1b929b69419b0ae97f53b527c99e9376b71624af..f6109572a77280e526f6266df4120212bfd7f11b 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -52,7 +52,7 @@ pub struct ScrollTerminal(pub i32); actions!( terminal, - [Sigint, Escape, Del, Return, Left, Right, Up, Down, Tab, Clear, Paste, Deploy, Quit] + [Sigint, Escape, Del, Return, Left, Right, Up, Down, Tab, Clear, Copy, Paste, Deploy, Quit] ); impl_internal_actions!(terminal, [Input, ScrollTerminal]); @@ -70,6 +70,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::up); cx.add_action(Terminal::down); cx.add_action(Terminal::tab); + cx.add_action(Terminal::copy); cx.add_action(Terminal::paste); cx.add_action(Terminal::scroll_terminal); } @@ -272,6 +273,16 @@ impl Terminal { cx.emit(Event::CloseTerminal); } + ///Attempt to paste the clipboard into the terminal + fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { + let term = self.term.lock(); + let copy_text = term.selection_to_string(); + match copy_text { + Some(s) => cx.write_to_clipboard(ClipboardItem::new(s)), + None => (), + } + } + ///Attempt to paste the clipboard into the terminal fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { if let Some(item) = cx.read_from_clipboard() { diff --git a/pbcpoy b/pbcpoy new file mode 100644 index 0000000000000000000000000000000000000000..f70f10e4db19068f79bc43844b49f3eece45c4e8 --- /dev/null +++ b/pbcpoy @@ -0,0 +1 @@ +A From e3f492e13a3c75c2d80a7c8a62a7a72ec24e2477 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:29:58 -0700 Subject: [PATCH 24/47] Hoisted assert clipboard into TestAppContext --- crates/editor/src/editor.rs | 2 +- crates/editor/src/test.rs | 8 -------- crates/gpui/src/app.rs | 8 ++++++++ crates/vim/src/vim_test_context.rs | 8 -------- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c1e25575557bef78921841f1c2a9eb8e589085a2..808926ff50f3e71b157f6b8380980fc1bd109044 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8232,7 +8232,7 @@ mod tests { fox ju|mps over the lazy dog"}); cx.update_editor(|e, cx| e.copy(&Copy, cx)); - cx.assert_clipboard_content(Some("fox jumps over\n")); + cx.cx.assert_clipboard_content(Some("fox jumps over\n")); // Paste with three selections, noticing how the copied full-line selection is inserted // before the empty selections but replaces the selection that is non-empty. diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index d1316a85a0bf38afe5c39cb596b6647b89119252..0affe06f64b555890ea3b6289ec497ace6aeb18d 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -404,14 +404,6 @@ impl<'a> EditorTestContext<'a> { editor_text_with_selections } - - pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) { - self.cx.update(|cx| { - let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned()); - let expected_content = expected_content.map(|content| content.to_owned()); - assert_eq!(actual_content, expected_content); - }) - } } impl<'a> Deref for EditorTestContext<'a> { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index b81714e0bc71474c753ee018b9fce161871f67f9..26c4fe01a8c54b54a080b54faf07a8b93bfa7b43 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -627,6 +627,14 @@ impl TestAppContext { } }) } + + pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) { + self.update(|cx| { + let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned()); + let expected_content = expected_content.map(|content| content.to_owned()); + assert_eq!(actual_content, expected_content); + }) + } } impl AsyncAppContext { diff --git a/crates/vim/src/vim_test_context.rs b/crates/vim/src/vim_test_context.rs index 57d0174703bbf5da9781c1f6f70f5bb23a9527d7..08ec4bd5e961315fa8c661ffbcfac89ab3a59207 100644 --- a/crates/vim/src/vim_test_context.rs +++ b/crates/vim/src/vim_test_context.rs @@ -147,14 +147,6 @@ impl<'a> VimTestContext<'a> { let mode = self.mode(); VimBindingTestContext::new(keystrokes, mode, mode, self) } - - pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) { - self.cx.update(|cx| { - let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned()); - let expected_content = expected_content.map(|content| content.to_owned()); - assert_eq!(actual_content, expected_content); - }) - } } impl<'a> Deref for VimTestContext<'a> { From 6642b78331bd05f36d3c1fd69daa634e4ee38db5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 7 Jul 2022 13:36:08 -0700 Subject: [PATCH 25/47] Add tooltips to pane nav buttons and make them trigger on click --- crates/workspace/src/toolbar.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index 9e0c085b1f45dbfc85816b7dae3591d6f21909c6..636df9a03955ff2ebc7c98c63f3b8d9963e3ea6c 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -112,6 +112,7 @@ impl View for Toolbar { let container_style = theme.container; let height = theme.height; let button_style = theme.nav_button; + let tooltip_style = cx.global::().theme.tooltip.clone(); Flex::column() .with_child( @@ -119,21 +120,27 @@ impl View for Toolbar { .with_child(nav_button( "icons/arrow-left.svg", button_style, + tooltip_style.clone(), enable_go_backward, spacing, super::GoBack { pane: Some(pane.clone()), }, + super::GoBack { pane: None }, + "Go Back", cx, )) .with_child(nav_button( "icons/arrow-right.svg", button_style, + tooltip_style.clone(), enable_go_forward, spacing, super::GoForward { pane: Some(pane.clone()), }, + super::GoForward { pane: None }, + "Go Forward", cx, )) .with_children(primary_left_items) @@ -152,9 +159,12 @@ impl View for Toolbar { fn nav_button( svg_path: &'static str, style: theme::Interactive, + tooltip_style: TooltipStyle, enabled: bool, spacing: f32, action: A, + tooltip_action: A, + action_name: &str, cx: &mut RenderContext, ) -> ElementBox { MouseEventHandler::new::(0, cx, |state, _| { @@ -181,7 +191,14 @@ fn nav_button( } else { CursorStyle::default() }) - .on_mouse_down(move |_, cx| cx.dispatch_action(action.clone())) + .on_click(move |_, _, cx| cx.dispatch_action(action.clone())) + .with_tooltip::( + 0, + action_name.to_string(), + Some(Box::new(tooltip_action)), + tooltip_style, + cx, + ) .contained() .with_margin_right(spacing) .boxed() From 4bd111111576f03c6329a43ffc92e63323643969 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:43:28 -0700 Subject: [PATCH 26/47] Added a small integration test --- crates/terminal/src/terminal.rs | 72 +++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 1d508fb49147450248fce4c29008bc23b142034e..84f205585732c22c92cf5fdc51e8720adc0e71ce 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -37,6 +37,8 @@ const RIGHT_SEQ: &str = "\x1b[C"; const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; const DEFAULT_TITLE: &str = "Terminal"; +const DEBUG_TERMINAL_WIDTH: f32 = 300.; +const DEBUG_TERMINAL_HEIGHT: f32 = 200.; pub mod color_translation; pub mod gpui_func_tools; @@ -146,8 +148,15 @@ impl Terminal { setup_env(&config); //The details here don't matter, the terminal will be resized on the first layout - //Set to something small for easier debugging - let size_info = SizeInfo::new(200., 100.0, 5., 5., 0., 0., false); + let size_info = SizeInfo::new( + DEBUG_TERMINAL_WIDTH, + DEBUG_TERMINAL_HEIGHT, + 5., + 5., + 0., + 0., + false, + ); //Set up the terminal... let term = Term::new(&config, size_info, ZedListener(events_tx.clone())); @@ -485,7 +494,12 @@ mod tests { use std::{path::Path, sync::atomic::AtomicUsize, time::Duration}; use super::*; - use alacritty_terminal::{grid::GridIterator, term::cell::Cell}; + use alacritty_terminal::{ + grid::GridIterator, + index::{Column, Line, Point, Side}, + selection::{Selection, SelectionType}, + term::cell::Cell, + }; use gpui::TestAppContext; use itertools::Itertools; use project::{FakeFs, Fs, RealFs, RemoveOptions, Worktree}; @@ -511,15 +525,6 @@ mod tests { .await; } - pub(crate) fn grid_as_str(grid_iterator: GridIterator) -> String { - let lines = grid_iterator.group_by(|i| i.point.line.0); - lines - .into_iter() - .map(|(_, line)| line.map(|i| i.c).collect::()) - .collect::>() - .join("\n") - } - #[gpui::test] async fn single_file_worktree(cx: &mut TestAppContext) { let mut async_cx = cx.to_async(); @@ -600,4 +605,47 @@ mod tests { .ok() .expect("Could not remove test directory"); } + + ///Basic integration test, can we get the terminal to show up, execute a command, + //and produce noticable output? + #[gpui::test] + async fn test_copy(cx: &mut TestAppContext) { + let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None)); + cx.set_condition_duration(Duration::from_secs(2)); + + terminal.update(cx, |terminal, cx| { + terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx); + terminal.carriage_return(&Return, cx); + }); + + terminal + .condition(cx, |terminal, _cx| { + let term = terminal.term.clone(); + let content = grid_as_str(term.lock().renderable_content().display_iter); + content.contains("7") + }) + .await; + + terminal.update(cx, |terminal, cx| { + let mut term = terminal.term.lock(); + term.selection = Some(Selection::new( + SelectionType::Semantic, + Point::new(Line(3), Column(0)), + Side::Right, + )); + drop(term); + terminal.copy(&Copy, cx) + }); + + cx.assert_clipboard_content(Some(&"7")); + } + + pub(crate) fn grid_as_str(grid_iterator: GridIterator) -> String { + let lines = grid_iterator.group_by(|i| i.point.line.0); + lines + .into_iter() + .map(|(_, line)| line.map(|i| i.c).collect::()) + .collect::>() + .join("\n") + } } From d981f4a3f42c70dfb0db0044db15be7626bfa18c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:45:27 -0700 Subject: [PATCH 27/47] tidied up magic constants --- crates/terminal/src/terminal.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 84f205585732c22c92cf5fdc51e8720adc0e71ce..d56864b631f74ec62e56b70642f1bc9d95c8a714 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -39,6 +39,8 @@ const DOWN_SEQ: &str = "\x1b[B"; const DEFAULT_TITLE: &str = "Terminal"; const DEBUG_TERMINAL_WIDTH: f32 = 300.; const DEBUG_TERMINAL_HEIGHT: f32 = 200.; +const DEBUG_CELL_WIDTH: f32 = 5.; +const DEBUG_LINE_HEIGHT: f32 = 5.; pub mod color_translation; pub mod gpui_func_tools; @@ -151,8 +153,8 @@ impl Terminal { let size_info = SizeInfo::new( DEBUG_TERMINAL_WIDTH, DEBUG_TERMINAL_HEIGHT, - 5., - 5., + DEBUG_CELL_WIDTH, + DEBUG_LINE_HEIGHT, 0., 0., false, From 28fd1ccbc655c895da316bca3b2b8aa912ea87db Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:55:58 -0700 Subject: [PATCH 28/47] Added another minor test --- crates/terminal/src/terminal_element.rs | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 4a162cc99c27e709121acf4fad1ad7714bb6f3ec..1856c8ac31021eebb7a392f0346824edbffb7aa5 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -645,3 +645,40 @@ fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContex }); } } + +mod test { + + #[test] + fn test_mouse_to_selection() { + let term_width = 100.; + let term_height = 200.; + let cell_width = 10.; + let line_height = 20.; + let mouse_pos_x = 100.; //Window relative + let mouse_pos_y = 100.; //Window relative + let origin_x = 10.; + let origin_y = 20.; + + let cur_size = alacritty_terminal::term::SizeInfo::new( + term_width, + term_height, + cell_width, + line_height, + 0., + 0., + false, + ); + + let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y); + let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in + let (point, _) = + crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0); + assert_eq!( + point, + alacritty_terminal::index::Point::new( + alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32), + alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize), + ) + ); + } +} From 49bd51c7c16324887f319db1f3ef146f095b7a36 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 14:38:21 -0700 Subject: [PATCH 29/47] Fixed integration test --- crates/terminal/src/terminal.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index d56864b631f74ec62e56b70642f1bc9d95c8a714..40b2cba6cb79670876f8c2a360db44df67b92b1d 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -37,7 +37,7 @@ const RIGHT_SEQ: &str = "\x1b[C"; const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; const DEFAULT_TITLE: &str = "Terminal"; -const DEBUG_TERMINAL_WIDTH: f32 = 300.; +const DEBUG_TERMINAL_WIDTH: f32 = 1000.; //This needs to be wide enough that the prompt can fill the whole space. const DEBUG_TERMINAL_HEIGHT: f32 = 200.; const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.; @@ -608,8 +608,7 @@ mod tests { .expect("Could not remove test directory"); } - ///Basic integration test, can we get the terminal to show up, execute a command, - //and produce noticable output? + ///If this test is failing for you, check that DEBUG_TERMINAL_WIDTH is wide enough to fit your entire command prompt! #[gpui::test] async fn test_copy(cx: &mut TestAppContext) { let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None)); @@ -632,7 +631,7 @@ mod tests { let mut term = terminal.term.lock(); term.selection = Some(Selection::new( SelectionType::Semantic, - Point::new(Line(3), Column(0)), + Point::new(Line(2), Column(0)), Side::Right, )); drop(term); From 2c6dcb82ef98aee1dedc0a94a25192a2c6c680e3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 6 Jul 2022 17:52:20 -0700 Subject: [PATCH 30/47] GPUI change proposals --- crates/gpui/src/elements/event_handler.rs | 2 +- .../gpui/src/elements/mouse_event_handler.rs | 7 +++++-- crates/gpui/src/presenter.rs | 20 +++++++++++++++---- crates/gpui/src/scene.rs | 2 +- crates/workspace/src/sidebar.rs | 2 +- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/elements/event_handler.rs b/crates/gpui/src/elements/event_handler.rs index 7144b21dd0f19ed42dd9403e40173fdec4cf7506..1b025c3d3549bd8959c8040038bf9545a38a3de3 100644 --- a/crates/gpui/src/elements/event_handler.rs +++ b/crates/gpui/src/elements/event_handler.rs @@ -90,7 +90,7 @@ impl Element for EventHandler { click: Some(Rc::new(|_, _, _| {})), right_mouse_down: Some(Rc::new(|_, _| {})), right_click: Some(Rc::new(|_, _, _| {})), - drag: Some(Rc::new(|_, _| {})), + drag: Some(Rc::new(|_, _, _| {})), mouse_down_out: Some(Rc::new(|_, _| {})), right_mouse_down_out: Some(Rc::new(|_, _| {})), }); diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 8f70daf9e6d695a5c91bc9e1ecbbe508e5b878ae..832aafaa9e8b890cd613027c982cd2ef841c266e 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -24,7 +24,7 @@ pub struct MouseEventHandler { right_click: Option>, mouse_down_out: Option>, right_mouse_down_out: Option>, - drag: Option>, + drag: Option>, hover: Option>, padding: Padding, } @@ -106,7 +106,10 @@ impl MouseEventHandler { self } - pub fn on_drag(mut self, handler: impl Fn(Vector2F, &mut EventContext) + 'static) -> Self { + pub fn on_drag( + mut self, + handler: impl Fn(Vector2F, Vector2F, &mut EventContext) + 'static, + ) -> Self { self.drag = Some(Rc::new(handler)); self } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 6285b1be9931b30ada4d4673681ce335398bf252..fd3b70916afc89da4b25cf3202c53562167a2b95 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -306,8 +306,11 @@ impl Presenter { .as_ref() .zip(self.prev_drag_position.as_mut()) { - dragged_region = - Some((clicked_region.clone(), position - *prev_drag_position)); + dragged_region = Some(( + clicked_region.clone(), + position - *prev_drag_position, + position, + )); *prev_drag_position = position; } @@ -366,11 +369,11 @@ impl Presenter { } } - if let Some((dragged_region, delta)) = dragged_region { + if let Some((dragged_region, delta, position)) = dragged_region { handled = true; if let Some(drag_callback) = dragged_region.drag { event_cx.with_current_view(dragged_region.view_id, |event_cx| { - drag_callback(delta, event_cx); + drag_callback(delta, position, event_cx); }) } } @@ -648,6 +651,15 @@ impl<'a> PaintContext<'a> { } } + pub fn paint_layer(&mut self, clip_bounds: Option, f: F) + where + F: FnOnce(&mut Self) -> (), + { + self.scene.push_layer(clip_bounds); + f(self); + self.scene.pop_layer(); + } + pub fn current_view_id(&self) -> usize { *self.view_stack.last().unwrap() } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 1f0e2c0ecc0e3921f7a37625a5b32457e923fef9..a413d96981b5ededf133ae242f5eb351e8ce2c7f 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -54,7 +54,7 @@ pub struct MouseRegion { pub click: Option>, pub right_mouse_down: Option>, pub right_click: Option>, - pub drag: Option>, + pub drag: Option>, pub mouse_down_out: Option>, pub right_mouse_down_out: Option>, } diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 341026aecf3c064343e4d12fb9a0603a03c56ce7..c31998aa93995ff5f2f58efd8929bcfb4da76410 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -188,7 +188,7 @@ impl Sidebar { }) .with_cursor_style(CursorStyle::ResizeLeftRight) .on_mouse_down(|_, _| {}) // This prevents the mouse down event from being propagated elsewhere - .on_drag(move |delta, cx| { + .on_drag(move |delta, _, cx| { let prev_width = *actual_width.borrow(); *custom_width.borrow_mut() = 0f32 .max(match side { From 9b6167aad825cd3abeb8143cec59f11ef04a7f11 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 6 Jul 2022 17:57:32 -0700 Subject: [PATCH 31/47] added inline hint --- crates/gpui/src/presenter.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index fd3b70916afc89da4b25cf3202c53562167a2b95..9810277a87a416f8d3845fba184dbe57a503fa37 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -651,6 +651,7 @@ impl<'a> PaintContext<'a> { } } + #[inline] pub fn paint_layer(&mut self, clip_bounds: Option, f: F) where F: FnOnce(&mut Self) -> (), From 86d57940405169aa5ea53b8fb0547f6142a3130c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 14:51:59 -0700 Subject: [PATCH 32/47] Rebasing onto main --- crates/terminal/src/color_translation.rs | 134 ++++++++++++++++ crates/terminal/src/terminal.rs | 44 ++---- crates/terminal/src/terminal_element.rs | 190 ++++++++++------------- 3 files changed, 230 insertions(+), 138 deletions(-) create mode 100644 crates/terminal/src/color_translation.rs diff --git a/crates/terminal/src/color_translation.rs b/crates/terminal/src/color_translation.rs new file mode 100644 index 0000000000000000000000000000000000000000..78c2a569dbbeac5806e6535a2c87b7c0b6b57c03 --- /dev/null +++ b/crates/terminal/src/color_translation.rs @@ -0,0 +1,134 @@ +use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb}; +use gpui::color::Color; +use theme::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), + } +} + +///Converts an 8 bit ANSI color to it's GPUI equivalent. +///Accepts usize for compatability 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 compatability 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!(i >= &16 && i <= &231); + 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) +} + +//Convenience method to convert from a GPUI color to an alacritty Rgb +pub fn to_alac_rgb(color: Color) -> AlacRgb { + AlacRgb { + r: color.r, + g: color.g, + b: color.g, + } +} + +#[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::color_translation::rgb_for_index(&(i as u8)); + assert_eq!(i, 16 + 36 * r + 6 * g + b); + } + } +} diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 4542e283ee2fd5ee5c0db34bace104fdada1f861..c2df86de6144ed89f03fb01b179524dd594f7251 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -4,19 +4,23 @@ use alacritty_terminal::{ event_loop::{EventLoop, Msg, Notifier}, grid::Scroll, sync::FairMutex, - term::{color::Rgb as AlacRgb, SizeInfo}, + term::SizeInfo, tty::{self, setup_env}, Term, }; +<<<<<<< HEAD use dirs::home_dir; +======= +use color_translation::{get_color_at_index, to_alac_rgb}; +>>>>>>> 3fe0d66d (Began working on selections, refactored colors) use futures::{ channel::mpsc::{unbounded, UnboundedSender}, StreamExt, }; use gpui::{ - actions, color::Color, elements::*, impl_internal_actions, platform::CursorStyle, - ClipboardItem, Entity, MutableAppContext, View, ViewContext, + actions, elements::*, impl_internal_actions, platform::CursorStyle, ClipboardItem, Entity, + MutableAppContext, View, ViewContext, }; use project::{LocalWorktree, Project, ProjectPath}; use settings::Settings; @@ -24,7 +28,7 @@ use smallvec::SmallVec; use std::{collections::HashMap, path::PathBuf, sync::Arc}; use workspace::{Item, Workspace}; -use crate::terminal_element::{get_color_at_index, TerminalEl}; +use crate::terminal_element::TerminalEl; //ASCII Control characters on a keyboard const ETX_CHAR: char = 3_u8 as char; //'End of text', the control code for 'ctrl-c' @@ -38,6 +42,7 @@ const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; const DEFAULT_TITLE: &str = "Terminal"; +pub mod color_translation; pub mod gpui_func_tools; pub mod terminal_element; @@ -63,7 +68,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::escape); cx.add_action(Terminal::quit); cx.add_action(Terminal::del); - cx.add_action(Terminal::carriage_return); //TODO figure out how to do this properly. Should we be checking the terminal mode? + cx.add_action(Terminal::carriage_return); cx.add_action(Terminal::left); cx.add_action(Terminal::right); cx.add_action(Terminal::up); @@ -131,7 +136,6 @@ impl Terminal { hold: false, }; - //Does this mangle the zed Env? I'm guessing it does... do child processes have a seperate ENV? let mut env: HashMap = HashMap::new(); //TODO: Properly set the current locale, env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string()); @@ -222,24 +226,7 @@ impl Terminal { AlacTermEvent::ColorRequest(index, format) => { let color = self.term.lock().colors()[index].unwrap_or_else(|| { let term_style = &cx.global::().theme.terminal; - match index { - 0..=255 => to_alac_rgb(get_color_at_index(&(index as u8), term_style)), - //These additional values are required to match the Alacritty Colors object's behavior - 256 => to_alac_rgb(term_style.foreground), - 257 => to_alac_rgb(term_style.background), - 258 => to_alac_rgb(term_style.cursor), - 259 => to_alac_rgb(term_style.dim_black), - 260 => to_alac_rgb(term_style.dim_red), - 261 => to_alac_rgb(term_style.dim_green), - 262 => to_alac_rgb(term_style.dim_yellow), - 263 => to_alac_rgb(term_style.dim_blue), - 264 => to_alac_rgb(term_style.dim_magenta), - 265 => to_alac_rgb(term_style.dim_cyan), - 266 => to_alac_rgb(term_style.dim_white), - 267 => to_alac_rgb(term_style.bright_foreground), - 268 => to_alac_rgb(term_style.black), //Dim Background, non-standard - _ => AlacRgb { r: 0, g: 0, b: 0 }, - } + to_alac_rgb(get_color_at_index(&index, term_style)) }); self.write_to_pty(&Input(format(color)), cx) } @@ -479,15 +466,6 @@ impl Item for Terminal { } } -//Convenience method for less lines -fn to_alac_rgb(color: Color) -> AlacRgb { - AlacRgb { - r: color.r, - g: color.g, - b: color.g, - } -} - fn get_working_directory(wt: &LocalWorktree) -> Option { Some(wt.abs_path().to_path_buf()) .filter(|path| path.is_dir()) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 408fb0dcecc35c844e78ad4240e48bdbd657662f..ad017c011fa250ec44125daa37b7d9b5be7eea7a 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -1,7 +1,6 @@ use alacritty_terminal::{ - ansi::Color as AnsiColor, grid::{Dimensions, GridIterator, Indexed}, - index::Point, + index::{Column as GridCol, Line as GridLine, Point, Side}, term::{ cell::{Cell, Flags}, SizeInfo, @@ -24,10 +23,12 @@ use gpui::{ use itertools::Itertools; use ordered_float::OrderedFloat; use settings::Settings; -use std::rc::Rc; +use std::{cmp::min, rc::Rc}; use theme::TerminalStyle; -use crate::{gpui_func_tools::paint_layer, Input, ScrollTerminal, Terminal}; +use crate::{ + color_translation::convert_color, gpui_func_tools::paint_layer, Input, ScrollTerminal, Terminal, +}; ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable ///Scroll multiplier that is set to 3 by default. This will be removed when I @@ -74,6 +75,7 @@ pub struct LayoutState { cursor: Option, background_color: Color, cur_size: SizeInfo, + display_offset: usize, } impl TerminalEl { @@ -194,6 +196,7 @@ impl Element for TerminalEl { cur_size, background_rects, background_color: terminal_theme.background, + display_offset: content.display_offset, }, ) } @@ -209,10 +212,50 @@ impl Element for TerminalEl { let clip_bounds = Some(visible_bounds); paint_layer(cx, clip_bounds, |cx| { //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse + + /* + To set a selection, + set the selection variable on the terminal + + CLICK: + Get the grid point associated with this mouse click + And the side????? - TODO - algorithm for calculating this in Processor::cell_side + On single left click -> Clear selection, start empty selection + On double left click -> start semantic selection + On double triple click -> start line selection + + MOUSE MOVED: + Find the new cell the mouse is over + Update the selection by calling terminal.selection.update() + */ + let cur_size = layout.cur_size.clone(); + let display_offset = layout.display_offset.clone(); cx.scene.push_mouse_region(MouseRegion { view_id: self.view.id(), - mouse_down: Some(Rc::new(|_, cx| cx.focus_parent_view())), + mouse_down: Some(Rc::new(move |pos, cx| { + let point = grid_cell(pos, cur_size, display_offset); + let side = cell_side(cur_size, pos.x() as usize); + + //One problem is we need a terminal + //Second problem is that we need # of clicks + //Third problem is that dragging reports deltas, and we need locations. + //Fourth (minor) is need to render the selection + + // if single_click { + // terminal.selection = Some(Selection::new(SelectionType::Simple, point, side)) + // } else if double_click { + // terminal.selection = Some(Selection::new(SelectionType::Semantic, point, side)) + // } else if triple_click { + // terminal.selection = Some(Selection::new(SelectionType::Lines, point, side)) + // } + + cx.focus_parent_view() + })), bounds: visible_bounds, + drag: Some(Rc::new(|delta, cx| { + //Calculate new point from delta + //terminal.selection.update(point, side) + })), ..Default::default() }); @@ -311,6 +354,19 @@ impl Element for TerminalEl { } } +/* +Mouse moved -> WindowEvent::CursorMoved +mouse press -> WindowEvent::MouseInput +update_selection_scrolling + + +copy_selection +start_selection +toggle_selection +update_selection +clear_selection + */ + ///Configures a text style from the current settings. fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle { TextStyle { @@ -430,98 +486,34 @@ fn cell_style(indexed: &Indexed<&Cell>, style: &TerminalStyle, text_style: &Text } } -///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent -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, style), - } -} +///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side() +fn cell_side(cur_size: SizeInfo, x: usize) -> Side { + let cell_x = x.saturating_sub(cur_size.cell_width() as usize) % cur_size.cell_width() as usize; + let half_cell_width = (cur_size.cell_width() / 2.0) as usize; -///Converts an 8 bit ANSI color to it's GPUI equivalent. -pub fn get_color_at_index(index: &u8, 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); //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 - 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 - } + let additional_padding = + (cur_size.width() - cur_size.cell_width() * 2.) % cur_size.cell_width(); + let end_of_grid = cur_size.width() - cur_size.cell_width() - additional_padding; + + if cell_x > half_cell_width + // Edge case when mouse leaves the window. + || x as f32 >= end_of_grid + { + Side::Right + } else { + Side::Left } } -///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!(i >= &16 && i <= &231); - 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) +///Copied (with modifications) from alacritty/src/event.rs > Mouse::point() +fn grid_cell(pos: Vector2F, cur_size: SizeInfo, display_offset: usize) -> Point { + let col = pos.x() - cur_size.cell_width() / cur_size.cell_width(); //TODO: underflow... + let col = min(GridCol(col as usize), cur_size.last_column()); + + let line = pos.y() - cur_size.padding_y() / cur_size.cell_height(); + let line = min(line as usize, cur_size.bottommost_line().0 as usize); + + Point::new(GridLine((line - display_offset) as i32), col) } ///Draws the grid as Alacritty sees it. Useful for checking if there is an inconsistency between @@ -554,15 +546,3 @@ fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContex }); } } - -#[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::terminal_element::rgb_for_index(&(i as u8)); - assert_eq!(i, 16 + 36 * r + 6 * g + b); - } - } -} From a8237858bc4d254f89206a83bbe61bafa891754e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 6 Jul 2022 17:37:12 -0700 Subject: [PATCH 33/47] Added basic selections --- crates/editor/src/element.rs | 20 +- crates/terminal/src/terminal_element.rs | 317 ++++++++++++++++-------- 2 files changed, 220 insertions(+), 117 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1169df3fd1d72f725a72beaa81154c58e9bd4884..976f932e580769c667886c2cb80f46d3521029b7 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1685,22 +1685,22 @@ impl Cursor { } #[derive(Debug)] -struct HighlightedRange { - start_y: f32, - line_height: f32, - lines: Vec, - color: Color, - corner_radius: f32, +pub struct HighlightedRange { + pub start_y: f32, + pub line_height: f32, + pub lines: Vec, + pub color: Color, + pub corner_radius: f32, } #[derive(Debug)] -struct HighlightedRangeLine { - start_x: f32, - end_x: f32, +pub struct HighlightedRangeLine { + pub start_x: f32, + pub end_x: f32, } impl HighlightedRange { - fn paint(&self, bounds: RectF, scene: &mut Scene) { + pub fn paint(&self, bounds: RectF, scene: &mut Scene) { if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x { self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene); self.paint_lines( diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index ad017c011fa250ec44125daa37b7d9b5be7eea7a..a339c2c6f942c18f1fcb8384ffe6a9471d1140d4 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -1,12 +1,15 @@ use alacritty_terminal::{ grid::{Dimensions, GridIterator, Indexed}, index::{Column as GridCol, Line as GridLine, Point, Side}, + selection::{Selection, SelectionRange, SelectionType}, + sync::FairMutex, term::{ cell::{Cell, Flags}, SizeInfo, }, + Term, }; -use editor::{Cursor, CursorShape}; +use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ color::Color, elements::*, @@ -23,11 +26,13 @@ use gpui::{ use itertools::Itertools; use ordered_float::OrderedFloat; use settings::Settings; -use std::{cmp::min, rc::Rc}; +use std::{cmp::min, ops::Range, rc::Rc, sync::Arc}; +use std::{fmt::Debug, ops::Sub}; use theme::TerminalStyle; use crate::{ - color_translation::convert_color, gpui_func_tools::paint_layer, Input, ScrollTerminal, Terminal, + color_translation::convert_color, gpui_func_tools::paint_layer, Input, ScrollTerminal, + Terminal, ZedListener, }; ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable @@ -45,14 +50,27 @@ pub struct TerminalEl { view: WeakViewHandle, } -///Helper types so I don't mix these two up +///New type pattern so I don't mix these two up struct CellWidth(f32); struct LineHeight(f32); +struct LayoutLine { + cells: Vec, + highlighted_range: Option>, +} + +///New type pattern to ensure that we use adjusted mouse positions throughout the code base, rather than +struct PaneRelativePos(Vector2F); + +///Functionally the constructor for the PaneRelativePos type, mutates the mouse_position +fn relative_pos(mouse_position: Vector2F, origin: Vector2F) -> PaneRelativePos { + PaneRelativePos(mouse_position.sub(origin)) //Avoid the extra allocation by mutating +} + #[derive(Clone, Debug, Default)] struct LayoutCell { point: Point, - text: Line, + text: Line, //NOTE TO SELF THIS IS BAD PERFORMANCE RN! background_color: Color, } @@ -68,14 +86,15 @@ impl LayoutCell { ///The information generated during layout that is nescessary for painting pub struct LayoutState { - cells: Vec<(Point, Line)>, - background_rects: Vec<(RectF, Color)>, //Vec index == Line index for the LineSpan + layout_lines: Vec, line_height: LineHeight, em_width: CellWidth, cursor: Option, background_color: Color, cur_size: SizeInfo, display_offset: usize, + terminal: Arc>>, + selection_color: Color, } impl TerminalEl { @@ -111,42 +130,31 @@ impl Element for TerminalEl { view_handle.update(cx.app, |view, _cx| view.set_size(cur_size)); //Now that we're done with the mutable portion, grab the immutable settings and view again - let terminal_theme = &(cx.global::()).theme.terminal; - let term = view_handle.read(cx).term.lock(); + let (selection_color, terminal_theme) = { + let theme = &(cx.global::()).theme; + (theme.editor.selection.selection, &theme.terminal) + }; + let terminal_mutex = view_handle.read(cx).term.clone(); + let term = terminal_mutex.lock(); let grid = term.grid(); let cursor_point = grid.cursor.point; let cursor_text = grid[cursor_point.line][cursor_point.column].c.to_string(); let content = term.renderable_content(); - let layout_cells = layout_cells( + //We have a 'SelectionRange' struct to work with, + //Allows us to query start, end, and contains + //content.selection.unwrap() + + let layout_lines = layout_lines( content.display_iter, &text_style, terminal_theme, cx.text_layout_cache, + content.selection, ); - let cells = layout_cells - .iter() - .map(|c| (c.point, c.text.clone())) - .collect::, Line)>>(); - let background_rects = layout_cells - .iter() - .map(|cell| { - ( - RectF::new( - vec2f( - cell.point.column as f32 * cell_width.0, - cell.point.line as f32 * line_height.0, - ), - vec2f(cell_width.0, line_height.0), - ), - cell.background_color, - ) - }) - .collect::>(); - let block_text = cx.text_layout_cache.layout_str( &cursor_text, text_style.font_size, @@ -185,18 +193,21 @@ impl Element for TerminalEl { Some(block_text.clone()), ) }); + let display_offset = content.display_offset; + drop(term); ( constraint.max, LayoutState { - cells, + layout_lines, line_height, em_width: cell_width, cursor, cur_size, - background_rects, background_color: terminal_theme.background, - display_offset: content.display_offset, + display_offset, + terminal: terminal_mutex, + selection_color, }, ) } @@ -210,6 +221,7 @@ impl Element for TerminalEl { ) -> Self::PaintState { //Setup element stuff let clip_bounds = Some(visible_bounds); + paint_layer(cx, clip_bounds, |cx| { //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse @@ -230,37 +242,45 @@ impl Element for TerminalEl { */ let cur_size = layout.cur_size.clone(); let display_offset = layout.display_offset.clone(); + let terminal_mutex = layout.terminal.clone(); + let origin = bounds.origin() + vec2f(layout.em_width.0, 0.); + + //TODO: Better way of doing this? + let mutex1 = terminal_mutex.clone(); + let _mutex2 = terminal_mutex.clone(); + cx.scene.push_mouse_region(MouseRegion { view_id: self.view.id(), - mouse_down: Some(Rc::new(move |pos, cx| { - let point = grid_cell(pos, cur_size, display_offset); - let side = cell_side(cur_size, pos.x() as usize); - - //One problem is we need a terminal - //Second problem is that we need # of clicks - //Third problem is that dragging reports deltas, and we need locations. - //Fourth (minor) is need to render the selection - - // if single_click { - // terminal.selection = Some(Selection::new(SelectionType::Simple, point, side)) - // } else if double_click { - // terminal.selection = Some(Selection::new(SelectionType::Semantic, point, side)) - // } else if triple_click { - // terminal.selection = Some(Selection::new(SelectionType::Lines, point, side)) - // } + click: Some(Rc::new(move |pos, click_count, cx| { + let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); + + let selection_type = match click_count { + 1 => Some(SelectionType::Simple), + 2 => Some(SelectionType::Semantic), + 3 => Some(SelectionType::Lines), + _ => None, + }; + let selection = selection_type + .map(|selection_type| Selection::new(selection_type, point, side)); + + let mut term = mutex1.lock(); + term.selection = selection; cx.focus_parent_view() })), bounds: visible_bounds, - drag: Some(Rc::new(|delta, cx| { - //Calculate new point from delta - //terminal.selection.update(point, side) + drag: Some(Rc::new(move |_delta, _cx| { + // let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); + + // let mut term = mutex2.lock(); + // if let Some(mut selection) = term.selection.take() { + // selection.update(point, side); + // term.selection = Some(selection); + // } })), ..Default::default() }); - let origin = bounds.origin() + vec2f(layout.em_width.0, 0.); - paint_layer(cx, clip_bounds, |cx| { //Start with a background color cx.scene.push_quad(Quad { @@ -271,25 +291,84 @@ impl Element for TerminalEl { }); //Draw cell backgrounds - for background_rect in &layout.background_rects { - let new_origin = origin + background_rect.0.origin(); - cx.scene.push_quad(Quad { - bounds: RectF::new(new_origin, background_rect.0.size()), - background: Some(background_rect.1), - border: Default::default(), - corner_radius: 0., + for layout_line in &layout.layout_lines { + for layout_cell in &layout_line.cells { + let position = vec2f( + origin.x() + layout_cell.point.column as f32 * layout.em_width.0, + origin.y() + layout_cell.point.line as f32 * layout.line_height.0, + ); + let size = vec2f(layout.em_width.0, layout.line_height.0); + + cx.scene.push_quad(Quad { + bounds: RectF::new(position, size), + background: Some(layout_cell.background_color), + border: Default::default(), + corner_radius: 0., + }) + } + } + }); + + //Draw Selection + paint_layer(cx, clip_bounds, |cx| { + let mut highlight_y = None; + let highlight_lines = layout + .layout_lines + .iter() + .filter_map(|line| { + if let Some(range) = &line.highlighted_range { + if let None = highlight_y { + highlight_y = Some( + origin.y() + + line.cells[0].point.line as f32 * layout.line_height.0, + ); + } + let start_x = origin.x() + + line.cells[range.start].point.column as f32 * layout.em_width.0; + let end_x = origin.x() + //TODO: Why -1? I know switch from count to index... but where... + + line.cells[range.end - 1].point.column as f32 * layout.em_width.0 + + layout.em_width.0; + + return Some(HighlightedRangeLine { start_x, end_x }); + } else { + return None; + } }) + .collect::>(); + + if let Some(y) = highlight_y { + let hr = HighlightedRange { + start_y: y, //Need to change this + line_height: layout.line_height.0, + lines: highlight_lines, + color: layout.selection_color, + //Copied from editor. TODO: move to theme or something + corner_radius: 0.15 * layout.line_height.0, + }; + hr.paint(bounds, cx.scene); } }); //Draw text paint_layer(cx, clip_bounds, |cx| { - for (point, cell) in &layout.cells { - let cell_origin = vec2f( - origin.x() + point.column as f32 * layout.em_width.0, - origin.y() + point.line as f32 * layout.line_height.0, - ); - cell.paint(cell_origin, visible_bounds, layout.line_height.0, cx); + for layout_line in &layout.layout_lines { + for layout_cell in &layout_line.cells { + let point = layout_cell.point; + + //Don't actually know the start_x for a line, until here: + let cell_origin = vec2f( + origin.x() + point.column as f32 * layout.em_width.0, + origin.y() + point.line as f32 * layout.line_height.0, + ); + + layout_cell.text.paint( + cell_origin, + visible_bounds, + layout.line_height.0, + cx, + ); + } } }); @@ -354,18 +433,17 @@ impl Element for TerminalEl { } } -/* -Mouse moved -> WindowEvent::CursorMoved -mouse press -> WindowEvent::MouseInput -update_selection_scrolling - - -copy_selection -start_selection -toggle_selection -update_selection -clear_selection - */ +fn mouse_to_cell_data( + pos: Vector2F, + origin: Vector2F, + cur_size: SizeInfo, + display_offset: usize, +) -> (Point, alacritty_terminal::index::Direction) { + let relative_pos = relative_pos(pos, origin); + let point = grid_cell(&relative_pos, cur_size, display_offset); + let side = cell_side(&relative_pos, cur_size); + (point, side) +} ///Configures a text style from the current settings. fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle { @@ -399,38 +477,59 @@ fn make_new_size( ) } -fn layout_cells( +//Let's say that calculating the display is correct, that means that either calculating the highlight ranges is incorrect +//OR calculating the click ranges is incorrect + +fn layout_lines( grid: GridIterator, text_style: &TextStyle, terminal_theme: &TerminalStyle, text_layout_cache: &TextLayoutCache, -) -> Vec { - let mut line_count: i32 = 0; + selection_range: Option, +) -> Vec { let lines = grid.group_by(|i| i.point.line); lines .into_iter() - .map(|(_, line)| { - line_count += 1; - line.map(|indexed_cell| { - let cell_text = &indexed_cell.c.to_string(); - - let cell_style = cell_style(&indexed_cell, terminal_theme, text_style); - - let layout_cell = text_layout_cache.layout_str( - cell_text, - text_style.font_size, - &[(cell_text.len(), cell_style)], - ); - LayoutCell::new( - Point::new(line_count - 1, indexed_cell.point.column.0 as i32), - layout_cell, - convert_color(&indexed_cell.bg, terminal_theme), - ) - }) - .collect::>() + .enumerate() + .map(|(line_index, (_, line))| { + let mut highlighted_range = None; + let cells = line + .enumerate() + .map(|(x_index, indexed_cell)| { + if selection_range + .map(|range| range.contains(indexed_cell.point)) + .unwrap_or(false) + { + let mut range = highlighted_range.take().unwrap_or(x_index..x_index + 1); + range.end = range.end.max(x_index + 1); + highlighted_range = Some(range); + } + + let cell_text = &indexed_cell.c.to_string(); + + let cell_style = cell_style(&indexed_cell, terminal_theme, text_style); + + //This is where we might be able to get better performance + let layout_cell = text_layout_cache.layout_str( + cell_text, + text_style.font_size, + &[(cell_text.len(), cell_style)], + ); + + LayoutCell::new( + Point::new(line_index as i32, indexed_cell.point.column.0 as i32), + layout_cell, + convert_color(&indexed_cell.bg, terminal_theme), + ) + }) + .collect::>(); + + LayoutLine { + cells, + highlighted_range, + } }) - .flatten() - .collect::>() + .collect::>() } // Compute the cursor position and expected block width, may return a zero width if x_for_index returns @@ -487,7 +586,8 @@ fn cell_style(indexed: &Indexed<&Cell>, style: &TerminalStyle, text_style: &Text } ///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side() -fn cell_side(cur_size: SizeInfo, x: usize) -> Side { +fn cell_side(pos: &PaneRelativePos, cur_size: SizeInfo) -> Side { + let x = pos.0.x() as usize; let cell_x = x.saturating_sub(cur_size.cell_width() as usize) % cur_size.cell_width() as usize; let half_cell_width = (cur_size.cell_width() / 2.0) as usize; @@ -506,11 +606,14 @@ fn cell_side(cur_size: SizeInfo, x: usize) -> Side { } ///Copied (with modifications) from alacritty/src/event.rs > Mouse::point() -fn grid_cell(pos: Vector2F, cur_size: SizeInfo, display_offset: usize) -> Point { - let col = pos.x() - cur_size.cell_width() / cur_size.cell_width(); //TODO: underflow... +///Position is a pane-relative position. That means the top left corner of the mouse +///Region should be (0,0) +fn grid_cell(pos: &PaneRelativePos, cur_size: SizeInfo, display_offset: usize) -> Point { + let pos = pos.0; + let col = pos.x() / cur_size.cell_width(); //TODO: underflow... let col = min(GridCol(col as usize), cur_size.last_column()); - let line = pos.y() - cur_size.padding_y() / cur_size.cell_height(); + let line = pos.y() / cur_size.cell_height(); let line = min(line as usize, cur_size.bottommost_line().0 as usize); Point::new(GridLine((line - display_offset) as i32), col) From 415e28e2d3719ae1335636f501f3f93cb71a5c0b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 14:54:13 -0700 Subject: [PATCH 34/47] Fixed merge conflict --- crates/terminal/src/terminal.rs | 10 +++------- crates/terminal/src/terminal_element.rs | 22 +++++++++++++--------- styles/package-lock.json | 1 - 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index c2df86de6144ed89f03fb01b179524dd594f7251..dbbd1341e653598669c26949c62a81b6cc61414a 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -9,11 +9,8 @@ use alacritty_terminal::{ Term, }; -<<<<<<< HEAD -use dirs::home_dir; -======= use color_translation::{get_color_at_index, to_alac_rgb}; ->>>>>>> 3fe0d66d (Began working on selections, refactored colors) +use dirs::home_dir; use futures::{ channel::mpsc::{unbounded, UnboundedSender}, StreamExt, @@ -475,13 +472,12 @@ fn get_working_directory(wt: &LocalWorktree) -> Option { #[cfg(test)] mod tests { - use std::{path::Path, sync::atomic::AtomicUsize, time::Duration}; - use super::*; use alacritty_terminal::{grid::GridIterator, term::cell::Cell}; use gpui::TestAppContext; use itertools::Itertools; use project::{FakeFs, Fs, RealFs, RemoveOptions, Worktree}; + use std::{path::Path, sync::atomic::AtomicUsize, time::Duration}; ///Basic integration test, can we get the terminal to show up, execute a command, //and produce noticable output? @@ -489,7 +485,6 @@ mod tests { async fn test_terminal(cx: &mut TestAppContext) { let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None)); cx.set_condition_duration(Duration::from_secs(2)); - terminal.update(cx, |terminal, cx| { terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx); terminal.carriage_return(&Return, cx); @@ -499,6 +494,7 @@ mod tests { .condition(cx, |terminal, _cx| { let term = terminal.term.clone(); let content = grid_as_str(term.lock().renderable_content().display_iter); + dbg!(&content); content.contains("7") }) .await; diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index a339c2c6f942c18f1fcb8384ffe6a9471d1140d4..ad928068ed3f94077e4f560407a994795ed71387 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -247,7 +247,7 @@ impl Element for TerminalEl { //TODO: Better way of doing this? let mutex1 = terminal_mutex.clone(); - let _mutex2 = terminal_mutex.clone(); + let mutex2 = terminal_mutex.clone(); cx.scene.push_mouse_region(MouseRegion { view_id: self.view.id(), @@ -269,14 +269,18 @@ impl Element for TerminalEl { cx.focus_parent_view() })), bounds: visible_bounds, - drag: Some(Rc::new(move |_delta, _cx| { - // let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); - - // let mut term = mutex2.lock(); - // if let Some(mut selection) = term.selection.take() { - // selection.update(point, side); - // term.selection = Some(selection); - // } + drag: Some(Rc::new(move |_delta, pos, cx| { + let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); + + let mut term = mutex2.lock(); + if let Some(mut selection) = term.selection.take() { + selection.update(point, side); + term.selection = Some(selection); + } else { + term.selection = Some(Selection::new(SelectionType::Simple, point, side)); + } + + cx.notify() })), ..Default::default() }); diff --git a/styles/package-lock.json b/styles/package-lock.json index 49304dc2fa98dfec942bf6687be56cefc13cdf80..2eb6d3a1bfaeaa206f0cc8a0a03efa0387d144c2 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": { From b5919c05557b0ee160e6e834df563e71b5f28243 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 14:54:36 -0700 Subject: [PATCH 35/47] Fixed merge conflict --- crates/terminal/src/terminal_element.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index ad928068ed3f94077e4f560407a994795ed71387..b629a6c345f2ecb06fe69ec98af21dc1f71c2568 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -255,6 +255,7 @@ impl Element for TerminalEl { let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); let selection_type = match click_count { + 0 => return, //This is a release 1 => Some(SelectionType::Simple), 2 => Some(SelectionType::Semantic), 3 => Some(SelectionType::Lines), @@ -279,8 +280,7 @@ impl Element for TerminalEl { } else { term.selection = Some(Selection::new(SelectionType::Simple, point, side)); } - - cx.notify() + cx.notify(); })), ..Default::default() }); From 8c1054fbb6998b09606f40c8aaa20f229c0b6f5c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 14:54:58 -0700 Subject: [PATCH 36/47] Fixed merge conflict --- crates/terminal/src/terminal_element.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index b629a6c345f2ecb06fe69ec98af21dc1f71c2568..35397b00283733eed1199fa9df759f308cd61568 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -143,10 +143,6 @@ impl Element for TerminalEl { let content = term.renderable_content(); - //We have a 'SelectionRange' struct to work with, - //Allows us to query start, end, and contains - //content.selection.unwrap() - let layout_lines = layout_lines( content.display_iter, &text_style, From 7c0d9f411a4964b774532f9aae582ef00cedb986 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:19:38 -0700 Subject: [PATCH 37/47] Added copying --- assets/keymaps/default.json | 3 ++- crates/terminal/src/terminal.rs | 13 ++++++++++++- pbcpoy | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 pbcpoy diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index bea53ece45b2a672879453bb522fd64814e635f3..6cd3660bf5120d4b16f1f6988588a537b7b92a31 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -417,7 +417,8 @@ "up": "terminal::Up", "down": "terminal::Down", "tab": "terminal::Tab", - "cmd-v": "terminal::Paste" + "cmd-v": "terminal::Paste", + "cmd-c": "terminal::Copy" } } ] \ No newline at end of file diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index dbbd1341e653598669c26949c62a81b6cc61414a..133edcd9067ffda1d74e3c0e7d6f3a340f8fe87c 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -53,7 +53,7 @@ pub struct ScrollTerminal(pub i32); actions!( terminal, - [Sigint, Escape, Del, Return, Left, Right, Up, Down, Tab, Clear, Paste, Deploy, Quit] + [Sigint, Escape, Del, Return, Left, Right, Up, Down, Tab, Clear, Copy, Paste, Deploy, Quit] ); impl_internal_actions!(terminal, [Input, ScrollTerminal]); @@ -71,6 +71,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Terminal::up); cx.add_action(Terminal::down); cx.add_action(Terminal::tab); + cx.add_action(Terminal::copy); cx.add_action(Terminal::paste); cx.add_action(Terminal::scroll_terminal); } @@ -275,6 +276,16 @@ impl Terminal { cx.emit(Event::CloseTerminal); } + ///Attempt to paste the clipboard into the terminal + fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { + let term = self.term.lock(); + let copy_text = term.selection_to_string(); + match copy_text { + Some(s) => cx.write_to_clipboard(ClipboardItem::new(s)), + None => (), + } + } + ///Attempt to paste the clipboard into the terminal fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { if let Some(item) = cx.read_from_clipboard() { diff --git a/pbcpoy b/pbcpoy new file mode 100644 index 0000000000000000000000000000000000000000..f70f10e4db19068f79bc43844b49f3eece45c4e8 --- /dev/null +++ b/pbcpoy @@ -0,0 +1 @@ +A From 9209c0dfeb6dd85566ad1df26cc41b2ce8fe5f9d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 14:55:22 -0700 Subject: [PATCH 38/47] Fixed merge conflict --- crates/terminal/src/terminal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 133edcd9067ffda1d74e3c0e7d6f3a340f8fe87c..3be25b86b12a7bcad33453319266b0b9638755ef 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -129,7 +129,7 @@ impl Terminal { .detach(); let pty_config = PtyConfig { - shell: None, + shell: None, //Use the users default shell working_directory: working_directory.clone(), hold: false, }; From 2a6e23ff28621dce634a2a17f1dd7187b5b46973 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:29:58 -0700 Subject: [PATCH 39/47] Hoisted assert clipboard into TestAppContext --- crates/editor/src/editor.rs | 2 +- crates/editor/src/test.rs | 8 -------- crates/gpui/src/app.rs | 8 ++++++++ crates/vim/src/vim_test_context.rs | 8 -------- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c1e25575557bef78921841f1c2a9eb8e589085a2..808926ff50f3e71b157f6b8380980fc1bd109044 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8232,7 +8232,7 @@ mod tests { fox ju|mps over the lazy dog"}); cx.update_editor(|e, cx| e.copy(&Copy, cx)); - cx.assert_clipboard_content(Some("fox jumps over\n")); + cx.cx.assert_clipboard_content(Some("fox jumps over\n")); // Paste with three selections, noticing how the copied full-line selection is inserted // before the empty selections but replaces the selection that is non-empty. diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index d1316a85a0bf38afe5c39cb596b6647b89119252..0affe06f64b555890ea3b6289ec497ace6aeb18d 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -404,14 +404,6 @@ impl<'a> EditorTestContext<'a> { editor_text_with_selections } - - pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) { - self.cx.update(|cx| { - let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned()); - let expected_content = expected_content.map(|content| content.to_owned()); - assert_eq!(actual_content, expected_content); - }) - } } impl<'a> Deref for EditorTestContext<'a> { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index b81714e0bc71474c753ee018b9fce161871f67f9..26c4fe01a8c54b54a080b54faf07a8b93bfa7b43 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -627,6 +627,14 @@ impl TestAppContext { } }) } + + pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) { + self.update(|cx| { + let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned()); + let expected_content = expected_content.map(|content| content.to_owned()); + assert_eq!(actual_content, expected_content); + }) + } } impl AsyncAppContext { diff --git a/crates/vim/src/vim_test_context.rs b/crates/vim/src/vim_test_context.rs index 57d0174703bbf5da9781c1f6f70f5bb23a9527d7..08ec4bd5e961315fa8c661ffbcfac89ab3a59207 100644 --- a/crates/vim/src/vim_test_context.rs +++ b/crates/vim/src/vim_test_context.rs @@ -147,14 +147,6 @@ impl<'a> VimTestContext<'a> { let mode = self.mode(); VimBindingTestContext::new(keystrokes, mode, mode, self) } - - pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) { - self.cx.update(|cx| { - let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned()); - let expected_content = expected_content.map(|content| content.to_owned()); - assert_eq!(actual_content, expected_content); - }) - } } impl<'a> Deref for VimTestContext<'a> { From cc985721c6efc7aa7cd3abe6112ea16d807463a3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:43:28 -0700 Subject: [PATCH 40/47] Added a small integration test --- crates/terminal/src/terminal.rs | 72 +++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 3be25b86b12a7bcad33453319266b0b9638755ef..660da65bbf4f3f409f69548ea662bb84b898fa27 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -38,6 +38,8 @@ const RIGHT_SEQ: &str = "\x1b[C"; const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; const DEFAULT_TITLE: &str = "Terminal"; +const DEBUG_TERMINAL_WIDTH: f32 = 300.; +const DEBUG_TERMINAL_HEIGHT: f32 = 200.; pub mod color_translation; pub mod gpui_func_tools; @@ -147,8 +149,15 @@ impl Terminal { setup_env(&config); //The details here don't matter, the terminal will be resized on the first layout - //Set to something small for easier debugging - let size_info = SizeInfo::new(200., 100.0, 5., 5., 0., 0., false); + let size_info = SizeInfo::new( + DEBUG_TERMINAL_WIDTH, + DEBUG_TERMINAL_HEIGHT, + 5., + 5., + 0., + 0., + false, + ); //Set up the terminal... let term = Term::new(&config, size_info, ZedListener(events_tx.clone())); @@ -484,7 +493,12 @@ fn get_working_directory(wt: &LocalWorktree) -> Option { mod tests { use super::*; - use alacritty_terminal::{grid::GridIterator, term::cell::Cell}; + use alacritty_terminal::{ + grid::GridIterator, + index::{Column, Line, Point, Side}, + selection::{Selection, SelectionType}, + term::cell::Cell, + }; use gpui::TestAppContext; use itertools::Itertools; use project::{FakeFs, Fs, RealFs, RemoveOptions, Worktree}; @@ -511,15 +525,6 @@ mod tests { .await; } - pub(crate) fn grid_as_str(grid_iterator: GridIterator) -> String { - let lines = grid_iterator.group_by(|i| i.point.line.0); - lines - .into_iter() - .map(|(_, line)| line.map(|i| i.c).collect::()) - .collect::>() - .join("\n") - } - #[gpui::test] async fn single_file_worktree(cx: &mut TestAppContext) { let mut async_cx = cx.to_async(); @@ -600,4 +605,47 @@ mod tests { .ok() .expect("Could not remove test directory"); } + + ///Basic integration test, can we get the terminal to show up, execute a command, + //and produce noticable output? + #[gpui::test] + async fn test_copy(cx: &mut TestAppContext) { + let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None)); + cx.set_condition_duration(Duration::from_secs(2)); + + terminal.update(cx, |terminal, cx| { + terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx); + terminal.carriage_return(&Return, cx); + }); + + terminal + .condition(cx, |terminal, _cx| { + let term = terminal.term.clone(); + let content = grid_as_str(term.lock().renderable_content().display_iter); + content.contains("7") + }) + .await; + + terminal.update(cx, |terminal, cx| { + let mut term = terminal.term.lock(); + term.selection = Some(Selection::new( + SelectionType::Semantic, + Point::new(Line(3), Column(0)), + Side::Right, + )); + drop(term); + terminal.copy(&Copy, cx) + }); + + cx.assert_clipboard_content(Some(&"7")); + } + + pub(crate) fn grid_as_str(grid_iterator: GridIterator) -> String { + let lines = grid_iterator.group_by(|i| i.point.line.0); + lines + .into_iter() + .map(|(_, line)| line.map(|i| i.c).collect::()) + .collect::>() + .join("\n") + } } From 102f502c2645289d7d16f0067693326b806bd315 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:45:27 -0700 Subject: [PATCH 41/47] tidied up magic constants --- crates/terminal/src/terminal.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 660da65bbf4f3f409f69548ea662bb84b898fa27..849b174deb67a466913b46d02188d97af2c7605b 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -40,6 +40,8 @@ const DOWN_SEQ: &str = "\x1b[B"; const DEFAULT_TITLE: &str = "Terminal"; const DEBUG_TERMINAL_WIDTH: f32 = 300.; const DEBUG_TERMINAL_HEIGHT: f32 = 200.; +const DEBUG_CELL_WIDTH: f32 = 5.; +const DEBUG_LINE_HEIGHT: f32 = 5.; pub mod color_translation; pub mod gpui_func_tools; @@ -152,8 +154,8 @@ impl Terminal { let size_info = SizeInfo::new( DEBUG_TERMINAL_WIDTH, DEBUG_TERMINAL_HEIGHT, - 5., - 5., + DEBUG_CELL_WIDTH, + DEBUG_LINE_HEIGHT, 0., 0., false, From ab0ca7d42a1f31e9c31b8af2e7905ed3c0330b90 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 13:55:58 -0700 Subject: [PATCH 42/47] Added another minor test --- crates/terminal/src/terminal_element.rs | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 35397b00283733eed1199fa9df759f308cd61568..5e476f96efbb7652182b1e5b6f3a54b08e87a0c5 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -649,3 +649,40 @@ fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContex }); } } + +mod test { + + #[test] + fn test_mouse_to_selection() { + let term_width = 100.; + let term_height = 200.; + let cell_width = 10.; + let line_height = 20.; + let mouse_pos_x = 100.; //Window relative + let mouse_pos_y = 100.; //Window relative + let origin_x = 10.; + let origin_y = 20.; + + let cur_size = alacritty_terminal::term::SizeInfo::new( + term_width, + term_height, + cell_width, + line_height, + 0., + 0., + false, + ); + + let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y); + let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in + let (point, _) = + crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0); + assert_eq!( + point, + alacritty_terminal::index::Point::new( + alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32), + alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize), + ) + ); + } +} From 59c8e8bdadc47b1ae60ac70efbeb0d0dd1cbf953 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 14:38:21 -0700 Subject: [PATCH 43/47] Fixed integration test --- crates/terminal/src/terminal.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 849b174deb67a466913b46d02188d97af2c7605b..dd076335856afb7711a36049cb4ab5309d9f0ed6 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -38,7 +38,7 @@ const RIGHT_SEQ: &str = "\x1b[C"; const UP_SEQ: &str = "\x1b[A"; const DOWN_SEQ: &str = "\x1b[B"; const DEFAULT_TITLE: &str = "Terminal"; -const DEBUG_TERMINAL_WIDTH: f32 = 300.; +const DEBUG_TERMINAL_WIDTH: f32 = 1000.; //This needs to be wide enough that the prompt can fill the whole space. const DEBUG_TERMINAL_HEIGHT: f32 = 200.; const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.; @@ -608,8 +608,7 @@ mod tests { .expect("Could not remove test directory"); } - ///Basic integration test, can we get the terminal to show up, execute a command, - //and produce noticable output? + ///If this test is failing for you, check that DEBUG_TERMINAL_WIDTH is wide enough to fit your entire command prompt! #[gpui::test] async fn test_copy(cx: &mut TestAppContext) { let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None)); @@ -632,7 +631,7 @@ mod tests { let mut term = terminal.term.lock(); term.selection = Some(Selection::new( SelectionType::Semantic, - Point::new(Line(3), Column(0)), + Point::new(Line(2), Column(0)), Side::Right, )); drop(term); From 1fab7be4b587e77b4b784860c13b7df6ec6bba18 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 15:10:08 -0700 Subject: [PATCH 44/47] Finished selections for now --- crates/terminal/src/terminal_element.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 5e476f96efbb7652182b1e5b6f3a54b08e87a0c5..59b95d8879d78ec7ac47cbc7fbf3eb83849487c7 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -244,9 +244,15 @@ impl Element for TerminalEl { //TODO: Better way of doing this? let mutex1 = terminal_mutex.clone(); let mutex2 = terminal_mutex.clone(); + let mutex3 = terminal_mutex.clone(); cx.scene.push_mouse_region(MouseRegion { view_id: self.view.id(), + mouse_down: Some(Rc::new(move |pos, _| { + let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); + mutex3.lock().selection = + Some(Selection::new(SelectionType::Simple, point, side)) + })), click: Some(Rc::new(move |pos, click_count, cx| { let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); @@ -263,7 +269,8 @@ impl Element for TerminalEl { let mut term = mutex1.lock(); term.selection = selection; - cx.focus_parent_view() + cx.focus_parent_view(); + cx.notify(); })), bounds: visible_bounds, drag: Some(Rc::new(move |_delta, pos, cx| { @@ -273,8 +280,6 @@ impl Element for TerminalEl { if let Some(mut selection) = term.selection.take() { selection.update(point, side); term.selection = Some(selection); - } else { - term.selection = Some(Selection::new(SelectionType::Simple, point, side)); } cx.notify(); })), From f86106a07e39e67ebf49dd482aa339a16fdd21be Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 15:24:06 -0700 Subject: [PATCH 45/47] Fixed a bug around selecting a single cell --- crates/terminal/src/terminal_element.rs | 138 ++++++++++++------------ 1 file changed, 68 insertions(+), 70 deletions(-) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 59b95d8879d78ec7ac47cbc7fbf3eb83849487c7..5e0849e33d5284f5d43003c7980a3cd66108dfc4 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -126,7 +126,6 @@ impl Element for TerminalEl { //Tell the view our new size. Requires a mutable borrow of cx and the view let cur_size = make_new_size(constraint, &cell_width, &line_height); //Note that set_size locks and mutates the terminal. - //TODO: Would be nice to lock once for the whole of layout view_handle.update(cx.app, |view, _cx| view.set_size(cur_size)); //Now that we're done with the mutable portion, grab the immutable settings and view again @@ -219,72 +218,20 @@ impl Element for TerminalEl { let clip_bounds = Some(visible_bounds); paint_layer(cx, clip_bounds, |cx| { - //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse - - /* - To set a selection, - set the selection variable on the terminal - - CLICK: - Get the grid point associated with this mouse click - And the side????? - TODO - algorithm for calculating this in Processor::cell_side - On single left click -> Clear selection, start empty selection - On double left click -> start semantic selection - On double triple click -> start line selection - - MOUSE MOVED: - Find the new cell the mouse is over - Update the selection by calling terminal.selection.update() - */ let cur_size = layout.cur_size.clone(); let display_offset = layout.display_offset.clone(); - let terminal_mutex = layout.terminal.clone(); let origin = bounds.origin() + vec2f(layout.em_width.0, 0.); - //TODO: Better way of doing this? - let mutex1 = terminal_mutex.clone(); - let mutex2 = terminal_mutex.clone(); - let mutex3 = terminal_mutex.clone(); - - cx.scene.push_mouse_region(MouseRegion { - view_id: self.view.id(), - mouse_down: Some(Rc::new(move |pos, _| { - let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); - mutex3.lock().selection = - Some(Selection::new(SelectionType::Simple, point, side)) - })), - click: Some(Rc::new(move |pos, click_count, cx| { - let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); - - let selection_type = match click_count { - 0 => return, //This is a release - 1 => Some(SelectionType::Simple), - 2 => Some(SelectionType::Semantic), - 3 => Some(SelectionType::Lines), - _ => None, - }; - - let selection = selection_type - .map(|selection_type| Selection::new(selection_type, point, side)); - - let mut term = mutex1.lock(); - term.selection = selection; - cx.focus_parent_view(); - cx.notify(); - })), - bounds: visible_bounds, - drag: Some(Rc::new(move |_delta, pos, cx| { - let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); - - let mut term = mutex2.lock(); - if let Some(mut selection) = term.selection.take() { - selection.update(point, side); - term.selection = Some(selection); - } - cx.notify(); - })), - ..Default::default() - }); + //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse + attach_mouse_handlers( + origin, + cur_size, + display_offset, + self.view.id(), + &layout.terminal, + visible_bounds, + cx, + ); paint_layer(cx, clip_bounds, |cx| { //Start with a background color @@ -331,8 +278,7 @@ impl Element for TerminalEl { let start_x = origin.x() + line.cells[range.start].point.column as f32 * layout.em_width.0; let end_x = origin.x() - //TODO: Why -1? I know switch from count to index... but where... - + line.cells[range.end - 1].point.column as f32 * layout.em_width.0 + + line.cells[range.end].point.column as f32 * layout.em_width.0 + layout.em_width.0; return Some(HighlightedRangeLine { start_x, end_x }); @@ -482,9 +428,6 @@ fn make_new_size( ) } -//Let's say that calculating the display is correct, that means that either calculating the highlight ranges is incorrect -//OR calculating the click ranges is incorrect - fn layout_lines( grid: GridIterator, text_style: &TextStyle, @@ -505,8 +448,8 @@ fn layout_lines( .map(|range| range.contains(indexed_cell.point)) .unwrap_or(false) { - let mut range = highlighted_range.take().unwrap_or(x_index..x_index + 1); - range.end = range.end.max(x_index + 1); + let mut range = highlighted_range.take().unwrap_or(x_index..x_index); + range.end = range.end.max(x_index); highlighted_range = Some(range); } @@ -590,6 +533,61 @@ fn cell_style(indexed: &Indexed<&Cell>, style: &TerminalStyle, text_style: &Text } } +fn attach_mouse_handlers( + origin: Vector2F, + cur_size: SizeInfo, + display_offset: usize, + view_id: usize, + terminal_mutex: &Arc>>, + visible_bounds: RectF, + cx: &mut PaintContext, +) { + let click_mutex = terminal_mutex.clone(); + let drag_mutex = terminal_mutex.clone(); + let mouse_down_mutex = terminal_mutex.clone(); + + cx.scene.push_mouse_region(MouseRegion { + view_id, + mouse_down: Some(Rc::new(move |pos, _| { + let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); + mouse_down_mutex.lock().selection = + Some(Selection::new(SelectionType::Simple, point, side)) + })), + click: Some(Rc::new(move |pos, click_count, cx| { + let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); + + let selection_type = match click_count { + 0 => return, //This is a release + 1 => Some(SelectionType::Simple), + 2 => Some(SelectionType::Semantic), + 3 => Some(SelectionType::Lines), + _ => None, + }; + + let selection = + selection_type.map(|selection_type| Selection::new(selection_type, point, side)); + + let mut term = click_mutex.lock(); + term.selection = selection; + cx.focus_parent_view(); + cx.notify(); + })), + bounds: visible_bounds, + drag: Some(Rc::new(move |_delta, pos, cx| { + let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); + + let mut term = drag_mutex.lock(); + if let Some(mut selection) = term.selection.take() { + selection.update(point, side); + term.selection = Some(selection); + } + + cx.notify(); + })), + ..Default::default() + }); +} + ///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side() fn cell_side(pos: &PaneRelativePos, cur_size: SizeInfo) -> Side { let x = pos.0.x() as usize; From 9fd2bf2fa1a6dd3025682ac1723fe28381e54d51 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 16:07:24 -0700 Subject: [PATCH 46/47] Updated drag API to pass old,new, instead of delta,new --- crates/gpui/src/presenter.rs | 11 ++++------- crates/workspace/src/sidebar.rs | 7 ++++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 9810277a87a416f8d3845fba184dbe57a503fa37..d04a1df674dd8df2c94c1f827f743932cc7377b1 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -306,11 +306,8 @@ impl Presenter { .as_ref() .zip(self.prev_drag_position.as_mut()) { - dragged_region = Some(( - clicked_region.clone(), - position - *prev_drag_position, - position, - )); + dragged_region = + Some((clicked_region.clone(), *prev_drag_position, position)); *prev_drag_position = position; } @@ -369,11 +366,11 @@ impl Presenter { } } - if let Some((dragged_region, delta, position)) = dragged_region { + if let Some((dragged_region, prev_position, position)) = dragged_region { handled = true; if let Some(drag_callback) = dragged_region.drag { event_cx.with_current_view(dragged_region.view_id, |event_cx| { - drag_callback(delta, position, event_cx); + drag_callback(prev_position, position, event_cx); }) } } diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index c31998aa93995ff5f2f58efd8929bcfb4da76410..a500f994924d09aaae9304c2d3c9b0326f8803f7 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -188,12 +188,13 @@ impl Sidebar { }) .with_cursor_style(CursorStyle::ResizeLeftRight) .on_mouse_down(|_, _| {}) // This prevents the mouse down event from being propagated elsewhere - .on_drag(move |delta, _, cx| { + .on_drag(move |old_position, new_position, cx| { + let delta = new_position.x() - old_position.x(); let prev_width = *actual_width.borrow(); *custom_width.borrow_mut() = 0f32 .max(match side { - Side::Left => prev_width + delta.x(), - Side::Right => prev_width - delta.x(), + Side::Left => prev_width + delta, + Side::Right => prev_width - delta, }) .round(); From 8cb6e476f04c1775c14de1be2c7e59a8719a2e25 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 7 Jul 2022 17:19:53 -0700 Subject: [PATCH 47/47] Fixed panic / bug around scrolling and selections in termainl --- crates/terminal/src/terminal_element.rs | 79 ++++++++++++++++++++----- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 5e0849e33d5284f5d43003c7980a3cd66108dfc4..525d9f4066b4188a9934ce695676b8cdec844544 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -92,7 +92,6 @@ pub struct LayoutState { cursor: Option, background_color: Color, cur_size: SizeInfo, - display_offset: usize, terminal: Arc>>, selection_color: Color, } @@ -188,7 +187,6 @@ impl Element for TerminalEl { Some(block_text.clone()), ) }); - let display_offset = content.display_offset; drop(term); ( @@ -200,7 +198,6 @@ impl Element for TerminalEl { cursor, cur_size, background_color: terminal_theme.background, - display_offset, terminal: terminal_mutex, selection_color, }, @@ -219,14 +216,12 @@ impl Element for TerminalEl { paint_layer(cx, clip_bounds, |cx| { let cur_size = layout.cur_size.clone(); - let display_offset = layout.display_offset.clone(); let origin = bounds.origin() + vec2f(layout.em_width.0, 0.); //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse attach_mouse_handlers( origin, cur_size, - display_offset, self.view.id(), &layout.terminal, visible_bounds, @@ -536,7 +531,6 @@ fn cell_style(indexed: &Indexed<&Cell>, style: &TerminalStyle, text_style: &Text fn attach_mouse_handlers( origin: Vector2F, cur_size: SizeInfo, - display_offset: usize, view_id: usize, terminal_mutex: &Arc>>, visible_bounds: RectF, @@ -549,12 +543,24 @@ fn attach_mouse_handlers( cx.scene.push_mouse_region(MouseRegion { view_id, mouse_down: Some(Rc::new(move |pos, _| { - let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); - mouse_down_mutex.lock().selection = - Some(Selection::new(SelectionType::Simple, point, side)) + let mut term = mouse_down_mutex.lock(); + let (point, side) = mouse_to_cell_data( + pos, + origin, + cur_size, + term.renderable_content().display_offset, + ); + term.selection = Some(Selection::new(SelectionType::Simple, point, side)) })), click: Some(Rc::new(move |pos, click_count, cx| { - let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); + let mut term = click_mutex.lock(); + + let (point, side) = mouse_to_cell_data( + pos, + origin, + cur_size, + term.renderable_content().display_offset, + ); let selection_type = match click_count { 0 => return, //This is a release @@ -567,16 +573,21 @@ fn attach_mouse_handlers( let selection = selection_type.map(|selection_type| Selection::new(selection_type, point, side)); - let mut term = click_mutex.lock(); term.selection = selection; cx.focus_parent_view(); cx.notify(); })), bounds: visible_bounds, drag: Some(Rc::new(move |_delta, pos, cx| { - let (point, side) = mouse_to_cell_data(pos, origin, cur_size, display_offset); - let mut term = drag_mutex.lock(); + + let (point, side) = mouse_to_cell_data( + pos, + origin, + cur_size, + term.renderable_content().display_offset, + ); + if let Some(mut selection) = term.selection.take() { selection.update(point, side); term.selection = Some(selection); @@ -617,9 +628,13 @@ fn grid_cell(pos: &PaneRelativePos, cur_size: SizeInfo, display_offset: usize) - let col = min(GridCol(col as usize), cur_size.last_column()); let line = pos.y() / cur_size.cell_height(); - let line = min(line as usize, cur_size.bottommost_line().0 as usize); + let line = min(line as i32, cur_size.bottommost_line().0); - Point::new(GridLine((line - display_offset) as i32), col) + //when clicking, need to ADD to get to the top left cell + //e.g. total_lines - viewport_height, THEN subtract display offset + //0 -> total_lines - viewport_height - display_offset + mouse_line + + Point::new(GridLine(line - display_offset as i32), col) } ///Draws the grid as Alacritty sees it. Useful for checking if there is an inconsistency between @@ -688,4 +703,38 @@ mod test { ) ); } + + #[test] + fn test_mouse_to_selection_off_edge() { + let term_width = 100.; + let term_height = 200.; + let cell_width = 10.; + let line_height = 20.; + let mouse_pos_x = 100.; //Window relative + let mouse_pos_y = 100.; //Window relative + let origin_x = 10.; + let origin_y = 20.; + + let cur_size = alacritty_terminal::term::SizeInfo::new( + term_width, + term_height, + cell_width, + line_height, + 0., + 0., + false, + ); + + let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y); + let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in + let (point, _) = + crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0); + assert_eq!( + point, + alacritty_terminal::index::Point::new( + alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32), + alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize), + ) + ); + } }