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 | // FLUTTER_NOLINT |
5 | |
6 | #include "flutter/shell/platform/common/cpp/text_input_model.h" |
7 | |
8 | #include <algorithm> |
9 | #include <codecvt> |
10 | #include <locale> |
11 | |
12 | #if defined(_MSC_VER) |
13 | // TODO(naifu): This temporary code is to solve link error.(VS2015/2017) |
14 | // https://social.msdn.microsoft.com/Forums/vstudio/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481/vs-2015-rc-linker-stdcodecvt-error |
15 | std::locale::id std::codecvt<char16_t, char, _Mbstatet>::id; |
16 | #endif // defined(_MSC_VER) |
17 | |
18 | namespace flutter { |
19 | |
20 | namespace { |
21 | |
22 | // Returns true if |code_point| is a leading surrogate of a surrogate pair. |
23 | bool IsLeadingSurrogate(char32_t code_point) { |
24 | return (code_point & 0xFFFFFC00) == 0xD800; |
25 | } |
26 | // Returns true if |code_point| is a trailing surrogate of a surrogate pair. |
27 | bool IsTrailingSurrogate(char32_t code_point) { |
28 | return (code_point & 0xFFFFFC00) == 0xDC00; |
29 | } |
30 | |
31 | } // namespace |
32 | |
33 | TextInputModel::TextInputModel() |
34 | : selection_base_(text_.begin()), selection_extent_(text_.begin()) {} |
35 | |
36 | TextInputModel::~TextInputModel() = default; |
37 | |
38 | bool TextInputModel::SetEditingState(size_t selection_base, |
39 | size_t selection_extent, |
40 | const std::string& text) { |
41 | if (selection_base > text.size() || selection_extent > text.size()) { |
42 | return false; |
43 | } |
44 | std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> |
45 | utf16_converter; |
46 | text_ = utf16_converter.from_bytes(text); |
47 | selection_base_ = text_.begin() + selection_base; |
48 | selection_extent_ = text_.begin() + selection_extent; |
49 | return true; |
50 | } |
51 | |
52 | void TextInputModel::DeleteSelected() { |
53 | selection_base_ = text_.erase(selection_start(), selection_end()); |
54 | selection_extent_ = selection_base_; |
55 | } |
56 | |
57 | void TextInputModel::AddCodePoint(char32_t c) { |
58 | if (c <= 0xFFFF) { |
59 | AddText(std::u16string({static_cast<char16_t>(c)})); |
60 | } else { |
61 | char32_t to_decompose = c - 0x10000; |
62 | AddText(std::u16string({ |
63 | // High surrogate. |
64 | static_cast<char16_t>((to_decompose >> 10) + 0xd800), |
65 | // Low surrogate. |
66 | static_cast<char16_t>((to_decompose % 0x400) + 0xdc00), |
67 | })); |
68 | } |
69 | } |
70 | |
71 | void TextInputModel::AddText(const std::u16string& text) { |
72 | if (selection_base_ != selection_extent_) { |
73 | DeleteSelected(); |
74 | } |
75 | selection_extent_ = text_.insert(selection_extent_, text.begin(), text.end()); |
76 | selection_extent_ += text.length(); |
77 | selection_base_ = selection_extent_; |
78 | } |
79 | |
80 | void TextInputModel::AddText(const std::string& text) { |
81 | std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> |
82 | utf16_converter; |
83 | AddText(utf16_converter.from_bytes(text)); |
84 | } |
85 | |
86 | bool TextInputModel::Backspace() { |
87 | if (selection_base_ != selection_extent_) { |
88 | DeleteSelected(); |
89 | return true; |
90 | } |
91 | if (selection_base_ != text_.begin()) { |
92 | int count = IsTrailingSurrogate(*(selection_base_ - 1)) ? 2 : 1; |
93 | selection_base_ = text_.erase(selection_base_ - count, selection_base_); |
94 | selection_extent_ = selection_base_; |
95 | return true; |
96 | } |
97 | return false; // No edits happened. |
98 | } |
99 | |
100 | bool TextInputModel::Delete() { |
101 | if (selection_base_ != selection_extent_) { |
102 | DeleteSelected(); |
103 | return true; |
104 | } |
105 | if (selection_base_ != text_.end()) { |
106 | int count = IsLeadingSurrogate(*selection_base_) ? 2 : 1; |
107 | selection_base_ = text_.erase(selection_base_, selection_base_ + count); |
108 | selection_extent_ = selection_base_; |
109 | return true; |
110 | } |
111 | return false; |
112 | } |
113 | |
114 | bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) { |
115 | auto start = selection_extent_; |
116 | if (offset_from_cursor < 0) { |
117 | for (int i = 0; i < -offset_from_cursor; i++) { |
118 | // If requested start is before the available text then reduce the |
119 | // number of characters to delete. |
120 | if (start == text_.begin()) { |
121 | count = i; |
122 | break; |
123 | } |
124 | start -= IsTrailingSurrogate(*(start - 1)) ? 2 : 1; |
125 | } |
126 | } else { |
127 | for (int i = 0; i < offset_from_cursor && start != text_.end(); i++) { |
128 | start += IsLeadingSurrogate(*start) ? 2 : 1; |
129 | } |
130 | } |
131 | |
132 | auto end = start; |
133 | for (int i = 0; i < count && end != text_.end(); i++) { |
134 | end += IsLeadingSurrogate(*start) ? 2 : 1; |
135 | } |
136 | |
137 | if (start == end) { |
138 | return false; |
139 | } |
140 | |
141 | auto new_base = text_.erase(start, end); |
142 | |
143 | // Cursor moves only if deleted area is before it. |
144 | if (offset_from_cursor <= 0) { |
145 | selection_base_ = new_base; |
146 | } |
147 | |
148 | // Clear selection. |
149 | selection_extent_ = selection_base_; |
150 | |
151 | return true; |
152 | } |
153 | |
154 | bool TextInputModel::MoveCursorToBeginning() { |
155 | if (selection_base_ == text_.begin() && selection_extent_ == text_.begin()) |
156 | return false; |
157 | |
158 | selection_base_ = text_.begin(); |
159 | selection_extent_ = text_.begin(); |
160 | |
161 | return true; |
162 | } |
163 | |
164 | bool TextInputModel::MoveCursorToEnd() { |
165 | if (selection_base_ == text_.end() && selection_extent_ == text_.end()) |
166 | return false; |
167 | |
168 | selection_base_ = text_.end(); |
169 | selection_extent_ = text_.end(); |
170 | |
171 | return true; |
172 | } |
173 | |
174 | bool TextInputModel::MoveCursorForward() { |
175 | // If about to move set to the end of the highlight (when not selecting). |
176 | if (selection_base_ != selection_extent_) { |
177 | selection_base_ = selection_end(); |
178 | selection_extent_ = selection_base_; |
179 | return true; |
180 | } |
181 | // If not at the end, move the extent forward. |
182 | if (selection_extent_ != text_.end()) { |
183 | int count = IsLeadingSurrogate(*selection_base_) ? 2 : 1; |
184 | selection_base_ += count; |
185 | selection_extent_ = selection_base_; |
186 | return true; |
187 | } |
188 | return false; |
189 | } |
190 | |
191 | bool TextInputModel::MoveCursorBack() { |
192 | // If about to move set to the beginning of the highlight |
193 | // (when not selecting). |
194 | if (selection_base_ != selection_extent_) { |
195 | selection_base_ = selection_start(); |
196 | selection_extent_ = selection_base_; |
197 | return true; |
198 | } |
199 | // If not at the start, move the beginning backward. |
200 | if (selection_base_ != text_.begin()) { |
201 | int count = IsTrailingSurrogate(*(selection_base_ - 1)) ? 2 : 1; |
202 | selection_base_ -= count; |
203 | selection_extent_ = selection_base_; |
204 | return true; |
205 | } |
206 | return false; |
207 | } |
208 | |
209 | std::string TextInputModel::GetText() const { |
210 | std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> |
211 | utf8_converter; |
212 | return utf8_converter.to_bytes(text_); |
213 | } |
214 | |
215 | int TextInputModel::GetCursorOffset() const { |
216 | // Measure the length of the current text up to the cursor. |
217 | // There is probably a much more efficient way of doing this. |
218 | auto leading_text = text_.substr(0, selection_extent_ - text_.begin()); |
219 | std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> |
220 | utf8_converter; |
221 | return utf8_converter.to_bytes(leading_text).size(); |
222 | } |
223 | |
224 | } // namespace flutter |
225 | |