diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index c85dda16ca..3bd4bd28a0 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -7,8 +7,8 @@ use crate::{ platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions}, presenter::Presenter, util::post_inc, - AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId, PathPromptOptions, - TextLayoutCache, + AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseRegionId, + PathPromptOptions, TextLayoutCache, }; pub use action::*; use anyhow::{anyhow, Context, Result}; @@ -28,7 +28,7 @@ use std::{ hash::{Hash, Hasher}, marker::PhantomData, mem, - ops::{Deref, DerefMut}, + ops::{Deref, DerefMut, Range}, path::{Path, PathBuf}, pin::Pin, rc::{self, Rc}, @@ -64,6 +64,33 @@ pub trait View: Entity + Sized { fn debug_json(&self, _: &AppContext) -> serde_json::Value { serde_json::Value::Null } + + fn text_for_range(&self, _: Range, _: &AppContext) -> Option { + None + } + fn selected_text_range(&self, _: &AppContext) -> Option> { + None + } + fn set_selected_text_range(&mut self, _: Range, _: &mut ViewContext) {} + fn marked_text_range(&self, _: &AppContext) -> Option> { + None + } + fn unmark_text(&mut self, _: &mut ViewContext) {} + fn replace_text_in_range( + &mut self, + _: Option>, + _: &str, + _: &mut ViewContext, + ) { + } + fn replace_and_mark_text_in_range( + &mut self, + _: Option>, + _: &str, + _: Option>, + _: &mut ViewContext, + ) { + } } pub trait ReadModel { @@ -154,6 +181,11 @@ pub struct TestAppContext { condition_duration: Option, } +pub struct WindowInputHandler { + app: Rc>, + window_id: usize, +} + impl App { pub fn new(asset_source: impl AssetSource) -> Result { let platform = platform::current::platform(); @@ -310,6 +342,121 @@ impl App { } } +impl WindowInputHandler { + fn read_focused_view(&self, f: F) -> Option + where + F: FnOnce(&dyn AnyView, &AppContext) -> T, + { + let app = self.app.borrow(); + let view_id = app.focused_view_id(self.window_id)?; + let view = app.cx.views.get(&(self.window_id, view_id))?; + let result = f(view.as_ref(), &app); + Some(result) + } + + fn update_focused_view(&mut self, f: F) -> Option + where + F: FnOnce(usize, usize, &mut dyn AnyView, &mut MutableAppContext) -> T, + { + let mut app = self.app.borrow_mut(); + app.update(|app| { + let view_id = app.focused_view_id(self.window_id)?; + let mut view = app.cx.views.remove(&(self.window_id, view_id))?; + let result = f(self.window_id, view_id, view.as_mut(), &mut *app); + app.cx.views.insert((self.window_id, view_id), view); + Some(result) + }) + } +} + +impl InputHandler for WindowInputHandler { + fn text_for_range(&self, range: Range) -> Option { + let result = self + .read_focused_view(|view, cx| view.text_for_range(range.clone(), cx)) + .flatten(); + + eprintln!("text_for_range({range:?}) -> {result:?}"); + + result + } + + fn selected_text_range(&self) -> Option> { + let result = self + .read_focused_view(|view, cx| view.selected_text_range(cx)) + .flatten(); + + eprintln!("selected_text_range() -> {result:?}"); + + result + } + + fn set_selected_text_range(&mut self, range: Range) { + eprintln!("set_selected_text_range({range:?})"); + + self.update_focused_view(|window_id, view_id, view, cx| { + view.set_selected_text_range(range, cx, window_id, view_id); + }); + } + + fn replace_text_in_range(&mut self, range: Option>, text: &str) { + eprintln!("replace_text_in_range({range:?}, {text:?})"); + + self.update_focused_view(|window_id, view_id, view, cx| { + view.replace_text_in_range(range, text, cx, window_id, view_id); + }); + } + + fn marked_text_range(&self) -> Option> { + let result = self + .read_focused_view(|view, cx| view.marked_text_range(cx)) + .flatten(); + + eprintln!("marked_text_range() -> {result:?}"); + + result + } + + fn unmark_text(&mut self) { + eprintln!("unmark_text()"); + + self.update_focused_view(|window_id, view_id, view, cx| { + view.unmark_text(cx, window_id, view_id); + }); + } + + fn replace_and_mark_text_in_range( + &mut self, + range: Option>, + new_text: &str, + new_selected_range: Option>, + ) { + eprintln!( + "replace_and_mark_text_in_range({range:?}, {new_text:?}, {new_selected_range:?})" + ); + + self.update_focused_view(|window_id, view_id, view, cx| { + view.replace_and_mark_text_in_range( + range, + new_text, + new_selected_range, + cx, + window_id, + view_id, + ); + }); + } + + // TODO - do these need to be handled separately? + + fn cancel_composition(&mut self) { + self.unmark_text(); + } + + fn finish_composition(&mut self) { + self.unmark_text(); + } +} + #[cfg(any(test, feature = "test-support"))] impl TestAppContext { pub fn new( @@ -1888,6 +2035,11 @@ impl MutableAppContext { })); } + window.set_input_handler(Box::new(WindowInputHandler { + app: self.upgrade().0, + window_id, + })); + let scene = presenter .borrow_mut() @@ -3179,6 +3331,35 @@ pub trait AnyView { fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize); fn keymap_context(&self, cx: &AppContext) -> keymap::Context; fn debug_json(&self, cx: &AppContext) -> serde_json::Value; + + fn text_for_range(&self, range: Range, cx: &AppContext) -> Option; + fn selected_text_range(&self, cx: &AppContext) -> Option>; + fn set_selected_text_range( + &mut self, + range: Range, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ); + fn marked_text_range(&self, cx: &AppContext) -> Option>; + fn unmark_text(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize); + fn replace_text_in_range( + &mut self, + range: Option>, + text: &str, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ); + fn replace_and_mark_text_in_range( + &mut self, + range: Option>, + new_text: &str, + new_selected_range: Option>, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ); } impl AnyView for T @@ -3229,6 +3410,59 @@ where fn debug_json(&self, cx: &AppContext) -> serde_json::Value { View::debug_json(self, cx) } + + fn text_for_range(&self, range: Range, cx: &AppContext) -> Option { + View::text_for_range(self, range, cx) + } + + fn selected_text_range(&self, cx: &AppContext) -> Option> { + View::selected_text_range(self, cx) + } + + fn set_selected_text_range( + &mut self, + range: Range, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ) { + let mut cx = ViewContext::new(cx, window_id, view_id); + View::set_selected_text_range(self, range, &mut cx) + } + + fn marked_text_range(&self, cx: &AppContext) -> Option> { + View::marked_text_range(self, cx) + } + + fn unmark_text(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize) { + let mut cx = ViewContext::new(cx, window_id, view_id); + View::unmark_text(self, &mut cx) + } + + fn replace_text_in_range( + &mut self, + range: Option>, + text: &str, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ) { + let mut cx = ViewContext::new(cx, window_id, view_id); + View::replace_text_in_range(self, range, text, &mut cx) + } + + fn replace_and_mark_text_in_range( + &mut self, + range: Option>, + new_text: &str, + new_selected_range: Option>, + cx: &mut MutableAppContext, + window_id: usize, + view_id: usize, + ) { + let mut cx = ViewContext::new(cx, window_id, view_id); + View::replace_and_mark_text_in_range(self, range, new_text, new_selected_range, &mut cx) + } } pub struct ModelContext<'a, T: ?Sized> { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 255995cdf3..22eaa00540 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -90,20 +90,20 @@ pub trait Dispatcher: Send + Sync { } pub trait InputHandler { - fn select(&mut self, range: Range); - fn selected_range(&self) -> Option>; - fn edit(&mut self, replacement_range: Option>, text: &str); - fn compose( + fn selected_text_range(&self) -> Option>; + fn set_selected_text_range(&mut self, range: Range); + fn text_for_range(&self, range: Range) -> Option; + fn replace_text_in_range(&mut self, replacement_range: Option>, text: &str); + fn replace_and_mark_text_in_range( &mut self, - marked_text: &str, + range: Option>, + new_text: &str, new_selected_range: Option>, - replacement_range: Option>, ); + fn marked_text_range(&self) -> Option>; + fn unmark_text(&mut self); fn cancel_composition(&mut self); fn finish_composition(&mut self); - fn unmark(&mut self); - fn marked_range(&self) -> Option>; - fn text_for_range(&self, range: Range) -> Option; } pub trait Window: WindowContext { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 9e749bc67d..e7bffa6ecd 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -254,6 +254,10 @@ unsafe fn build_classes() { attributed_substring_for_proposed_range as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id, ); + decl.add_method( + sel!(doCommandBySelector:), + do_command_by_selector as extern "C" fn(&Object, Sel, Sel), + ); decl.register() }; @@ -723,7 +727,7 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> window_state_borrow.pending_key_event = Some(Default::default()); drop(window_state_borrow); let had_marked_text = - with_input_handler(this, |input_handler| input_handler.marked_range()) + with_input_handler(this, |input_handler| input_handler.marked_text_range()) .flatten() .is_some(); @@ -745,15 +749,19 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> // } let pending_event = window_state.borrow_mut().pending_key_event.take().unwrap(); + + dbg!(&pending_event); + let mut inserted_text = false; let has_marked_text = pending_event.set_marked_text.is_some() - || with_input_handler(this, |input_handler| input_handler.marked_range()) + || with_input_handler(this, |input_handler| input_handler.marked_text_range()) .flatten() .is_some(); if let Some(text) = pending_event.insert_text.as_ref() { - if !text.is_empty() && (had_marked_text || has_marked_text || text.chars().count() > 1) - { - with_input_handler(this, |input_handler| input_handler.edit(None, &text)); + if !text.is_empty() { + with_input_handler(this, |input_handler| { + input_handler.replace_text_in_range(None, &text) + }); inserted_text = true; } } @@ -762,7 +770,11 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> if let Some((text, new_selected_range, replacement_range)) = pending_event.set_marked_text { - input_handler.compose(&text, new_selected_range, replacement_range) + input_handler.replace_and_mark_text_in_range( + replacement_range, + &text, + new_selected_range, + ) } else if had_marked_text && !inserted_text { if pending_event.unmark_text { input_handler.finish_composition(); @@ -784,23 +796,12 @@ extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> handled = true; } else { if let Some(text) = pending_event.insert_text { - // TODO: we may not need this anymore. - event.input = Some(text); - - if event.input.as_ref().unwrap().chars().count() == 1 { - event.keystroke.key = event.input.clone().unwrap(); + if text.chars().count() == 1 { + event.keystroke.key = text; } } handled = callback(Event::KeyDown(event.clone())); - if !handled { - if let Some(input) = event.input { - with_input_handler(this, |input_handler| { - input_handler.edit(None, &input); - }); - handled = true; - } - } } window_state.borrow_mut().event_callback = Some(callback); @@ -1066,19 +1067,19 @@ extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id { } extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL { - with_input_handler(this, |input_handler| input_handler.marked_range()) + with_input_handler(this, |input_handler| input_handler.marked_text_range()) .flatten() .is_some() as BOOL } extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange { - with_input_handler(this, |input_handler| input_handler.marked_range()) + with_input_handler(this, |input_handler| input_handler.marked_text_range()) .flatten() .map_or(NSRange::invalid(), |range| range.into()) } extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange { - with_input_handler(this, |input_handler| input_handler.selected_range()) + with_input_handler(this, |input_handler| input_handler.selected_text_range()) .flatten() .map_or(NSRange::invalid(), |range| range.into()) } @@ -1108,11 +1109,11 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS } else { drop(window_state); with_input_handler(this, |input_handler| { - input_handler.edit(replacement_range.to_range(), text); + input_handler.replace_text_in_range(replacement_range.to_range(), text); }); } - with_input_handler(this, |input_handler| input_handler.unmark()); + with_input_handler(this, |input_handler| input_handler.unmark_text()); } } @@ -1145,7 +1146,11 @@ extern "C" fn set_marked_text( } else { drop(window_state); with_input_handler(this, |input_handler| { - input_handler.compose(text, selected_range, replacement_range); + input_handler.replace_and_mark_text_in_range( + replacement_range, + text, + selected_range, + ); }); } } @@ -1181,7 +1186,7 @@ extern "C" fn attributed_substring_for_proposed_range( return None; } - let selected_range = input_handler.selected_range()?; + let selected_range = input_handler.selected_text_range()?; let intersection = cmp::max(requested_range.start, selected_range.start) ..cmp::min(requested_range.end, selected_range.end); if intersection.start >= intersection.end { @@ -1199,6 +1204,10 @@ extern "C" fn attributed_substring_for_proposed_range( .unwrap_or(nil) } +extern "C" fn do_command_by_selector(_: &Object, _: Sel, _: Sel) { + // +} + async fn synthetic_drag( window_state: Weak>, drag_id: usize,