From 2ea0b89e7cfaa367e3f1c66f20f034b01f6b009c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 20 Jul 2022 15:07:09 +0200 Subject: [PATCH] WIP --- crates/gpui/src/platform.rs | 19 ++ crates/gpui/src/platform/mac/window.rs | 313 ++++++++++++++++++++++--- crates/gpui/src/platform/test.rs | 2 + 3 files changed, 303 insertions(+), 31 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 44348a34c2..3797843144 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -26,6 +26,7 @@ use serde::Deserialize; use std::{ any::Any, fmt::{self, Display}, + ops::Range, path::{Path, PathBuf}, rc::Rc, str::FromStr, @@ -88,6 +89,23 @@ pub trait Dispatcher: Send + Sync { fn run_on_main_thread(&self, task: Runnable); } +pub trait InputHandler { + fn select(&mut self, range: Range); + fn selected_range(&self) -> Option>; + fn set_composition( + &mut self, + marked_text: &str, + new_selected_range: Option>, + replacement_range: Option>, + ); + fn commit(&mut self, text: &str, replacement_range: Option>); + 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 { fn as_any_mut(&mut self) -> &mut dyn Any; fn on_event(&mut self, callback: Box bool>); @@ -95,6 +113,7 @@ pub trait Window: WindowContext { fn on_resize(&mut self, callback: Box); fn on_should_close(&mut self, callback: Box bool>); fn on_close(&mut self, callback: Box); + fn set_input_handler(&mut self, input_handler: Box); fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; fn activate(&self); fn set_title(&mut self, title: &str); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 8e7510e6aa..6b827a7711 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -7,7 +7,8 @@ use crate::{ }, keymap::Keystroke, platform::{self, Event, WindowBounds, WindowContext}, - KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent, Scene, + InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseEvent, MouseMovedEvent, + Scene, }; use block::ConcreteBlock; use cocoa::{ @@ -16,7 +17,9 @@ use cocoa::{ NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask, }, base::{id, nil}, - foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger}, + foundation::{ + NSAutoreleasePool, NSInteger, NSNotFound, NSPoint, NSRect, NSSize, NSString, NSUInteger, + }, quartzcore::AutoresizingMask, }; use core_graphics::display::CGRect; @@ -34,9 +37,13 @@ use smol::Timer; use std::{ any::Any, cell::{Cell, RefCell}, + cmp, convert::TryInto, - ffi::c_void, - mem, ptr, + ffi::{c_void, CStr}, + mem, + ops::Range, + os::raw::c_char, + ptr, rc::{Rc, Weak}, sync::Arc, time::Duration, @@ -48,12 +55,44 @@ static mut WINDOW_CLASS: *const Class = ptr::null(); static mut VIEW_CLASS: *const Class = ptr::null(); #[repr(C)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] struct NSRange { pub location: NSUInteger, pub length: NSUInteger, } +impl NSRange { + fn invalid() -> Self { + Self { + location: NSNotFound as NSUInteger, + length: 0, + } + } + + fn is_valid(&self) -> bool { + self.location != NSNotFound as NSUInteger + } + + fn to_range(&self) -> Option> { + if self.is_valid() { + let start = self.location as usize; + let end = start + self.length as usize; + Some(start..end) + } else { + None + } + } +} + +impl From> for NSRange { + fn from(range: Range) -> Self { + NSRange { + location: range.start as NSUInteger, + length: range.len() as NSUInteger, + } + } +} + unsafe impl objc::Encode for NSRange { fn encode() -> objc::Encoding { let encoding = format!( @@ -189,6 +228,10 @@ unsafe fn build_classes() { sel!(hasMarkedText), has_marked_text as extern "C" fn(&Object, Sel) -> BOOL, ); + decl.add_method( + sel!(markedRange), + marked_range as extern "C" fn(&Object, Sel) -> NSRange, + ); decl.add_method( sel!(selectedRange), selected_range as extern "C" fn(&Object, Sel) -> NSRange, @@ -205,10 +248,11 @@ unsafe fn build_classes() { sel!(setMarkedText:selectedRange:replacementRange:), set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange), ); + decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel)); decl.add_method( sel!(attributedSubstringForProposedRange:actualRange:), attributed_substring_for_proposed_range - as extern "C" fn(&Object, Sel, NSRange, id) -> id, + as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id, ); decl.register() @@ -225,6 +269,8 @@ struct WindowState { resize_callback: Option>, should_close_callback: Option bool>>, close_callback: Option>, + input_handler: Option>, + pending_key_event: Option, synthetic_drag_counter: usize, executor: Rc, scene_to_render: Option, @@ -236,6 +282,13 @@ struct WindowState { previous_modifiers_changed_event: Option, } +#[derive(Default, Debug)] +struct PendingKeyEvent { + set_marked_text: Option<(String, Option>, Option>)>, + unmark_text: bool, + insert_text: Option, +} + impl Window { pub fn open( id: usize, @@ -311,6 +364,8 @@ impl Window { should_close_callback: None, close_callback: None, activate_callback: None, + input_handler: None, + pending_key_event: None, synthetic_drag_counter: 0, executor, scene_to_render: Default::default(), @@ -419,6 +474,10 @@ impl platform::Window for Window { self.0.as_ref().borrow_mut().activate_callback = Some(callback); } + fn set_input_handler(&mut self, input_handler: Box) { + self.0.as_ref().borrow_mut().input_handler = Some(input_handler); + } + fn prompt( &self, level: platform::PromptLevel, @@ -636,38 +695,103 @@ extern "C" fn dealloc_view(this: &Object, _: Sel) { } extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL { + let had_marked_text = with_input_handler(this, |input_handler| input_handler.marked_range()) + .flatten() + .is_some(); let window_state = unsafe { get_window_state(this) }; + let mut window_state_borrow = window_state.as_ref().borrow_mut(); let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) }; if let Some(event) = event { - match &event { - Event::KeyDown(KeyDownEvent { - keystroke, - input, - is_held, - }) => { - let keydown = (keystroke.clone(), input.clone()); + let mut event = match event { + Event::KeyDown(event) => { + let keydown = (event.keystroke.clone(), event.input.clone()); // Ignore events from held-down keys after some of the initially-pressed keys // were released. - if *is_held { + if event.is_held { if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) { return YES; } } else { window_state_borrow.last_fresh_keydown = Some(keydown); } + + event } _ => return NO, + }; + + // TODO: handle "live conversion" + window_state_borrow.pending_key_event = Some(Default::default()); + drop(window_state_borrow); + + // TODO: + // Since Mac Eisu Kana keys cannot be handled by interpretKeyEvents to enable/ + // disable an IME, we need to pass the event to processInputKeyBindings. + // processInputKeyBindings is available at least on 10.11-11.0. + // if (keyCode == kVK_JIS_Eisu || keyCode == kVK_JIS_Kana) { + // if ([NSTextInputContext + // respondsToSelector:@selector(processInputKeyBindings:)]) { + // [NSTextInputContext performSelector:@selector(processInputKeyBindings:) + // withObject:theEvent]; + // } + // } else { + unsafe { + let input_context: id = msg_send![this, inputContext]; + let _: BOOL = msg_send![input_context, handleEvent: native_event]; + } + // } + + let pending_event = window_state.borrow_mut().pending_key_event.take().unwrap(); + let mut inserted_text = false; + let has_marked_text = pending_event.set_marked_text.is_some(); + if let Some(text) = pending_event.insert_text.as_ref() { + if !text.is_empty() && (had_marked_text || has_marked_text || text.len() > 1) { + with_input_handler(this, |input_handler| input_handler.commit(&text, None)); + inserted_text = true; + } } - if let Some(mut callback) = window_state_borrow.event_callback.take() { - drop(window_state_borrow); - let handled = callback(event); - window_state.borrow_mut().event_callback = Some(callback); - handled as BOOL + with_input_handler(this, |input_handler| { + if let Some((text, new_selected_range, replacement_range)) = + pending_event.set_marked_text + { + input_handler.set_composition(&text, new_selected_range, replacement_range) + } else if had_marked_text && !inserted_text { + if pending_event.unmark_text { + input_handler.finish_composition(); + } else { + input_handler.cancel_composition(); + } + } + }); + + if has_marked_text { + YES } else { - NO + let mut handled = false; + let mut window_state_borrow = window_state.borrow_mut(); + if let Some(mut callback) = window_state_borrow.event_callback.take() { + drop(window_state_borrow); + + if inserted_text { + handled = true; + } else if let Some(text) = pending_event.insert_text { + if text.len() == 1 { + event.keystroke.key = text; + handled = callback(Event::KeyDown(event)); + } else if event.keystroke.cmd || event.keystroke.ctrl { + handled = callback(Event::KeyDown(event)); + } + } else { + handled = callback(Event::KeyDown(event)); + } + + window_state.borrow_mut().event_callback = Some(callback); + } + + handled as BOOL } } else { NO @@ -926,27 +1050,138 @@ extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id { unsafe { msg_send![class!(NSArray), array] } } -extern "C" fn has_marked_text(_: &Object, _: Sel) -> BOOL { - false as BOOL +extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL { + with_input_handler(this, |input_handler| input_handler.marked_range()) + .flatten() + .is_some() as BOOL } -extern "C" fn selected_range(_: &Object, _: Sel) -> NSRange { - NSRange { - location: 0, - length: 0, - } +extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange { + with_input_handler(this, |input_handler| input_handler.marked_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()) + .flatten() + .map_or(NSRange::invalid(), |range| range.into()) } extern "C" fn first_rect_for_character_range(_: &Object, _: Sel, _: NSRange, _: id) -> NSRect { NSRect::new(NSPoint::new(0., 0.), NSSize::new(20., 20.)) } -extern "C" fn insert_text(_: &Object, _: Sel, _: id, _: NSRange) {} +extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) { + unsafe { + let is_attributed_string: BOOL = + msg_send![text, isKindOfClass: [class!(NSAttributedString)]]; + let text: id = if is_attributed_string == YES { + msg_send![text, string] + } else { + text + }; + let text = CStr::from_ptr(text.UTF8String() as *mut c_char) + .to_str() + .unwrap(); -extern "C" fn set_marked_text(_: &Object, _: Sel, _: id, _: NSRange, _: NSRange) {} + let window_state = get_window_state(this); + let mut window_state = window_state.borrow_mut(); + if window_state.pending_key_event.is_some() && !replacement_range.is_valid() { + window_state.pending_key_event.as_mut().unwrap().insert_text = Some(text.to_string()); + drop(window_state); + } else { + drop(window_state); + with_input_handler(this, |input_handler| { + input_handler.commit(text, replacement_range.to_range()); + }); + } -extern "C" fn attributed_substring_for_proposed_range(_: &Object, _: Sel, _: NSRange, _: id) -> id { - unsafe { msg_send![class!(NSAttributedString), alloc] } + with_input_handler(this, |input_handler| input_handler.unmark()); + } +} + +extern "C" fn set_marked_text( + this: &Object, + _: Sel, + text: id, + selected_range: NSRange, + replacement_range: NSRange, +) { + println!("set_marked_text"); + unsafe { + let is_attributed_string: BOOL = + msg_send![text, isKindOfClass: [class!(NSAttributedString)]]; + let text: id = if is_attributed_string == YES { + msg_send![text, string] + } else { + text + }; + let selected_range = selected_range.to_range(); + let replacement_range = replacement_range.to_range(); + let text = CStr::from_ptr(text.UTF8String() as *mut c_char) + .to_str() + .unwrap(); + + let window_state = get_window_state(this); + let mut window_state = window_state.borrow_mut(); + if let Some(pending) = window_state.pending_key_event.as_mut() { + pending.set_marked_text = Some((text.to_string(), selected_range, replacement_range)); + } else { + drop(window_state); + with_input_handler(this, |input_handler| { + input_handler.set_composition(text, selected_range, replacement_range); + }); + } + } +} + +extern "C" fn unmark_text(this: &Object, _: Sel) { + println!("unmark_text"); + let window_state = unsafe { get_window_state(this) }; + let mut window_state = window_state.borrow_mut(); + if let Some(pending) = window_state.pending_key_event.as_mut() { + pending.unmark_text = true; + pending.set_marked_text.take(); + } else { + drop(window_state); + with_input_handler(this, |input_handler| input_handler.finish_composition()); + } +} + +extern "C" fn attributed_substring_for_proposed_range( + this: &Object, + _: Sel, + range: NSRange, + actual_range: *mut c_void, +) -> id { + with_input_handler(this, |input_handler| { + let actual_range = actual_range as *mut NSRange; + if !actual_range.is_null() { + unsafe { *actual_range = NSRange::invalid() }; + } + + let requested_range = range.to_range()?; + if requested_range.is_empty() { + return None; + } + + let selected_range = input_handler.selected_range()?; + let intersection = cmp::max(requested_range.start, selected_range.start) + ..cmp::min(requested_range.end, selected_range.end); + if intersection.start >= intersection.end { + return None; + } + + unsafe { + let selected_text = ns_string(&input_handler.text_for_range(intersection)?); + let string: id = msg_send![class!(NSAttributedString), alloc]; + let string: id = msg_send![string, initWithString: selected_text]; + Some(string) + } + }) + .flatten() + .unwrap_or(nil) } async fn synthetic_drag( @@ -974,3 +1209,19 @@ async fn synthetic_drag( unsafe fn ns_string(string: &str) -> id { NSString::alloc(nil).init_str(string).autorelease() } + +fn with_input_handler(window: &Object, f: F) -> Option +where + F: FnOnce(&mut dyn InputHandler) -> R, +{ + let window_state = unsafe { get_window_state(window) }; + let mut window_state_borrow = window_state.as_ref().borrow_mut(); + if let Some(mut input_handler) = window_state_borrow.input_handler.take() { + drop(window_state_borrow); + let result = f(input_handler.as_mut()); + window_state.borrow_mut().input_handler = Some(input_handler); + Some(result) + } else { + None + } +} diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index e1f39d046d..dfb92b77f0 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -255,6 +255,8 @@ impl super::Window for Window { self.close_handlers.push(callback); } + fn set_input_handler(&mut self, _: Box) {} + fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str]) -> oneshot::Receiver { let (done_tx, done_rx) = oneshot::channel(); self.pending_prompts.borrow_mut().push_back(done_tx);