Skip to content

Commit 7c2fbc9

Browse files
[SuperEditor] Make selected text color strategy consider each colored span (Resolves #2093)
1 parent 279dbdc commit 7c2fbc9

File tree

2 files changed

+117
-17
lines changed

2 files changed

+117
-17
lines changed

super_editor/lib/src/default_editor/layout_single_column/_styler_user_selection.dart

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -132,25 +132,52 @@ class SingleColumnLayoutSelectionStyler extends SingleColumnLayoutStylePhase {
132132
editorStyleLog.finer(' - extent: ${textSelection?.extent}');
133133

134134
if (viewModel is TextComponentViewModel) {
135-
final componentTextColor = viewModel.textStyleBuilder({}).color;
136-
137-
final textWithSelectionAttributions =
138-
textSelection != null && _selectedTextColorStrategy != null && componentTextColor != null
139-
? (viewModel.text.copyText(0)
140-
..addAttribution(
141-
ColorAttribution(_selectedTextColorStrategy!(
142-
originalTextColor: componentTextColor,
143-
selectionHighlightColor: _selectionStyles.selectionColor,
144-
)),
145-
SpanRange(textSelection.start, textSelection.end - 1),
146-
// The selected range might already have a color attribution. We want to override it
147-
// with the selected text color.
148-
overwriteConflictingSpans: true,
149-
))
150-
: viewModel.text;
135+
AttributedText? textWithSelectionAttributions;
136+
137+
if (textSelection != null && _selectedTextColorStrategy != null) {
138+
final selectedRange = SpanRange(textSelection.start, textSelection.end - 1);
139+
140+
final componentTextColor = viewModel.textStyleBuilder({}).color;
141+
if (componentTextColor != null) {
142+
// Compute the selected text color for the default color of the node. If there is any
143+
// text color attributions in the selected range, they will override this color.
144+
textWithSelectionAttributions = viewModel.text.copyText(0)
145+
..addAttribution(
146+
ColorAttribution(_selectedTextColorStrategy!(
147+
originalTextColor: componentTextColor,
148+
selectionHighlightColor: _selectionStyles.selectionColor,
149+
)),
150+
selectedRange,
151+
// Override any existing color attributions.
152+
overwriteConflictingSpans: true,
153+
);
154+
}
155+
156+
final coloredSpans = viewModel.text.getAttributionSpansInRange(
157+
attributionFilter: (attr) => attr is ColorAttribution,
158+
range: selectedRange,
159+
// We should only change the selected portion of each span.
160+
resizeSpansToFitInRange: true,
161+
);
162+
if (coloredSpans.isNotEmpty) {
163+
// Compute and apply the selected text color for each span that has a color attribution.
164+
textWithSelectionAttributions ??= viewModel.text.copyText(0);
165+
for (final span in coloredSpans) {
166+
textWithSelectionAttributions.addAttribution(
167+
ColorAttribution(_selectedTextColorStrategy!(
168+
originalTextColor: (span.attribution as ColorAttribution).color,
169+
selectionHighlightColor: _selectionStyles.selectionColor,
170+
)),
171+
SpanRange(span.start, span.end),
172+
// Override any existing color attributions.
173+
overwriteConflictingSpans: true,
174+
);
175+
}
176+
}
177+
}
151178

152179
viewModel
153-
..text = textWithSelectionAttributions
180+
..text = textWithSelectionAttributions ?? viewModel.text
154181
..selection = textSelection
155182
..selectionColor = _selectionStyles.selectionColor
156183
..highlightWhenEmpty = highlightWhenEmpty;

super_editor/test/super_editor/supereditor_selection_test.dart

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,79 @@ void main() {
8484
expect(richText.getSpanForPosition(const TextPosition(offset: 5))!.style!.color, Colors.green);
8585
expect(richText.getSpanForPosition(const TextPosition(offset: 16))!.style!.color, Colors.green);
8686
});
87+
88+
testWidgetsOnArbitraryDesktop("overrides each colored span", (tester) async {
89+
final stylesheet = defaultStylesheet.copyWith(
90+
selectedTextColorStrategy: ({required Color originalTextColor, required Color selectionHighlightColor}) {
91+
if (originalTextColor == Colors.green) {
92+
return Colors.red;
93+
}
94+
95+
if (originalTextColor == Colors.yellow) {
96+
return Colors.blue;
97+
}
98+
99+
return Colors.white;
100+
},
101+
);
102+
103+
// Pump an editor with a paragraph with the following colors:
104+
// Lorem ipsum dolor
105+
// gggggg-----------
106+
// ------yyyyyy-----
107+
// ------------bbbbb (black, the default color)
108+
await tester //
109+
.createDocument()
110+
.withCustomContent(
111+
MutableDocument(
112+
nodes: [
113+
ParagraphNode(
114+
id: '1',
115+
text: AttributedText(
116+
'Lorem ipsum dolor',
117+
AttributedSpans(
118+
attributions: [
119+
SpanMarker(
120+
attribution: const ColorAttribution(Colors.green),
121+
offset: 0,
122+
markerType: SpanMarkerType.start),
123+
SpanMarker(
124+
attribution: const ColorAttribution(Colors.green),
125+
offset: 5,
126+
markerType: SpanMarkerType.end),
127+
SpanMarker(
128+
attribution: const ColorAttribution(Colors.yellow),
129+
offset: 6,
130+
markerType: SpanMarkerType.start),
131+
SpanMarker(
132+
attribution: const ColorAttribution(Colors.yellow),
133+
offset: 11,
134+
markerType: SpanMarkerType.end),
135+
],
136+
),
137+
),
138+
),
139+
],
140+
),
141+
)
142+
.useStylesheet(stylesheet)
143+
.pump();
144+
145+
// Triple tap to select the whole paragraph.
146+
await tester.tripleTapInParagraph('1', 2);
147+
148+
// Ensure that all spans changed colors.
149+
final richText = SuperEditorInspector.findRichTextInParagraph('1');
150+
151+
expect(richText.getSpanForPosition(const TextPosition(offset: 0))!.style!.color, Colors.red);
152+
expect(richText.getSpanForPosition(const TextPosition(offset: 5))!.style!.color, Colors.red);
153+
154+
expect(richText.getSpanForPosition(const TextPosition(offset: 6))!.style!.color, Colors.blue);
155+
expect(richText.getSpanForPosition(const TextPosition(offset: 11))!.style!.color, Colors.blue);
156+
157+
expect(richText.getSpanForPosition(const TextPosition(offset: 12))!.style!.color, Colors.white);
158+
expect(richText.getSpanForPosition(const TextPosition(offset: 16))!.style!.color, Colors.white);
159+
});
87160
});
88161

89162
testWidgetsOnArbitraryDesktop("calculates upstream document selection within a single node", (tester) async {

0 commit comments

Comments
 (0)