Skip to main content

gpui_platform/
input_handler.rs

1//! Platform text input handling shared by `gpui` and its platform backends.
2
3use crate::ClipboardItem;
4use gpui_types::{Bounds, Pixels, Point, px};
5use std::{any::Any, ops::Range};
6
7/// A struct representing a selection in a text buffer, in UTF16 characters.
8/// This is different from a range because the head may be before the tail.
9#[derive(Debug)]
10pub struct UTF16Selection {
11    /// The range of text in the document this selection corresponds to
12    /// in UTF16 characters.
13    pub range: Range<usize>,
14    /// Whether the head of this selection is at the start (true), or end (false)
15    /// of the range
16    pub reversed: bool,
17}
18
19/// The gpui-side implementation backing a [`PlatformInputHandler`].
20///
21/// Platform backends only ever call the methods on [`PlatformInputHandler`];
22/// `gpui` provides the implementation, which is where the `Window`/`App`
23/// dependencies live.
24#[expect(missing_docs)]
25pub trait PlatformInputHandlerDelegate: Any {
26    fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection>;
27    fn marked_text_range(&mut self) -> Option<Range<usize>>;
28    fn text_for_range(
29        &mut self,
30        range_utf16: Range<usize>,
31        adjusted: &mut Option<Range<usize>>,
32    ) -> Option<String>;
33    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
34    fn replace_and_mark_text_in_range(
35        &mut self,
36        range_utf16: Option<Range<usize>>,
37        new_text: &str,
38        new_selected_range: Option<Range<usize>>,
39    );
40    fn unmark_text(&mut self);
41    fn paste(&mut self, item: ClipboardItem);
42    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
43    fn apple_press_and_hold_enabled(&mut self) -> bool;
44    fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize>;
45    fn set_selected_text_range(&mut self, range_utf16: Range<usize>);
46    fn element_bounds(&mut self) -> Option<Bounds<Pixels>>;
47    fn text_length_utf16(&mut self) -> Option<usize>;
48    fn query_accepts_text_input(&mut self) -> bool;
49    fn query_prefers_ime_for_printable_keys(&mut self) -> bool;
50    fn text_input_editable_range(&mut self) -> Option<Range<usize>>;
51    /// Allows `gpui` to recover its concrete delegate for the methods that
52    /// require a `Window` or `App`.
53    fn as_any_mut(&mut self) -> &mut dyn Any;
54}
55
56/// Handles text input from the platform's IME system on behalf of a window.
57///
58/// This is the vocabulary platform backends speak; the behavior is supplied by
59/// a [`PlatformInputHandlerDelegate`].
60#[allow(dead_code)]
61pub struct PlatformInputHandler {
62    delegate: Box<dyn PlatformInputHandlerDelegate>,
63}
64
65#[allow(dead_code)]
66#[expect(missing_docs)]
67impl PlatformInputHandler {
68    /// Creates a handler from the delegate that implements it.
69    pub fn from_delegate(delegate: Box<dyn PlatformInputHandlerDelegate>) -> Self {
70        Self { delegate }
71    }
72
73    /// Exposes the delegate as `Any` so `gpui` can downcast to its own
74    /// implementation for the `Window`/`App` methods.
75    pub fn delegate_as_any_mut(&mut self) -> &mut dyn Any {
76        self.delegate.as_any_mut()
77    }
78
79    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
80        self.delegate.selected_text_range(ignore_disabled_input)
81    }
82
83    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
84        self.delegate.marked_text_range()
85    }
86
87    pub fn text_for_range(
88        &mut self,
89        range_utf16: Range<usize>,
90        adjusted: &mut Option<Range<usize>>,
91    ) -> Option<String> {
92        self.delegate.text_for_range(range_utf16, adjusted)
93    }
94
95    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
96        self.delegate.replace_text_in_range(replacement_range, text);
97    }
98
99    pub fn replace_and_mark_text_in_range(
100        &mut self,
101        range_utf16: Option<Range<usize>>,
102        new_text: &str,
103        new_selected_range: Option<Range<usize>>,
104    ) {
105        self.delegate
106            .replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range);
107    }
108
109    pub fn unmark_text(&mut self) {
110        self.delegate.unmark_text();
111    }
112
113    pub fn paste(&mut self, item: ClipboardItem) {
114        self.delegate.paste(item);
115    }
116
117    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
118        self.delegate.bounds_for_range(range_utf16)
119    }
120
121    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
122        self.delegate.apple_press_and_hold_enabled()
123    }
124
125    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
126        self.delegate.character_index_for_point(point)
127    }
128
129    pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
130        self.delegate.set_selected_text_range(range_utf16);
131    }
132
133    pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
134        self.delegate.element_bounds()
135    }
136
137    pub fn text_length_utf16(&mut self) -> Option<usize> {
138        self.delegate.text_length_utf16()
139    }
140
141    pub fn query_accepts_text_input(&mut self) -> bool {
142        self.delegate.query_accepts_text_input()
143    }
144
145    pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
146        self.delegate.query_prefers_ime_for_printable_keys()
147    }
148
149    pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
150        self.delegate.text_input_editable_range()
151    }
152
153    pub fn compute_ime_candidate_bounds(
154        marked_range: Option<Range<usize>>,
155        selection: &UTF16Selection,
156        mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
157    ) -> Option<Bounds<Pixels>> {
158        if let Some(marked_range) = marked_range {
159            // Default to the start of the marked (composing) range.
160            let mut line_start = marked_range.start;
161
162            // Walk backward from the caret looking for a line break. A change in
163            // the Y coordinate means we crossed into the previous visual line, so
164            // the line start is one position after the break point.
165            let caret = selection.range.end;
166            if let Some(caret_bounds) = bounds_for_range(caret..caret) {
167                for i in (marked_range.start..caret).rev() {
168                    if let Some(b) = bounds_for_range(i..i) {
169                        if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
170                            line_start = i + 1;
171                            break;
172                        }
173                    }
174                }
175            }
176            bounds_for_range(line_start..line_start)
177        } else {
178            // No active composition — use the selection endpoint.
179            let offset = if selection.reversed {
180                selection.range.start
181            } else {
182                selection.range.end
183            };
184            bounds_for_range(offset..offset)
185        }
186    }
187
188    pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
189        let marked_range = self.marked_text_range();
190        let selection = self.selected_text_range(true)?;
191        Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
192            self.bounds_for_range(range)
193        })
194    }
195}