Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 2bd95d9

Browse files
Add FlTextInputPlugin (#18314)
* Add FlTextInputPlugin
1 parent 368fe27 commit 2bd95d9

File tree

11 files changed

+568
-12
lines changed

11 files changed

+568
-12
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_standard_method_codec.cc
12301230
FILE: ../../../flutter/shell/platform/linux/fl_standard_method_codec_test.cc
12311231
FILE: ../../../flutter/shell/platform/linux/fl_string_codec.cc
12321232
FILE: ../../../flutter/shell/platform/linux/fl_string_codec_test.cc
1233+
FILE: ../../../flutter/shell/platform/linux/fl_text_input_plugin.cc
1234+
FILE: ../../../flutter/shell/platform/linux/fl_text_input_plugin.h
12331235
FILE: ../../../flutter/shell/platform/linux/fl_value.cc
12341236
FILE: ../../../flutter/shell/platform/linux/fl_value_test.cc
12351237
FILE: ../../../flutter/shell/platform/linux/fl_view.cc

shell/platform/common/cpp/BUILD.gn

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,28 @@ source_set("common_cpp_library_headers") {
3030
configs += [ ":desktop_library_implementation" ]
3131
}
3232

33+
source_set("common_cpp_input") {
34+
public = [
35+
"text_input_model.h",
36+
]
37+
38+
sources = [
39+
"text_input_model.cc",
40+
]
41+
42+
configs += [ ":desktop_library_implementation" ]
43+
44+
if (is_win) {
45+
# For wstring_conversion. See issue #50053.
46+
defines = [ "_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING" ]
47+
}
48+
}
49+
3350
source_set("common_cpp") {
3451
public = [
3552
"incoming_message_dispatcher.h",
3653
"json_message_codec.h",
3754
"json_method_codec.h",
38-
"text_input_model.h",
3955
]
4056

4157
# TODO: Refactor flutter_glfw.cc to move the implementations corresponding
@@ -44,7 +60,6 @@ source_set("common_cpp") {
4460
"incoming_message_dispatcher.cc",
4561
"json_message_codec.cc",
4662
"json_method_codec.cc",
47-
"text_input_model.cc",
4863
]
4964

5065
configs += [ ":desktop_library_implementation" ]
@@ -59,11 +74,6 @@ source_set("common_cpp") {
5974
":common_cpp_core",
6075
"//third_party/rapidjson",
6176
]
62-
63-
if (is_win) {
64-
# For wstring_conversion. See issue #50053.
65-
defines = [ "_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING" ]
66-
}
6777
}
6878

6979
# The portion of common_cpp that has no dependencies on the public/
@@ -117,6 +127,7 @@ executable("common_cpp_unittests") {
117127
deps = [
118128
":common_cpp",
119129
":common_cpp_fixtures",
130+
":common_cpp_input",
120131
"//flutter/shell/platform/common/cpp/client_wrapper:client_wrapper",
121132
"//flutter/shell/platform/common/cpp/client_wrapper:client_wrapper_library_stubs",
122133
"//flutter/testing",

shell/platform/common/cpp/text_input_model.cc

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ void TextInputModel::AddText(const std::u16string& text) {
8585
selection_base_ = selection_extent_;
8686
}
8787

88+
void TextInputModel::AddText(const std::string& text) {
89+
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
90+
utf16_converter;
91+
AddText(utf16_converter.from_bytes(text));
92+
}
93+
8894
bool TextInputModel::Backspace() {
8995
if (selection_base_ != selection_extent_) {
9096
DeleteSelected();
@@ -113,6 +119,46 @@ bool TextInputModel::Delete() {
113119
return false;
114120
}
115121

122+
bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) {
123+
auto start = selection_extent_;
124+
if (offset_from_cursor < 0) {
125+
for (int i = 0; i < -offset_from_cursor; i++) {
126+
// If requested start is before the available text then reduce the
127+
// number of characters to delete.
128+
if (start == text_.begin()) {
129+
count = i;
130+
break;
131+
}
132+
start -= IsTrailingSurrogate(*(start - 1)) ? 2 : 1;
133+
}
134+
} else {
135+
for (int i = 0; i < offset_from_cursor && start != text_.end(); i++) {
136+
start += IsLeadingSurrogate(*start) ? 2 : 1;
137+
}
138+
}
139+
140+
auto end = start;
141+
for (int i = 0; i < count && end != text_.end(); i++) {
142+
end += IsLeadingSurrogate(*start) ? 2 : 1;
143+
}
144+
145+
if (start == end) {
146+
return false;
147+
}
148+
149+
auto new_base = text_.erase(start, end);
150+
151+
// Cursor moves only if deleted area is before it.
152+
if (offset_from_cursor <= 0) {
153+
selection_base_ = new_base;
154+
}
155+
156+
// Clear selection.
157+
selection_extent_ = selection_base_;
158+
159+
return true;
160+
}
161+
116162
bool TextInputModel::MoveCursorToBeginning() {
117163
if (selection_base_ == text_.begin() && selection_extent_ == text_.begin())
118164
return false;
@@ -172,4 +218,13 @@ std::string TextInputModel::GetText() const {
172218
return utf8_converter.to_bytes(text_);
173219
}
174220

221+
int TextInputModel::GetCursorOffset() const {
222+
// Measure the length of the current text up to the cursor.
223+
// There is probably a much more efficient way of doing this.
224+
auto leading_text = text_.substr(0, selection_extent_ - text_.begin());
225+
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
226+
utf8_converter;
227+
return utf8_converter.to_bytes(leading_text).size();
228+
}
229+
175230
} // namespace flutter

shell/platform/common/cpp/text_input_model.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,18 @@ class TextInputModel {
3333
// code point.
3434
void AddCodePoint(char32_t c);
3535

36-
// Adds a UTF-16 text.
36+
// Adds UTF-16 text.
3737
//
3838
// Either appends after the cursor (when selection base and extent are the
3939
// same), or deletes the selected text, replacing it with the given text.
4040
void AddText(const std::u16string& text);
4141

42+
// Adds UTF-8 text.
43+
//
44+
// Either appends after the cursor (when selection base and extent are the
45+
// same), or deletes the selected text, replacing it with the given text.
46+
void AddText(const std::string& text);
47+
4248
// Deletes either the selection, or one character ahead of the cursor.
4349
//
4450
// Deleting one character ahead of the cursor occurs when the selection base
@@ -47,6 +53,17 @@ class TextInputModel {
4753
// Returns true if any deletion actually occurred.
4854
bool Delete();
4955

56+
// Deletes text near the cursor.
57+
//
58+
// A section is made starting at @offset code points past the cursor (negative
59+
// values go before the cursor). @count code points are removed. The selection
60+
// may go outside the bounds of the text and will result in only the part
61+
// selection that covers the available text being deleted. The existing
62+
// selection is ignored and removed after this operation.
63+
//
64+
// Returns true if any deletion actually occurred.
65+
bool DeleteSurrounding(int offset_from_cursor, int count);
66+
5067
// Deletes either the selection, or one character behind the cursor.
5168
//
5269
// Deleting one character behind the cursor occurs when the selection base
@@ -77,9 +94,13 @@ class TextInputModel {
7794
// Returns true if the cursor could be moved.
7895
bool MoveCursorToEnd();
7996

80-
// Get the current text
97+
// Gets the current text as UTF-8.
8198
std::string GetText() const;
8299

100+
// Gets the cursor position as a byte offset in UTF-8 string returned from
101+
// GetText().
102+
int GetCursorOffset() const;
103+
83104
// The position of the cursor
84105
int selection_base() const {
85106
return static_cast<int>(selection_base_ - text_.begin());

shell/platform/common/cpp/text_input_model_unittests.cc

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ TEST(TextInputModel, AddCodePointSelectionWideCharacter) {
103103
TEST(TextInputModel, AddText) {
104104
auto model = std::make_unique<TextInputModel>("", "");
105105
model->AddText(u"ABCDE");
106-
model->AddText(u"😄");
107-
model->AddText(u"FGHIJ");
106+
model->AddText("😄");
107+
model->AddText("FGHIJ");
108108
EXPECT_EQ(model->selection_base(), 12);
109109
EXPECT_EQ(model->selection_extent(), 12);
110110
EXPECT_STREQ(model->GetText().c_str(), "ABCDE😄FGHIJ");
@@ -113,7 +113,7 @@ TEST(TextInputModel, AddText) {
113113
TEST(TextInputModel, AddTextSelection) {
114114
auto model = std::make_unique<TextInputModel>("", "");
115115
EXPECT_TRUE(model->SetEditingState(1, 4, "ABCDE"));
116-
model->AddText(u"xy");
116+
model->AddText("xy");
117117
EXPECT_EQ(model->selection_base(), 3);
118118
EXPECT_EQ(model->selection_extent(), 3);
119119
EXPECT_STREQ(model->GetText().c_str(), "AxyE");
@@ -173,6 +173,96 @@ TEST(TextInputModel, DeleteSelection) {
173173
EXPECT_STREQ(model->GetText().c_str(), "AE");
174174
}
175175

176+
TEST(TextInputModel, DeleteSurroundingAtCursor) {
177+
auto model = std::make_unique<TextInputModel>("", "");
178+
model->SetEditingState(2, 2, "ABCDE");
179+
EXPECT_TRUE(model->DeleteSurrounding(0, 1));
180+
EXPECT_EQ(model->selection_base(), 2);
181+
EXPECT_EQ(model->selection_extent(), 2);
182+
EXPECT_STREQ(model->GetText().c_str(), "ABDE");
183+
}
184+
185+
TEST(TextInputModel, DeleteSurroundingAtCursorAll) {
186+
auto model = std::make_unique<TextInputModel>("", "");
187+
model->SetEditingState(2, 2, "ABCDE");
188+
EXPECT_TRUE(model->DeleteSurrounding(0, 3));
189+
EXPECT_EQ(model->selection_base(), 2);
190+
EXPECT_EQ(model->selection_extent(), 2);
191+
EXPECT_STREQ(model->GetText().c_str(), "AB");
192+
}
193+
194+
TEST(TextInputModel, DeleteSurroundingAtCursorGreedy) {
195+
auto model = std::make_unique<TextInputModel>("", "");
196+
model->SetEditingState(2, 2, "ABCDE");
197+
EXPECT_TRUE(model->DeleteSurrounding(0, 4));
198+
EXPECT_EQ(model->selection_base(), 2);
199+
EXPECT_EQ(model->selection_extent(), 2);
200+
EXPECT_STREQ(model->GetText().c_str(), "AB");
201+
}
202+
203+
TEST(TextInputModel, DeleteSurroundingBeforeCursor) {
204+
auto model = std::make_unique<TextInputModel>("", "");
205+
model->SetEditingState(2, 2, "ABCDE");
206+
EXPECT_TRUE(model->DeleteSurrounding(-1, 1));
207+
EXPECT_EQ(model->selection_base(), 1);
208+
EXPECT_EQ(model->selection_extent(), 1);
209+
EXPECT_STREQ(model->GetText().c_str(), "ACDE");
210+
}
211+
212+
TEST(TextInputModel, DeleteSurroundingBeforeCursorAll) {
213+
auto model = std::make_unique<TextInputModel>("", "");
214+
model->SetEditingState(2, 2, "ABCDE");
215+
EXPECT_TRUE(model->DeleteSurrounding(-2, 2));
216+
EXPECT_EQ(model->selection_base(), 0);
217+
EXPECT_EQ(model->selection_extent(), 0);
218+
EXPECT_STREQ(model->GetText().c_str(), "CDE");
219+
}
220+
221+
TEST(TextInputModel, DeleteSurroundingBeforeCursorGreedy) {
222+
auto model = std::make_unique<TextInputModel>("", "");
223+
model->SetEditingState(2, 2, "ABCDE");
224+
EXPECT_TRUE(model->DeleteSurrounding(-3, 3));
225+
EXPECT_EQ(model->selection_base(), 0);
226+
EXPECT_EQ(model->selection_extent(), 0);
227+
EXPECT_STREQ(model->GetText().c_str(), "CDE");
228+
}
229+
230+
TEST(TextInputModel, DeleteSurroundingAfterCursor) {
231+
auto model = std::make_unique<TextInputModel>("", "");
232+
model->SetEditingState(2, 2, "ABCDE");
233+
EXPECT_TRUE(model->DeleteSurrounding(1, 1));
234+
EXPECT_EQ(model->selection_base(), 2);
235+
EXPECT_EQ(model->selection_extent(), 2);
236+
EXPECT_STREQ(model->GetText().c_str(), "ABCE");
237+
}
238+
239+
TEST(TextInputModel, DeleteSurroundingAfterCursorAll) {
240+
auto model = std::make_unique<TextInputModel>("", "");
241+
model->SetEditingState(2, 2, "ABCDE");
242+
EXPECT_TRUE(model->DeleteSurrounding(1, 2));
243+
EXPECT_EQ(model->selection_base(), 2);
244+
EXPECT_EQ(model->selection_extent(), 2);
245+
EXPECT_STREQ(model->GetText().c_str(), "ABC");
246+
}
247+
248+
TEST(TextInputModel, DeleteSurroundingAfterCursorGreedy) {
249+
auto model = std::make_unique<TextInputModel>("", "");
250+
model->SetEditingState(2, 2, "ABCDE");
251+
EXPECT_TRUE(model->DeleteSurrounding(1, 3));
252+
EXPECT_EQ(model->selection_base(), 2);
253+
EXPECT_EQ(model->selection_extent(), 2);
254+
EXPECT_STREQ(model->GetText().c_str(), "ABC");
255+
}
256+
257+
TEST(TextInputModel, DeleteSurroundingSelection) {
258+
auto model = std::make_unique<TextInputModel>("", "");
259+
model->SetEditingState(2, 3, "ABCDE");
260+
EXPECT_TRUE(model->DeleteSurrounding(0, 1));
261+
EXPECT_EQ(model->selection_base(), 3);
262+
EXPECT_EQ(model->selection_extent(), 3);
263+
EXPECT_STREQ(model->GetText().c_str(), "ABCE");
264+
}
265+
176266
TEST(TextInputModel, BackspaceStart) {
177267
auto model = std::make_unique<TextInputModel>("", "");
178268
EXPECT_TRUE(model->SetEditingState(0, 0, "ABCDE"));
@@ -380,4 +470,25 @@ TEST(TextInputModel, MoveCursorToEndSelection) {
380470
EXPECT_STREQ(model->GetText().c_str(), "ABCDE");
381471
}
382472

473+
TEST(TextInputModel, GetCursorOffset) {
474+
auto model = std::make_unique<TextInputModel>("", "");
475+
// These characters take 1, 2, 3 and 4 bytes in UTF-8.
476+
model->SetEditingState(0, 0, "$¢€𐍈");
477+
EXPECT_EQ(model->GetCursorOffset(), 0);
478+
EXPECT_TRUE(model->MoveCursorForward());
479+
EXPECT_EQ(model->GetCursorOffset(), 1);
480+
EXPECT_TRUE(model->MoveCursorForward());
481+
EXPECT_EQ(model->GetCursorOffset(), 3);
482+
EXPECT_TRUE(model->MoveCursorForward());
483+
EXPECT_EQ(model->GetCursorOffset(), 6);
484+
EXPECT_TRUE(model->MoveCursorForward());
485+
EXPECT_EQ(model->GetCursorOffset(), 10);
486+
}
487+
488+
TEST(TextInputModel, GetCursorOffsetSelection) {
489+
auto model = std::make_unique<TextInputModel>("", "");
490+
model->SetEditingState(1, 4, "ABCDE");
491+
EXPECT_EQ(model->GetCursorOffset(), 4);
492+
}
493+
383494
} // namespace flutter

shell/platform/glfw/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ source_set("flutter_glfw") {
5353
":flutter_glfw_headers",
5454
"//build/secondary/third_party/glfw",
5555
"//flutter/shell/platform/common/cpp:common_cpp",
56+
"//flutter/shell/platform/common/cpp:common_cpp_input",
5657
"//flutter/shell/platform/common/cpp/client_wrapper:client_wrapper",
5758
"//flutter/shell/platform/embedder:embedder_with_symbol_prefix",
5859
"//flutter/shell/platform/glfw/client_wrapper:client_wrapper_glfw",

shell/platform/linux/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ source_set("flutter_linux") {
9494
"fl_standard_message_codec.cc",
9595
"fl_standard_method_codec.cc",
9696
"fl_string_codec.cc",
97+
"fl_text_input_plugin.cc",
9798
"fl_value.cc",
9899
"fl_view.cc",
99100
]
@@ -108,6 +109,7 @@ source_set("flutter_linux") {
108109
defines = [ "FLUTTER_LINUX_COMPILATION" ]
109110

110111
deps = [
112+
"//flutter/shell/platform/common/cpp:common_cpp_input",
111113
"//flutter/shell/platform/embedder:embedder_with_symbol_prefix",
112114
"//third_party/rapidjson",
113115
]

0 commit comments

Comments
 (0)