1use std::rc::Rc;
4
5use inset_embedder::{TargetPlatform, TextAffinity, TextRange, TextSelection};
6
7use crate::TextEditingValue;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub enum MaxLengthEnforcement {
15 None,
18
19 Enforced,
22
23 TruncateAfterCompositionEnds,
27}
28
29pub trait TextInputFormatter {
32 fn format_edit_update(
34 &self,
35 old_value: &TextEditingValue,
36 new_value: &TextEditingValue,
37 ) -> TextEditingValue;
38}
39
40pub type TextInputFormatFunction =
43 Rc<dyn Fn(&TextEditingValue, &TextEditingValue) -> TextEditingValue>;
44
45#[derive(Clone)]
47pub struct TextInputFormatterRef(pub Rc<dyn TextInputFormatter>);
48
49impl TextInputFormatterRef {
50 pub fn with_function(format_function: TextInputFormatFunction) -> TextInputFormatterRef {
53 TextInputFormatterRef(Rc::new(SimpleTextInputFormatter { format_function }))
54 }
55}
56
57impl TextInputFormatter for TextInputFormatterRef {
58 fn format_edit_update(
59 &self,
60 old_value: &TextEditingValue,
61 new_value: &TextEditingValue,
62 ) -> TextEditingValue {
63 self.0.format_edit_update(old_value, new_value)
64 }
65}
66
67struct SimpleTextInputFormatter {
68 format_function: TextInputFormatFunction,
69}
70
71impl TextInputFormatter for SimpleTextInputFormatter {
72 fn format_edit_update(
73 &self,
74 old_value: &TextEditingValue,
75 new_value: &TextEditingValue,
76 ) -> TextEditingValue {
77 (self.format_function)(old_value, new_value)
78 }
79}
80
81struct MutableTextRange {
83 base: i32,
84 extent: i32,
85}
86
87impl MutableTextRange {
88 fn from_composing_range(range: TextRange) -> Option<MutableTextRange> {
89 if range.is_valid() && !range.is_collapsed() {
90 Some(MutableTextRange {
91 base: range.start,
92 extent: range.end,
93 })
94 } else {
95 None
96 }
97 }
98
99 fn from_text_selection(selection: TextSelection) -> Option<MutableTextRange> {
100 if selection.is_valid() {
101 Some(MutableTextRange {
102 base: selection.base_offset,
103 extent: selection.extent_offset,
104 })
105 } else {
106 None
107 }
108 }
109}
110
111struct TextEditingValueAccumulator {
114 input_value: TextEditingValue,
115 string_buffer: String,
116 selection: Option<MutableTextRange>,
117 composing_region: Option<MutableTextRange>,
118}
119
120impl TextEditingValueAccumulator {
121 fn new(input_value: TextEditingValue) -> TextEditingValueAccumulator {
122 let selection = MutableTextRange::from_text_selection(input_value.selection);
123 let composing_region = MutableTextRange::from_composing_range(input_value.composing);
124 TextEditingValueAccumulator {
125 input_value,
126 string_buffer: String::new(),
127 selection,
128 composing_region,
129 }
130 }
131
132 fn finalize(self) -> TextEditingValue {
133 let selection = self.selection;
134 let composing_region = self.composing_region;
135 TextEditingValue::new()
136 .text(self.string_buffer)
137 .composing(match &composing_region {
138 None => TextRange::EMPTY,
139 Some(range) if range.base == range.extent => TextRange::EMPTY,
140 Some(range) => TextRange::new(range.base, range.extent),
141 })
142 .selection(match selection {
143 None => TextSelection::collapsed(-1, TextAffinity::Downstream),
144 Some(range) => TextSelection {
145 base_offset: range.base,
146 extent_offset: range.extent,
147 affinity: self.input_value.selection.affinity,
148 is_directional: self.input_value.selection.is_directional,
149 },
150 })
151 }
152}
153
154#[derive(Clone, Debug)]
159pub enum FilterPattern {
160 Literal(String),
162 Digits,
164}
165
166impl FilterPattern {
167 fn all_matches(&self, text: &str) -> Vec<(i32, i32)> {
168 match self {
169 FilterPattern::Literal(needle) => {
170 if needle.is_empty() {
171 return Vec::new();
172 }
173 let mut matches = Vec::new();
174 let mut search_from = 0usize;
175 while let Some(byte) = text[search_from..].find(needle) {
176 let start_byte = search_from + byte;
177 let end_byte = start_byte + needle.len();
178 matches.push((utf16_len(&text[..start_byte]), utf16_len(&text[..end_byte])));
179 search_from = end_byte;
180 }
181 matches
182 }
183 FilterPattern::Digits => {
184 let mut matches = Vec::new();
185 let mut utf16 = 0i32;
186 for ch in text.chars() {
187 let width = ch.len_utf16() as i32;
188 if ch.is_ascii_digit() {
189 matches.push((utf16, utf16 + width));
190 }
191 utf16 += width;
192 }
193 matches
194 }
195 }
196 }
197}
198
199#[derive(Clone, Debug)]
203pub struct FilteringTextInputFormatter {
204 pub filter_pattern: FilterPattern,
206 pub allow: bool,
208 pub replacement_string: String,
210}
211
212impl FilteringTextInputFormatter {
213 pub fn new(filter_pattern: FilterPattern, allow: bool) -> FilteringTextInputFormatter {
216 FilteringTextInputFormatter {
217 filter_pattern,
218 allow,
219 replacement_string: String::new(),
220 }
221 }
222
223 pub fn replacement_string(mut self, replacement_string: impl Into<String>) -> Self {
225 self.replacement_string = replacement_string.into();
226 self
227 }
228
229 pub fn allow(filter_pattern: FilterPattern) -> FilteringTextInputFormatter {
231 FilteringTextInputFormatter::new(filter_pattern, true)
232 }
233
234 pub fn deny(filter_pattern: FilterPattern) -> FilteringTextInputFormatter {
236 FilteringTextInputFormatter::new(filter_pattern, false)
237 }
238
239 pub fn single_line_formatter() -> TextInputFormatterRef {
241 TextInputFormatterRef(Rc::new(FilteringTextInputFormatter::deny(
242 FilterPattern::Literal("\n".into()),
243 )))
244 }
245
246 pub fn digits_only() -> TextInputFormatterRef {
248 TextInputFormatterRef(Rc::new(FilteringTextInputFormatter::allow(
249 FilterPattern::Digits,
250 )))
251 }
252
253 fn process_region(
254 &self,
255 is_banned_region: bool,
256 region_start: i32,
257 region_end: i32,
258 state: &mut TextEditingValueAccumulator,
259 ) {
260 let replacement_string = if is_banned_region {
261 if region_start == region_end {
262 String::new()
263 } else {
264 self.replacement_string.clone()
265 }
266 } else {
267 utf16_slice(&state.input_value.text, region_start, region_end).to_owned()
268 };
269
270 let region_len = region_end - region_start;
271 let replacement_len = utf16_len(&replacement_string);
272 state.string_buffer.push_str(&replacement_string);
273
274 if replacement_len == region_len {
275 return;
276 }
277
278 let adjust_index = |original_index: i32| -> i32 {
279 let replaced_length = if original_index <= region_start && original_index < region_end {
280 0
281 } else {
282 replacement_len
283 };
284 let removed_length = original_index.clamp(region_start, region_end) - region_start;
285 replaced_length - removed_length
286 };
287
288 if let Some(selection) = state.selection.as_mut() {
289 selection.base += adjust_index(state.input_value.selection.base_offset);
290 selection.extent += adjust_index(state.input_value.selection.extent_offset);
291 }
292 if let Some(composing) = state.composing_region.as_mut() {
293 composing.base += adjust_index(state.input_value.composing.start);
294 composing.extent += adjust_index(state.input_value.composing.end);
295 }
296 }
297}
298
299impl TextInputFormatter for FilteringTextInputFormatter {
300 fn format_edit_update(
301 &self,
302 _old_value: &TextEditingValue,
303 new_value: &TextEditingValue,
304 ) -> TextEditingValue {
305 let mut format_state = TextEditingValueAccumulator::new(new_value.clone());
306 let matches = self.filter_pattern.all_matches(&new_value.text);
307 let mut previous_end = 0i32;
308 for (start, end) in matches {
309 self.process_region(self.allow, previous_end, start, &mut format_state);
310 self.process_region(!self.allow, start, end, &mut format_state);
311 previous_end = end;
312 }
313 self.process_region(
314 self.allow,
315 previous_end,
316 utf16_len(&new_value.text),
317 &mut format_state,
318 );
319 format_state.finalize()
320 }
321}
322
323pub struct LengthLimitingTextInputFormatter {
326 pub max_length: Option<i32>,
329 pub max_length_enforcement: Option<MaxLengthEnforcement>,
331}
332
333impl LengthLimitingTextInputFormatter {
334 pub fn new(max_length: Option<i32>) -> LengthLimitingTextInputFormatter {
337 debug_assert!(
338 max_length.is_none_or(|n| n == -1 || n > 0),
339 "maxLength must be null, -1 or greater than zero"
340 );
341 LengthLimitingTextInputFormatter {
342 max_length,
343 max_length_enforcement: None,
344 }
345 }
346
347 pub fn max_length_enforcement(mut self, value: MaxLengthEnforcement) -> Self {
349 self.max_length_enforcement = Some(value);
350 self
351 }
352
353 pub fn get_default_max_length_enforcement(
356 platform: Option<TargetPlatform>,
357 ) -> MaxLengthEnforcement {
358 match platform.unwrap_or(TargetPlatform::Android) {
359 TargetPlatform::Android | TargetPlatform::Windows => MaxLengthEnforcement::Enforced,
360 TargetPlatform::IOS
361 | TargetPlatform::MacOS
362 | TargetPlatform::Linux
363 | TargetPlatform::Fuchsia => MaxLengthEnforcement::TruncateAfterCompositionEnds,
364 }
365 }
366
367 pub fn truncate(value: &TextEditingValue, max_length: i32) -> TextEditingValue {
370 let truncated: String = value.text.chars().take(max_length as usize).collect();
371 let truncated_len = utf16_len(&truncated);
372 TextEditingValue::new()
373 .text(truncated)
374 .selection(
375 value
376 .selection
377 .copy_with()
378 .base_offset(value.selection.start().min(truncated_len))
379 .extent_offset(value.selection.end().min(truncated_len)),
380 )
381 .composing(
382 if !value.composing.is_collapsed() && truncated_len > value.composing.start {
383 TextRange::new(
384 value.composing.start,
385 value.composing.end.min(truncated_len),
386 )
387 } else {
388 TextRange::EMPTY
389 },
390 )
391 }
392
393 fn character_count(text: &str) -> i32 {
394 text.chars().count() as i32
395 }
396}
397
398impl TextInputFormatter for LengthLimitingTextInputFormatter {
399 fn format_edit_update(
400 &self,
401 old_value: &TextEditingValue,
402 new_value: &TextEditingValue,
403 ) -> TextEditingValue {
404 let Some(max_length) = self.max_length else {
405 return new_value.clone();
406 };
407 if max_length == -1 || Self::character_count(&new_value.text) <= max_length {
408 return new_value.clone();
409 }
410 debug_assert!(max_length > 0);
411 match self
412 .max_length_enforcement
413 .unwrap_or(Self::get_default_max_length_enforcement(None))
414 {
415 MaxLengthEnforcement::None => new_value.clone(),
416 MaxLengthEnforcement::Enforced => {
417 if Self::character_count(&old_value.text) == max_length
418 && old_value.selection.is_collapsed()
419 {
420 return old_value.clone();
421 }
422 Self::truncate(new_value, max_length)
423 }
424 MaxLengthEnforcement::TruncateAfterCompositionEnds => {
425 if Self::character_count(&old_value.text) == max_length
426 && !old_value.composing.is_valid()
427 {
428 return old_value.clone();
429 }
430 if new_value.composing.is_valid() {
431 return new_value.clone();
432 }
433 Self::truncate(new_value, max_length)
434 }
435 }
436 }
437}
438
439fn utf16_len(text: &str) -> i32 {
440 text.encode_utf16().count() as i32
441}
442
443fn utf16_slice(text: &str, start: i32, end: i32) -> &str {
444 let start = start.max(0) as usize;
445 let end = end.max(0) as usize;
446 let mut byte_start = None;
447 let mut byte_end = None;
448 let mut units = 0usize;
449 for (i, ch) in text.char_indices() {
450 if byte_start.is_none() && units == start {
451 byte_start = Some(i);
452 }
453 if units == end {
454 byte_end = Some(i);
455 break;
456 }
457 units += ch.len_utf16();
458 }
459 let byte_start = byte_start.unwrap_or(text.len());
460 let byte_end = byte_end.unwrap_or(text.len());
461 &text[byte_start.min(byte_end)..byte_end]
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 #[test]
469 fn single_line_formatter_strips_newlines() {
470 let formatter = FilteringTextInputFormatter::single_line_formatter();
471 let old = TextEditingValue::EMPTY;
472 let new = TextEditingValue::new()
473 .text("a\nb")
474 .selection(TextSelection::collapsed(3, TextAffinity::Downstream));
475 let out = formatter.format_edit_update(&old, &new);
476 assert_eq!(out.text, "ab");
477 assert_eq!(out.selection.extent_offset, 2);
478 }
479
480 #[test]
481 fn digits_only_keeps_digits() {
482 let formatter = FilteringTextInputFormatter::digits_only();
483 let old = TextEditingValue::EMPTY;
484 let new = TextEditingValue::new().text("a1b2");
485 let out = formatter.format_edit_update(&old, &new);
486 assert_eq!(out.text, "12");
487 }
488}