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
15std::locale::id std::codecvt<char16_t, char, _Mbstatet>::id;
16#endif // defined(_MSC_VER)
17
18namespace flutter {
19
20namespace {
21
22// Returns true if |code_point| is a leading surrogate of a surrogate pair.
23bool 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.
27bool IsTrailingSurrogate(char32_t code_point) {
28 return (code_point & 0xFFFFFC00) == 0xDC00;
29}
30
31} // namespace
32
33TextInputModel::TextInputModel()
34 : selection_base_(text_.begin()), selection_extent_(text_.begin()) {}
35
36TextInputModel::~TextInputModel() = default;
37
38bool 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
52void TextInputModel::DeleteSelected() {
53 selection_base_ = text_.erase(selection_start(), selection_end());
54 selection_extent_ = selection_base_;
55}
56
57void 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
71void 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
80void 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
86bool 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
100bool 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
114bool 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
154bool 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
164bool 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
174bool 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
191bool 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
209std::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
215int 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