Skip to content

Commit

Permalink
[SuperEditor][AttributedText] Fix placeholder breaking attribution be…
Browse files Browse the repository at this point in the history
…havior (Resolves #2565) (#2566)
  • Loading branch information
angelosilvestre authored and web-flow committed Feb 11, 2025
1 parent 573872e commit 407da3f
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 2 deletions.
8 changes: 6 additions & 2 deletions attributed_text/lib/src/attributed_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -421,16 +421,20 @@ class AttributedText {
final textEndCopyOffset =
(endOffset ?? length) - placeholdersBeforeStartOffset.length - placeholdersAfterStartBeforeEndOffset.length;

// The span marker offsets are based on the text with placeholders, so we need
// to copy the text with placeholders to ensure the span markers are correct.
final textWithPlaceholders = toPlainText();

// Note: -1 because copyText() uses an exclusive `start` and `end` but
// _copyAttributionRegion() uses an inclusive `start` and `end`.
final startCopyOffset = startOffset < _text.length ? startOffset : _text.length - 1;
final startCopyOffset = startOffset < textWithPlaceholders.length ? startOffset : textWithPlaceholders.length - 1;
int endCopyOffset;
if (endOffset == startOffset) {
endCopyOffset = startCopyOffset;
} else if (endOffset != null) {
endCopyOffset = endOffset - 1;
} else {
endCopyOffset = _text.length - 1;
endCopyOffset = textWithPlaceholders.length - 1;
}
_log.fine('offsets, start: $startCopyOffset, end: $endCopyOffset');

Expand Down
40 changes: 40 additions & 0 deletions attributed_text/test/attributed_text_placeholders_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,46 @@ void main() {
}),
);
});

test("empty text with leading placeholder (with attributions)", () {
// Create an empty text containing a placeholder with an attribution around it.
final text = AttributedText(
'',
AttributedSpans(
attributions: [
const SpanMarker(
attribution: _bold,
offset: 0,
markerType: SpanMarkerType.start,
),
const SpanMarker(
attribution: _bold,
offset: 0,
markerType: SpanMarkerType.end,
),
],
),
{
0: const _FakePlaceholder('leading'),
},
);

expect(
text.copyText(0),
AttributedText(
"",
AttributedSpans(
attributions: const [
SpanMarker(attribution: _bold, offset: 0, markerType: SpanMarkerType.start),
SpanMarker(attribution: _bold, offset: 0, markerType: SpanMarkerType.end),
],
),
{
0: const _FakePlaceholder("leading"),
},
),
);
});
});

group("copy and append >", () {
Expand Down
54 changes: 54 additions & 0 deletions super_editor/test/super_editor/text_entry/inline_widgets_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,58 @@ void main() {
),
);
});

testWidgetsOnArbitraryDesktop("can insert an inline widget with attributions in an empty paragraph",
(tester) async {
final editor = await _pumpScaffold(tester);

// Insert an empty text containing a placeholder with an attribution around it.
editor.execute([
InsertStyledTextAtCaretRequest(
AttributedText(
'',
AttributedSpans(
attributions: [
const SpanMarker(
attribution: _emojiAttribution,
offset: 0,
markerType: SpanMarkerType.start,
),
const SpanMarker(
attribution: _emojiAttribution,
offset: 0,
markerType: SpanMarkerType.end,
),
],
),
{
0: const _TestPlaceholder(),
}),
),
]);
await tester.pump();

// Ensure we can type after the inline placeholder was added.
await tester.typeImeText('hello');

// Ensure the inline widget was kept, the text was inserted, and the attribution
// was not applied to the inserted characters.
expect(
SuperEditorInspector.findTextInComponent('1'),
AttributedText(
'hello',
AttributedSpans(
attributions: const [
SpanMarker(attribution: _emojiAttribution, offset: 0, markerType: SpanMarkerType.start),
SpanMarker(attribution: _emojiAttribution, offset: 0, markerType: SpanMarkerType.end),
],
),
{
0: const _TestPlaceholder(),
},
),
);
});
});
}

Expand Down Expand Up @@ -165,3 +217,5 @@ Widget? _buildInlineTestWidget(BuildContext context, TextStyle style, Object pla
class _TestPlaceholder {
const _TestPlaceholder();
}

const _emojiAttribution = NamedAttribution('emoji');

0 comments on commit 407da3f

Please sign in to comment.