| Flutter iOS Embedder |

text_input_model.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "flutter/shell/platform/common/text_input_model.h"
6
7 #include <algorithm>
8 #include <string>
9
10 #include "flutter/fml/string_conversion.h"
11
12 namespace flutter {
13
14 namespace {
15
16 // Returns true if |code_point| is a leading surrogate of a surrogate pair.
17 bool IsLeadingSurrogate(char32_t code_point) {
18 return (code_point & 0xFFFFFC00) == 0xD800;
19 }
20 // Returns true if |code_point| is a trailing surrogate of a surrogate pair.
21 bool IsTrailingSurrogate(char32_t code_point) {
22 return (code_point & 0xFFFFFC00) == 0xDC00;
23 }
24
25 } // namespace
26
27 TextInputModel::TextInputModel() = default;
28
29 TextInputModel::~TextInputModel() = default;
30
31 bool TextInputModel::SetText(const std::string& text,
32 const TextRange& selection,
33 const TextRange& composing_range) {
34 text_ = fml::Utf8ToUtf16(text);
35 if (!text_range().Contains(selection) ||
36 !text_range().Contains(composing_range)) {
37 return false;
38 }
39
40 selection_ = selection;
41 composing_range_ = composing_range;
42 composing_ = !composing_range.collapsed();
43 return true;
44 }
45
46 bool TextInputModel::SetSelection(const TextRange& range) {
47 if (composing_ && !range.collapsed()) {
48 return false;
49 }
50 if (!editable_range().Contains(range)) {
51 return false;
52 }
53 selection_ = range;
54 return true;
55 }
56
57 bool TextInputModel::SetComposingRange(const TextRange& range,
58 size_t cursor_offset) {
59 if (!composing_ || !text_range().Contains(range)) {
60 return false;
61 }
62 composing_range_ = range;
63 selection_ = TextRange(range.start() + cursor_offset);
64 return true;
65 }
66
67 void TextInputModel::BeginComposing() {
68 composing_ = true;
69 composing_range_ = TextRange(selection_.start());
70 }
71
72 void TextInputModel::UpdateComposingText(const std::u16string& text,
73 const TextRange& selection) {
74 // Preserve selection if we get a no-op update to the composing region.
75 if (text.length() == 0 && composing_range_.collapsed()) {
76 return;
77 }
78 const TextRange& rangeToDelete =
79 composing_range_.collapsed() ? selection_ : composing_range_;
80 text_.replace(rangeToDelete.start(), rangeToDelete.length(), text);
81 composing_range_.set_end(composing_range_.start() + text.length());
82 selection_ = TextRange(selection.start() + composing_range_.start(),
83 selection.extent() + composing_range_.start());
84 }
85
86 void TextInputModel::UpdateComposingText(const std::u16string& text) {
87 UpdateComposingText(text, TextRange(text.length()));
88 }
89
90 void TextInputModel::UpdateComposingText(const std::string& text) {
91 UpdateComposingText(fml::Utf8ToUtf16(text));
92 }
93
94 void TextInputModel::CommitComposing() {
95 // Preserve selection if no composing text was entered.
96 if (composing_range_.collapsed()) {
97 return;
98 }
99 composing_range_ = TextRange(composing_range_.end());
100 selection_ = composing_range_;
101 }
102
103 void TextInputModel::EndComposing() {
104 composing_ = false;
105 composing_range_ = TextRange(0);
106 }
107
108 bool TextInputModel::DeleteSelected() {
109 if (selection_.collapsed()) {
110 return false;
111 }
112 size_t start = selection_.start();
113 text_.erase(start, selection_.length());
114 selection_ = TextRange(start);
115 if (composing_) {
116 // This occurs only immediately after composing has begun with a selection.
117 composing_range_ = selection_;
118 }
119 return true;
120 }
121
122 void TextInputModel::AddCodePoint(char32_t c) {
123 if (c <= 0xFFFF) {
124 AddText(std::u16string({static_cast<char16_t>(c)}));
125 } else {
126 char32_t to_decompose = c - 0x10000;
127 AddText(std::u16string({
128 // High surrogate.
129 static_cast<char16_t>((to_decompose >> 10) + 0xd800),
130 // Low surrogate.
131 static_cast<char16_t>((to_decompose % 0x400) + 0xdc00),
132 }));
133 }
134 }
135
136 void TextInputModel::AddText(const std::u16string& text) {
137 DeleteSelected();
138 if (composing_) {
139 // Delete the current composing text, set the cursor to composing start.
140 text_.erase(composing_range_.start(), composing_range_.length());
141 selection_ = TextRange(composing_range_.start());
142 composing_range_.set_end(composing_range_.start() + text.length());
143 }
144 size_t position = selection_.position();
145 text_.insert(position, text);
146 selection_ = TextRange(position + text.length());
147 }
148
149 void TextInputModel::AddText(const std::string& text) {
150 AddText(fml::Utf8ToUtf16(text));
151 }
152
153 bool TextInputModel::Backspace() {
154 if (DeleteSelected()) {
155 return true;
156 }
157 // There is no selection. Delete the preceding codepoint.
158 size_t position = selection_.position();
159 if (position != editable_range().start()) {
160 int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1;
161 text_.erase(position - count, count);
162 selection_ = TextRange(position - count);
163 if (composing_) {
164 composing_range_.set_end(composing_range_.end() - count);
165 }
166 return true;
167 }
168 return false;
169 }
170
171 bool TextInputModel::Delete() {
172 if (DeleteSelected()) {
173 return true;
174 }
175 // There is no selection. Delete the preceding codepoint.
176 size_t position = selection_.position();
177 if (position < editable_range().end()) {
178 int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1;
179 text_.erase(position, count);
180 if (composing_) {
181 composing_range_.set_end(composing_range_.end() - count);
182 }
183 return true;
184 }
185 return false;
186 }
187
188 bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) {
189 size_t max_pos = editable_range().end();
190 size_t start = selection_.extent();
191 if (offset_from_cursor < 0) {
192 for (int i = 0; i < -offset_from_cursor; i++) {
193 // If requested start is before the available text then reduce the
194 // number of characters to delete.
195 if (start == editable_range().start()) {
196 count = i;
197 break;
198 }
199 start -= IsTrailingSurrogate(text_.at(start - 1)) ? 2 : 1;
200 }
201 } else {
202 for (int i = 0; i < offset_from_cursor && start != max_pos; i++) {
203 start += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;
204 }
205 }
206
207 auto end = start;
208 for (int i = 0; i < count && end != max_pos; i++) {
209 end += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;
210 }
211
212 if (start == end) {
213 return false;
214 }
215
216 auto deleted_length = end - start;
217 text_.erase(start, deleted_length);
218
219 // Cursor moves only if deleted area is before it.
220 selection_ = TextRange(offset_from_cursor <= 0 ? start : selection_.start());
221
222 // Adjust composing range.
223 if (composing_) {
224 composing_range_.set_end(composing_range_.end() - deleted_length);
225 }
226 return true;
227 }
228
229 bool TextInputModel::MoveCursorToBeginning() {
230 size_t min_pos = editable_range().start();
231 if (selection_.collapsed() && selection_.position() == min_pos) {
232 return false;
233 }
234 selection_ = TextRange(min_pos);
235 return true;
236 }
237
238 bool TextInputModel::MoveCursorToEnd() {
239 size_t max_pos = editable_range().end();
240 if (selection_.collapsed() && selection_.position() == max_pos) {
241 return false;
242 }
243 selection_ = TextRange(max_pos);
244 return true;
245 }
246
247 bool TextInputModel::SelectToBeginning() {
248 size_t min_pos = editable_range().start();
249 if (selection_.collapsed() && selection_.position() == min_pos) {
250 return false;
251 }
252 selection_ = TextRange(selection_.base(), min_pos);
253 return true;
254 }
255
256 bool TextInputModel::SelectToEnd() {
257 size_t max_pos = editable_range().end();
258 if (selection_.collapsed() && selection_.position() == max_pos) {
259 return false;
260 }
261 selection_ = TextRange(selection_.base(), max_pos);
262 return true;
263 }
264
265 bool TextInputModel::MoveCursorForward() {
266 // If there's a selection, move to the end of the selection.
267 if (!selection_.collapsed()) {
268 selection_ = TextRange(selection_.end());
269 return true;
270 }
271 // Otherwise, move the cursor forward.
272 size_t position = selection_.position();
273 if (position != editable_range().end()) {
274 int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1;
275 selection_ = TextRange(position + count);
276 return true;
277 }
278 return false;
279 }
280
281 bool TextInputModel::MoveCursorBack() {
282 // If there's a selection, move to the beginning of the selection.
283 if (!selection_.collapsed()) {
284 selection_ = TextRange(selection_.start());
285 return true;
286 }
287 // Otherwise, move the cursor backward.
288 size_t position = selection_.position();
289 if (position != editable_range().start()) {
290 int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1;
291 selection_ = TextRange(position - count);
292 return true;
293 }
294 return false;
295 }
296
297 std::string TextInputModel::GetText() const {
298 return fml::Utf16ToUtf8(text_);
299 }
300
301 int TextInputModel::GetCursorOffset() const {
302 // Measure the length of the current text up to the selection extent.
303 // There is probably a much more efficient way of doing this.
304 auto leading_text = text_.substr(0, selection_.extent());
305 return fml::Utf16ToUtf8(leading_text).size();
306 }
307
308 } // namespace flutter
flutter::TextInputModel::MoveCursorForward
bool MoveCursorForward()
Definition: text_input_model.cc:265
flutter::TextInputModel::selection
TextRange selection() const
Definition: text_input_model.h:201
flutter::TextInputModel::SelectToBeginning
bool SelectToBeginning()
Definition: text_input_model.cc:247
flutter::TextInputModel::MoveCursorToBeginning
bool MoveCursorToBeginning()
Definition: text_input_model.cc:229
flutter::TextInputModel::GetText
std::string GetText() const
Definition: text_input_model.cc:297
flutter::TextInputModel::DeleteSurrounding
bool DeleteSurrounding(int offset_from_cursor, int count)
Definition: text_input_model.cc:188
flutter::TextInputModel::CommitComposing
void CommitComposing()
Definition: text_input_model.cc:94
flutter::TextInputModel::~TextInputModel
virtual ~TextInputModel()
flutter::TextInputModel::AddText
void AddText(const std::u16string &text)
Definition: text_input_model.cc:136
flutter::TextInputModel::MoveCursorBack
bool MoveCursorBack()
Definition: text_input_model.cc:281
flutter::TextInputModel::AddCodePoint
void AddCodePoint(char32_t c)
Definition: text_input_model.cc:122
flutter::TextInputModel::MoveCursorToEnd
bool MoveCursorToEnd()
Definition: text_input_model.cc:238
flutter::TextInputModel::SetComposingRange
bool SetComposingRange(const TextRange &range, size_t cursor_offset)
Definition: text_input_model.cc:57
flutter::TextInputModel::GetCursorOffset
int GetCursorOffset() const
Definition: text_input_model.cc:301
flutter::TextInputModel::BeginComposing
void BeginComposing()
Definition: text_input_model.cc:67
flutter::TextInputModel::Backspace
bool Backspace()
Definition: text_input_model.cc:153
flutter::TextInputModel::text_range
TextRange text_range() const
Definition: text_input_model.h:198
flutter::TextInputModel::composing_range
TextRange composing_range() const
Definition: text_input_model.h:206
flutter::TextInputModel::Delete
bool Delete()
Definition: text_input_model.cc:171
flutter::TextInputModel::SelectToEnd
bool SelectToEnd()
Definition: text_input_model.cc:256
flutter::TextInputModel::SetSelection
bool SetSelection(const TextRange &range)
Definition: text_input_model.cc:46
flutter::TextInputModel::EndComposing
void EndComposing()
Definition: text_input_model.cc:103
flutter::TextInputModel::SetText
bool SetText(const std::string &text, const TextRange &selection=TextRange(0), const TextRange &composing_range=TextRange(0))
Definition: text_input_model.cc:31
flutter::TextInputModel::UpdateComposingText
void UpdateComposingText(const std::u16string &text, const TextRange &selection)
Definition: text_input_model.cc:72
flutter::TextInputModel::TextInputModel
TextInputModel()
flutter::TextRange
Definition: text_range.h:19
flutter::TextRange::set_end
void set_end(size_t pos)
Definition: text_range.h:57
flutter::TextRange::position
size_t position() const
Definition: text_range.h:68
flutter::TextRange::start
size_t start() const
Definition: text_range.h:42
flutter::TextRange::base
size_t base() const
Definition: text_range.h:30
flutter::TextRange::collapsed
bool collapsed() const
Definition: text_range.h:77
flutter::TextRange::length
size_t length() const
Definition: text_range.h:74
flutter::TextRange::extent
size_t extent() const
Definition: text_range.h:36
flutter::TextRange::end
size_t end() const
Definition: text_range.h:54
flutter
Definition: accessibility_bridge.h:26
text_input_model.h