Skip to content

Commit 405576f

Browse files
[SuperEditor] Make selected text color strategy consider each colored span (Resolves #2093)
1 parent bae3cbb commit 405576f

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

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

0 commit comments

Comments
 (0)