Skip to content

Commit fd7730d

Browse files
[SuperEditor][AttributedText] Fix placeholder breaking attribution behavior (Resolves #2565) (#2566)
1 parent 3f504da commit fd7730d

File tree

4 files changed

+103
-5
lines changed

4 files changed

+103
-5
lines changed

attributed_text/lib/src/attributed_text.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,16 +421,20 @@ class AttributedText {
421421
final textEndCopyOffset =
422422
(endOffset ?? length) - placeholdersBeforeStartOffset.length - placeholdersAfterStartBeforeEndOffset.length;
423423

424+
// The span marker offsets are based on the text with placeholders, so we need
425+
// to copy the text with placeholders to ensure the span markers are correct.
426+
final textWithPlaceholders = toPlainText();
427+
424428
// Note: -1 because copyText() uses an exclusive `start` and `end` but
425429
// _copyAttributionRegion() uses an inclusive `start` and `end`.
426-
final startCopyOffset = startOffset < _text.length ? startOffset : _text.length - 1;
430+
final startCopyOffset = startOffset < textWithPlaceholders.length ? startOffset : textWithPlaceholders.length - 1;
427431
int endCopyOffset;
428432
if (endOffset == startOffset) {
429433
endCopyOffset = startCopyOffset;
430434
} else if (endOffset != null) {
431435
endCopyOffset = endOffset - 1;
432436
} else {
433-
endCopyOffset = _text.length - 1;
437+
endCopyOffset = textWithPlaceholders.length - 1;
434438
}
435439
_log.fine('offsets, start: $startCopyOffset, end: $endCopyOffset');
436440

attributed_text/test/attributed_text_placeholders_test.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,46 @@ void main() {
440440
}),
441441
);
442442
});
443+
444+
test("empty text with leading placeholder (with attributions)", () {
445+
// Create an empty text containing a placeholder with an attribution around it.
446+
final text = AttributedText(
447+
'',
448+
AttributedSpans(
449+
attributions: [
450+
const SpanMarker(
451+
attribution: _bold,
452+
offset: 0,
453+
markerType: SpanMarkerType.start,
454+
),
455+
const SpanMarker(
456+
attribution: _bold,
457+
offset: 0,
458+
markerType: SpanMarkerType.end,
459+
),
460+
],
461+
),
462+
{
463+
0: const _FakePlaceholder('leading'),
464+
},
465+
);
466+
467+
expect(
468+
text.copyText(0),
469+
AttributedText(
470+
"",
471+
AttributedSpans(
472+
attributions: const [
473+
SpanMarker(attribution: _bold, offset: 0, markerType: SpanMarkerType.start),
474+
SpanMarker(attribution: _bold, offset: 0, markerType: SpanMarkerType.end),
475+
],
476+
),
477+
{
478+
0: const _FakePlaceholder("leading"),
479+
},
480+
),
481+
);
482+
});
443483
});
444484

445485
group("copy and append >", () {

super_editor/pubspec.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ dependencies:
4848
flutter_test_robots: ^0.0.24
4949
clock: ^1.1.1
5050

51-
#dependency_overrides:
51+
dependency_overrides:
5252
# Override to local mono-repo path so devs can test this repo
5353
# against changes that they're making to other mono-repo packages
54-
# attributed_text:
55-
# path: ../attributed_text
54+
attributed_text:
55+
path: ../attributed_text
5656
# super_text_layout:
5757
# path: ../super_text_layout
5858

super_editor/test/super_editor/text_entry/inline_widgets_test.dart

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,58 @@ void main() {
133133
),
134134
);
135135
});
136+
137+
testWidgetsOnArbitraryDesktop("can insert an inline widget with attributions in an empty paragraph",
138+
(tester) async {
139+
final editor = await _pumpScaffold(tester);
140+
141+
// Insert an empty text containing a placeholder with an attribution around it.
142+
editor.execute([
143+
InsertStyledTextAtCaretRequest(
144+
AttributedText(
145+
'',
146+
AttributedSpans(
147+
attributions: [
148+
const SpanMarker(
149+
attribution: _emojiAttribution,
150+
offset: 0,
151+
markerType: SpanMarkerType.start,
152+
),
153+
const SpanMarker(
154+
attribution: _emojiAttribution,
155+
offset: 0,
156+
markerType: SpanMarkerType.end,
157+
),
158+
],
159+
),
160+
{
161+
0: const _TestPlaceholder(),
162+
}),
163+
),
164+
]);
165+
await tester.pump();
166+
167+
// Ensure we can type after the inline placeholder was added.
168+
await tester.typeImeText('hello');
169+
170+
// Ensure the inline widget was kept, the text was inserted, and the attribution
171+
// was not applied to the inserted characters.
172+
expect(
173+
SuperEditorInspector.findTextInComponent('1'),
174+
AttributedText(
175+
'hello',
176+
AttributedSpans(
177+
attributions: const [
178+
SpanMarker(attribution: _emojiAttribution, offset: 0, markerType: SpanMarkerType.start),
179+
SpanMarker(attribution: _emojiAttribution, offset: 0, markerType: SpanMarkerType.end),
180+
],
181+
),
182+
{
183+
0: const _TestPlaceholder(),
184+
},
185+
),
186+
);
187+
});
136188
});
137189
}
138190

@@ -165,3 +217,5 @@ Widget? _buildInlineTestWidget(BuildContext context, TextStyle style, Object pla
165217
class _TestPlaceholder {
166218
const _TestPlaceholder();
167219
}
220+
221+
const _emojiAttribution = NamedAttribution('emoji');

0 commit comments

Comments
 (0)