| Flutter macOS 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 // Preserve selection if we get a no-op update to the composing region.
74 if (text.length() == 0 && composing_range_.collapsed()) {
75 return;
76 }
77 DeleteSelected();
78 text_.replace(composing_range_.start(), composing_range_.length(), text);
79 composing_range_.set_end(composing_range_.start() + text.length());
80 selection_ = TextRange(composing_range_.end());
81 }
82
83 void TextInputModel::UpdateComposingText(const std::string& text) {
84 UpdateComposingText(fml::Utf8ToUtf16(text));
85 }
86
87 void TextInputModel::CommitComposing() {
88 // Preserve selection if no composing text was entered.
89 if (composing_range_.collapsed()) {
90 return;
91 }
92 composing_range_ = TextRange(composing_range_.end());
93 selection_ = composing_range_;
94 }
95
96 void TextInputModel::EndComposing() {
97 composing_ = false;
98 composing_range_ = TextRange(0);
99 }
100
101 bool TextInputModel::DeleteSelected() {
102 if (selection_.collapsed()) {
103 return false;
104 }
105 size_t start = selection_.start();
106 text_.erase(start, selection_.length());
107 selection_ = TextRange(start);
108 if (composing_) {
109 // This occurs only immediately after composing has begun with a selection.
110 composing_range_ = selection_;
111 }
112 return true;
113 }
114
115 void TextInputModel::AddCodePoint(char32_t c) {
116 if (c <= 0xFFFF) {
117 AddText(std::u16string({static_cast<char16_t>(c)}));
118 } else {
119 char32_t to_decompose = c - 0x10000;
120 AddText(std::u16string({
121 // High surrogate.
122 static_cast<char16_t>((to_decompose >> 10) + 0xd800),
123 // Low surrogate.
124 static_cast<char16_t>((to_decompose % 0x400) + 0xdc00),
125 }));
126 }
127 }
128
129 void TextInputModel::AddText(const std::u16string& text) {
130 DeleteSelected();
131 if (composing_) {
132 // Delete the current composing text, set the cursor to composing start.
133 text_.erase(composing_range_.start(), composing_range_.length());
134 selection_ = TextRange(composing_range_.start());
135 composing_range_.set_end(composing_range_.start() + text.length());
136 }
137 size_t position = selection_.position();
138 text_.insert(position, text);
139 selection_ = TextRange(position + text.length());
140 }
141
142 void TextInputModel::AddText(const std::string& text) {
143 AddText(fml::Utf8ToUtf16(text));
144 }
145
146 bool TextInputModel::Backspace() {
147 if (DeleteSelected()) {
148 return true;
149 }
150 // There is no selection. Delete the preceding codepoint.
151 size_t position = selection_.position();
152 if (position != editable_range().start()) {
153 int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1;
154 text_.erase(position - count, count);
155 selection_ = TextRange(position - count);
156 if (composing_) {
157 composing_range_.set_end(composing_range_.end() - count);
158 }
159 return true;
160 }
161 return false;
162 }
163
164 bool TextInputModel::Delete() {
165 if (DeleteSelected()) {
166 return true;
167 }
168 // There is no selection. Delete the preceding codepoint.
169 size_t position = selection_.position();
170 if (position < editable_range().end()) {
171 int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1;
172 text_.erase(position, count);
173 if (composing_) {
174 composing_range_.set_end(composing_range_.end() - count);
175 }
176 return true;
177 }
178 return false;
179 }
180
181 bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) {
182 size_t max_pos = editable_range().end();
183 size_t start = selection_.extent();
184 if (offset_from_cursor < 0) {
185 for (int i = 0; i < -offset_from_cursor; i++) {
186 // If requested start is before the available text then reduce the
187 // number of characters to delete.
188 if (start == editable_range().start()) {
189 count = i;
190 break;
191 }
192 start -= IsTrailingSurrogate(text_.at(start - 1)) ? 2 : 1;
193 }
194 } else {
195 for (int i = 0; i < offset_from_cursor && start != max_pos; i++) {
196 start += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;
197 }
198 }
199
200 auto end = start;
201 for (int i = 0; i < count && end != max_pos; i++) {
202 end += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;
203 }
204
205 if (start == end) {
206 return false;
207 }
208
209 auto deleted_length = end - start;
210 text_.erase(start, deleted_length);
211
212 // Cursor moves only if deleted area is before it.
213 selection_ = TextRange(offset_from_cursor <= 0 ? start : selection_.start());
214
215 // Adjust composing range.
216 if (composing_) {
217 composing_range_.set_end(composing_range_.end() - deleted_length);
218 }
219 return true;
220 }
221
222 bool TextInputModel::MoveCursorToBeginning() {
223 size_t min_pos = editable_range().start();
224 if (selection_.collapsed() && selection_.position() == min_pos) {
225 return false;
226 }
227 selection_ = TextRange(min_pos);
228 return true;
229 }
230
231 bool TextInputModel::MoveCursorToEnd() {
232 size_t max_pos = editable_range().end();
233 if (selection_.collapsed() && selection_.position() == max_pos) {
234 return false;
235 }
236 selection_ = TextRange(max_pos);
237 return true;
238 }
239
240 bool TextInputModel::SelectToBeginning() {
241 size_t min_pos = editable_range().start();
242 if (selection_.collapsed() && selection_.position() == min_pos) {
243 return false;
244 }
245 selection_ = TextRange(selection_.base(), min_pos);
246 return true;
247 }
248
249 bool TextInputModel::SelectToEnd() {
250 size_t max_pos = editable_range().end();
251 if (selection_.collapsed() && selection_.position() == max_pos) {
252 return false;
253 }
254 selection_ = TextRange(selection_.base(), max_pos);
255 return true;
256 }
257
258 bool TextInputModel::MoveCursorForward() {
259 // If there's a selection, move to the end of the selection.
260 if (!selection_.collapsed()) {
261 selection_ = TextRange(selection_.end());
262 return true;
263 }
264 // Otherwise, move the cursor forward.
265 size_t position = selection_.position();
266 if (position != editable_range().end()) {
267 int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1;
268 selection_ = TextRange(position + count);
269 return true;
270 }
271 return false;
272 }
273
274 bool TextInputModel::MoveCursorBack() {
275 // If there's a selection, move to the beginning of the selection.
276 if (!selection_.collapsed()) {
277 selection_ = TextRange(selection_.start());
278 return true;
279 }
280 // Otherwise, move the cursor backward.
281 size_t position = selection_.position();
282 if (position != editable_range().start()) {
283 int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1;
284 selection_ = TextRange(position - count);
285 return true;
286 }
287 return false;
288 }
289
290 std::string TextInputModel::GetText() const {
291 return fml::Utf16ToUtf8(text_);
292 }
293
294 int TextInputModel::GetCursorOffset() const {
295 // Measure the length of the current text up to the selection extent.
296 // There is probably a much more efficient way of doing this.
297 auto leading_text = text_.substr(0, selection_.extent());
298 return fml::Utf16ToUtf8(leading_text).size();
299 }
300
301 } // namespace flutter
flutter::TextInputModel::MoveCursorToBeginning
bool MoveCursorToBeginning()
Definition: text_input_model.cc:222
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::TextRange::end
size_t end() const
Definition: text_range.h:54
flutter::TextInputModel::text_range
TextRange text_range() const
Definition: text_input_model.h:193
flutter::TextInputModel::SelectToEnd
bool SelectToEnd()
Definition: text_input_model.cc:249
flutter::TextInputModel::composing_range
TextRange composing_range() const
Definition: text_input_model.h:201
flutter::TextRange::position
size_t position() const
Definition: text_range.h:68
flutter::TextInputModel::selection
TextRange selection() const
Definition: text_input_model.h:196
flutter::TextInputModel::BeginComposing
void BeginComposing()
Definition: text_input_model.cc:67
flutter::TextInputModel::~TextInputModel
virtual ~TextInputModel()
flutter::TextInputModel::DeleteSurrounding
bool DeleteSurrounding(int offset_from_cursor, int count)
Definition: text_input_model.cc:181
text_input_model.h
flutter::TextRange
Definition: text_range.h:19
flutter::TextRange::base
size_t base() const
Definition: text_range.h:30
flutter::TextInputModel::TextInputModel
TextInputModel()
flutter::TextInputModel::UpdateComposingText
void UpdateComposingText(const std::u16string &text)
Definition: text_input_model.cc:72
flutter
Definition: AccessibilityBridgeMac.h:16
flutter::TextInputModel::Backspace
bool Backspace()
Definition: text_input_model.cc:146
flutter::TextInputModel::GetText
std::string GetText() const
Definition: text_input_model.cc:290
flutter::TextInputModel::SetSelection
bool SetSelection(const TextRange &range)
Definition: text_input_model.cc:46
flutter::TextInputModel::SelectToBeginning
bool SelectToBeginning()
Definition: text_input_model.cc:240
flutter::TextRange::collapsed
bool collapsed() const
Definition: text_range.h:77
flutter::TextInputModel::SetComposingRange
bool SetComposingRange(const TextRange &range, size_t cursor_offset)
Definition: text_input_model.cc:57
flutter::TextInputModel::MoveCursorForward
bool MoveCursorForward()
Definition: text_input_model.cc:258
flutter::TextInputModel::GetCursorOffset
int GetCursorOffset() const
Definition: text_input_model.cc:294
flutter::TextInputModel::CommitComposing
void CommitComposing()
Definition: text_input_model.cc:87
flutter::TextRange::extent
size_t extent() const
Definition: text_range.h:36
flutter::TextInputModel::EndComposing
void EndComposing()
Definition: text_input_model.cc:96
flutter::TextInputModel::MoveCursorToEnd
bool MoveCursorToEnd()
Definition: text_input_model.cc:231
flutter::TextRange::start
size_t start() const
Definition: text_range.h:42
flutter::TextRange::length
size_t length() const
Definition: text_range.h:74
flutter::TextInputModel::MoveCursorBack
bool MoveCursorBack()
Definition: text_input_model.cc:274
flutter::TextRange::set_end
void set_end(size_t pos)
Definition: text_range.h:57
flutter::TextInputModel::AddText
void AddText(const std::u16string &text)
Definition: text_input_model.cc:129
flutter::TextInputModel::Delete
bool Delete()
Definition: text_input_model.cc:164
flutter::TextInputModel::AddCodePoint
void AddCodePoint(char32_t c)
Definition: text_input_model.cc:115