From b2ae08b1598649ae60aa6c8bd7edd09c8180fc98 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 7 Nov 2023 16:30:04 -0700 Subject: [PATCH 1/3] Implement an InputHandler trait for gpui2 Co-Authored-By: Marshall Co-Authored-By: Max Co-Authored-By: Julia --- crates/editor2/src/editor.rs | 399 ++++++++++++----------- 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, 322 insertions(+), 202 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 c63c531cebddcfa1f807ee5460a54b1976d9c175..61db3fb49253d1a5ceeb06f7fe45900f841cb8ab 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; -// } - -// 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) -// } - -// fn unmark_text(&mut self, cx: &mut ViewContext) { -// self.clear_highlights::(cx); -// self.ime_transaction.take(); -// } - -// 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; -// } - -// 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) -// }; +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_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 -// } -// }) -// }); + 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; + // } -// cx.emit(Event::InputHandled { -// utf16_range_to_replace: range_to_replace, -// text: text.into(), -// }); + // let range = self.selections.newest::(cx).range(); + // Some(range.start.0..range.end.0) + todo!() + } -// if let Some(new_selected_ranges) = new_selected_ranges { -// this.change_selections(None, cx, |selections| { -// selections.select_ranges(new_selected_ranges) -// }); -// } + 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!() + } -// this.handle_input(text, cx); -// }); + fn unmark_text(&mut self, cx: &mut ViewContext) { + // self.clear_highlights::(cx); + // self.ime_transaction.take(); + todo!() + } -// if let Some(transaction) = self.ime_transaction { -// self.buffer.update(cx, |buffer, cx| { -// buffer.group_until_transaction(transaction, 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; + // } -// self.unmark_text(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 + // } + // }) + // }); + + // 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) + // }); + // } -// 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; -// } + // this.handle_input(text, cx); + // }); -// 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 -// }; + // if let Some(transaction) = self.ime_transaction { + // self.buffer.update(cx, |buffer, cx| { + // buffer.group_until_transaction(transaction, cx); + // }); + // } -// 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 -// } -// }) -// }); + // self.unmark_text(cx); + todo!() + } -// cx.emit(Event::InputHandled { -// utf16_range_to_replace: range_to_replace, -// text: text.into(), -// }); + 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; + // } -// if let Some(ranges) = ranges_to_replace { -// this.change_selections(None, cx, |s| s.select_ranges(ranges)); -// } + // 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 + // } + // }) + // }); + + // 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)); + // } -// 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(), -// this.style(cx).composition_mark, -// 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::>(); + + // drop(snapshot); + // this.change_selections(None, cx, |selections| { + // selections.select_ranges(new_selected_ranges) + // }); + // } + // }); -// 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::>(); - -// 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(&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 5af5d97e03ceed35569acbb950e0f75d3fceb7c6..3ff68e18978161185f55dffa3eb0eab55a49ed3e 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 63d2143a6726773be7508c77c72bdd6b1052fe3d..42d34e0c202e9ceaddbabeb85e6f6fac23502d39 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 348da17356dd3f48d1a507cfcb0c8d19a4413d2b..e59b196a91ff252da9a41179912b1e7e718ba0c1 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 5b0349c7d48ff9a84478b624205b65c9c1be2c75..4fc940069cafce0b63cc1e2605e22dfeb25ba685 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 0000000000000000000000000000000000000000..caae5838ce46171a1584b272d7e916d100e13881 --- /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 2/3] Assign editors as text input handlers Co-authored-by: Marshall --- crates/editor2/src/editor.rs | 780 +++++++++++++++++----------------- crates/editor2/src/element.rs | 2 +- crates/gpui2/src/window.rs | 24 +- 3 files changed, 406 insertions(+), 400 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 61db3fb49253d1a5ceeb06f7fe45900f841cb8ab..ccab3ee1caaa9168e73b17be235c138861ca6c90 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 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(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 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); - - // 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; - // } - // } + /// 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; + } + } - // (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)?; - - // // 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 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(); + + 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 3ff68e18978161185f55dffa3eb0eab55a49ed3e..4e16ff450413d7f143820a8e8797e9f69ef7b51f 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 4fc940069cafce0b63cc1e2605e22dfeb25ba685..b7977bbf44d077f0c8a4be60386419561456bc5d 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 3/3] Get basic text input working Co-authored-by: Marshall --- crates/editor2/src/editor.rs | 283 ++++++++++++++-------------- crates/gpui2/src/window.rs | 11 +- crates/workspace2/src/workspace2.rs | 2 +- 3 files changed, 151 insertions(+), 145 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index ccab3ee1caaa9168e73b17be235c138861ca6c90..29742f365120a63df882e8f1bc77dba67264e1df 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"); - // } - - // cx.emit(Event::Edited); - // Some(tx_id) - // } else { - // None - // } + 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 + } } // 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 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(), - // }); - - // 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::>() - // }; - - // if text.is_empty() { - // this.unmark_text(cx); - // } else { - // this.highlight_text::( - // marked_ranges.clone(), - // this.style(cx).composition_mark, - // 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::>(); - - // 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); - // }); - // } + 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 + }; - // if self.text_highlights::(cx).is_none() { - // self.ime_transaction.take(); - // } - todo!() + 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(), + }); + + 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::>() + }; + + 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); + + 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) + }); + } + }); + + 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(); + } } 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 b7977bbf44d077f0c8a4be60386419561456bc5d..50c7b772f882f07aa99e668676f1e7b5ddc8688a 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 90204f60386e02cb08eb171d381b9e97b6fbea7f..b51b0186efd4ab4bccd8822f407141a05eeb2b10 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) } }