Clip UTF-16 offsets in text for range (#20968)
When launching the Pinyin keyboard, macOS will sometimes try to peek one character back in the string. This caused a panic if the preceding character was an emoji. The docs say "don't assume the range is valid", so now we don't. Release Notes: - (macOS) Fixed a panic when using the Pinyin keyboard with emojis
This commit is contained in:
parent
7285cdb955
commit
ebaa270baf
6 changed files with 70 additions and 16 deletions
|
@ -14428,15 +14428,16 @@ impl ViewInputHandler for Editor {
|
||||||
fn text_for_range(
|
fn text_for_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
range_utf16: Range<usize>,
|
range_utf16: Range<usize>,
|
||||||
|
adjusted_range: &mut Option<Range<usize>>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
Some(
|
let snapshot = self.buffer.read(cx).read(cx);
|
||||||
self.buffer
|
let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
|
||||||
.read(cx)
|
let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
|
||||||
.read(cx)
|
if (start.0..end.0) != range_utf16 {
|
||||||
.text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
|
adjusted_range.replace(start.0..end.0);
|
||||||
.collect(),
|
}
|
||||||
)
|
Some(snapshot.text_for_range(start..end).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_text_range(
|
fn selected_text_range(
|
||||||
|
|
|
@ -15,7 +15,10 @@ actions!(
|
||||||
SelectAll,
|
SelectAll,
|
||||||
Home,
|
Home,
|
||||||
End,
|
End,
|
||||||
ShowCharacterPalette
|
ShowCharacterPalette,
|
||||||
|
Paste,
|
||||||
|
Cut,
|
||||||
|
Copy,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -107,6 +110,28 @@ impl TextInput {
|
||||||
cx.show_character_palette();
|
cx.show_character_palette();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) {
|
||||||
|
self.replace_text_in_range(None, &text.replace("\n", " "), cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
|
||||||
|
if !self.selected_range.is_empty() {
|
||||||
|
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||||
|
(&self.content[self.selected_range.clone()]).to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn cut(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
|
||||||
|
if !self.selected_range.is_empty() {
|
||||||
|
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||||
|
(&self.content[self.selected_range.clone()]).to_string(),
|
||||||
|
));
|
||||||
|
self.replace_text_in_range(None, "", cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||||
self.selected_range = offset..offset;
|
self.selected_range = offset..offset;
|
||||||
cx.notify()
|
cx.notify()
|
||||||
|
@ -219,9 +244,11 @@ impl ViewInputHandler for TextInput {
|
||||||
fn text_for_range(
|
fn text_for_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
range_utf16: Range<usize>,
|
range_utf16: Range<usize>,
|
||||||
|
actual_range: &mut Option<Range<usize>>,
|
||||||
_cx: &mut ViewContext<Self>,
|
_cx: &mut ViewContext<Self>,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
let range = self.range_from_utf16(&range_utf16);
|
let range = self.range_from_utf16(&range_utf16);
|
||||||
|
actual_range.replace(self.range_to_utf16(&range));
|
||||||
Some(self.content[range].to_string())
|
Some(self.content[range].to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -497,6 +524,9 @@ impl Render for TextInput {
|
||||||
.on_action(cx.listener(Self::home))
|
.on_action(cx.listener(Self::home))
|
||||||
.on_action(cx.listener(Self::end))
|
.on_action(cx.listener(Self::end))
|
||||||
.on_action(cx.listener(Self::show_character_palette))
|
.on_action(cx.listener(Self::show_character_palette))
|
||||||
|
.on_action(cx.listener(Self::paste))
|
||||||
|
.on_action(cx.listener(Self::cut))
|
||||||
|
.on_action(cx.listener(Self::copy))
|
||||||
.on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
|
.on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
|
||||||
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
|
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
|
||||||
.on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
|
.on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
|
||||||
|
@ -602,6 +632,9 @@ fn main() {
|
||||||
KeyBinding::new("shift-left", SelectLeft, None),
|
KeyBinding::new("shift-left", SelectLeft, None),
|
||||||
KeyBinding::new("shift-right", SelectRight, None),
|
KeyBinding::new("shift-right", SelectRight, None),
|
||||||
KeyBinding::new("cmd-a", SelectAll, None),
|
KeyBinding::new("cmd-a", SelectAll, None),
|
||||||
|
KeyBinding::new("cmd-v", Paste, None),
|
||||||
|
KeyBinding::new("cmd-c", Copy, None),
|
||||||
|
KeyBinding::new("cmd-x", Cut, None),
|
||||||
KeyBinding::new("home", Home, None),
|
KeyBinding::new("home", Home, None),
|
||||||
KeyBinding::new("end", End, None),
|
KeyBinding::new("end", End, None),
|
||||||
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
|
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
|
||||||
|
|
|
@ -9,8 +9,12 @@ use std::ops::Range;
|
||||||
/// See [`InputHandler`] for details on how to implement each method.
|
/// See [`InputHandler`] for details on how to implement each method.
|
||||||
pub trait ViewInputHandler: 'static + Sized {
|
pub trait ViewInputHandler: 'static + Sized {
|
||||||
/// See [`InputHandler::text_for_range`] for details
|
/// See [`InputHandler::text_for_range`] for details
|
||||||
fn text_for_range(&mut self, range: Range<usize>, cx: &mut ViewContext<Self>)
|
fn text_for_range(
|
||||||
-> Option<String>;
|
&mut self,
|
||||||
|
range: Range<usize>,
|
||||||
|
adjusted_range: &mut Option<Range<usize>>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<String>;
|
||||||
|
|
||||||
/// See [`InputHandler::selected_text_range`] for details
|
/// See [`InputHandler::selected_text_range`] for details
|
||||||
fn selected_text_range(
|
fn selected_text_range(
|
||||||
|
@ -89,10 +93,12 @@ impl<V: ViewInputHandler> InputHandler for ElementInputHandler<V> {
|
||||||
fn text_for_range(
|
fn text_for_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
range_utf16: Range<usize>,
|
range_utf16: Range<usize>,
|
||||||
|
adjusted_range: &mut Option<Range<usize>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
self.view
|
self.view.update(cx, |view, cx| {
|
||||||
.update(cx, |view, cx| view.text_for_range(range_utf16, cx))
|
view.text_for_range(range_utf16, adjusted_range, cx)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_text_in_range(
|
fn replace_text_in_range(
|
||||||
|
|
|
@ -643,9 +643,13 @@ impl PlatformInputHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
|
#[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
|
||||||
fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
|
fn text_for_range(
|
||||||
|
&mut self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
adjusted: &mut Option<Range<usize>>,
|
||||||
|
) -> Option<String> {
|
||||||
self.cx
|
self.cx
|
||||||
.update(|cx| self.handler.text_for_range(range_utf16, cx))
|
.update(|cx| self.handler.text_for_range(range_utf16, adjusted, cx))
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
@ -712,6 +716,7 @@ impl PlatformInputHandler {
|
||||||
|
|
||||||
/// A struct representing a selection in a text buffer, in UTF16 characters.
|
/// A struct representing a selection in a text buffer, in UTF16 characters.
|
||||||
/// This is different from a range because the head may be before the tail.
|
/// This is different from a range because the head may be before the tail.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct UTF16Selection {
|
pub struct UTF16Selection {
|
||||||
/// The range of text in the document this selection corresponds to
|
/// The range of text in the document this selection corresponds to
|
||||||
/// in UTF16 characters.
|
/// in UTF16 characters.
|
||||||
|
@ -749,6 +754,7 @@ pub trait InputHandler: 'static {
|
||||||
fn text_for_range(
|
fn text_for_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
range_utf16: Range<usize>,
|
range_utf16: Range<usize>,
|
||||||
|
adjusted_range: &mut Option<Range<usize>>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Option<String>;
|
) -> Option<String>;
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ use std::{
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
ffi::{c_void, CStr},
|
ffi::{c_void, CStr},
|
||||||
mem,
|
mem,
|
||||||
|
ops::Range,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
ptr::{self, NonNull},
|
ptr::{self, NonNull},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
@ -1754,15 +1755,21 @@ extern "C" fn attributed_substring_for_proposed_range(
|
||||||
this: &Object,
|
this: &Object,
|
||||||
_: Sel,
|
_: Sel,
|
||||||
range: NSRange,
|
range: NSRange,
|
||||||
_actual_range: *mut c_void,
|
actual_range: *mut c_void,
|
||||||
) -> id {
|
) -> id {
|
||||||
with_input_handler(this, |input_handler| {
|
with_input_handler(this, |input_handler| {
|
||||||
let range = range.to_range()?;
|
let range = range.to_range()?;
|
||||||
if range.is_empty() {
|
if range.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
let mut adjusted: Option<Range<usize>> = None;
|
||||||
|
|
||||||
let selected_text = input_handler.text_for_range(range.clone())?;
|
let selected_text = input_handler.text_for_range(range.clone(), &mut adjusted)?;
|
||||||
|
if let Some(adjusted) = adjusted {
|
||||||
|
if adjusted != range {
|
||||||
|
unsafe { (actual_range as *mut NSRange).write(NSRange::from(adjusted)) };
|
||||||
|
}
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
let string: id = msg_send![class!(NSAttributedString), alloc];
|
let string: id = msg_send![class!(NSAttributedString), alloc];
|
||||||
let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
|
let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
|
||||||
|
|
|
@ -1001,6 +1001,7 @@ impl InputHandler for TerminalInputHandler {
|
||||||
fn text_for_range(
|
fn text_for_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: std::ops::Range<usize>,
|
_: std::ops::Range<usize>,
|
||||||
|
_: &mut Option<std::ops::Range<usize>>,
|
||||||
_: &mut WindowContext,
|
_: &mut WindowContext,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
None
|
None
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue