Skip to main content

gpui/
input.rs

1use crate::{
2    App, Bounds, ClipboardItem, Context, Entity, InputHandler, Pixels, TextInputConfiguration,
3    UTF16Selection, Window,
4};
5use std::ops::Range;
6
7/// Converts a UTF-8 byte offset into a UTF-16 code-unit offset.
8///
9/// Platform text input counts in UTF-16 code units, while Rust strings index
10/// by UTF-8 bytes. Use this to translate an offset in a `String` into the offset
11/// the platform expects. An offset past the end returns the UTF-16 length of
12/// `text`. An offset inside a character returns the offset of that character's
13/// end.
14///
15/// # Examples
16///
17/// ```
18/// use gpui::utf8_to_utf16_offset;
19///
20/// // "A" is 1 byte and 1 code unit. The emoji is 4 bytes and 2 code units.
21/// assert_eq!(utf8_to_utf16_offset("A😀B", 5), 3);
22/// assert_eq!(utf8_to_utf16_offset("A😀B", 100), 4);
23/// ```
24pub fn utf8_to_utf16_offset(text: &str, utf8_offset: usize) -> usize {
25    text.char_indices()
26        .take_while(|(offset, _)| *offset < utf8_offset)
27        .map(|(_, character)| character.len_utf16())
28        .sum()
29}
30
31/// Converts a UTF-16 code-unit offset into a UTF-8 byte offset.
32///
33/// The reverse of [`utf8_to_utf16_offset`]. Use it on ranges that the platform
34/// sends, before slicing a `String`. An offset past the end returns the UTF-8
35/// length of `text`. An offset inside a surrogate pair returns the offset of the
36/// end of that character.
37///
38/// # Examples
39///
40/// ```
41/// use gpui::utf16_to_utf8_offset;
42///
43/// assert_eq!(utf16_to_utf8_offset("A😀B", 3), 5);
44/// assert_eq!(utf16_to_utf8_offset("A😀B", 100), 6);
45/// ```
46pub fn utf16_to_utf8_offset(text: &str, utf16_offset: usize) -> usize {
47    let mut consumed_utf16 = 0;
48    for (utf8_offset, character) in text.char_indices() {
49        if consumed_utf16 >= utf16_offset {
50            return utf8_offset;
51        }
52        consumed_utf16 += character.len_utf16();
53    }
54    text.len()
55}
56
57/// Text input for a view: the methods the platform calls for typed text, IME
58/// composition, paste and selection.
59///
60/// Implement this on a view that edits text, such as a text field. Then wrap the
61/// view in an [`ElementInputHandler`] and install it during paint with
62/// [`Window::handle_input`](crate::Window::handle_input). The platform's input method sends its requests
63/// through that wrapper, and each method runs with the view updated.
64///
65/// Ranges are in UTF-16 code units, the unit the platform uses, and are
66/// relative to the whole document. Convert with [`utf8_to_utf16_offset`] and
67/// [`utf16_to_utf8_offset`] when your text is a UTF-8 `String`. Every method has
68/// the same meaning as the matching method on [`InputHandler`], which documents
69/// the platform side.
70///
71/// On touch platforms, dismissing the software keyboard can leave the view focused. Call
72/// [`Window::request_virtual_keyboard`] from an editing gesture, such as a tap on the field,
73/// to show it again. After edits or selection changes that your app makes itself, call
74/// [`Window::notify_text_input_changed`] or [`Window::notify_text_selection_changed`] so the
75/// platform's text services stay in sync. UIKit also needs
76/// [`text_length_utf16`](Self::text_length_utf16), or a bounded
77/// [`text_input_editable_range`](Self::text_input_editable_range), and
78/// [`set_selected_text_range`](Self::set_selected_text_range). For native selection
79/// rectangles, implement [`selection_bounds_for_range`](Self::selection_bounds_for_range),
80/// which returns all of a range's line fragments at once.
81///
82/// The text guide has a complete editable field: [text input](crate::_documentation::guides::text_input).
83///
84/// # Examples
85///
86/// ```
87/// use gpui::{
88///     Bounds, Context, ElementInputHandler, EntityInputHandler, FocusHandle, IntoElement,
89///     Pixels, Point, Render, TestApp, UTF16Selection, Window, canvas, div, prelude::*,
90/// };
91/// use std::ops::Range;
92///
93/// struct Field {
94///     focus_handle: FocusHandle,
95///     text: String,
96/// }
97///
98/// impl EntityInputHandler for Field {
99///     fn text_for_range(
100///         &mut self,
101///         range: Range<usize>,
102///         adjusted_range: &mut Option<Range<usize>>,
103///         _window: &mut Window,
104///         _cx: &mut Context<Self>,
105///     ) -> Option<String> {
106///         *adjusted_range = Some(range.clone());
107///         let start = gpui::utf16_to_utf8_offset(&self.text, range.start);
108///         let end = gpui::utf16_to_utf8_offset(&self.text, range.end);
109///         Some(self.text[start..end].to_string())
110///     }
111///
112///     fn selected_text_range(
113///         &mut self,
114///         _ignore_disabled_input: bool,
115///         _window: &mut Window,
116///         _cx: &mut Context<Self>,
117///     ) -> Option<UTF16Selection> {
118///         let end = gpui::utf8_to_utf16_offset(&self.text, self.text.len());
119///         Some(UTF16Selection { range: end..end, reversed: false })
120///     }
121///
122///     fn marked_text_range(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<Range<usize>> {
123///         None
124///     }
125///
126///     fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
127///
128///     fn replace_text_in_range(
129///         &mut self,
130///         range: Option<Range<usize>>,
131///         text: &str,
132///         _window: &mut Window,
133///         cx: &mut Context<Self>,
134///     ) {
135///         let range = range.unwrap_or_else(|| {
136///             let end = gpui::utf8_to_utf16_offset(&self.text, self.text.len());
137///             end..end
138///         });
139///         let start = gpui::utf16_to_utf8_offset(&self.text, range.start);
140///         let end = gpui::utf16_to_utf8_offset(&self.text, range.end);
141///         self.text.replace_range(start..end, text);
142///         cx.notify();
143///     }
144///
145///     fn replace_and_mark_text_in_range(
146///         &mut self,
147///         range: Option<Range<usize>>,
148///         new_text: &str,
149///         _new_selected_range: Option<Range<usize>>,
150///         window: &mut Window,
151///         cx: &mut Context<Self>,
152///     ) {
153///         self.replace_text_in_range(range, new_text, window, cx);
154///     }
155///
156///     fn bounds_for_range(
157///         &mut self,
158///         _range: Range<usize>,
159///         element_bounds: Bounds<Pixels>,
160///         _window: &mut Window,
161///         _cx: &mut Context<Self>,
162///     ) -> Option<Bounds<Pixels>> {
163///         Some(element_bounds)
164///     }
165///
166///     fn character_index_for_point(
167///         &mut self,
168///         _point: Point<Pixels>,
169///         _window: &mut Window,
170///         _cx: &mut Context<Self>,
171///     ) -> Option<usize> {
172///         Some(0)
173///     }
174/// }
175///
176/// impl Render for Field {
177///     fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
178///         let view = cx.entity();
179///         let focus_handle = self.focus_handle.clone();
180///         div().size_full().track_focus(&self.focus_handle).child(
181///             canvas(
182///                 |_, _, _| {},
183///                 move |bounds, _, window, cx| {
184///                     window.handle_input(&focus_handle, ElementInputHandler::new(bounds, view), cx);
185///                 },
186///             )
187///             .size_full(),
188///         )
189///     }
190/// }
191///
192/// let mut app = TestApp::new();
193/// let mut window = app.open_window(|window, cx| {
194///     let focus_handle = cx.focus_handle();
195///     window.focus(&focus_handle, cx);
196///     Field { focus_handle, text: String::new() }
197/// });
198///
199/// window.simulate_input("Ada");
200/// assert_eq!(window.read(|field, _| field.text.clone()), "Ada");
201/// ```
202pub trait EntityInputHandler: 'static + Sized {
203    /// Returns the text in a UTF-16 range, for the platform to read back.
204    ///
205    /// The platform uses this to read text around the cursor, for example for
206    /// autocorrect or the character picker. If you return text for a different
207    /// range than was asked for, such as one snapped to a character boundary,
208    /// write the range you returned into `adjusted_range`.
209    fn text_for_range(
210        &mut self,
211        range: Range<usize>,
212        adjusted_range: &mut Option<Range<usize>>,
213        window: &mut Window,
214        cx: &mut Context<Self>,
215    ) -> Option<String>;
216
217    /// Returns the current selection as a UTF-16 range and direction.
218    ///
219    /// Returns `None` if there is no selection to report. `ignore_disabled_input`
220    /// is passed through from the platform, and each implementation decides what it
221    /// means. The bundled text elements ignore it.
222    fn selected_text_range(
223        &mut self,
224        ignore_disabled_input: bool,
225        window: &mut Window,
226        cx: &mut Context<Self>,
227    ) -> Option<UTF16Selection>;
228
229    /// Returns the UTF-16 range of the text in the current IME composition, or
230    /// `None` if no composition is active.
231    ///
232    /// Composition is the text an input method is still building, such as the
233    /// underlined kana before a Japanese conversion is confirmed.
234    fn marked_text_range(
235        &self,
236        window: &mut Window,
237        cx: &mut Context<Self>,
238    ) -> Option<Range<usize>>;
239
240    /// Ends the IME composition and keeps its text as ordinary text.
241    ///
242    /// The platform calls this when it finishes composing. After this call,
243    /// [`EntityInputHandler::marked_text_range`] returns `None`.
244    fn unmark_text(&mut self, window: &mut Window, cx: &mut Context<Self>);
245
246    /// Inserts clipboard contents at the selection, for platforms that deliver
247    /// paste as an input event, such as the web.
248    ///
249    /// The default inserts only the plain-text part of `item`, through
250    /// [`EntityInputHandler::replace_text_in_range`] with a `None` range. Override
251    /// it to handle rich content.
252    fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut Context<Self>) {
253        if let Some(text) = item.text() {
254            self.replace_text_in_range(None, &text, window, cx);
255        }
256    }
257
258    /// Replaces a UTF-16 range with typed text, or inserts at a cursor.
259    ///
260    /// This is how typed characters reach your field. A `None` range means the
261    /// platform gave no range. The bundled text elements then replace the marked
262    /// text if there is any, and otherwise the selection.
263    fn replace_text_in_range(
264        &mut self,
265        range: Option<Range<usize>>,
266        text: &str,
267        window: &mut Window,
268        cx: &mut Context<Self>,
269    );
270
271    /// Replaces a range with IME text and marks that text as composing.
272    ///
273    /// The platform calls this for each step of an input method's composition.
274    /// The marked text is underlined and stays editable until
275    /// [`EntityInputHandler::unmark_text`] is called. `new_selected_range` is the
276    /// selection within `new_text`, in UTF-16 units, counted from the start of
277    /// `new_text`. When it is `None`, the cursor goes to the end of the new text.
278    fn replace_and_mark_text_in_range(
279        &mut self,
280        range: Option<Range<usize>>,
281        new_text: &str,
282        new_selected_range: Option<Range<usize>>,
283        window: &mut Window,
284        cx: &mut Context<Self>,
285    );
286
287    /// Returns where a UTF-16 range is drawn, so the platform can place its
288    /// candidate window and other overlays next to the text.
289    ///
290    /// `element_bounds` is the bounds of the element that installed this handler.
291    /// Return `None` when the text cannot be measured. Returned bounds are in window
292    /// coordinates, in logical pixels.
293    fn bounds_for_range(
294        &mut self,
295        range_utf16: Range<usize>,
296        element_bounds: Bounds<Pixels>,
297        window: &mut Window,
298        cx: &mut Context<Self>,
299    ) -> Option<Bounds<Pixels>>;
300
301    /// Returns the UTF-16 index of the character at `point`, which the platform uses
302    /// to map a screen position to text.
303    ///
304    /// `point` is in window coordinates, in logical pixels.
305    /// See [`InputHandler::selection_bounds_for_range`] for details.
306    fn selection_bounds_for_range(
307        &mut self,
308        _range_utf16: Range<usize>,
309        _element_bounds: Bounds<Pixels>,
310        _window: &mut Window,
311        _cx: &mut Context<Self>,
312    ) -> Vec<Bounds<Pixels>> {
313        Vec::new()
314    }
315
316    /// See [`InputHandler::character_index_for_point`] for details
317    fn character_index_for_point(
318        &mut self,
319        point: crate::Point<Pixels>,
320        window: &mut Window,
321        cx: &mut Context<Self>,
322    ) -> Option<usize>;
323
324    /// Sets the selection to a UTF-16 range, when the platform moves it.
325    ///
326    /// Platforms call this for system-driven selection changes, such as a dragged
327    /// selection handle or Select All from system UI. It is the reverse of
328    /// [`EntityInputHandler::selected_text_range`]. The default does nothing.
329    fn set_selected_text_range(
330        &mut self,
331        _range_utf16: Range<usize>,
332        _window: &mut Window,
333        _cx: &mut Context<Self>,
334    ) {
335    }
336
337    /// Returns the length of the document in UTF-16 code units, or `None` if it is
338    /// not known. The default is `None`.
339    fn text_length_utf16(
340        &mut self,
341        _window: &mut Window,
342        _cx: &mut Context<Self>,
343    ) -> Option<usize> {
344        None
345    }
346
347    /// Returns whether the field accepts typed text right now.
348    ///
349    /// Return `false` for a read-only or disabled field. The default is `true`.
350    /// For a keystroke with [`KeyDownEvent::prefer_character_input`](crate::KeyDownEvent::prefer_character_input) set, the window
351    /// sends the keystroke to text input instead of key bindings only when this
352    /// returns `true`.
353    fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
354        true
355    }
356
357    /// Returns platform text assistance settings for this field, such as
358    /// autocorrect and the action key on the software keyboard.
359    ///
360    /// The window asks on every frame and forwards the result only when it
361    /// changes, so return a cheap value. You can vary it with application state,
362    /// such as the cursor position. The default disables text assistance.
363    fn text_input_configuration(
364        &mut self,
365        _window: &mut Window,
366        _cx: &mut Context<Self>,
367    ) -> TextInputConfiguration {
368        TextInputConfiguration::default()
369    }
370
371    /// Returns the UTF-16 range that the platform may read and edit around the
372    /// selection, or `None` for no limit.
373    ///
374    /// Platforms that mirror text into an IME-editable buffer use this range to
375    /// keep multi-step edits, such as word deletion and autocorrect, inside the
376    /// region you choose. The range should contain the selection. The default is
377    /// `None`.
378    fn text_input_editable_range(
379        &mut self,
380        _window: &mut Window,
381        _cx: &mut Context<Self>,
382    ) -> Option<Range<usize>> {
383        None
384    }
385}
386
387/// Connects an [`EntityInputHandler`] view to the platform's text input.
388///
389/// This is the usual way to install an [`EntityInputHandler`]. It implements
390/// [`InputHandler`] by forwarding each call to the view, with the element's
391/// bounds filled in for the methods that need them. Create one during paint and
392/// pass it to [`Window::handle_input`](crate::Window::handle_input). The handler is active for the next
393/// frame only, so install it again on every paint.
394pub struct ElementInputHandler<V> {
395    view: Entity<V>,
396    element_bounds: Bounds<Pixels>,
397}
398
399impl<V: 'static> ElementInputHandler<V> {
400    /// Creates a handler that forwards platform text input to `view`.
401    ///
402    /// Pass the bounds of the element that is painting. They are returned from
403    /// [`InputHandler::element_bounds`] and passed to
404    /// [`EntityInputHandler::bounds_for_range`]. Call this in
405    /// [`Element::paint`][element_paint], then pass the handler to
406    /// [`Window::handle_input`](crate::Window::handle_input). The window installs the handler only while the
407    /// given focus handle is focused.
408    ///
409    /// [element_paint]: crate::Element::paint
410    pub fn new(element_bounds: Bounds<Pixels>, view: Entity<V>) -> Self {
411        ElementInputHandler {
412            view,
413            element_bounds,
414        }
415    }
416}
417
418impl<V: EntityInputHandler> InputHandler for ElementInputHandler<V> {
419    fn selected_text_range(
420        &mut self,
421        ignore_disabled_input: bool,
422        window: &mut Window,
423        cx: &mut App,
424    ) -> Option<UTF16Selection> {
425        self.view.update(cx, |view, cx| {
426            view.selected_text_range(ignore_disabled_input, window, cx)
427        })
428    }
429
430    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>> {
431        self.view
432            .update(cx, |view, cx| view.marked_text_range(window, cx))
433    }
434
435    fn text_for_range(
436        &mut self,
437        range_utf16: Range<usize>,
438        adjusted_range: &mut Option<Range<usize>>,
439        window: &mut Window,
440        cx: &mut App,
441    ) -> Option<String> {
442        self.view.update(cx, |view, cx| {
443            view.text_for_range(range_utf16, adjusted_range, window, cx)
444        })
445    }
446
447    fn replace_text_in_range(
448        &mut self,
449        replacement_range: Option<Range<usize>>,
450        text: &str,
451        window: &mut Window,
452        cx: &mut App,
453    ) {
454        self.view.update(cx, |view, cx| {
455            view.replace_text_in_range(replacement_range, text, window, cx)
456        });
457    }
458
459    fn replace_and_mark_text_in_range(
460        &mut self,
461        range_utf16: Option<Range<usize>>,
462        new_text: &str,
463        new_selected_range: Option<Range<usize>>,
464        window: &mut Window,
465        cx: &mut App,
466    ) {
467        self.view.update(cx, |view, cx| {
468            view.replace_and_mark_text_in_range(
469                range_utf16,
470                new_text,
471                new_selected_range,
472                window,
473                cx,
474            )
475        });
476    }
477
478    fn unmark_text(&mut self, window: &mut Window, cx: &mut App) {
479        self.view
480            .update(cx, |view, cx| view.unmark_text(window, cx));
481    }
482
483    fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
484        self.view
485            .update(cx, |view, cx| view.paste(item, window, cx));
486    }
487
488    fn bounds_for_range(
489        &mut self,
490        range_utf16: Range<usize>,
491        window: &mut Window,
492        cx: &mut App,
493    ) -> Option<Bounds<Pixels>> {
494        self.view.update(cx, |view, cx| {
495            view.bounds_for_range(range_utf16, self.element_bounds, window, cx)
496        })
497    }
498
499    fn selection_bounds_for_range(
500        &mut self,
501        range_utf16: Range<usize>,
502        window: &mut Window,
503        cx: &mut App,
504    ) -> Vec<Bounds<Pixels>> {
505        self.view.update(cx, |view, cx| {
506            view.selection_bounds_for_range(range_utf16, self.element_bounds, window, cx)
507        })
508    }
509
510    fn character_index_for_point(
511        &mut self,
512        point: crate::Point<Pixels>,
513        window: &mut Window,
514        cx: &mut App,
515    ) -> Option<usize> {
516        self.view.update(cx, |view, cx| {
517            view.character_index_for_point(point, window, cx)
518        })
519    }
520
521    fn set_selected_text_range(
522        &mut self,
523        range_utf16: Range<usize>,
524        window: &mut Window,
525        cx: &mut App,
526    ) {
527        self.view.update(cx, |view, cx| {
528            view.set_selected_text_range(range_utf16, window, cx)
529        })
530    }
531
532    fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
533        Some(self.element_bounds)
534    }
535
536    fn text_length_utf16(&mut self, window: &mut Window, cx: &mut App) -> Option<usize> {
537        self.view
538            .update(cx, |view, cx| view.text_length_utf16(window, cx))
539    }
540
541    fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
542        self.view
543            .update(cx, |view, cx| view.accepts_text_input(window, cx))
544    }
545
546    fn prefers_ime_for_printable_keys(&mut self, window: &mut Window, cx: &mut App) -> bool {
547        self.view
548            .update(cx, |view, cx| view.accepts_text_input(window, cx))
549    }
550
551    fn text_input_configuration(
552        &mut self,
553        window: &mut Window,
554        cx: &mut App,
555    ) -> TextInputConfiguration {
556        self.view
557            .update(cx, |view, cx| view.text_input_configuration(window, cx))
558    }
559
560    fn text_input_editable_range(
561        &mut self,
562        window: &mut Window,
563        cx: &mut App,
564    ) -> Option<Range<usize>> {
565        self.view
566            .update(cx, |view, cx| view.text_input_editable_range(window, cx))
567    }
568}
569
570#[cfg(test)]
571mod configuration_tests {
572    use super::*;
573    use crate::{
574        AnyWindowHandle, AppContext as _, FocusHandle, InteractiveElement as _, IntoElement,
575        ParentElement as _, Render, Styled as _, TestAppContext, TextInputAction,
576        TextInputStateChange, canvas, div,
577    };
578
579    #[gpui::test]
580    fn text_input_configuration_and_focus_state_are_forwarded_on_change(cx: &mut TestAppContext) {
581        let custom = TextInputConfiguration {
582            autocorrect: true,
583            input_action: TextInputAction::Send,
584            ..Default::default()
585        };
586        let window = cx.add_window({
587            let custom = custom.clone();
588            move |_, cx| ConfigurationTestView {
589                focus_handle: cx.focus_handle(),
590                configuration: custom,
591                accepts_text_input: true,
592            }
593        });
594        let view = window.root(cx).unwrap();
595        let test_window = cx.test_window(window.into());
596        let window = AnyWindowHandle::from(window);
597        let draw = |cx: &mut TestAppContext| {
598            cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
599                .unwrap();
600        };
601
602        // Nothing is focused, so the platform learns the default configuration.
603        draw(cx);
604        assert_eq!(
605            test_window.text_input_configurations(),
606            vec![TextInputConfiguration::default()]
607        );
608        assert!(test_window.text_input_state_changes().is_empty());
609
610        // Focusing the view routes its configuration to the platform.
611        cx.update_window(window, |_, window, cx| {
612            let focus_handle = view.read(cx).focus_handle.clone();
613            window.focus(&focus_handle, cx);
614        })
615        .unwrap();
616        draw(cx);
617        assert_eq!(
618            test_window.text_input_configurations(),
619            vec![TextInputConfiguration::default(), custom.clone()]
620        );
621        assert_eq!(
622            test_window.text_input_state_changes(),
623            vec![TextInputStateChange::FocusGained]
624        );
625
626        // Redrawing without a change forwards nothing.
627        draw(cx);
628        assert_eq!(test_window.text_input_configurations().len(), 2);
629        assert_eq!(test_window.text_input_state_changes().len(), 1);
630
631        // Changing the configuration forwards the new value.
632        let updated = TextInputConfiguration {
633            suggestions: true,
634            ..custom
635        };
636        view.update(cx, {
637            let updated = updated.clone();
638            |view, cx| {
639                view.configuration = updated;
640                cx.notify();
641            }
642        });
643        draw(cx);
644        assert_eq!(
645            test_window.text_input_configurations().last(),
646            Some(&updated)
647        );
648        assert_eq!(test_window.text_input_configurations().len(), 3);
649        assert_eq!(test_window.text_input_state_changes().len(), 1);
650
651        // Losing focus reverts the platform to the default configuration.
652        cx.update_window(window, |_, window, cx| window.blur(cx))
653            .unwrap();
654        draw(cx);
655        assert_eq!(
656            test_window.text_input_configurations().last(),
657            Some(&TextInputConfiguration::default())
658        );
659        assert_eq!(test_window.text_input_configurations().len(), 4);
660        assert_eq!(
661            test_window.text_input_state_changes(),
662            vec![
663                TextInputStateChange::FocusGained,
664                TextInputStateChange::FocusLost
665            ]
666        );
667    }
668
669    #[gpui::test]
670    fn text_notifications_require_a_focused_input_and_track_field_changes(cx: &mut TestAppContext) {
671        let window = cx.add_window(|_, cx| ConfigurationTestView {
672            focus_handle: cx.focus_handle(),
673            configuration: TextInputConfiguration::default(),
674            accepts_text_input: true,
675        });
676        let view = window.root(cx).unwrap();
677        let mut test_window = cx.test_window(window.into());
678        let window = AnyWindowHandle::from(window);
679        let first_focus = view.read_with(cx, |view, _| view.focus_handle.clone());
680        cx.update_window(window, |_, window, cx| {
681            window.focus(&first_focus, cx);
682            window.draw(cx).clear(cx);
683            window.notify_text_input_changed(&first_focus);
684            window.notify_text_selection_changed(&first_focus);
685        })
686        .unwrap();
687
688        use crate::PlatformWindow as _;
689        let mut installed_handler = test_window.take_input_handler().unwrap();
690        assert!(installed_handler.query_accepts_focused_text_input());
691        assert_eq!(installed_handler.focus_id(), Some(first_focus.id));
692        let second_focus = cx
693            .update_window(window, |_, window, cx| {
694                let second_focus = view.update(cx, |view, cx| {
695                    view.focus_handle = cx.focus_handle();
696                    cx.notify();
697                    view.focus_handle.clone()
698                });
699                window.focus(&second_focus, cx);
700                second_focus
701            })
702            .unwrap();
703        assert!(!installed_handler.query_accepts_focused_text_input());
704        test_window.set_input_handler(installed_handler);
705        cx.update_window(window, |_, window, cx| {
706            window.draw(cx).clear(cx);
707            window.notify_text_input_changed(&first_focus);
708        })
709        .unwrap();
710        assert_eq!(
711            test_window.text_input_state_changes(),
712            [
713                TextInputStateChange::FocusGained,
714                TextInputStateChange::ContentChanged,
715                TextInputStateChange::SelectionChanged,
716            ]
717        );
718
719        let installed_handler = test_window.take_input_handler().unwrap();
720        assert_eq!(installed_handler.focus_id(), Some(second_focus.id));
721        test_window.set_input_handler(installed_handler);
722        cx.update_window(window, |_, window, cx| {
723            window.draw(cx).clear(cx);
724            window.notify_text_selection_changed(&second_focus);
725        })
726        .unwrap();
727
728        view.update(cx, |view, cx| {
729            view.accepts_text_input = false;
730            cx.notify();
731        });
732        cx.update_window(window, |_, window, cx| {
733            window.draw(cx).clear(cx);
734            window.notify_text_input_changed(&second_focus);
735            window.notify_text_selection_changed(&second_focus);
736            let ordinary_focus = cx.focus_handle();
737            window.focus(&ordinary_focus, cx);
738            window.draw(cx).clear(cx);
739            window.notify_text_input_changed(&ordinary_focus);
740        })
741        .unwrap();
742        assert_eq!(
743            test_window.text_input_state_changes(),
744            [
745                TextInputStateChange::FocusGained,
746                TextInputStateChange::ContentChanged,
747                TextInputStateChange::SelectionChanged,
748                TextInputStateChange::SelectionChanged,
749                TextInputStateChange::FocusLost,
750            ]
751        );
752    }
753
754    struct ConfigurationTestView {
755        focus_handle: FocusHandle,
756        configuration: TextInputConfiguration,
757        accepts_text_input: bool,
758    }
759
760    impl Render for ConfigurationTestView {
761        fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
762            let view = cx.entity();
763            let focus_handle = self.focus_handle.clone();
764            div().size_full().track_focus(&self.focus_handle).child(
765                canvas(
766                    |_, _, _| {},
767                    move |bounds, _, window, cx| {
768                        window.handle_input(
769                            &focus_handle,
770                            ElementInputHandler::new(bounds, view),
771                            cx,
772                        );
773                    },
774                )
775                .size_full(),
776            )
777        }
778    }
779
780    impl EntityInputHandler for ConfigurationTestView {
781        fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
782            self.accepts_text_input
783        }
784
785        fn text_for_range(
786            &mut self,
787            _range: std::ops::Range<usize>,
788            _adjusted_range: &mut Option<std::ops::Range<usize>>,
789            _window: &mut Window,
790            _cx: &mut Context<Self>,
791        ) -> Option<String> {
792            None
793        }
794
795        fn selected_text_range(
796            &mut self,
797            _ignore_disabled_input: bool,
798            _window: &mut Window,
799            _cx: &mut Context<Self>,
800        ) -> Option<UTF16Selection> {
801            None
802        }
803
804        fn marked_text_range(
805            &self,
806            _window: &mut Window,
807            _cx: &mut Context<Self>,
808        ) -> Option<std::ops::Range<usize>> {
809            None
810        }
811
812        fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
813
814        fn replace_text_in_range(
815            &mut self,
816            _range: Option<std::ops::Range<usize>>,
817            _text: &str,
818            _window: &mut Window,
819            _cx: &mut Context<Self>,
820        ) {
821        }
822
823        fn replace_and_mark_text_in_range(
824            &mut self,
825            _range: Option<std::ops::Range<usize>>,
826            _new_text: &str,
827            _new_selected_range: Option<std::ops::Range<usize>>,
828            _window: &mut Window,
829            _cx: &mut Context<Self>,
830        ) {
831        }
832
833        fn bounds_for_range(
834            &mut self,
835            _range_utf16: std::ops::Range<usize>,
836            _element_bounds: Bounds<Pixels>,
837            _window: &mut Window,
838            _cx: &mut Context<Self>,
839        ) -> Option<Bounds<Pixels>> {
840            None
841        }
842
843        fn character_index_for_point(
844            &mut self,
845            _point: crate::Point<Pixels>,
846            _window: &mut Window,
847            _cx: &mut Context<Self>,
848        ) -> Option<usize> {
849            None
850        }
851
852        fn text_input_configuration(
853            &mut self,
854            _window: &mut Window,
855            _cx: &mut Context<Self>,
856        ) -> TextInputConfiguration {
857            self.configuration.clone()
858        }
859    }
860}
861
862#[cfg(test)]
863mod tests {
864    use super::*;
865
866    #[test]
867    fn text_offsets_convert_across_unicode_encodings() {
868        let cases = [
869            ("", vec![(0, 0)]),
870            ("plain", vec![(0, 0), (1, 1), (5, 5)]),
871            (
872                "A😀日本e\u{301}",
873                vec![(0, 0), (1, 1), (5, 3), (8, 4), (11, 5), (12, 6), (14, 7)],
874            ),
875        ];
876
877        for (text, boundaries) in cases {
878            for (utf8, utf16) in boundaries {
879                assert_eq!(utf8_to_utf16_offset(text, utf8), utf16, "{text:?}");
880                assert_eq!(utf16_to_utf8_offset(text, utf16), utf8, "{text:?}");
881            }
882
883            assert_eq!(
884                utf8_to_utf16_offset(text, usize::MAX),
885                text.encode_utf16().count()
886            );
887            assert_eq!(utf16_to_utf8_offset(text, usize::MAX), text.len());
888        }
889
890        let text = "A😀B";
891        assert_eq!(utf16_to_utf8_offset(text, 2), 5);
892        assert_eq!(utf8_to_utf16_offset(text, 2), 3);
893    }
894}