mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-25 19:29:49 +00:00
WIP
This commit is contained in:
parent
7757fbe241
commit
2ea0b89e7c
3 changed files with 303 additions and 31 deletions
|
@ -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<usize>);
|
||||
fn selected_range(&self) -> Option<Range<usize>>;
|
||||
fn set_composition(
|
||||
&mut self,
|
||||
marked_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
replacement_range: Option<Range<usize>>,
|
||||
);
|
||||
fn commit(&mut self, text: &str, replacement_range: Option<Range<usize>>);
|
||||
fn cancel_composition(&mut self);
|
||||
fn finish_composition(&mut self);
|
||||
fn unmark(&mut self);
|
||||
fn marked_range(&self) -> Option<Range<usize>>;
|
||||
fn text_for_range(&self, range: Range<usize>) -> Option<String>;
|
||||
}
|
||||
|
||||
pub trait Window: WindowContext {
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
|
||||
|
@ -95,6 +113,7 @@ pub trait Window: WindowContext {
|
|||
fn on_resize(&mut self, callback: Box<dyn FnMut()>);
|
||||
fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
|
||||
fn on_close(&mut self, callback: Box<dyn FnOnce()>);
|
||||
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
|
||||
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
||||
fn activate(&self);
|
||||
fn set_title(&mut self, title: &str);
|
||||
|
|
|
@ -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<Range<usize>> {
|
||||
if self.is_valid() {
|
||||
let start = self.location as usize;
|
||||
let end = start + self.length as usize;
|
||||
Some(start..end)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range<usize>> for NSRange {
|
||||
fn from(range: Range<usize>) -> 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<Box<dyn FnMut()>>,
|
||||
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
||||
close_callback: Option<Box<dyn FnOnce()>>,
|
||||
input_handler: Option<Box<dyn InputHandler>>,
|
||||
pending_key_event: Option<PendingKeyEvent>,
|
||||
synthetic_drag_counter: usize,
|
||||
executor: Rc<executor::Foreground>,
|
||||
scene_to_render: Option<Scene>,
|
||||
|
@ -236,6 +282,13 @@ struct WindowState {
|
|||
previous_modifiers_changed_event: Option<Event>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct PendingKeyEvent {
|
||||
set_marked_text: Option<(String, Option<Range<usize>>, Option<Range<usize>>)>,
|
||||
unmark_text: bool,
|
||||
insert_text: Option<String>,
|
||||
}
|
||||
|
||||
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<dyn InputHandler>) {
|
||||
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<F, R>(window: &Object, f: F) -> Option<R>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -255,6 +255,8 @@ impl super::Window for Window {
|
|||
self.close_handlers.push(callback);
|
||||
}
|
||||
|
||||
fn set_input_handler(&mut self, _: Box<dyn crate::InputHandler>) {}
|
||||
|
||||
fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str]) -> oneshot::Receiver<usize> {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
self.pending_prompts.borrow_mut().push_back(done_tx);
|
||||
|
|
Loading…
Reference in a new issue