From 3a72f2122a221348a7796dda1c982a4ea7f50c0d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 7 Nov 2023 11:12:16 -0700 Subject: [PATCH 01/14] Implement Editor::single_line --- crates/editor2/src/editor.rs | 25 ++++++++++--------------- crates/editor2/src/element.rs | 17 +++++++++++------ crates/gpui2/src/style.rs | 4 ++++ 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 13afe8aeba..99de3733a1 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -36,10 +36,10 @@ pub use element::{ use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, div, px, AnyElement, AppContext, BackgroundExecutor, Context, DispatchContext, Div, - Element, Entity, EventEmitter, FocusHandle, FontStyle, FontWeight, Hsla, Model, Pixels, Render, - Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView, - WindowContext, + actions, div, px, relative, AnyElement, AppContext, BackgroundExecutor, Context, + DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontStyle, FontWeight, Hsla, + Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, + WeakView, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -593,7 +593,6 @@ pub struct EditorStyle { pub background: Hsla, pub local_player: PlayerColor, pub text: TextStyle, - pub line_height_scalar: f32, pub scrollbar_width: Pixels, pub syntax: Arc, pub diagnostic_style: DiagnosticStyle, @@ -1795,14 +1794,11 @@ impl InlayHintRefreshReason { } impl Editor { - // pub fn single_line( - // field_editor_style: Option>, - // cx: &mut ViewContext, - // ) -> Self { - // let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new())); - // let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); - // Self::new(EditorMode::SingleLine, buffer, None, field_editor_style, cx) - // } + pub fn single_line(cx: &mut ViewContext) -> Self { + let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new())); + let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)); + Self::new(EditorMode::SingleLine, buffer, None, cx) + } // pub fn multi_line( // field_editor_style: Option>, @@ -9372,14 +9368,13 @@ impl Render for Editor { font_size: settings.buffer_font_size.into(), font_weight: FontWeight::NORMAL, font_style: FontStyle::Normal, - line_height: Default::default(), + line_height: relative(settings.buffer_line_height.value()), underline: None, }; EditorElement::new(EditorStyle { background: cx.theme().colors().editor_background, local_player: cx.theme().players().local(), text: text_style, - line_height_scalar: settings.buffer_line_height.value(), scrollbar_width: px(12.), syntax: cx.theme().syntax().clone(), diagnostic_style: cx.theme().diagnostic_style(), diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 09f9ef1a59..2236f8aeaf 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -8,10 +8,11 @@ use crate::{ use anyhow::Result; use collections::{BTreeMap, HashMap}; use gpui::{ - black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, BorrowWindow, - Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, Edges, Element, ElementId, - Entity, Hsla, KeyDownEvent, KeyListener, KeyMatch, Line, Pixels, ScrollWheelEvent, ShapedGlyph, - Size, StatefulInteraction, Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, + black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, + BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, + Edges, Element, ElementId, Entity, Hsla, KeyDownEvent, KeyListener, KeyMatch, Line, Pixels, + ScrollWheelEvent, ShapedGlyph, Size, StatefulInteraction, Style, TextRun, TextStyle, + TextSystem, ViewContext, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -1605,7 +1606,7 @@ impl EditorElement { let style = self.style.clone(); let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); let font_size = style.text.font_size.to_pixels(cx.rem_size()); - let line_height = (font_size * style.line_height_scalar).round(); + let line_height = style.text.line_height_in_pixels(cx.rem_size()); let em_width = cx .text_system() .typographic_bounds(font_id, font_size, 'm') @@ -2593,7 +2594,11 @@ impl Element for EditorElement { let rem_size = cx.rem_size(); let mut style = Style::default(); style.size.width = relative(1.).into(); - style.size.height = relative(1.).into(); + style.size.height = match editor.mode { + EditorMode::SingleLine => self.style.text.line_height_in_pixels(cx.rem_size()).into(), + EditorMode::AutoHeight { .. } => todo!(), + EditorMode::Full => relative(1.).into(), + }; cx.request_layout(&style, None) } diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 551a87624c..5de173c2d4 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -189,6 +189,10 @@ impl TextStyle { } } + pub fn line_height_in_pixels(&self, rem_size: Pixels) -> Pixels { + self.line_height.to_pixels(self.font_size, rem_size) + } + pub fn to_run(&self, len: usize) -> TextRun { TextRun { len, From 91f356a2f163adbc4781e864751477ac0cb629f3 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 7 Nov 2023 13:36:01 -0500 Subject: [PATCH 02/14] Begin documenting theme colors --- crates/theme2/src/colors.rs | 96 +++++++++++++++++++++- crates/theme2/src/default_colors.rs | 8 +- crates/theme_importer/src/theme_printer.rs | 4 +- crates/ui2/src/components/tab.rs | 2 +- 4 files changed, 101 insertions(+), 9 deletions(-) diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index e1df841c24..86373eb27a 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -69,25 +69,77 @@ pub struct GitStatusColors { #[refineable(debug, deserialize)] pub struct ThemeColors { pub border: Hsla, + /// Border color used for deemphasized borders, like a visual divider between two sections pub border_variant: Hsla, + /// Border color used for focused elements, like keyboard focused list item. pub border_focused: Hsla, + /// Border color used for selected elements, like an active search filter or selected checkbox. pub border_selected: Hsla, + /// Border color used for transparent borders. Used for placeholder borders when an element gains a border on state change. pub border_transparent: Hsla, + /// Border color used for disabled elements, like a disabled input or button. pub border_disabled: Hsla, + /// Used for elevated surfaces, like a context menu, popup, or dialog. pub elevated_surface_background: Hsla, + /// Used for grounded surfaces like a panel or tab. pub surface_background: Hsla, + /// Used the app background and blank panels or windows. pub background: Hsla, + /// Used for the background of an element that should have a different background than the surface it's on. + /// + /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... + /// + /// For an element that should have the same background as the surface it's on, use `ghost_element_background`. pub element_background: Hsla, + /// Used for the hover state of an element that should have a different background than the surface it's on. + /// + /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen. pub element_hover: Hsla, + /// Used for the active state of an element that should have a different background than the surface it's on. + /// + /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressd. pub element_active: Hsla, + /// Used for the selected state of an element that should have a different background than the surface it's on. + /// + /// Selected states are triggered by the element being selected (or "activated") by the user. + /// + /// This could include a selected checkbox, a toggleable button that is toggled on, etc. pub element_selected: Hsla, + /// Used for the disabled state of an element that should have a different background than the surface it's on. + /// + /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. pub element_disabled: Hsla, - pub element_placeholder: Hsla, - pub element_drop_target: Hsla, + /// Used for the text color of an element that should have a different background than the surface it's on. + /// + /// Example: A input with some default placeholder text. + pub element_placeholder_text: Hsla, + /// Background color of the area that shows where a dragged element will be dropped. + pub drop_target_background: Hsla, + /// Border color of the area that shows where a dragged element will be dropped. + // pub drop_target_border: Hsla, + /// Used for the background of a ghost element that should have the same background as the surface it's on. + /// + /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... + /// + /// For an element that should have a different background than the surface it's on, use `element_background`. pub ghost_element_background: Hsla, + /// Used for the hover state of a ghost element that should have the same background as the surface it's on. + /// + /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen. pub ghost_element_hover: Hsla, + /// Used for the active state of a ghost element that should have the same background as the surface it's on. + /// + /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressd. pub ghost_element_active: Hsla, + /// Used for the selected state of a ghost element that should have the same background as the surface it's on. + /// + /// Selected states are triggered by the element being selected (or "activated") by the user. + /// + /// This could include a selected checkbox, a toggleable button that is toggled on, etc. pub ghost_element_selected: Hsla, + /// Used for the disabled state of a ghost element that should have the same background as the surface it's on. + /// + /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. pub ghost_element_disabled: Hsla, pub text: Hsla, pub text_muted: Hsla, @@ -134,6 +186,46 @@ pub struct ThemeColors { pub terminal_ansi_magenta: Hsla, pub terminal_ansi_cyan: Hsla, pub terminal_ansi_white: Hsla, + // new colors + + // == elevation == + // elevatation_0_shadow + // elevatation_0_shadow_color + // elevatation_1_shadow + // elevatation_1_shadow_color + // elevatation_2_shadow + // elevatation_2_shadow_color + // elevatation_3_shadow + // elevatation_3_shadow_color + // elevatation_4_shadow + // elevatation_4_shadow_color + // elevatation_5_shadow + // elevatation_5_shadow_color + + // == rich text == + // headline + // paragraph + // link + // link_hover + // code_block_background + // code_block_border + + // == misc == + // inverted_element_* + // foreground: Overall foreground color. This color is only used if not overridden by a component. + // disabledForeground: Overall foreground for disabled elements. This color is only used if not overridden by a component. + // widget.border: Border color of widgets such as Find/Replace inside the editor. + // widget.shadow: Shadow color of widgets such as Find/Replace inside the editor. + // selection - foreground, background + // active_element_border + // inactive_element_border + // element_seperator + // scrollbar_thumb_background + // scrollbar_thumb_hover_background + // scrollbar_thumb_border + // scrollbar_track_background + // scrollbar_track_border + // scrollbar_status_opacity } #[derive(Refineable, Clone)] diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index f79518e769..93773ffab1 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -216,8 +216,8 @@ impl ThemeColors { element_active: neutral().light().step_5(), element_selected: neutral().light().step_5(), element_disabled: neutral().light_alpha().step_3(), - element_placeholder: neutral().light().step_11(), - element_drop_target: blue().light_alpha().step_2(), + element_placeholder_text: neutral().light().step_11(), + drop_target_background: blue().light_alpha().step_2(), ghost_element_background: system.transparent, ghost_element_hover: neutral().light().step_4(), ghost_element_active: neutral().light().step_5(), @@ -289,8 +289,8 @@ impl ThemeColors { element_active: neutral().dark().step_5(), element_selected: neutral().dark().step_5(), element_disabled: neutral().dark_alpha().step_3(), - element_placeholder: neutral().dark().step_11(), - element_drop_target: blue().dark_alpha().step_2(), + element_placeholder_text: neutral().dark().step_11(), + drop_target_background: blue().dark_alpha().step_2(), ghost_element_background: system.transparent, ghost_element_hover: neutral().dark().step_4(), ghost_element_active: neutral().dark().step_5(), diff --git a/crates/theme_importer/src/theme_printer.rs b/crates/theme_importer/src/theme_printer.rs index aa74692164..1857567604 100644 --- a/crates/theme_importer/src/theme_printer.rs +++ b/crates/theme_importer/src/theme_printer.rs @@ -140,8 +140,8 @@ impl<'a> Debug for ThemeColorsRefinementPrinter<'a> { ("element_active", self.0.element_active), ("element_selected", self.0.element_selected), ("element_disabled", self.0.element_disabled), - ("element_placeholder", self.0.element_placeholder), - ("element_drop_target", self.0.element_drop_target), + ("element_placeholder_text", self.0.element_placeholder_text), + ("drop_target_background", self.0.drop_target_background), ("ghost_element_background", self.0.ghost_element_background), ("ghost_element_hover", self.0.ghost_element_hover), ("ghost_element_active", self.0.ghost_element_active), diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 416db2d172..e936dc924a 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -127,7 +127,7 @@ impl Tab { div() .id(self.id.clone()) .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone())) - .drag_over::(|d| d.bg(cx.theme().colors().element_drop_target)) + .drag_over::(|d| d.bg(cx.theme().colors().drop_target_background)) .on_drop(|_view, state: View, cx| { eprintln!("{:?}", state.read(cx)); }) From df84ba422273c0cccda1118bb0cca9c01d325a3c Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 7 Nov 2023 14:04:09 -0500 Subject: [PATCH 03/14] Continue documenting theme colors --- crates/theme2/src/colors.rs | 170 +++++++++++++-------- crates/theme2/src/default_colors.rs | 2 - crates/theme_importer/src/theme_printer.rs | 1 - 3 files changed, 110 insertions(+), 63 deletions(-) diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index 72eba6b76d..8ee4b5fd47 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -71,53 +71,49 @@ pub struct GitStatusColors { #[refineable(debug, deserialize)] pub struct ThemeColors { pub border: Hsla, - /// Border color used for deemphasized borders, like a visual divider between two sections + /// Border color. Used for deemphasized borders, like a visual divider between two sections pub border_variant: Hsla, - /// Border color used for focused elements, like keyboard focused list item. + /// Border color. Used for focused elements, like keyboard focused list item. pub border_focused: Hsla, - /// Border color used for selected elements, like an active search filter or selected checkbox. + /// Border color. Used for selected elements, like an active search filter or selected checkbox. pub border_selected: Hsla, - /// Border color used for transparent borders. Used for placeholder borders when an element gains a border on state change. + /// Border color. Used for transparent borders. Used for placeholder borders when an element gains a border on state change. pub border_transparent: Hsla, - /// Border color used for disabled elements, like a disabled input or button. + /// Border color. Used for disabled elements, like a disabled input or button. pub border_disabled: Hsla, - /// Used for elevated surfaces, like a context menu, popup, or dialog. + /// Border color. Used for elevated surfaces, like a context menu, popup, or dialog. pub elevated_surface_background: Hsla, - /// Used for grounded surfaces like a panel or tab. + /// Background Color. Used for grounded surfaces like a panel or tab. pub surface_background: Hsla, - /// Used the app background and blank panels or windows. + /// Background Color. Used for the app background and blank panels or windows. pub background: Hsla, - /// Used for the background of an element that should have a different background than the surface it's on. + /// Background Color. Used for the background of an element that should have a different background than the surface it's on. /// /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... /// /// For an element that should have the same background as the surface it's on, use `ghost_element_background`. pub element_background: Hsla, - /// Used for the hover state of an element that should have a different background than the surface it's on. + /// Background Color. Used for the hover state of an element that should have a different background than the surface it's on. /// /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen. pub element_hover: Hsla, - /// Used for the active state of an element that should have a different background than the surface it's on. + /// Background Color. Used for the active state of an element that should have a different background than the surface it's on. /// /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressd. pub element_active: Hsla, - /// Used for the selected state of an element that should have a different background than the surface it's on. + /// Background Color. Used for the selected state of an element that should have a different background than the surface it's on. /// /// Selected states are triggered by the element being selected (or "activated") by the user. /// /// This could include a selected checkbox, a toggleable button that is toggled on, etc. pub element_selected: Hsla, - /// Used for the disabled state of an element that should have a different background than the surface it's on. + /// Background Color. Used for the disabled state of an element that should have a different background than the surface it's on. /// /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. pub element_disabled: Hsla, - /// Used for the text color of an element that should have a different background than the surface it's on. - /// - /// Example: A input with some default placeholder text. - pub element_placeholder_text: Hsla, - /// Background color of the area that shows where a dragged element will be dropped. + /// Background Color. Used for the area that shows where a dragged element will be dropped. pub drop_target_background: Hsla, - /// Border color of the area that shows where a dragged element will be dropped. + /// Border Color. Used to show the area that shows where a dragged element will be dropped. // pub drop_target_border: Hsla, /// Used for the background of a ghost element that should have the same background as the surface it's on. /// @@ -125,109 +121,163 @@ pub struct ThemeColors { /// /// For an element that should have a different background than the surface it's on, use `element_background`. pub ghost_element_background: Hsla, - /// Used for the hover state of a ghost element that should have the same background as the surface it's on. + /// Background Color. Used for the hover state of a ghost element that should have the same background as the surface it's on. /// /// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen. pub ghost_element_hover: Hsla, - /// Used for the active state of a ghost element that should have the same background as the surface it's on. + /// Background Color. Used for the active state of a ghost element that should have the same background as the surface it's on. /// /// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressd. pub ghost_element_active: Hsla, - /// Used for the selected state of a ghost element that should have the same background as the surface it's on. + /// Background Color. Used for the selected state of a ghost element that should have the same background as the surface it's on. /// /// Selected states are triggered by the element being selected (or "activated") by the user. /// /// This could include a selected checkbox, a toggleable button that is toggled on, etc. pub ghost_element_selected: Hsla, - /// Used for the disabled state of a ghost element that should have the same background as the surface it's on. + /// Background Color. Used for the disabled state of a ghost element that should have the same background as the surface it's on. /// /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. pub ghost_element_disabled: Hsla, + /// Text Color. Default text color used for most text. pub text: Hsla, + /// Text Color. Color of muted or deemphasized text. It is a subdued version of the standard text color. pub text_muted: Hsla, + /// Text Color. Color of the placeholder text typically shown in input fields to guide the user to enter valid data. pub text_placeholder: Hsla, + /// Text Color. Color used for text denoting disabled elements. Typically, the color is faded or grayed out to emphasize the disabled state. pub text_disabled: Hsla, + /// Text Color. Color used for emphasis or highlighting certain text, like an active filter or a matched character in a search. pub text_accent: Hsla, + /// Fill Color. Used for the default fill color of an icon. pub icon: Hsla, + /// Fill Color. Used for the muted or deemphasized fill color of an icon. + /// + /// This might be used to show an icon in an inactive pane, or to demphasize a series of icons to give them less visual weight. pub icon_muted: Hsla, + /// Fill Color. Used for the disabled fill color of an icon. + /// + /// Disabled states are shown when a user cannot interact with an element, like a icon button. pub icon_disabled: Hsla, + /// Fill Color. Used for the placeholder fill color of an icon. + /// + /// This might be used to show an icon in an input that disappears when the user enters text. pub icon_placeholder: Hsla, + /// Fill Color. Used for the accent fill color of an icon. + /// + /// This might be used to show when a toggleable icon button is selected. pub icon_accent: Hsla, + + // === + // UI Elements + // === pub status_bar_background: Hsla, pub title_bar_background: Hsla, pub toolbar_background: Hsla, pub tab_bar_background: Hsla, pub tab_inactive_background: Hsla, pub tab_active_background: Hsla, + // pub panel_background: Hsla, + // pub pane_focused_border: Hsla, + // /// The color of the scrollbar thumb. + // pub scrollbar_thumb_background: Hsla, + // /// The color of the scrollbar thumb when hovered over. + // pub scrollbar_thumb_hover_background: Hsla, + // /// The border color of the scrollbar thumb. + // pub scrollbar_thumb_border: Hsla, + // /// The background color of the scrollbar track. + // pub scrollbar_track_background: Hsla, + // /// The border color of the scrollbar track. + // pub scrollbar_track_border: Hsla, + // /// The opacity of the scrollbar status marks, like diagnostic states and git status.. + // pub scrollbar_status_opacity: Hsla, + + // === + // Editor + // === pub editor_background: Hsla, + // pub editor_inactive_background: Hsla, pub editor_gutter_background: Hsla, pub editor_subheader_background: Hsla, pub editor_active_line_background: Hsla, pub editor_highlighted_line_background: Hsla, + /// Text Color. Used for the text of the line number in the editor gutter. pub editor_line_number: Hsla, + /// Text Color. Used for the text of the line number in the editor gutter when the line is highlighted. pub editor_active_line_number: Hsla, + /// Text Color. Used to mark invisible characters in the editor. + /// + /// Example: spaces, tabs, carriage returns, etc. pub editor_invisible: Hsla, pub editor_wrap_guide: Hsla, pub editor_active_wrap_guide: Hsla, pub editor_document_highlight_read_background: Hsla, pub editor_document_highlight_write_background: Hsla, + + // === + // Terminal + // === + /// Terminal Background Color pub terminal_background: Hsla, + /// Bright Black Color for ANSI Terminal pub terminal_ansi_bright_black: Hsla, + /// Bright Red Color for ANSI Terminal pub terminal_ansi_bright_red: Hsla, + /// Bright Green Color for ANSI Terminal pub terminal_ansi_bright_green: Hsla, + /// Bright Yellow Color for ANSI Terminal pub terminal_ansi_bright_yellow: Hsla, + /// Bright Blue Color for ANSI Terminal pub terminal_ansi_bright_blue: Hsla, + /// Bright Magenta Color for ANSI Terminal pub terminal_ansi_bright_magenta: Hsla, + /// Bright Cyan Color for ANSI Terminal pub terminal_ansi_bright_cyan: Hsla, + /// Bright White Color for ANSI Terminal pub terminal_ansi_bright_white: Hsla, + /// Black Color for ANSI Terminal pub terminal_ansi_black: Hsla, + /// Red Color for ANSI Terminal pub terminal_ansi_red: Hsla, + /// Green Color for ANSI Terminal pub terminal_ansi_green: Hsla, + /// Yellow Color for ANSI Terminal pub terminal_ansi_yellow: Hsla, + /// Blue Color for ANSI Terminal pub terminal_ansi_blue: Hsla, + /// Magenta Color for ANSI Terminal pub terminal_ansi_magenta: Hsla, + /// Cyan Color for ANSI Terminal pub terminal_ansi_cyan: Hsla, + /// White Color for ANSI Terminal pub terminal_ansi_white: Hsla, // new colors - // == elevation == - // elevatation_0_shadow - // elevatation_0_shadow_color - // elevatation_1_shadow - // elevatation_1_shadow_color - // elevatation_2_shadow - // elevatation_2_shadow_color - // elevatation_3_shadow - // elevatation_3_shadow_color - // elevatation_4_shadow - // elevatation_4_shadow_color - // elevatation_5_shadow - // elevatation_5_shadow_color + // === + // Elevation + // === + // elevation_0_shadow + // elevation_0_shadow_color + // elevation_1_shadow + // elevation_1_shadow_color + // elevation_2_shadow + // elevation_2_shadow_color + // elevation_3_shadow + // elevation_3_shadow_color + // elevation_4_shadow + // elevation_4_shadow_color + // elevation_5_shadow + // elevation_5_shadow_color - // == rich text == - // headline - // paragraph - // link - // link_hover - // code_block_background - // code_block_border - - // == misc == - // inverted_element_* - // foreground: Overall foreground color. This color is only used if not overridden by a component. - // disabledForeground: Overall foreground for disabled elements. This color is only used if not overridden by a component. - // widget.border: Border color of widgets such as Find/Replace inside the editor. - // widget.shadow: Shadow color of widgets such as Find/Replace inside the editor. - // selection - foreground, background - // active_element_border - // inactive_element_border - // element_seperator - // scrollbar_thumb_background - // scrollbar_thumb_hover_background - // scrollbar_thumb_border - // scrollbar_track_background - // scrollbar_track_border - // scrollbar_status_opacity + // === + // UI Text + // === + // pub headline: Hsla, + // pub paragraph: Hsla, + // pub link: Hsla, + // pub link_hover: Hsla, + // pub code_block_background: Hsla, + // pub code_block_border: Hsla, } #[derive(Refineable, Clone)] diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 9804af234a..7252e82972 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -220,7 +220,6 @@ impl ThemeColors { element_active: neutral().light().step_5(), element_selected: neutral().light().step_5(), element_disabled: neutral().light_alpha().step_3(), - element_placeholder_text: neutral().light().step_11(), drop_target_background: blue().light_alpha().step_2(), ghost_element_background: system.transparent, ghost_element_hover: neutral().light().step_4(), @@ -293,7 +292,6 @@ impl ThemeColors { element_active: neutral().dark().step_5(), element_selected: neutral().dark().step_5(), element_disabled: neutral().dark_alpha().step_3(), - element_placeholder_text: neutral().dark().step_11(), drop_target_background: blue().dark_alpha().step_2(), ghost_element_background: system.transparent, ghost_element_hover: neutral().dark().step_4(), diff --git a/crates/theme_importer/src/theme_printer.rs b/crates/theme_importer/src/theme_printer.rs index 1857567604..0f20a8d60f 100644 --- a/crates/theme_importer/src/theme_printer.rs +++ b/crates/theme_importer/src/theme_printer.rs @@ -140,7 +140,6 @@ impl<'a> Debug for ThemeColorsRefinementPrinter<'a> { ("element_active", self.0.element_active), ("element_selected", self.0.element_selected), ("element_disabled", self.0.element_disabled), - ("element_placeholder_text", self.0.element_placeholder_text), ("drop_target_background", self.0.drop_target_background), ("ghost_element_background", self.0.ghost_element_background), ("ghost_element_hover", self.0.ghost_element_hover), From b804b25c214547328f091a01e8b8c05b5f403433 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 7 Nov 2023 12:00:05 -0700 Subject: [PATCH 04/14] Fix confusing error message --- crates/gpui2/src/element.rs | 5 ++++- crates/gpui2/src/interactive.rs | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 2a0f557272..8fdc17de07 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -134,7 +134,10 @@ where .layout(state, frame_state.as_mut().unwrap(), cx); } } - _ => panic!("must call initialize before layout"), + ElementRenderPhase::Start => panic!("must call initialize before layout"), + ElementRenderPhase::LayoutRequested { .. } | ElementRenderPhase::Painted => { + panic!("element rendered twice") + } }; self.phase = ElementRenderPhase::LayoutRequested { diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 0725a10aca..84beb56ef8 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -397,9 +397,10 @@ pub trait ElementInteraction: 'static { None }), )); - let result = stateful.stateless.initialize(cx, f); - stateful.key_listeners.pop(); - result + + cx.with_key_dispatch_context(stateful.dispatch_context.clone(), |cx| { + cx.with_key_listeners(mem::take(&mut stateful.key_listeners), f) + }) }) } else { let stateless = self.as_stateless_mut(); From 0233864e92cf263bcd868e1402395fbed18e8078 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 7 Nov 2023 12:04:37 -0700 Subject: [PATCH 05/14] Fix loading keyfiles --- crates/settings2/src/keymap_file.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/settings2/src/keymap_file.rs b/crates/settings2/src/keymap_file.rs index e51bd76e5e..93635935cb 100644 --- a/crates/settings2/src/keymap_file.rs +++ b/crates/settings2/src/keymap_file.rs @@ -1,7 +1,7 @@ use crate::{settings_store::parse_json_with_comments, SettingsAssets}; use anyhow::{anyhow, Context, Result}; use collections::BTreeMap; -use gpui::{AppContext, KeyBinding, SharedString}; +use gpui::{actions, Action, AppContext, KeyBinding, SharedString}; use schemars::{ gen::{SchemaGenerator, SchemaSettings}, schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation}, @@ -137,8 +137,10 @@ impl KeymapFile { } } +actions!(NoAction); + fn no_action() -> Box { - todo!() + NoAction.boxed_clone() } #[cfg(test)] From 1e6a0f1c7bbcb25389def8232aaef3769246a8de Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 7 Nov 2023 12:07:04 -0700 Subject: [PATCH 06/14] Wire up GoToLine modal --- crates/go_to_line2/src/go_to_line.rs | 8 +++----- crates/workspace2/src/modal_layer.rs | 14 +++----------- crates/workspace2/src/workspace2.rs | 6 +++--- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index 764602c986..13d283ecff 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -1,12 +1,10 @@ -use gpui::{div, px, red, AppContext, Div, Render, Styled, ViewContext, VisualContext}; -use serde::Deserialize; +use gpui::{actions, div, px, red, AppContext, Div, Render, Styled, ViewContext, VisualContext}; use workspace::ModalRegistry; -// actions!(go_to_line, [Toggle]); -#[derive(Clone, Default, PartialEq, Deserialize)] -struct Toggle; +actions!(Toggle); pub fn init(cx: &mut AppContext) { + cx.register_action_type::(); cx.global_mut::() .register_modal(Toggle, |_, cx| { // if let Some(editor) = workspace diff --git a/crates/workspace2/src/modal_layer.rs b/crates/workspace2/src/modal_layer.rs index e7cee53b2b..01f940273a 100644 --- a/crates/workspace2/src/modal_layer.rs +++ b/crates/workspace2/src/modal_layer.rs @@ -1,8 +1,8 @@ use std::{any::TypeId, sync::Arc}; use gpui::{ - div, AnyView, AppContext, Component, DispatchPhase, Div, ParentElement, Render, - StatelessInteractive, View, ViewContext, + div, AnyView, AppContext, DispatchPhase, Div, ParentElement, Render, StatelessInteractive, + View, ViewContext, }; use crate::Workspace; @@ -28,10 +28,6 @@ struct ToggleModal { name: String, } -// complete change of plan? -// on_action(ToggleModal{ name}) -// register_modal(name, |workspace, cx| { ... }) - impl ModalRegistry { pub fn register_modal(&mut self, action: A, build_view: B) where @@ -40,12 +36,10 @@ impl ModalRegistry { { let build_view = Arc::new(build_view); - dbg!("yonder"); self.registered_modals.push(( TypeId::of::(), Box::new(move |mut div| { let build_view = build_view.clone(); - dbg!("this point"); div.on_action( move |workspace: &mut Workspace, @@ -75,9 +69,7 @@ impl ModalLayer { Self { open_modal: None } } - pub fn render(&self, cx: &ViewContext) -> impl Component { - dbg!("rendering ModalLayer"); - + pub fn render(&self, workspace: &Workspace, cx: &ViewContext) -> Div { let mut div = div(); // div, c workspace.toggle_modal()div.on_action()) { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 6b8077cd38..90204f6038 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -3707,7 +3707,9 @@ impl Render for Workspace { .bg(cx.theme().colors().background) .child(self.render_titlebar(cx)) .child( - div() + self.modal_layer + .read(cx) + .render(self, cx) .flex_1() .w_full() .flex() @@ -3840,8 +3842,6 @@ impl Render for Workspace { // .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))), // ), ) - // .child(self.modal_layer.clone()) - .child(self.modal_layer.read(cx).render(cx)) } } From a21c49c015ca05e76e6ed688e9256d9d860a108c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 7 Nov 2023 14:05:23 -0700 Subject: [PATCH 07/14] Make it possible to render a single line editor --- crates/editor2/src/element.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 2236f8aeaf..5af5d97e03 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -1595,7 +1595,7 @@ impl EditorElement { &mut self, editor: &mut Editor, cx: &mut ViewContext<'_, Editor>, - bounds: Bounds, + mut bounds: Bounds, ) -> LayoutState { // let mut size = constraint.max; // if size.x.is_infinite() { @@ -1673,8 +1673,7 @@ impl EditorElement { // .min(line_height * max_lines as f32), // ) } else if let EditorMode::SingleLine = editor_mode { - todo!() - // size.set_y(line_height.max(constraint.min_along(Axis::Vertical))) + bounds.size.height = line_height.min(bounds.size.height); } // todo!() // else if size.y.is_infinite() { From 5c450843a53a3b27688138a58b2befacba363e6d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 7 Nov 2023 14:19:15 -0700 Subject: [PATCH 08/14] Add text and focus to editor --- crates/editor2/src/editor.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 99de3733a1..c63c531ceb 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -8363,9 +8363,9 @@ impl Editor { // .max_point() // } - // pub fn text(&self, cx: &AppContext) -> String { - // self.buffer.read(cx).read(cx).text() - // } + pub fn text(&self, cx: &AppContext) -> String { + self.buffer.read(cx).read(cx).text() + } // pub fn set_text(&mut self, text: impl Into>, cx: &mut ViewContext) { // self.transact(cx, |this, cx| { @@ -9185,6 +9185,10 @@ impl Editor { // }); // supports // } + + fn focus(&self, cx: &mut WindowContext) { + cx.focus(&self.focus_handle) + } } pub trait CollaborationHub { From b2ae08b1598649ae60aa6c8bd7edd09c8180fc98 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 7 Nov 2023 16:30:04 -0700 Subject: [PATCH 09/14] Implement an InputHandler trait for gpui2 Co-Authored-By: Marshall Co-Authored-By: Max Co-Authored-By: Julia --- crates/editor2/src/editor.rs | 385 ++++++++++++----------- crates/editor2/src/element.rs | 4 + crates/gpui2/src/app.rs | 2 +- crates/gpui2/src/gpui2.rs | 2 + crates/gpui2/src/window.rs | 28 +- crates/gpui2/src/window_input_handler.rs | 89 ++++++ 6 files changed, 315 insertions(+), 195 deletions(-) create mode 100644 crates/gpui2/src/window_input_handler.rs diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index c63c531ceb..61db3fb492 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -38,8 +38,8 @@ use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ actions, div, px, relative, AnyElement, AppContext, BackgroundExecutor, Context, DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontStyle, FontWeight, Hsla, - Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, - WeakView, WindowContext, + Model, Pixels, PlatformInputHandler, Render, Styled, Subscription, Task, TextStyle, View, + ViewContext, VisualContext, WeakView, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -82,7 +82,7 @@ use std::{ }; pub use sum_tree::Bias; use sum_tree::TreeMap; -use text::Rope; +use text::{OffsetUtf16, Rope}; use theme::{ ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, }; @@ -9485,214 +9485,225 @@ impl Render for Editor { // false // } -// -// fn text_for_range(&self, range_utf16: Range, cx: &AppContext) -> Option { -// Some( -// self.buffer -// .read(cx) -// .read(cx) -// .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end)) -// .collect(), -// ) -// } -// fn selected_text_range(&self, cx: &AppContext) -> Option> { -// // Prevent the IME menu from appearing when holding down an alphabetic key -// // while input is disabled. -// if !self.input_enabled { -// return None; -// } +impl PlatformInputHandler for Editor { + fn text_for_range(&self, range_utf16: Range) -> Option { + // Some( + // self.buffer + // .read(cx) + // .read(cx) + // .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end)) + // .collect(), + // ) + todo!() + } -// let range = self.selections.newest::(cx).range(); -// Some(range.start.0..range.end.0) -// } + fn selected_text_range(&self) -> Option> { + // Prevent the IME menu from appearing when holding down an alphabetic key + // while input is disabled. + // if !self.input_enabled { + // return None; + // } -// fn marked_text_range(&self, cx: &AppContext) -> Option> { -// let snapshot = self.buffer.read(cx).read(cx); -// let range = self.text_highlights::(cx)?.1.get(0)?; -// Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0) -// } + // let range = self.selections.newest::(cx).range(); + // Some(range.start.0..range.end.0) + todo!() + } -// fn unmark_text(&mut self, cx: &mut ViewContext) { -// self.clear_highlights::(cx); -// self.ime_transaction.take(); -// } + fn marked_text_range(&self, cx: &AppContext) -> Option> { + // let snapshot = self.buffer.read(cx).read(cx); + // let range = self.text_highlights::(cx)?.1.get(0)?; + // Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0) + todo!() + } -// fn replace_text_in_range( -// &mut self, -// range_utf16: Option>, -// text: &str, -// cx: &mut ViewContext, -// ) { -// if !self.input_enabled { -// cx.emit(Event::InputIgnored { text: text.into() }); -// return; -// } + fn unmark_text(&mut self, cx: &mut ViewContext) { + // self.clear_highlights::(cx); + // self.ime_transaction.take(); + todo!() + } -// self.transact(cx, |this, cx| { -// let new_selected_ranges = if let Some(range_utf16) = range_utf16 { -// let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); -// Some(this.selection_replacement_ranges(range_utf16, cx)) -// } else { -// this.marked_text_ranges(cx) -// }; + fn replace_text_in_range( + &mut self, + //range_utf16: Option>, + // text: &str, + cx: &mut ViewContext, + ) { + // if !self.input_enabled { + // cx.emit(Event::InputIgnored { text: text.into() }); + // return; + // } -// let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| { -// let newest_selection_id = this.selections.newest_anchor().id; -// this.selections -// .all::(cx) -// .iter() -// .zip(ranges_to_replace.iter()) -// .find_map(|(selection, range)| { -// if selection.id == newest_selection_id { -// Some( -// (range.start.0 as isize - selection.head().0 as isize) -// ..(range.end.0 as isize - selection.head().0 as isize), -// ) -// } else { -// None -// } -// }) -// }); + // self.transact(cx, |this, cx| { + // let new_selected_ranges = if let Some(range_utf16) = range_utf16 { + // let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); + // Some(this.selection_replacement_ranges(range_utf16, cx)) + // } else { + // this.marked_text_ranges(cx) + // }; -// cx.emit(Event::InputHandled { -// utf16_range_to_replace: range_to_replace, -// text: text.into(), -// }); + // let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| { + // let newest_selection_id = this.selections.newest_anchor().id; + // this.selections + // .all::(cx) + // .iter() + // .zip(ranges_to_replace.iter()) + // .find_map(|(selection, range)| { + // if selection.id == newest_selection_id { + // Some( + // (range.start.0 as isize - selection.head().0 as isize) + // ..(range.end.0 as isize - selection.head().0 as isize), + // ) + // } else { + // None + // } + // }) + // }); -// if let Some(new_selected_ranges) = new_selected_ranges { -// this.change_selections(None, cx, |selections| { -// selections.select_ranges(new_selected_ranges) -// }); -// } + // cx.emit(Event::InputHandled { + // utf16_range_to_replace: range_to_replace, + // text: text.into(), + // }); -// this.handle_input(text, cx); -// }); + // if let Some(new_selected_ranges) = new_selected_ranges { + // this.change_selections(None, cx, |selections| { + // selections.select_ranges(new_selected_ranges) + // }); + // } -// if let Some(transaction) = self.ime_transaction { -// self.buffer.update(cx, |buffer, cx| { -// buffer.group_until_transaction(transaction, cx); -// }); -// } + // this.handle_input(text, cx); + // }); -// self.unmark_text(cx); -// } + // if let Some(transaction) = self.ime_transaction { + // self.buffer.update(cx, |buffer, cx| { + // buffer.group_until_transaction(transaction, cx); + // }); + // } -// fn replace_and_mark_text_in_range( -// &mut self, -// range_utf16: Option>, -// text: &str, -// new_selected_range_utf16: Option>, -// cx: &mut ViewContext, -// ) { -// if !self.input_enabled { -// cx.emit(Event::InputIgnored { text: text.into() }); -// return; -// } + // self.unmark_text(cx); + todo!() + } -// let transaction = self.transact(cx, |this, cx| { -// let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) { -// let snapshot = this.buffer.read(cx).read(cx); -// if let Some(relative_range_utf16) = range_utf16.as_ref() { -// for marked_range in &mut marked_ranges { -// marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end; -// marked_range.start.0 += relative_range_utf16.start; -// marked_range.start = -// snapshot.clip_offset_utf16(marked_range.start, Bias::Left); -// marked_range.end = -// snapshot.clip_offset_utf16(marked_range.end, Bias::Right); -// } -// } -// Some(marked_ranges) -// } else if let Some(range_utf16) = range_utf16 { -// let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); -// Some(this.selection_replacement_ranges(range_utf16, cx)) -// } else { -// None -// }; + fn replace_and_mark_text_in_range( + &mut self, + range_utf16: Option>, + text: &str, + new_selected_range_utf16: Option>, + cx: &mut ViewContext, + ) { + // if !self.input_enabled { + // cx.emit(Event::InputIgnored { text: text.into() }); + // return; + // } -// let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| { -// let newest_selection_id = this.selections.newest_anchor().id; -// this.selections -// .all::(cx) -// .iter() -// .zip(ranges_to_replace.iter()) -// .find_map(|(selection, range)| { -// if selection.id == newest_selection_id { -// Some( -// (range.start.0 as isize - selection.head().0 as isize) -// ..(range.end.0 as isize - selection.head().0 as isize), -// ) -// } else { -// None -// } -// }) -// }); + // let transaction = self.transact(cx, |this, cx| { + // let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) { + // let snapshot = this.buffer.read(cx).read(cx); + // if let Some(relative_range_utf16) = range_utf16.as_ref() { + // for marked_range in &mut marked_ranges { + // marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end; + // marked_range.start.0 += relative_range_utf16.start; + // marked_range.start = + // snapshot.clip_offset_utf16(marked_range.start, Bias::Left); + // marked_range.end = + // snapshot.clip_offset_utf16(marked_range.end, Bias::Right); + // } + // } + // Some(marked_ranges) + // } else if let Some(range_utf16) = range_utf16 { + // let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); + // Some(this.selection_replacement_ranges(range_utf16, cx)) + // } else { + // None + // }; -// cx.emit(Event::InputHandled { -// utf16_range_to_replace: range_to_replace, -// text: text.into(), -// }); + // let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| { + // let newest_selection_id = this.selections.newest_anchor().id; + // this.selections + // .all::(cx) + // .iter() + // .zip(ranges_to_replace.iter()) + // .find_map(|(selection, range)| { + // if selection.id == newest_selection_id { + // Some( + // (range.start.0 as isize - selection.head().0 as isize) + // ..(range.end.0 as isize - selection.head().0 as isize), + // ) + // } else { + // None + // } + // }) + // }); -// if let Some(ranges) = ranges_to_replace { -// this.change_selections(None, cx, |s| s.select_ranges(ranges)); -// } + // cx.emit(Event::InputHandled { + // utf16_range_to_replace: range_to_replace, + // text: text.into(), + // }); -// let marked_ranges = { -// let snapshot = this.buffer.read(cx).read(cx); -// this.selections -// .disjoint_anchors() -// .iter() -// .map(|selection| { -// selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) -// }) -// .collect::>() -// }; + // if let Some(ranges) = ranges_to_replace { + // this.change_selections(None, cx, |s| s.select_ranges(ranges)); + // } -// if text.is_empty() { -// this.unmark_text(cx); -// } else { -// this.highlight_text::( -// marked_ranges.clone(), -// this.style(cx).composition_mark, -// cx, -// ); -// } + // let marked_ranges = { + // let snapshot = this.buffer.read(cx).read(cx); + // this.selections + // .disjoint_anchors() + // .iter() + // .map(|selection| { + // selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) + // }) + // .collect::>() + // }; -// this.handle_input(text, cx); + // if text.is_empty() { + // this.unmark_text(cx); + // } else { + // this.highlight_text::( + // marked_ranges.clone(), + // this.style(cx).composition_mark, + // cx, + // ); + // } -// if let Some(new_selected_range) = new_selected_range_utf16 { -// let snapshot = this.buffer.read(cx).read(cx); -// let new_selected_ranges = marked_ranges -// .into_iter() -// .map(|marked_range| { -// let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0; -// let new_start = OffsetUtf16(new_selected_range.start + insertion_start); -// let new_end = OffsetUtf16(new_selected_range.end + insertion_start); -// snapshot.clip_offset_utf16(new_start, Bias::Left) -// ..snapshot.clip_offset_utf16(new_end, Bias::Right) -// }) -// .collect::>(); + // this.handle_input(text, cx); -// drop(snapshot); -// this.change_selections(None, cx, |selections| { -// selections.select_ranges(new_selected_ranges) -// }); -// } -// }); + // if let Some(new_selected_range) = new_selected_range_utf16 { + // let snapshot = this.buffer.read(cx).read(cx); + // let new_selected_ranges = marked_ranges + // .into_iter() + // .map(|marked_range| { + // let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0; + // let new_start = OffsetUtf16(new_selected_range.start + insertion_start); + // let new_end = OffsetUtf16(new_selected_range.end + insertion_start); + // snapshot.clip_offset_utf16(new_start, Bias::Left) + // ..snapshot.clip_offset_utf16(new_end, Bias::Right) + // }) + // .collect::>(); -// self.ime_transaction = self.ime_transaction.or(transaction); -// if let Some(transaction) = self.ime_transaction { -// self.buffer.update(cx, |buffer, cx| { -// buffer.group_until_transaction(transaction, cx); -// }); -// } + // drop(snapshot); + // this.change_selections(None, cx, |selections| { + // selections.select_ranges(new_selected_ranges) + // }); + // } + // }); -// if self.text_highlights::(cx).is_none() { -// self.ime_transaction.take(); -// } -// } -// } + // self.ime_transaction = self.ime_transaction.or(transaction); + // if let Some(transaction) = self.ime_transaction { + // self.buffer.update(cx, |buffer, cx| { + // buffer.group_until_transaction(transaction, cx); + // }); + // } + + // if self.text_highlights::(cx).is_none() { + // self.ime_transaction.take(); + // } + todo!() + } + + fn bounds_for_range(&self, range_utf16: Range) -> Option> { + todo!() + } +} // fn build_style( // settings: &ThemeSettings, diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 5af5d97e03..3ff68e1897 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2623,6 +2623,10 @@ impl Element for EditorElement { } }); + if editor.focus_handle.is_focused(cx) { + cx.set_input_handler(editor.handle); + } + cx.with_content_mask(ContentMask { bounds }, |cx| { let gutter_bounds = Bounds { origin: bounds.origin, diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 63d2143a67..42d34e0c20 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -154,7 +154,7 @@ type ReleaseListener = Box; // } pub struct AppContext { - this: Weak, + pub(crate) this: Weak, pub(crate) platform: Rc, app_metadata: AppMetadata, text_system: Arc, diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 348da17356..e59b196a91 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -24,6 +24,7 @@ mod text_system; mod util; mod view; mod window; +mod window_input_handler; mod private { /// A mechanism for restricting implementations of a trait to only those in GPUI. @@ -64,6 +65,7 @@ pub use text_system::*; pub use util::arc_cow::ArcCow; pub use view::*; pub use window::*; +pub use window_input_handler::*; use derive_more::{Deref, DerefMut}; use std::{ diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 5b0349c7d4..4fc940069c 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -2,13 +2,14 @@ use crate::{ px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, - GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, - KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, - PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, - RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, - Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, - VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, InputHandler, IsZero, KeyListener, + KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, + PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, + Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, + SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, + TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, + WindowBounds, WindowInputHandler, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use collections::HashMap; @@ -191,6 +192,7 @@ pub struct Window { default_prevented: bool, mouse_position: Point, requested_cursor_style: Option, + requested_input_handler: Option>, scale_factor: f32, bounds: WindowBounds, bounds_observers: SubscriberSet<(), AnyObserver>, @@ -285,6 +287,7 @@ impl Window { default_prevented: true, mouse_position, requested_cursor_style: None, + requested_input_handler: None, scale_factor, bounds, bounds_observers: SubscriberSet::new(), @@ -676,6 +679,17 @@ impl<'a> WindowContext<'a> { self.window.requested_cursor_style = Some(style) } + pub fn set_input_handler(&mut self, handler: WeakView, cx: ViewContext) + where + V: InputHandler + 'static, + { + self.window.requested_input_handler = Some(Box::new(WindowInputHandler { + cx: cx.app.this.clone(), + window: cx.window_handle(), + handler, + })) + } + /// Called during painting to invoke the given closure in a new stacking context. The given /// z-index is interpreted relative to the previous call to `stack`. pub fn stack(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { diff --git a/crates/gpui2/src/window_input_handler.rs b/crates/gpui2/src/window_input_handler.rs new file mode 100644 index 0000000000..caae5838ce --- /dev/null +++ b/crates/gpui2/src/window_input_handler.rs @@ -0,0 +1,89 @@ +use crate::{AnyWindowHandle, AppCell, Context, PlatformInputHandler, ViewContext, WeakView}; +use std::{ops::Range, rc::Weak}; + +pub struct WindowInputHandler +where + V: InputHandler, +{ + pub cx: Weak, + pub window: AnyWindowHandle, + pub handler: WeakView, +} + +impl PlatformInputHandler for WindowInputHandler { + fn selected_text_range(&self) -> Option> { + self.update(|view, cx| view.selected_text_range(cx)) + .flatten() + } + + fn marked_text_range(&self) -> Option> { + self.update(|view, cx| view.marked_text_range(cx)).flatten() + } + + fn text_for_range(&self, range_utf16: std::ops::Range) -> Option { + self.update(|view, cx| view.text_for_range(range_utf16, cx)) + .flatten() + } + + fn replace_text_in_range( + &mut self, + replacement_range: Option>, + text: &str, + ) { + self.update(|view, cx| view.replace_text_in_range(replacement_range, text, cx)); + } + + fn replace_and_mark_text_in_range( + &mut self, + range_utf16: Option>, + new_text: &str, + new_selected_range: Option>, + ) { + self.update(|view, cx| { + view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx) + }); + } + + fn unmark_text(&mut self) { + self.update(|view, cx| view.unmark_text(cx)); + } + + fn bounds_for_range(&self, range_utf16: std::ops::Range) -> Option> { + self.update(|view, cx| view.bounds_for_range(range_utf16, cx)) + .flatten() + } +} + +impl WindowInputHandler { + fn update(&self, f: impl FnOnce(&mut V, &mut ViewContext) -> T) -> Option { + let cx = self.cx.upgrade()?; + let mut cx = cx.borrow_mut(); + cx.update_window(self.window, |_, cx| self.handler.update(cx, f).ok()) + .ok()? + } +} + +pub trait InputHandler: Sized { + fn text_for_range(&self, range: Range, cx: &mut ViewContext) -> Option; + fn selected_text_range(&self, cx: &mut ViewContext) -> Option>; + fn marked_text_range(&self, cx: &mut ViewContext) -> Option>; + fn unmark_text(&mut self, cx: &mut ViewContext); + fn replace_text_in_range( + &mut self, + range: Option>, + text: &str, + cx: &mut ViewContext, + ); + fn replace_and_mark_text_in_range( + &mut self, + range: Option>, + new_text: &str, + new_selected_range: Option>, + cx: &mut ViewContext, + ); + fn bounds_for_range( + &self, + range_utf16: std::ops::Range, + cx: &mut ViewContext, + ) -> Option>; +} From bd12e3edb637eb9a081bf96e9dd03ef4f653184c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 7 Nov 2023 15:44:00 -0800 Subject: [PATCH 10/14] Assign editors as text input handlers Co-authored-by: Marshall --- crates/editor2/src/editor.rs | 772 +++++++++++++++++----------------- crates/editor2/src/element.rs | 2 +- crates/gpui2/src/window.rs | 24 +- 3 files changed, 402 insertions(+), 396 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 61db3fb492..ccab3ee1ca 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -37,9 +37,9 @@ use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ actions, div, px, relative, AnyElement, AppContext, BackgroundExecutor, Context, - DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontStyle, FontWeight, Hsla, - Model, Pixels, PlatformInputHandler, Render, Styled, Subscription, Task, TextStyle, View, - ViewContext, VisualContext, WeakView, WindowContext, + DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontStyle, FontWeight, + HighlightStyle, Hsla, InputHandler, Model, Pixels, PlatformInputHandler, Render, Styled, + Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -56,6 +56,7 @@ use language::{ use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; use lsp::{DiagnosticSeverity, Documentation, LanguageServerId}; use movement::TextLayoutDetails; +use multi_buffer::ToOffsetUtf16; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, @@ -67,7 +68,7 @@ use rpc::proto::*; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, }; -use selections_collection::{MutableSelectionsCollection, SelectionsCollection}; +use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use smallvec::SmallVec; @@ -2769,197 +2770,197 @@ impl Editor { // cx.propagate(); // } - // pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext) { - // let text: Arc = text.into(); + pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext) { + let text: Arc = text.into(); - // if self.read_only { - // return; - // } + if self.read_only { + return; + } - // let selections = self.selections.all_adjusted(cx); - // let mut brace_inserted = false; - // let mut edits = Vec::new(); - // let mut new_selections = Vec::with_capacity(selections.len()); - // let mut new_autoclose_regions = Vec::new(); - // let snapshot = self.buffer.read(cx).read(cx); + let selections = self.selections.all_adjusted(cx); + let mut brace_inserted = false; + let mut edits = Vec::new(); + let mut new_selections = Vec::with_capacity(selections.len()); + let mut new_autoclose_regions = Vec::new(); + let snapshot = self.buffer.read(cx).read(cx); - // for (selection, autoclose_region) in - // self.selections_with_autoclose_regions(selections, &snapshot) - // { - // if let Some(scope) = snapshot.language_scope_at(selection.head()) { - // // Determine if the inserted text matches the opening or closing - // // bracket of any of this language's bracket pairs. - // let mut bracket_pair = None; - // let mut is_bracket_pair_start = false; - // if !text.is_empty() { - // // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified) - // // and they are removing the character that triggered IME popup. - // for (pair, enabled) in scope.brackets() { - // if enabled && pair.close && pair.start.ends_with(text.as_ref()) { - // bracket_pair = Some(pair.clone()); - // is_bracket_pair_start = true; - // break; - // } else if pair.end.as_str() == text.as_ref() { - // bracket_pair = Some(pair.clone()); - // break; - // } - // } - // } + for (selection, autoclose_region) in + self.selections_with_autoclose_regions(selections, &snapshot) + { + if let Some(scope) = snapshot.language_scope_at(selection.head()) { + // Determine if the inserted text matches the opening or closing + // bracket of any of this language's bracket pairs. + let mut bracket_pair = None; + let mut is_bracket_pair_start = false; + if !text.is_empty() { + // `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified) + // and they are removing the character that triggered IME popup. + for (pair, enabled) in scope.brackets() { + if enabled && pair.close && pair.start.ends_with(text.as_ref()) { + bracket_pair = Some(pair.clone()); + is_bracket_pair_start = true; + break; + } else if pair.end.as_str() == text.as_ref() { + bracket_pair = Some(pair.clone()); + break; + } + } + } - // if let Some(bracket_pair) = bracket_pair { - // if selection.is_empty() { - // if is_bracket_pair_start { - // let prefix_len = bracket_pair.start.len() - text.len(); + if let Some(bracket_pair) = bracket_pair { + if selection.is_empty() { + if is_bracket_pair_start { + let prefix_len = bracket_pair.start.len() - text.len(); - // // If the inserted text is a suffix of an opening bracket and the - // // selection is preceded by the rest of the opening bracket, then - // // insert the closing bracket. - // let following_text_allows_autoclose = snapshot - // .chars_at(selection.start) - // .next() - // .map_or(true, |c| scope.should_autoclose_before(c)); - // let preceding_text_matches_prefix = prefix_len == 0 - // || (selection.start.column >= (prefix_len as u32) - // && snapshot.contains_str_at( - // Point::new( - // selection.start.row, - // selection.start.column - (prefix_len as u32), - // ), - // &bracket_pair.start[..prefix_len], - // )); - // if following_text_allows_autoclose && preceding_text_matches_prefix { - // let anchor = snapshot.anchor_before(selection.end); - // new_selections.push((selection.map(|_| anchor), text.len())); - // new_autoclose_regions.push(( - // anchor, - // text.len(), - // selection.id, - // bracket_pair.clone(), - // )); - // edits.push(( - // selection.range(), - // format!("{}{}", text, bracket_pair.end).into(), - // )); - // brace_inserted = true; - // continue; - // } - // } + // If the inserted text is a suffix of an opening bracket and the + // selection is preceded by the rest of the opening bracket, then + // insert the closing bracket. + let following_text_allows_autoclose = snapshot + .chars_at(selection.start) + .next() + .map_or(true, |c| scope.should_autoclose_before(c)); + let preceding_text_matches_prefix = prefix_len == 0 + || (selection.start.column >= (prefix_len as u32) + && snapshot.contains_str_at( + Point::new( + selection.start.row, + selection.start.column - (prefix_len as u32), + ), + &bracket_pair.start[..prefix_len], + )); + if following_text_allows_autoclose && preceding_text_matches_prefix { + let anchor = snapshot.anchor_before(selection.end); + new_selections.push((selection.map(|_| anchor), text.len())); + new_autoclose_regions.push(( + anchor, + text.len(), + selection.id, + bracket_pair.clone(), + )); + edits.push(( + selection.range(), + format!("{}{}", text, bracket_pair.end).into(), + )); + brace_inserted = true; + continue; + } + } - // if let Some(region) = autoclose_region { - // // If the selection is followed by an auto-inserted closing bracket, - // // then don't insert that closing bracket again; just move the selection - // // past the closing bracket. - // let should_skip = selection.end == region.range.end.to_point(&snapshot) - // && text.as_ref() == region.pair.end.as_str(); - // if should_skip { - // let anchor = snapshot.anchor_after(selection.end); - // new_selections - // .push((selection.map(|_| anchor), region.pair.end.len())); - // continue; - // } - // } - // } - // // If an opening bracket is 1 character long and is typed while - // // text is selected, then surround that text with the bracket pair. - // else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 { - // edits.push((selection.start..selection.start, text.clone())); - // edits.push(( - // selection.end..selection.end, - // bracket_pair.end.as_str().into(), - // )); - // brace_inserted = true; - // new_selections.push(( - // Selection { - // id: selection.id, - // start: snapshot.anchor_after(selection.start), - // end: snapshot.anchor_before(selection.end), - // reversed: selection.reversed, - // goal: selection.goal, - // }, - // 0, - // )); - // continue; - // } - // } - // } + if let Some(region) = autoclose_region { + // If the selection is followed by an auto-inserted closing bracket, + // then don't insert that closing bracket again; just move the selection + // past the closing bracket. + let should_skip = selection.end == region.range.end.to_point(&snapshot) + && text.as_ref() == region.pair.end.as_str(); + if should_skip { + let anchor = snapshot.anchor_after(selection.end); + new_selections + .push((selection.map(|_| anchor), region.pair.end.len())); + continue; + } + } + } + // If an opening bracket is 1 character long and is typed while + // text is selected, then surround that text with the bracket pair. + else if is_bracket_pair_start && bracket_pair.start.chars().count() == 1 { + edits.push((selection.start..selection.start, text.clone())); + edits.push(( + selection.end..selection.end, + bracket_pair.end.as_str().into(), + )); + brace_inserted = true; + new_selections.push(( + Selection { + id: selection.id, + start: snapshot.anchor_after(selection.start), + end: snapshot.anchor_before(selection.end), + reversed: selection.reversed, + goal: selection.goal, + }, + 0, + )); + continue; + } + } + } - // // If not handling any auto-close operation, then just replace the selected - // // text with the given input and move the selection to the end of the - // // newly inserted text. - // let anchor = snapshot.anchor_after(selection.end); - // new_selections.push((selection.map(|_| anchor), 0)); - // edits.push((selection.start..selection.end, text.clone())); - // } + // If not handling any auto-close operation, then just replace the selected + // text with the given input and move the selection to the end of the + // newly inserted text. + let anchor = snapshot.anchor_after(selection.end); + new_selections.push((selection.map(|_| anchor), 0)); + edits.push((selection.start..selection.end, text.clone())); + } - // drop(snapshot); - // self.transact(cx, |this, cx| { - // this.buffer.update(cx, |buffer, cx| { - // buffer.edit(edits, this.autoindent_mode.clone(), cx); - // }); + drop(snapshot); + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + buffer.edit(edits, this.autoindent_mode.clone(), cx); + }); - // let new_anchor_selections = new_selections.iter().map(|e| &e.0); - // let new_selection_deltas = new_selections.iter().map(|e| e.1); - // let snapshot = this.buffer.read(cx).read(cx); - // let new_selections = resolve_multiple::(new_anchor_selections, &snapshot) - // .zip(new_selection_deltas) - // .map(|(selection, delta)| Selection { - // id: selection.id, - // start: selection.start + delta, - // end: selection.end + delta, - // reversed: selection.reversed, - // goal: SelectionGoal::None, - // }) - // .collect::>(); + let new_anchor_selections = new_selections.iter().map(|e| &e.0); + let new_selection_deltas = new_selections.iter().map(|e| e.1); + let snapshot = this.buffer.read(cx).read(cx); + let new_selections = resolve_multiple::(new_anchor_selections, &snapshot) + .zip(new_selection_deltas) + .map(|(selection, delta)| Selection { + id: selection.id, + start: selection.start + delta, + end: selection.end + delta, + reversed: selection.reversed, + goal: SelectionGoal::None, + }) + .collect::>(); - // let mut i = 0; - // for (position, delta, selection_id, pair) in new_autoclose_regions { - // let position = position.to_offset(&snapshot) + delta; - // let start = snapshot.anchor_before(position); - // let end = snapshot.anchor_after(position); - // while let Some(existing_state) = this.autoclose_regions.get(i) { - // match existing_state.range.start.cmp(&start, &snapshot) { - // Ordering::Less => i += 1, - // Ordering::Greater => break, - // Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) { - // Ordering::Less => i += 1, - // Ordering::Equal => break, - // Ordering::Greater => break, - // }, - // } - // } - // this.autoclose_regions.insert( - // i, - // AutocloseRegion { - // selection_id, - // range: start..end, - // pair, - // }, - // ); - // } + let mut i = 0; + for (position, delta, selection_id, pair) in new_autoclose_regions { + let position = position.to_offset(&snapshot) + delta; + let start = snapshot.anchor_before(position); + let end = snapshot.anchor_after(position); + while let Some(existing_state) = this.autoclose_regions.get(i) { + match existing_state.range.start.cmp(&start, &snapshot) { + Ordering::Less => i += 1, + Ordering::Greater => break, + Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) { + Ordering::Less => i += 1, + Ordering::Equal => break, + Ordering::Greater => break, + }, + } + } + this.autoclose_regions.insert( + i, + AutocloseRegion { + selection_id, + range: start..end, + pair, + }, + ); + } - // drop(snapshot); - // let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx); - // this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); + drop(snapshot); + let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); - // if !brace_inserted && EditorSettings>(cx).use_on_type_format { - // if let Some(on_type_format_task) = - // this.trigger_on_type_formatting(text.to_string(), cx) - // { - // on_type_format_task.detach_and_log_err(cx); - // } - // } + if !brace_inserted && EditorSettings::get_global(cx).use_on_type_format { + if let Some(on_type_format_task) = + this.trigger_on_type_formatting(text.to_string(), cx) + { + on_type_format_task.detach_and_log_err(cx); + } + } - // if had_active_copilot_suggestion { - // this.refresh_copilot_suggestions(true, cx); - // if !this.has_active_copilot_suggestion(cx) { - // this.trigger_completion_on_input(&text, cx); - // } - // } else { - // this.trigger_completion_on_input(&text, cx); - // this.refresh_copilot_suggestions(true, cx); - // } - // }); - // } + if had_active_copilot_suggestion { + this.refresh_copilot_suggestions(true, cx); + if !this.has_active_copilot_suggestion(cx) { + this.trigger_completion_on_input(&text, cx); + } + } else { + this.trigger_completion_on_input(&text, cx); + this.refresh_copilot_suggestions(true, cx); + } + }); + } // pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext) { // self.transact(cx, |this, cx| { @@ -3259,22 +3260,22 @@ impl Editor { }); } - // fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { - // if !EditorSettings>(cx).show_completions_on_input { - // return; - // } + fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { + if !EditorSettings::get_global(cx).show_completions_on_input { + return; + } - // let selection = self.selections.newest_anchor(); - // if self - // .buffer - // .read(cx) - // .is_completion_trigger(selection.head(), text, cx) - // { - // self.show_completions(&ShowCompletions, cx); - // } else { - // self.hide_context_menu(cx); - // } - // } + let selection = self.selections.newest_anchor(); + if self + .buffer + .read(cx) + .is_completion_trigger(selection.head(), text, cx) + { + self.show_completions(&ShowCompletions, cx); + } else { + self.hide_context_menu(cx); + } + } // /// If any empty selections is touching the start of its innermost containing autoclose // /// region, expand it to select the brackets. @@ -3305,37 +3306,37 @@ impl Editor { // self.change_selections(None, cx, |selections| selections.select(new_selections)); // } - // /// Iterate the given selections, and for each one, find the smallest surrounding - // /// autoclose region. This uses the ordering of the selections and the autoclose - // /// regions to avoid repeated comparisons. - // fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>( - // &'a self, - // selections: impl IntoIterator>, - // buffer: &'a MultiBufferSnapshot, - // ) -> impl Iterator, Option<&'a AutocloseRegion>)> { - // let mut i = 0; - // let mut regions = self.autoclose_regions.as_slice(); - // selections.into_iter().map(move |selection| { - // let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); + /// Iterate the given selections, and for each one, find the smallest surrounding + /// autoclose region. This uses the ordering of the selections and the autoclose + /// regions to avoid repeated comparisons. + fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>( + &'a self, + selections: impl IntoIterator>, + buffer: &'a MultiBufferSnapshot, + ) -> impl Iterator, Option<&'a AutocloseRegion>)> { + let mut i = 0; + let mut regions = self.autoclose_regions.as_slice(); + selections.into_iter().map(move |selection| { + let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer); - // let mut enclosing = None; - // while let Some(pair_state) = regions.get(i) { - // if pair_state.range.end.to_offset(buffer) < range.start { - // regions = ®ions[i + 1..]; - // i = 0; - // } else if pair_state.range.start.to_offset(buffer) > range.end { - // break; - // } else { - // if pair_state.selection_id == selection.id { - // enclosing = Some(pair_state); - // } - // i += 1; - // } - // } + let mut enclosing = None; + while let Some(pair_state) = regions.get(i) { + if pair_state.range.end.to_offset(buffer) < range.start { + regions = ®ions[i + 1..]; + i = 0; + } else if pair_state.range.start.to_offset(buffer) > range.end { + break; + } else { + if pair_state.selection_id == selection.id { + enclosing = Some(pair_state); + } + i += 1; + } + } - // (selection.clone(), enclosing) - // }) - // } + (selection.clone(), enclosing) + }) + } /// Remove any autoclose regions that no longer contain their selection. fn invalidate_autoclose_regions( @@ -3537,51 +3538,51 @@ impl Editor { cx.notify(); } - // fn trigger_on_type_formatting( - // &self, - // input: String, - // cx: &mut ViewContext, - // ) -> Option>> { - // if input.len() != 1 { - // return None; - // } + fn trigger_on_type_formatting( + &self, + input: String, + cx: &mut ViewContext, + ) -> Option>> { + if input.len() != 1 { + return None; + } - // let project = self.project.as_ref()?; - // let position = self.selections.newest_anchor().head(); - // let (buffer, buffer_position) = self - // .buffer - // .read(cx) - // .text_anchor_for_position(position.clone(), cx)?; + let project = self.project.as_ref()?; + let position = self.selections.newest_anchor().head(); + let (buffer, buffer_position) = self + .buffer + .read(cx) + .text_anchor_for_position(position.clone(), cx)?; - // // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances, - // // hence we do LSP request & edit on host side only — add formats to host's history. - // let push_to_lsp_host_history = true; - // // If this is not the host, append its history with new edits. - // let push_to_client_history = project.read(cx).is_remote(); + // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances, + // hence we do LSP request & edit on host side only — add formats to host's history. + let push_to_lsp_host_history = true; + // If this is not the host, append its history with new edits. + let push_to_client_history = project.read(cx).is_remote(); - // let on_type_formatting = project.update(cx, |project, cx| { - // project.on_type_format( - // buffer.clone(), - // buffer_position, - // input, - // push_to_lsp_host_history, - // cx, - // ) - // }); - // Some(cx.spawn(|editor, mut cx| async move { - // if let Some(transaction) = on_type_formatting.await? { - // if push_to_client_history { - // buffer.update(&mut cx, |buffer, _| { - // buffer.push_transaction(transaction, Instant::now()); - // }); - // } - // editor.update(&mut cx, |editor, cx| { - // editor.refresh_document_highlights(cx); - // })?; - // } - // Ok(()) - // })) - // } + let on_type_formatting = project.update(cx, |project, cx| { + project.on_type_format( + buffer.clone(), + buffer_position, + input, + push_to_lsp_host_history, + cx, + ) + }); + Some(cx.spawn(|editor, mut cx| async move { + if let Some(transaction) = on_type_formatting.await? { + if push_to_client_history { + buffer.update(&mut cx, |buffer, _| { + buffer.push_transaction(transaction, Instant::now()); + }); + } + editor.update(&mut cx, |editor, cx| { + editor.refresh_document_highlights(cx); + })?; + } + Ok(()) + })) + } fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext) { if self.pending_rename.is_some() { @@ -8712,12 +8713,12 @@ impl Editor { // cx.notify(); // } - // pub fn text_highlights<'a, T: 'static>( - // &'a self, - // cx: &'a AppContext, - // ) -> Option<(HighlightStyle, &'a [Range])> { - // self.display_map.read(cx).text_highlights(TypeId::of::()) - // } + pub fn text_highlights<'a, T: 'static>( + &'a self, + cx: &'a AppContext, + ) -> Option<(HighlightStyle, &'a [Range])> { + self.display_map.read(cx).text_highlights(TypeId::of::()) + } pub fn clear_highlights(&mut self, cx: &mut ViewContext) { let cleared = self @@ -8934,43 +8935,43 @@ impl Editor { // .detach_and_log_err(cx); // } - // fn marked_text_ranges(&self, cx: &AppContext) -> Option>> { - // let snapshot = self.buffer.read(cx).read(cx); - // let (_, ranges) = self.text_highlights::(cx)?; - // Some( - // ranges - // .iter() - // .map(move |range| { - // range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot) - // }) - // .collect(), - // ) - // } + fn marked_text_ranges(&self, cx: &AppContext) -> Option>> { + let snapshot = self.buffer.read(cx).read(cx); + let (_, ranges) = self.text_highlights::(cx)?; + Some( + ranges + .iter() + .map(move |range| { + range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot) + }) + .collect(), + ) + } - // fn selection_replacement_ranges( - // &self, - // range: Range, - // cx: &AppContext, - // ) -> Vec> { - // let selections = self.selections.all::(cx); - // let newest_selection = selections - // .iter() - // .max_by_key(|selection| selection.id) - // .unwrap(); - // let start_delta = range.start.0 as isize - newest_selection.start.0 as isize; - // let end_delta = range.end.0 as isize - newest_selection.end.0 as isize; - // let snapshot = self.buffer.read(cx).read(cx); - // selections - // .into_iter() - // .map(|mut selection| { - // selection.start.0 = - // (selection.start.0 as isize).saturating_add(start_delta) as usize; - // selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize; - // snapshot.clip_offset_utf16(selection.start, Bias::Left) - // ..snapshot.clip_offset_utf16(selection.end, Bias::Right) - // }) - // .collect() - // } + fn selection_replacement_ranges( + &self, + range: Range, + cx: &AppContext, + ) -> Vec> { + let selections = self.selections.all::(cx); + let newest_selection = selections + .iter() + .max_by_key(|selection| selection.id) + .unwrap(); + let start_delta = range.start.0 as isize - newest_selection.start.0 as isize; + let end_delta = range.end.0 as isize - newest_selection.end.0 as isize; + let snapshot = self.buffer.read(cx).read(cx); + selections + .into_iter() + .map(|mut selection| { + selection.start.0 = + (selection.start.0 as isize).saturating_add(start_delta) as usize; + selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize; + snapshot.clip_offset_utf16(selection.start, Bias::Left) + ..snapshot.clip_offset_utf16(selection.end, Bias::Right) + }) + .collect() + } fn report_copilot_event( &self, @@ -9486,102 +9487,101 @@ impl Render for Editor { // false // } -impl PlatformInputHandler for Editor { - fn text_for_range(&self, range_utf16: Range) -> Option { - // Some( - // self.buffer - // .read(cx) - // .read(cx) - // .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end)) - // .collect(), - // ) - todo!() +impl InputHandler for Editor { + fn text_for_range( + &self, + range_utf16: Range, + cx: &mut ViewContext, + ) -> Option { + Some( + self.buffer + .read(cx) + .read(cx) + .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end)) + .collect(), + ) } - fn selected_text_range(&self) -> Option> { + fn selected_text_range(&self, cx: &mut ViewContext) -> Option> { // Prevent the IME menu from appearing when holding down an alphabetic key // while input is disabled. - // if !self.input_enabled { - // return None; - // } + if !self.input_enabled { + return None; + } - // let range = self.selections.newest::(cx).range(); - // Some(range.start.0..range.end.0) - todo!() + let range = self.selections.newest::(cx).range(); + Some(range.start.0..range.end.0) } - fn marked_text_range(&self, cx: &AppContext) -> Option> { - // let snapshot = self.buffer.read(cx).read(cx); - // let range = self.text_highlights::(cx)?.1.get(0)?; - // Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0) - todo!() + fn marked_text_range(&self, cx: &mut ViewContext) -> Option> { + let snapshot = self.buffer.read(cx).read(cx); + let range = self.text_highlights::(cx)?.1.get(0)?; + Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0) } fn unmark_text(&mut self, cx: &mut ViewContext) { - // self.clear_highlights::(cx); - // self.ime_transaction.take(); - todo!() + self.clear_highlights::(cx); + self.ime_transaction.take(); } fn replace_text_in_range( &mut self, - //range_utf16: Option>, - // text: &str, + range_utf16: Option>, + text: &str, cx: &mut ViewContext, ) { - // if !self.input_enabled { - // cx.emit(Event::InputIgnored { text: text.into() }); - // return; - // } + if !self.input_enabled { + cx.emit(Event::InputIgnored { text: text.into() }); + return; + } - // self.transact(cx, |this, cx| { - // let new_selected_ranges = if let Some(range_utf16) = range_utf16 { - // let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); - // Some(this.selection_replacement_ranges(range_utf16, cx)) - // } else { - // this.marked_text_ranges(cx) - // }; + self.transact(cx, |this, cx| { + let new_selected_ranges = if let Some(range_utf16) = range_utf16 { + let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); + Some(this.selection_replacement_ranges(range_utf16, cx)) + } else { + this.marked_text_ranges(cx) + }; - // let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| { - // let newest_selection_id = this.selections.newest_anchor().id; - // this.selections - // .all::(cx) - // .iter() - // .zip(ranges_to_replace.iter()) - // .find_map(|(selection, range)| { - // if selection.id == newest_selection_id { - // Some( - // (range.start.0 as isize - selection.head().0 as isize) - // ..(range.end.0 as isize - selection.head().0 as isize), - // ) - // } else { - // None - // } - // }) - // }); + let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| { + let newest_selection_id = this.selections.newest_anchor().id; + this.selections + .all::(cx) + .iter() + .zip(ranges_to_replace.iter()) + .find_map(|(selection, range)| { + if selection.id == newest_selection_id { + Some( + (range.start.0 as isize - selection.head().0 as isize) + ..(range.end.0 as isize - selection.head().0 as isize), + ) + } else { + None + } + }) + }); - // cx.emit(Event::InputHandled { - // utf16_range_to_replace: range_to_replace, - // text: text.into(), - // }); + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); - // if let Some(new_selected_ranges) = new_selected_ranges { - // this.change_selections(None, cx, |selections| { - // selections.select_ranges(new_selected_ranges) - // }); - // } + if let Some(new_selected_ranges) = new_selected_ranges { + this.change_selections(None, cx, |selections| { + selections.select_ranges(new_selected_ranges) + }); + } - // this.handle_input(text, cx); - // }); + this.handle_input(text, cx); + }); - // if let Some(transaction) = self.ime_transaction { - // self.buffer.update(cx, |buffer, cx| { - // buffer.group_until_transaction(transaction, cx); - // }); - // } + if let Some(transaction) = self.ime_transaction { + self.buffer.update(cx, |buffer, cx| { + buffer.group_until_transaction(transaction, cx); + }); + } - // self.unmark_text(cx); - todo!() + self.unmark_text(cx); } fn replace_and_mark_text_in_range( @@ -9700,7 +9700,11 @@ impl PlatformInputHandler for Editor { todo!() } - fn bounds_for_range(&self, range_utf16: Range) -> Option> { + fn bounds_for_range( + &self, + range_utf16: Range, + cx: &mut ViewContext, + ) -> Option> { todo!() } } diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 3ff68e1897..4e16ff4504 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -2624,7 +2624,7 @@ impl Element for EditorElement { }); if editor.focus_handle.is_focused(cx) { - cx.set_input_handler(editor.handle); + cx.handle_text_input(); } cx.with_content_mask(ContentMask { bounds }, |cx| { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 4fc940069c..b7977bbf44 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -679,17 +679,6 @@ impl<'a> WindowContext<'a> { self.window.requested_cursor_style = Some(style) } - pub fn set_input_handler(&mut self, handler: WeakView, cx: ViewContext) - where - V: InputHandler + 'static, - { - self.window.requested_input_handler = Some(Box::new(WindowInputHandler { - cx: cx.app.this.clone(), - window: cx.window_handle(), - handler, - })) - } - /// Called during painting to invoke the given closure in a new stacking context. The given /// z-index is interpreted relative to the previous call to `stack`. pub fn stack(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { @@ -2009,6 +1998,19 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } +impl ViewContext<'_, V> +where + V: InputHandler + 'static, +{ + pub fn handle_text_input(&mut self) { + self.window.requested_input_handler = Some(Box::new(WindowInputHandler { + cx: self.app.this.clone(), + window: self.window_handle(), + handler: self.view().downgrade(), + })) + } +} + impl ViewContext<'_, V> where V: EventEmitter, From 9fe3073af76f56fbfa82167b0ebe03ea8527bfa0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 7 Nov 2023 16:33:02 -0800 Subject: [PATCH 11/14] Get basic text input working Co-authored-by: Marshall --- crates/editor2/src/editor.rs | 263 ++++++++++++++-------------- crates/gpui2/src/window.rs | 11 +- crates/workspace2/src/workspace2.rs | 2 +- 3 files changed, 141 insertions(+), 135 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index ccab3ee1ca..29742f3651 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -8135,15 +8135,14 @@ impl Editor { } fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext) { - todo!() - // self.end_selection(cx); - // if let Some(tx_id) = self - // .buffer - // .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx)) - // { - // self.selection_history - // .insert_transaction(tx_id, self.selections.disjoint_anchors()); - // } + self.end_selection(cx); + if let Some(tx_id) = self + .buffer + .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx)) + { + self.selection_history + .insert_transaction(tx_id, self.selections.disjoint_anchors()); + } } fn end_transaction_at( @@ -8151,22 +8150,21 @@ impl Editor { now: Instant, cx: &mut ViewContext, ) -> Option { - todo!() - // if let Some(tx_id) = self - // .buffer - // .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) - // { - // if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { - // *end_selections = Some(self.selections.disjoint_anchors()); - // } else { - // error!("unexpectedly ended a transaction that wasn't started by this editor"); - // } + if let Some(tx_id) = self + .buffer + .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) + { + if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { + *end_selections = Some(self.selections.disjoint_anchors()); + } else { + log::error!("unexpectedly ended a transaction that wasn't started by this editor"); + } - // cx.emit(Event::Edited); - // Some(tx_id) - // } else { - // None - // } + cx.emit(Event::Edited); + Some(tx_id) + } else { + None + } } // pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext) { @@ -8689,17 +8687,17 @@ impl Editor { // results // } - // pub fn highlight_text( - // &mut self, - // ranges: Vec>, - // style: HighlightStyle, - // cx: &mut ViewContext, - // ) { - // self.display_map.update(cx, |map, _| { - // map.highlight_text(TypeId::of::(), ranges, style) - // }); - // cx.notify(); - // } + pub fn highlight_text( + &mut self, + ranges: Vec>, + style: HighlightStyle, + cx: &mut ViewContext, + ) { + self.display_map.update(cx, |map, _| { + map.highlight_text(TypeId::of::(), ranges, style) + }); + cx.notify(); + } // pub fn highlight_inlays( // &mut self, @@ -9591,113 +9589,112 @@ impl InputHandler for Editor { new_selected_range_utf16: Option>, cx: &mut ViewContext, ) { - // if !self.input_enabled { - // cx.emit(Event::InputIgnored { text: text.into() }); - // return; - // } + if !self.input_enabled { + cx.emit(Event::InputIgnored { text: text.into() }); + return; + } - // let transaction = self.transact(cx, |this, cx| { - // let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) { - // let snapshot = this.buffer.read(cx).read(cx); - // if let Some(relative_range_utf16) = range_utf16.as_ref() { - // for marked_range in &mut marked_ranges { - // marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end; - // marked_range.start.0 += relative_range_utf16.start; - // marked_range.start = - // snapshot.clip_offset_utf16(marked_range.start, Bias::Left); - // marked_range.end = - // snapshot.clip_offset_utf16(marked_range.end, Bias::Right); - // } - // } - // Some(marked_ranges) - // } else if let Some(range_utf16) = range_utf16 { - // let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); - // Some(this.selection_replacement_ranges(range_utf16, cx)) - // } else { - // None - // }; + let transaction = self.transact(cx, |this, cx| { + let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) { + let snapshot = this.buffer.read(cx).read(cx); + if let Some(relative_range_utf16) = range_utf16.as_ref() { + for marked_range in &mut marked_ranges { + marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end; + marked_range.start.0 += relative_range_utf16.start; + marked_range.start = + snapshot.clip_offset_utf16(marked_range.start, Bias::Left); + marked_range.end = + snapshot.clip_offset_utf16(marked_range.end, Bias::Right); + } + } + Some(marked_ranges) + } else if let Some(range_utf16) = range_utf16 { + let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end); + Some(this.selection_replacement_ranges(range_utf16, cx)) + } else { + None + }; - // let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| { - // let newest_selection_id = this.selections.newest_anchor().id; - // this.selections - // .all::(cx) - // .iter() - // .zip(ranges_to_replace.iter()) - // .find_map(|(selection, range)| { - // if selection.id == newest_selection_id { - // Some( - // (range.start.0 as isize - selection.head().0 as isize) - // ..(range.end.0 as isize - selection.head().0 as isize), - // ) - // } else { - // None - // } - // }) - // }); + let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| { + let newest_selection_id = this.selections.newest_anchor().id; + this.selections + .all::(cx) + .iter() + .zip(ranges_to_replace.iter()) + .find_map(|(selection, range)| { + if selection.id == newest_selection_id { + Some( + (range.start.0 as isize - selection.head().0 as isize) + ..(range.end.0 as isize - selection.head().0 as isize), + ) + } else { + None + } + }) + }); - // cx.emit(Event::InputHandled { - // utf16_range_to_replace: range_to_replace, - // text: text.into(), - // }); + cx.emit(Event::InputHandled { + utf16_range_to_replace: range_to_replace, + text: text.into(), + }); - // if let Some(ranges) = ranges_to_replace { - // this.change_selections(None, cx, |s| s.select_ranges(ranges)); - // } + if let Some(ranges) = ranges_to_replace { + this.change_selections(None, cx, |s| s.select_ranges(ranges)); + } - // let marked_ranges = { - // let snapshot = this.buffer.read(cx).read(cx); - // this.selections - // .disjoint_anchors() - // .iter() - // .map(|selection| { - // selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) - // }) - // .collect::>() - // }; + let marked_ranges = { + let snapshot = this.buffer.read(cx).read(cx); + this.selections + .disjoint_anchors() + .iter() + .map(|selection| { + selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) + }) + .collect::>() + }; - // if text.is_empty() { - // this.unmark_text(cx); - // } else { - // this.highlight_text::( - // marked_ranges.clone(), - // this.style(cx).composition_mark, - // cx, - // ); - // } + if text.is_empty() { + this.unmark_text(cx); + } else { + this.highlight_text::( + marked_ranges.clone(), + HighlightStyle::default(), // todo!() this.style(cx).composition_mark, + cx, + ); + } - // this.handle_input(text, cx); + this.handle_input(text, cx); - // if let Some(new_selected_range) = new_selected_range_utf16 { - // let snapshot = this.buffer.read(cx).read(cx); - // let new_selected_ranges = marked_ranges - // .into_iter() - // .map(|marked_range| { - // let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0; - // let new_start = OffsetUtf16(new_selected_range.start + insertion_start); - // let new_end = OffsetUtf16(new_selected_range.end + insertion_start); - // snapshot.clip_offset_utf16(new_start, Bias::Left) - // ..snapshot.clip_offset_utf16(new_end, Bias::Right) - // }) - // .collect::>(); + if let Some(new_selected_range) = new_selected_range_utf16 { + let snapshot = this.buffer.read(cx).read(cx); + let new_selected_ranges = marked_ranges + .into_iter() + .map(|marked_range| { + let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0; + let new_start = OffsetUtf16(new_selected_range.start + insertion_start); + let new_end = OffsetUtf16(new_selected_range.end + insertion_start); + snapshot.clip_offset_utf16(new_start, Bias::Left) + ..snapshot.clip_offset_utf16(new_end, Bias::Right) + }) + .collect::>(); - // drop(snapshot); - // this.change_selections(None, cx, |selections| { - // selections.select_ranges(new_selected_ranges) - // }); - // } - // }); + drop(snapshot); + this.change_selections(None, cx, |selections| { + selections.select_ranges(new_selected_ranges) + }); + } + }); - // self.ime_transaction = self.ime_transaction.or(transaction); - // if let Some(transaction) = self.ime_transaction { - // self.buffer.update(cx, |buffer, cx| { - // buffer.group_until_transaction(transaction, cx); - // }); - // } + self.ime_transaction = self.ime_transaction.or(transaction); + if let Some(transaction) = self.ime_transaction { + self.buffer.update(cx, |buffer, cx| { + buffer.group_until_transaction(transaction, cx); + }); + } - // if self.text_highlights::(cx).is_none() { - // self.ime_transaction.take(); - // } - todo!() + if self.text_highlights::(cx).is_none() { + self.ime_transaction.take(); + } } fn bounds_for_range( @@ -9705,7 +9702,9 @@ impl InputHandler for Editor { range_utf16: Range, cx: &mut ViewContext, ) -> Option> { - todo!() + // todo!() + // See how we did it before: `rect_for_range` + None } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index b7977bbf44..50c7b772f8 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -255,7 +255,7 @@ impl Window { handle .update(&mut cx, |_, cx| cx.dispatch_event(event)) .log_err() - .unwrap_or(true) + .unwrap_or(false) }) }); @@ -1011,6 +1011,9 @@ impl<'a> WindowContext<'a> { .take() .unwrap_or(CursorStyle::Arrow); self.platform.set_cursor_style(cursor_style); + if let Some(handler) = self.window.requested_input_handler.take() { + self.window.platform_window.set_input_handler(handler); + } self.window.dirty = false; } @@ -1155,6 +1158,7 @@ impl<'a> WindowContext<'a> { .insert(any_mouse_event.type_id(), handlers); } } else if let Some(any_key_event) = event.keyboard_event() { + let mut did_handle_action = false; let key_dispatch_stack = mem::take(&mut self.window.key_dispatch_stack); let key_event_type = any_key_event.type_id(); let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new(); @@ -1175,6 +1179,7 @@ impl<'a> WindowContext<'a> { self.dispatch_action(action, &key_dispatch_stack[..ix]); } if !self.app.propagate_event { + did_handle_action = true; break; } } @@ -1203,6 +1208,7 @@ impl<'a> WindowContext<'a> { } if !self.app.propagate_event { + did_handle_action = true; break; } } @@ -1216,6 +1222,7 @@ impl<'a> WindowContext<'a> { drop(context_stack); self.window.key_dispatch_stack = key_dispatch_stack; + return did_handle_action; } true @@ -2007,7 +2014,7 @@ where cx: self.app.this.clone(), window: self.window_handle(), handler: self.view().downgrade(), - })) + })); } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 90204f6038..b51b0186ef 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -2694,7 +2694,7 @@ impl Workspace { .any(|item| item.has_conflict(cx) || item.is_dirty(cx)); if is_edited != self.window_edited { self.window_edited = is_edited; - todo!() + // todo!() // cx.set_window_edited(self.window_edited) } } From a39865b4b2178c969e2c74ef95bf78e51c155560 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 7 Nov 2023 20:27:00 -0500 Subject: [PATCH 12/14] Update default player colors --- crates/theme2/src/colors.rs | 6 +++ crates/theme2/src/default_colors.rs | 78 ++++++++++++++++++----------- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index 8ee4b5fd47..929838379e 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -20,6 +20,12 @@ pub struct PlayerColor { pub selection: Hsla, } +/// A collection of colors that are used to color players in the editor. +/// +/// The first color is always the local player's color, usually a blue. +/// +/// The rest of the default colors crisscross back and forth on the +/// color wheel so that the colors are as distinct as possible. #[derive(Clone)] pub struct PlayerColors(pub Vec); diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 7252e82972..a3345ffbaf 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -27,17 +27,17 @@ impl Default for SystemColors { impl Default for StatusColors { fn default() -> Self { Self { - conflict: red().dark().step_11(), - created: grass().dark().step_11(), - deleted: red().dark().step_11(), - error: red().dark().step_11(), - hidden: neutral().dark().step_11(), - ignored: neutral().dark().step_11(), - info: blue().dark().step_11(), - modified: yellow().dark().step_11(), - renamed: blue().dark().step_11(), - success: grass().dark().step_11(), - warning: yellow().dark().step_11(), + conflict: red().dark().step_9(), + created: grass().dark().step_9(), + deleted: red().dark().step_9(), + error: red().dark().step_9(), + hidden: neutral().dark().step_9(), + ignored: neutral().dark().step_9(), + info: blue().dark().step_9(), + modified: yellow().dark().step_9(), + renamed: blue().dark().step_9(), + success: grass().dark().step_9(), + warning: yellow().dark().step_9(), } } } @@ -45,12 +45,12 @@ impl Default for StatusColors { impl Default for GitStatusColors { fn default() -> Self { Self { - conflict: orange().dark().step_11(), - created: grass().dark().step_11(), - deleted: red().dark().step_11(), - ignored: neutral().dark().step_11(), - modified: yellow().dark().step_11(), - renamed: blue().dark().step_11(), + conflict: orange().dark().step_9(), + created: grass().dark().step_9(), + deleted: red().dark().step_9(), + ignored: neutral().dark().step_9(), + modified: yellow().dark().step_9(), + renamed: blue().dark().step_9(), } } } @@ -59,24 +59,44 @@ impl Default for PlayerColors { fn default() -> Self { Self(vec![ PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), + cursor: blue().dark().step_9(), + background: blue().dark().step_4(), + selection: blue().dark().step_3(), }, PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), + cursor: orange().dark().step_9(), + background: orange().dark().step_4(), + selection: orange().dark().step_3(), }, PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), + cursor: pink().dark().step_9(), + background: pink().dark().step_4(), + selection: pink().dark().step_3(), }, PlayerColor { - cursor: hsla(0.0, 0.0, 0.0, 1.0), - background: hsla(0.0, 0.0, 0.0, 1.0), - selection: hsla(0.0, 0.0, 0.0, 1.0), + cursor: lime().dark().step_9(), + background: lime().dark().step_4(), + selection: lime().dark().step_3(), + }, + PlayerColor { + cursor: purple().dark().step_9(), + background: purple().dark().step_4(), + selection: purple().dark().step_3(), + }, + PlayerColor { + cursor: amber().dark().step_9(), + background: amber().dark().step_4(), + selection: amber().dark().step_3(), + }, + PlayerColor { + cursor: jade().dark().step_9(), + background: jade().dark().step_4(), + selection: jade().dark().step_3(), + }, + PlayerColor { + cursor: red().dark().step_9(), + background: red().dark().step_4(), + selection: red().dark().step_3(), }, ]) } From 79b4f78cb3104cc53138d3767c6d1045e86d040b Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 7 Nov 2023 21:08:33 -0500 Subject: [PATCH 13/14] Extend the theme crate to enable stories, add players story --- Cargo.lock | 1 + crates/storybook2/src/story_selector.rs | 2 + crates/theme2/Cargo.toml | 3 + crates/theme2/src/colors.rs | 35 +----- crates/theme2/src/default_colors.rs | 145 ++++++++++++++++-------- crates/theme2/src/default_theme.rs | 6 +- crates/theme2/src/players.rs | 86 ++++++++++++++ crates/theme2/src/story.rs | 38 +++++++ crates/theme2/src/theme2.rs | 7 ++ 9 files changed, 237 insertions(+), 86 deletions(-) create mode 100644 crates/theme2/src/players.rs create mode 100644 crates/theme2/src/story.rs diff --git a/Cargo.lock b/Cargo.lock index 7d17b48cc7..19b1ca6c0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9005,6 +9005,7 @@ dependencies = [ "fs2", "gpui2", "indexmap 1.9.3", + "itertools 0.11.0", "parking_lot 0.11.2", "refineable", "schemars", diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index f59208ccb8..47807def25 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -38,6 +38,7 @@ pub enum ComponentStory { Palette, Panel, ProjectPanel, + Players, RecentProjects, Scroll, Tab, @@ -79,6 +80,7 @@ impl ComponentStory { Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into(), Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into(), Self::Palette => cx.build_view(|cx| ui::PaletteStory).into(), + Self::Players => cx.build_view(|_| theme2::PlayerStory).into(), Self::Panel => cx.build_view(|cx| ui::PanelStory).into(), Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into(), Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into(), diff --git a/crates/theme2/Cargo.toml b/crates/theme2/Cargo.toml index d57c22ede7..45ba4587ba 100644 --- a/crates/theme2/Cargo.toml +++ b/crates/theme2/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" publish = false [features] +default = ["stories"] +stories = ["dep:itertools"] test-support = [ "gpui/test-support", "fs/test-support", @@ -30,6 +32,7 @@ settings = { package = "settings2", path = "../settings2" } toml.workspace = true uuid.workspace = true util = { path = "../util" } +itertools = { version = "0.11.0", optional = true } [dev-dependencies] gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index 929838379e..8e3db63537 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use gpui::Hsla; use refineable::Refineable; -use crate::SyntaxTheme; +use crate::{PlayerColors, SyntaxTheme}; #[derive(Clone)] pub struct SystemColors { @@ -13,39 +13,6 @@ pub struct SystemColors { pub mac_os_traffic_light_green: Hsla, } -#[derive(Debug, Clone, Copy)] -pub struct PlayerColor { - pub cursor: Hsla, - pub background: Hsla, - pub selection: Hsla, -} - -/// A collection of colors that are used to color players in the editor. -/// -/// The first color is always the local player's color, usually a blue. -/// -/// The rest of the default colors crisscross back and forth on the -/// color wheel so that the colors are as distinct as possible. -#[derive(Clone)] -pub struct PlayerColors(pub Vec); - -impl PlayerColors { - pub fn local(&self) -> PlayerColor { - // todo!("use a valid color"); - *self.0.first().unwrap() - } - - pub fn absent(&self) -> PlayerColor { - // todo!("use a valid color"); - *self.0.last().unwrap() - } - - pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor { - let len = self.0.len() - 1; - self.0[(participant_index as usize % len) + 1] - } -} - #[derive(Refineable, Clone, Debug)] #[refineable(debug)] pub struct StatusColors { diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index a3345ffbaf..3a626205f9 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -3,12 +3,106 @@ use std::num::ParseIntError; use gpui::{hsla, Hsla, Rgba}; use crate::{ - colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, + colors::{GitStatusColors, StatusColors, SystemColors, ThemeColors}, scale::{ColorScaleSet, ColorScales}, syntax::SyntaxTheme, - ColorScale, + ColorScale, PlayerColor, PlayerColors, }; +impl Default for PlayerColors { + fn default() -> Self { + Self(vec![ + PlayerColor { + cursor: blue().dark().step_9(), + background: blue().dark().step_5(), + selection: blue().dark().step_3(), + }, + PlayerColor { + cursor: orange().dark().step_9(), + background: orange().dark().step_5(), + selection: orange().dark().step_3(), + }, + PlayerColor { + cursor: pink().dark().step_9(), + background: pink().dark().step_5(), + selection: pink().dark().step_3(), + }, + PlayerColor { + cursor: lime().dark().step_9(), + background: lime().dark().step_5(), + selection: lime().dark().step_3(), + }, + PlayerColor { + cursor: purple().dark().step_9(), + background: purple().dark().step_5(), + selection: purple().dark().step_3(), + }, + PlayerColor { + cursor: amber().dark().step_9(), + background: amber().dark().step_5(), + selection: amber().dark().step_3(), + }, + PlayerColor { + cursor: jade().dark().step_9(), + background: jade().dark().step_5(), + selection: jade().dark().step_3(), + }, + PlayerColor { + cursor: red().dark().step_9(), + background: red().dark().step_5(), + selection: red().dark().step_3(), + }, + ]) + } +} + +impl PlayerColors { + pub fn default_light() -> Self { + Self(vec![ + PlayerColor { + cursor: blue().light().step_9(), + background: blue().light().step_4(), + selection: blue().light().step_3(), + }, + PlayerColor { + cursor: orange().light().step_9(), + background: orange().light().step_4(), + selection: orange().light().step_3(), + }, + PlayerColor { + cursor: pink().light().step_9(), + background: pink().light().step_4(), + selection: pink().light().step_3(), + }, + PlayerColor { + cursor: lime().light().step_9(), + background: lime().light().step_4(), + selection: lime().light().step_3(), + }, + PlayerColor { + cursor: purple().light().step_9(), + background: purple().light().step_4(), + selection: purple().light().step_3(), + }, + PlayerColor { + cursor: amber().light().step_9(), + background: amber().light().step_4(), + selection: amber().light().step_3(), + }, + PlayerColor { + cursor: jade().light().step_9(), + background: jade().light().step_4(), + selection: jade().light().step_3(), + }, + PlayerColor { + cursor: red().light().step_9(), + background: red().light().step_4(), + selection: red().light().step_3(), + }, + ]) + } +} + fn neutral() -> ColorScaleSet { slate() } @@ -55,53 +149,6 @@ impl Default for GitStatusColors { } } -impl Default for PlayerColors { - fn default() -> Self { - Self(vec![ - PlayerColor { - cursor: blue().dark().step_9(), - background: blue().dark().step_4(), - selection: blue().dark().step_3(), - }, - PlayerColor { - cursor: orange().dark().step_9(), - background: orange().dark().step_4(), - selection: orange().dark().step_3(), - }, - PlayerColor { - cursor: pink().dark().step_9(), - background: pink().dark().step_4(), - selection: pink().dark().step_3(), - }, - PlayerColor { - cursor: lime().dark().step_9(), - background: lime().dark().step_4(), - selection: lime().dark().step_3(), - }, - PlayerColor { - cursor: purple().dark().step_9(), - background: purple().dark().step_4(), - selection: purple().dark().step_3(), - }, - PlayerColor { - cursor: amber().dark().step_9(), - background: amber().dark().step_4(), - selection: amber().dark().step_3(), - }, - PlayerColor { - cursor: jade().dark().step_9(), - background: jade().dark().step_4(), - selection: jade().dark().step_3(), - }, - PlayerColor { - cursor: red().dark().step_9(), - background: red().dark().step_4(), - selection: red().dark().step_3(), - }, - ]) - } -} - impl SyntaxTheme { pub fn default_light() -> Self { Self { diff --git a/crates/theme2/src/default_theme.rs b/crates/theme2/src/default_theme.rs index 3c9634c989..a0947e47c3 100644 --- a/crates/theme2/src/default_theme.rs +++ b/crates/theme2/src/default_theme.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use crate::{ - colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyles}, - default_color_scales, Appearance, SyntaxTheme, Theme, ThemeFamily, + colors::{GitStatusColors, StatusColors, SystemColors, ThemeColors, ThemeStyles}, + default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily, }; fn zed_pro_daylight() -> Theme { @@ -15,7 +15,7 @@ fn zed_pro_daylight() -> Theme { colors: ThemeColors::default_light(), status: StatusColors::default(), git: GitStatusColors::default(), - player: PlayerColors::default(), + player: PlayerColors::default_light(), syntax: Arc::new(SyntaxTheme::default_light()), }, } diff --git a/crates/theme2/src/players.rs b/crates/theme2/src/players.rs new file mode 100644 index 0000000000..6f7a20980c --- /dev/null +++ b/crates/theme2/src/players.rs @@ -0,0 +1,86 @@ +use gpui::Hsla; + +#[derive(Debug, Clone, Copy)] +pub struct PlayerColor { + pub cursor: Hsla, + pub background: Hsla, + pub selection: Hsla, +} + +/// A collection of colors that are used to color players in the editor. +/// +/// The first color is always the local player's color, usually a blue. +/// +/// The rest of the default colors crisscross back and forth on the +/// color wheel so that the colors are as distinct as possible. +#[derive(Clone)] +pub struct PlayerColors(pub Vec); + +impl PlayerColors { + pub fn local(&self) -> PlayerColor { + // todo!("use a valid color"); + *self.0.first().unwrap() + } + + pub fn absent(&self) -> PlayerColor { + // todo!("use a valid color"); + *self.0.last().unwrap() + } + + pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor { + let len = self.0.len() - 1; + self.0[(participant_index as usize % len) + 1] + } +} + +#[cfg(feature = "stories")] +pub use stories::*; + +#[cfg(feature = "stories")] +mod stories { + use super::*; + use crate::{ActiveTheme, Story}; + use gpui::{div, Div, ParentElement, Render, Styled, ViewContext}; + + pub struct PlayerStory; + + impl Render for PlayerStory { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + Story::container(cx) + .child(Story::title_for::<_, PlayerColors>(cx)) + .child(Story::label(cx, "Player Colors")) + .child( + div() + .flex() + .flex_col() + .gap_1() + .child( + div().flex().gap_1().children( + cx.theme() + .players() + .0 + .clone() + .iter_mut() + .map(|color| div().w_8().h_8().rounded_md().bg(color.cursor)), + ), + ) + .child( + div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|color| { + div().w_8().h_8().rounded_md().bg(color.background) + }), + ), + ) + .child( + div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|color| { + div().w_8().h_8().rounded_md().bg(color.selection) + }), + ), + ), + ) + } + } +} diff --git a/crates/theme2/src/story.rs b/crates/theme2/src/story.rs new file mode 100644 index 0000000000..8b3754b59e --- /dev/null +++ b/crates/theme2/src/story.rs @@ -0,0 +1,38 @@ +use gpui::{div, Component, Div, ParentElement, Styled, ViewContext}; + +use crate::ActiveTheme; + +pub struct Story {} + +impl Story { + pub fn container(cx: &mut ViewContext) -> Div { + div() + .size_full() + .flex() + .flex_col() + .pt_2() + .px_4() + .font("Zed Mono") + .bg(cx.theme().colors().background) + } + + pub fn title(cx: &mut ViewContext, title: &str) -> impl Component { + div() + .text_xl() + .text_color(cx.theme().colors().text) + .child(title.to_owned()) + } + + pub fn title_for(cx: &mut ViewContext) -> impl Component { + Self::title(cx, std::any::type_name::()) + } + + pub fn label(cx: &mut ViewContext, label: &str) -> impl Component { + div() + .mt_4() + .mb_2() + .text_xs() + .text_color(cx.theme().colors().text) + .child(label.to_owned()) + } +} diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index 88db3c55f4..9019eba07a 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -1,6 +1,7 @@ mod colors; mod default_colors; mod default_theme; +mod players; mod registry; mod scale; mod settings; @@ -14,6 +15,7 @@ use ::settings::Settings; pub use colors::*; pub use default_colors::*; pub use default_theme::*; +pub use players::*; pub use registry::*; pub use scale::*; pub use settings::*; @@ -120,3 +122,8 @@ pub struct DiagnosticStyle { pub hint: Hsla, pub ignored: Hsla, } + +#[cfg(feature = "stories")] +mod story; +#[cfg(feature = "stories")] +pub use story::*; From 0dd6ea6d411aaa86e2978e344d34defbb42c6be1 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 7 Nov 2023 21:51:12 -0500 Subject: [PATCH 14/14] Add new default player colors and the players story. --- crates/theme2/src/players.rs | 144 +++++++++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 30 deletions(-) diff --git a/crates/theme2/src/players.rs b/crates/theme2/src/players.rs index 6f7a20980c..0e36ff5947 100644 --- a/crates/theme2/src/players.rs +++ b/crates/theme2/src/players.rs @@ -40,7 +40,7 @@ pub use stories::*; mod stories { use super::*; use crate::{ActiveTheme, Story}; - use gpui::{div, Div, ParentElement, Render, Styled, ViewContext}; + use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext}; pub struct PlayerStory; @@ -48,39 +48,123 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - Story::container(cx) - .child(Story::title_for::<_, PlayerColors>(cx)) - .child(Story::label(cx, "Player Colors")) - .child( - div() - .flex() - .flex_col() - .gap_1() - .child( - div().flex().gap_1().children( - cx.theme() - .players() - .0 - .clone() - .iter_mut() - .map(|color| div().w_8().h_8().rounded_md().bg(color.cursor)), - ), - ) - .child( - div().flex().gap_1().children( - cx.theme().players().0.clone().iter_mut().map(|color| { - div().w_8().h_8().rounded_md().bg(color.background) + Story::container(cx).child( + div() + .flex() + .flex_col() + .gap_4() + .child(Story::title_for::<_, PlayerColors>(cx)) + .child(Story::label(cx, "Player Colors")) + .child( + div() + .flex() + .flex_col() + .gap_1() + .child( + div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div().w_8().h_8().rounded_md().bg(player.cursor) + }), + ), + ) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div().w_8().h_8().rounded_md().bg(player.background) }), - ), - ) - .child( - div().flex().gap_1().children( - cx.theme().players().0.clone().iter_mut().map(|color| { - div().w_8().h_8().rounded_md().bg(color.selection) + )) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div().w_8().h_8().rounded_md().bg(player.selection) }), + )), + ) + .child(Story::label(cx, "Avatar Rings")) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div() + .my_1() + .rounded_full() + .border_2() + .border_color(player.cursor) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size_6() + .bg(gpui::red()), + ) + }), + )) + .child(Story::label(cx, "Player Backgrounds")) + .child(div().flex().gap_1().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div() + .my_1() + .rounded_xl() + .flex() + .items_center() + .h_8() + .py_0p5() + .px_1p5() + .bg(player.background) + .child( + div().relative().neg_mx_1().rounded_full().z_index(3) + .border_2() + .border_color(player.background) + .size(px(28.)) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size(px(24.)) + .bg(gpui::red()), + ), + ).child( + div().relative().neg_mx_1().rounded_full().z_index(2) + .border_2() + .border_color(player.background) + .size(px(28.)) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size(px(24.)) + .bg(gpui::red()), ), + ).child( + div().relative().neg_mx_1().rounded_full().z_index(1) + .border_2() + .border_color(player.background) + .size(px(28.)) + .child( + img() + .rounded_full() + .uri("https://avatars.githubusercontent.com/u/1714999?v=4") + .size(px(24.)) + .bg(gpui::red()), ), - ) + ) + }), + )) + .child(Story::label(cx, "Player Selections")) + .child(div().flex().flex_col().gap_px().children( + cx.theme().players().0.clone().iter_mut().map(|player| { + div() + .flex() + .child( + div() + .flex() + .flex_none() + .rounded_sm() + .px_0p5() + .text_color(cx.theme().colors().text) + .bg(player.selection) + .child("The brown fox jumped over the lazy dog."), + ) + .child(div().flex_1()) + }), + )), + ) } } }