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

Commit fbe6859

Browse files
authored
Store selection base/extent as integers (#21663)
Previously, the selection base and extent were stored internally as iterators over text_. Since iterators must be treated as invalidated whenever the underlying container changes, this requires that selection_base_ and selection_extent_ be re-assigned after every change to text_. This is not currently particularly problematic, but once we add fields to track the base and extent of the composing region for multi-step input method support, as well as support for the sub-range within the composing region to which edits/completions apply, we end up having to regenerate a lot of iterators with each change, many of which are logically unchanged in position. A side benefit is that this simplifies inspection of these fields when debugging.
1 parent a068e45 commit fbe6859

File tree

2 files changed

+51
-51
lines changed

2 files changed

+51
-51
lines changed

shell/platform/common/cpp/text_input_model.cc

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,31 @@ bool IsTrailingSurrogate(char32_t code_point) {
3030

3131
} // namespace
3232

33-
TextInputModel::TextInputModel()
34-
: selection_base_(text_.begin()), selection_extent_(text_.begin()) {}
33+
TextInputModel::TextInputModel() = default;
3534

3635
TextInputModel::~TextInputModel() = default;
3736

3837
void TextInputModel::SetText(const std::string& text) {
3938
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
4039
utf16_converter;
4140
text_ = utf16_converter.from_bytes(text);
42-
selection_base_ = text_.begin();
43-
selection_extent_ = selection_base_;
41+
selection_base_ = 0;
42+
selection_extent_ = 0;
4443
}
4544

4645
bool TextInputModel::SetSelection(size_t base, size_t extent) {
47-
if (base > text_.size() || extent > text_.size()) {
46+
auto max_pos = text_.length();
47+
if (base > max_pos || extent > max_pos) {
4848
return false;
4949
}
50-
selection_base_ = text_.begin() + base;
51-
selection_extent_ = text_.begin() + extent;
50+
selection_base_ = base;
51+
selection_extent_ = extent;
5252
return true;
5353
}
5454

5555
void TextInputModel::DeleteSelected() {
56-
selection_base_ = text_.erase(selection_start(), selection_end());
56+
text_.erase(selection_start(), selection_end() - selection_start());
57+
selection_base_ = selection_start();
5758
selection_extent_ = selection_base_;
5859
}
5960

@@ -75,7 +76,7 @@ void TextInputModel::AddText(const std::u16string& text) {
7576
if (selection_base_ != selection_extent_) {
7677
DeleteSelected();
7778
}
78-
selection_extent_ = text_.insert(selection_extent_, text.begin(), text.end());
79+
text_.insert(selection_extent_, text);
7980
selection_extent_ += text.length();
8081
selection_base_ = selection_extent_;
8182
}
@@ -87,27 +88,32 @@ void TextInputModel::AddText(const std::string& text) {
8788
}
8889

8990
bool TextInputModel::Backspace() {
91+
// If there's a selection, delete it.
9092
if (selection_base_ != selection_extent_) {
9193
DeleteSelected();
9294
return true;
9395
}
94-
if (selection_base_ != text_.begin()) {
95-
int count = IsTrailingSurrogate(*(selection_base_ - 1)) ? 2 : 1;
96-
selection_base_ = text_.erase(selection_base_ - count, selection_base_);
96+
// There's no selection; delete the preceding codepoint.
97+
if (selection_base_ != 0) {
98+
int count = IsTrailingSurrogate(text_.at(selection_base_ - 1)) ? 2 : 1;
99+
text_.erase(selection_base_ - count, count);
100+
selection_base_ -= count;
97101
selection_extent_ = selection_base_;
98102
return true;
99103
}
100-
return false; // No edits happened.
104+
return false;
101105
}
102106

103107
bool TextInputModel::Delete() {
108+
// If there's a selection, delete it.
104109
if (selection_base_ != selection_extent_) {
105110
DeleteSelected();
106111
return true;
107112
}
108-
if (selection_base_ != text_.end()) {
109-
int count = IsLeadingSurrogate(*selection_base_) ? 2 : 1;
110-
selection_base_ = text_.erase(selection_base_, selection_base_ + count);
113+
// There's no selection; delete the following codepoint.
114+
if (selection_base_ != text_.length()) {
115+
int count = IsLeadingSurrogate(text_.at(selection_base_)) ? 2 : 1;
116+
text_.erase(selection_base_, count);
111117
selection_extent_ = selection_base_;
112118
return true;
113119
}
@@ -120,32 +126,32 @@ bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) {
120126
for (int i = 0; i < -offset_from_cursor; i++) {
121127
// If requested start is before the available text then reduce the
122128
// number of characters to delete.
123-
if (start == text_.begin()) {
129+
if (start == 0) {
124130
count = i;
125131
break;
126132
}
127-
start -= IsTrailingSurrogate(*(start - 1)) ? 2 : 1;
133+
start -= IsTrailingSurrogate(text_.at(start - 1)) ? 2 : 1;
128134
}
129135
} else {
130-
for (int i = 0; i < offset_from_cursor && start != text_.end(); i++) {
131-
start += IsLeadingSurrogate(*start) ? 2 : 1;
136+
for (int i = 0; i < offset_from_cursor && start != text_.length(); i++) {
137+
start += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;
132138
}
133139
}
134140

135141
auto end = start;
136-
for (int i = 0; i < count && end != text_.end(); i++) {
137-
end += IsLeadingSurrogate(*start) ? 2 : 1;
142+
for (int i = 0; i < count && end != text_.length(); i++) {
143+
end += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;
138144
}
139145

140146
if (start == end) {
141147
return false;
142148
}
143149

144-
auto new_base = text_.erase(start, end);
150+
text_.erase(start, end - start);
145151

146152
// Cursor moves only if deleted area is before it.
147153
if (offset_from_cursor <= 0) {
148-
selection_base_ = new_base;
154+
selection_base_ = start;
149155
}
150156

151157
// Clear selection.
@@ -155,22 +161,21 @@ bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) {
155161
}
156162

157163
bool TextInputModel::MoveCursorToBeginning() {
158-
if (selection_base_ == text_.begin() && selection_extent_ == text_.begin())
164+
if (selection_base_ == 0 && selection_extent_ == 0)
159165
return false;
160166

161-
selection_base_ = text_.begin();
162-
selection_extent_ = text_.begin();
163-
167+
selection_base_ = 0;
168+
selection_extent_ = 0;
164169
return true;
165170
}
166171

167172
bool TextInputModel::MoveCursorToEnd() {
168-
if (selection_base_ == text_.end() && selection_extent_ == text_.end())
173+
auto max_pos = text_.length();
174+
if (selection_base_ == max_pos && selection_extent_ == max_pos)
169175
return false;
170176

171-
selection_base_ = text_.end();
172-
selection_extent_ = text_.end();
173-
177+
selection_base_ = max_pos;
178+
selection_extent_ = max_pos;
174179
return true;
175180
}
176181

@@ -182,8 +187,8 @@ bool TextInputModel::MoveCursorForward() {
182187
return true;
183188
}
184189
// If not at the end, move the extent forward.
185-
if (selection_extent_ != text_.end()) {
186-
int count = IsLeadingSurrogate(*selection_base_) ? 2 : 1;
190+
if (selection_extent_ != text_.length()) {
191+
int count = IsLeadingSurrogate(text_.at(selection_base_)) ? 2 : 1;
187192
selection_base_ += count;
188193
selection_extent_ = selection_base_;
189194
return true;
@@ -200,8 +205,8 @@ bool TextInputModel::MoveCursorBack() {
200205
return true;
201206
}
202207
// If not at the start, move the beginning backward.
203-
if (selection_base_ != text_.begin()) {
204-
int count = IsTrailingSurrogate(*(selection_base_ - 1)) ? 2 : 1;
208+
if (selection_base_ != 0) {
209+
int count = IsTrailingSurrogate(text_.at(selection_base_ - 1)) ? 2 : 1;
205210
selection_base_ -= count;
206211
selection_extent_ = selection_base_;
207212
return true;
@@ -218,7 +223,7 @@ std::string TextInputModel::GetText() const {
218223
int TextInputModel::GetCursorOffset() const {
219224
// Measure the length of the current text up to the cursor.
220225
// There is probably a much more efficient way of doing this.
221-
auto leading_text = text_.substr(0, selection_extent_ - text_.begin());
226+
auto leading_text = text_.substr(0, selection_extent_);
222227
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
223228
utf8_converter;
224229
return utf8_converter.to_bytes(leading_text).size();

shell/platform/common/cpp/text_input_model.h

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#ifndef FLUTTER_SHELL_PLATFORM_CPP_TEXT_INPUT_MODEL_H_
66
#define FLUTTER_SHELL_PLATFORM_CPP_TEXT_INPUT_MODEL_H_
77

8+
#include <algorithm>
89
#include <memory>
910
#include <string>
1011

@@ -103,32 +104,26 @@ class TextInputModel {
103104
int GetCursorOffset() const;
104105

105106
// The position where the selection starts.
106-
int selection_base() const {
107-
return static_cast<int>(selection_base_ - text_.begin());
108-
}
107+
int selection_base() const { return selection_base_; }
109108

110109
// The position of the cursor.
111-
int selection_extent() const {
112-
return static_cast<int>(selection_extent_ - text_.begin());
113-
}
110+
int selection_extent() const { return selection_extent_; }
114111

115112
private:
116113
void DeleteSelected();
117114

118115
std::u16string text_;
119-
std::u16string::iterator selection_base_;
120-
std::u16string::iterator selection_extent_;
116+
size_t selection_base_ = 0;
117+
size_t selection_extent_ = 0;
121118

122119
// Returns the left hand side of the selection.
123-
std::u16string::iterator selection_start() {
124-
return selection_base_ < selection_extent_ ? selection_base_
125-
: selection_extent_;
120+
size_t selection_start() {
121+
return std::min(selection_base_, selection_extent_);
126122
}
127123

128124
// Returns the right hand side of the selection.
129-
std::u16string::iterator selection_end() {
130-
return selection_base_ > selection_extent_ ? selection_base_
131-
: selection_extent_;
125+
size_t selection_end() {
126+
return std::max(selection_base_, selection_extent_);
132127
}
133128
};
134129

0 commit comments

Comments
 (0)