From 3a7aef0329747c45cdf0eea03158d14aaa329d08 Mon Sep 17 00:00:00 2001 From: Joshua Matthews Date: Mon, 21 Nov 2022 15:12:01 -0500 Subject: [PATCH] Changing AttributedSpans and related public APIs to use exclusive end indexes --- attributed_text/lib/src/attributed_spans.dart | 152 ++++---- attributed_text/lib/src/attributed_text.dart | 31 +- attributed_text/lib/src/span_range.dart | 3 + .../test/attributed_spans_test.dart | 338 +++++++++++------- .../test/attributed_text_test.dart | 106 +++--- .../demos/components/demo_text_with_hint.dart | 5 +- .../demos/demo_markdown_serialization.dart | 13 +- .../lib/demos/example_editor/_toolbar.dart | 4 +- .../supertextfield/_interactive_demo.dart | 8 +- .../common_editor_operations.dart | 4 +- super_editor/lib/src/default_editor/text.dart | 13 +- .../attributed_text_styles.dart | 2 +- .../attributed_text_editing_controller.dart | 6 +- .../test/src/attributed_text_styles_test.dart | 53 +-- .../common_editor_operations_test.dart | 20 +- .../document_input_ime_test.dart | 13 +- .../document_keyboard_actions_test.dart | 11 +- .../super_editor_content_conversion_test.dart | 11 +- .../supereditor_components_test.dart | 7 +- ...tributed_text_editing_controller_test.dart | 27 +- ...tributed_text_editing_controller_test.dart | 12 +- .../lib/src/markdown_to_document_parsing.dart | 10 +- .../test/attributed_text_markdown_test.dart | 26 +- .../test/custom_block_serializer_test.dart | 7 +- .../test/super_editor_markdown_test.dart | 113 +++--- website/lib/homepage/featured_editor.dart | 22 +- 26 files changed, 519 insertions(+), 498 deletions(-) diff --git a/attributed_text/lib/src/attributed_spans.dart b/attributed_text/lib/src/attributed_spans.dart index db8017263..8c9766a78 100644 --- a/attributed_text/lib/src/attributed_spans.dart +++ b/attributed_text/lib/src/attributed_spans.dart @@ -43,8 +43,17 @@ class AttributedSpans { /// [attributions] may be omitted to create an [AttributedSpans] /// with no spans. AttributedSpans({ - List? attributions, - }) : markers = [...?attributions] { + List? attributions, + }) : markers = (attributions ?? []) + .expand((span) => [ + SpanMarker(attribution: span.attribution, offset: span.start, markerType: SpanMarkerType.start), + SpanMarker(attribution: span.attribution, offset: span.end - 1, markerType: SpanMarkerType.end), + ]) + .toList() { + _sortAttributions(); + } + + AttributedSpans._fromMarkers(List markers) : markers = [...markers] { _sortAttributions(); } @@ -58,14 +67,14 @@ class AttributedSpans { /// Returns `true` if this [AttributedSpans] contains at least one /// unit of attribution for each of the given [attributions] - /// within the given range (inclusive). + /// between start (inclusive) and end (exclusive). bool hasAttributionsWithin({ required Set attributions, required int start, required int end, }) { final attributionsToFind = Set.from(attributions); - for (int i = start; i <= end; ++i) { + for (int i = start; i < end; ++i) { for (final attribution in attributionsToFind) { if (hasAttributionAt(i, attribution: attribution)) { attributionsToFind.remove(attribution); @@ -88,18 +97,12 @@ class AttributedSpans { required int start, required int end, }) { - final matchingAttributions = {}; - for (int i = start; i <= end; ++i) { - for (final attribution in attributions) { - final otherAttributions = getAllAttributionsAt(start); - for (final otherAttribution in otherAttributions) { - if (otherAttribution.id == attribution.id) { - matchingAttributions.add(otherAttribution); - } - } - } - } - return matchingAttributions; + final ids = attributions.map((attr) => attr.id).toSet(); + return getAttributionSpansInRange( + attributionFilter: (attr) => ids.contains(attr.id), + start: start, + end: end, + ).map((span) => span.attribution).toSet(); } /// Returns `true` if the given [offset] has the given [attribution]. @@ -128,7 +131,7 @@ class AttributedSpans { /// For example, imagine spans applied to text like this: "Hello, |world!|". /// The text between the bars has a "bold" attribution. Invoking this method /// with the "bold" attribution and an offset of `10` would return an - /// `AttributionSpan` of "bold" from `7` to `14`. + /// `AttributionSpan` of "bold" from `7` to `15`. AttributionSpan expandAttributionToSpan({ required Attribution attribution, required int offset, @@ -147,7 +150,7 @@ class AttributedSpans { return AttributionSpan( attribution: attribution, start: markerBefore.offset, - end: markerAfter.offset, + end: markerAfter.offset + 1, ); } @@ -170,7 +173,7 @@ class AttributedSpans { } /// Returns spans for each attribution that (at least partially) appear - /// between [start] and [end], inclusive, as selected by [attributionFilter]. + /// between [start] (inclusive) and [end] (exclusive), as selected by [attributionFilter]. /// /// By default, the returned spans represent the full, contiguous span /// of each attribution. This means that if a portion of an attribution @@ -190,7 +193,7 @@ class AttributedSpans { final matchingAttributionSpans = {}; // For every unit in the given range... - for (int i = start; i <= end; ++i) { + for (int i = start; i < end; ++i) { final attributionsAtOffset = getAllAttributionsAt(i); // For every attribution overlaps this unit... for (final attribution in attributionsAtOffset) { @@ -249,7 +252,7 @@ class AttributedSpans { // that all desired attributions are present at the given offset. return SpanRange( start: maxStartMarkerOffset!, - end: minEndMarkerOffset!, + end: minEndMarkerOffset! + 1, ); } @@ -278,7 +281,7 @@ class AttributedSpans { .firstWhereOrNull((marker) => marker.isEnd && marker.offset >= offset); } - /// Applies the [newAttribution] from [start] to [end], inclusive. + /// Applies the [newAttribution] from [start] (inclusive) to [end] (exclusive). /// /// If [newAttribution] spans already exist at [start] or [end], and those /// spans are compatible, the spans are expanded to include the new region @@ -291,7 +294,7 @@ class AttributedSpans { required int start, required int end, }) { - if (start < 0 || start > end) { + if (start < 0 || start >= end) { _log.warning("Tried to add an attribution ($newAttribution) at an invalid start/end: $start -> $end"); return; } @@ -306,7 +309,7 @@ class AttributedSpans { for (final matchingAttribution in matchingAttributions) { if (!newAttribution.canMergeWith(matchingAttribution)) { late int conflictStart; - for (int i = start; i <= end; ++i) { + for (int i = start; i < end; ++i) { if (hasAttributionAt(i, attribution: matchingAttribution)) { conflictStart = i; break; @@ -355,7 +358,7 @@ class AttributedSpans { final markersToDelete = markers .where((attribution) => attribution.attribution == newAttribution) .where((attribution) => attribution.offset > start) - .where((attribution) => attribution.offset <= end) + .where((attribution) => attribution.offset < end) .toList(); _log.fine('Removing ${markersToDelete.length} markers between $start and $end'); markers.removeWhere((element) => markersToDelete.contains(element)); @@ -372,7 +375,7 @@ class AttributedSpans { _log.fine('Inserting ending marker at: $end'); _insertMarker(SpanMarker( attribution: newAttribution, - offset: end, + offset: end - 1, markerType: SpanMarkerType.end, )); } @@ -390,15 +393,15 @@ class AttributedSpans { }()); } - /// Removes [attributionToRemove] between [start] and [end], inclusive. + /// Removes [attributionToRemove] between [start] (inclusive) and [end] (exclusive). void removeAttribution({ required Attribution attributionToRemove, required int start, required int end, }) { _log.info('Removing attribution $attributionToRemove from $start to $end'); - if (start < 0 || start > end) { - throw Exception('removeAttribution() did not satisfy start < 0 and start > end, start: $start, end: $end'); + if (start < 0 || start >= end) { + throw Exception('removeAttribution() did not satisfy 0 <= start < end, start: $start, end: $end'); } if (!hasAttributionsWithin(attributions: {attributionToRemove}, start: start, end: end)) { @@ -444,13 +447,13 @@ class AttributedSpans { // Determine if we need to insert a new end-cap after the // removal region. - if (hasAttributionAt(end + 1, attribution: attributionToRemove)) { - final markersAtEnd = _getMarkerAt(attributionToRemove, end + 1, SpanMarkerType.start); + if (hasAttributionAt(end, attribution: attributionToRemove)) { + final markersAtEnd = _getMarkerAt(attributionToRemove, end, SpanMarkerType.start); if (markersAtEnd.isEmpty) { - _log.finer('Creating a new "start" marker to appear after the removal range at ${end + 1}'); + _log.finer('Creating a new "start" marker to appear after the removal range at ${end}'); endCapMarkersToInsert.add(SpanMarker( attribution: attributionToRemove, - offset: end + 1, + offset: end, markerType: SpanMarkerType.start, )); } @@ -468,7 +471,7 @@ class AttributedSpans { final markersToDelete = markers .where((attribution) => attribution.attribution == attributionToRemove) .where((attribution) => attribution.offset >= start) - .where((attribution) => attribution.offset <= end) + .where((attribution) => attribution.offset < end) .toList(); _log.finer('removing ${markersToDelete.length} markers between $start and $end'); markers.removeWhere((element) => markersToDelete.contains(element)); @@ -479,9 +482,9 @@ class AttributedSpans { }); } - /// If ALL of the units between [start] and [end], inclusive, contain the + /// If ALL of the units between [start] (inclusive) and [end] (exclusive) contain the /// given [attribution], that attribution is removed from those units. - /// Otherwise, all of the units between [start] and [end], inclusive, + /// Otherwise, all of the units between [start] and [end], /// are assigned the [attribution]. void toggleAttribution({ required dynamic attribution, @@ -496,8 +499,8 @@ class AttributedSpans { } } - /// Returns [true] if the given [attribution] exists from [start] to - /// [end], inclusive, without any breaks in between. Otherwise, returns + /// Returns [true] if the given [attribution] exists from [start] (inclusive) to + /// [end] (exclusive), without any breaks in between. Otherwise, returns /// [false]. bool _isContinuousAttribution({ required Attribution attribution, @@ -533,7 +536,7 @@ class AttributedSpans { // If there is even one additional marker in the `range` // of interest, it means that the given attribution is // not applied to the entire range. - return nextMarker.offset >= end; + return nextMarker.offset >= end - 1; } /// Finds and returns the nearest marker that appears at or before the @@ -667,14 +670,14 @@ class AttributedSpans { } } - /// Returns of a copy of this [AttributedSpans] between [startOffset] - /// and [endOffset]. + /// Returns of a copy of this [AttributedSpans] between [startOffset] (inclusive) + /// and [endOffset] (exclusive). /// /// If no [endOffset] is provided, a copy is made from [startOffset] /// to the [offset] of the last marker in this [AttributedSpans]. AttributedSpans copyAttributionRegion(int startOffset, [int? endOffset]) { - endOffset = endOffset ?? markers.lastOrNull?.offset ?? 0; - _log.fine('start: $startOffset, end: $endOffset'); + final effectiveEndOffset = endOffset == null ? markers.lastOrNull?.offset ?? 0 : endOffset - 1; + _log.fine('start: $startOffset, end: $effectiveEndOffset'); final List cutAttributions = []; @@ -723,7 +726,7 @@ class AttributedSpans { // Directly copy every marker that appears within the cut // region. markers // - .where((marker) => startOffset <= marker.offset && marker.offset <= endOffset!) // + .where((marker) => startOffset <= marker.offset && marker.offset <= effectiveEndOffset) // .forEach((marker) { _log.fine('copying "${marker.attribution}" at ${marker.offset} from original AttributionSpans to copy region.'); cutAttributions.add(marker.copyWith( @@ -736,11 +739,11 @@ class AttributedSpans { // `end` markers at the end of the copy range. markers // .reversed // - .where((marker) => marker.offset > endOffset!) // + .where((marker) => marker.offset > effectiveEndOffset) // .forEach((marker) { _log.fine('marker after the copy region: $marker'); - // Track any markers that end after the `endOffset` - // and start before `endOffset`. + // Track any markers that end after the `effectiveEndOffset + // and start before `effectiveEndOffset. if (marker.isEnd) { _log.fine('remembering this marker to insert in copied region'); foundEndMarkers.putIfAbsent(marker.attribution, () => 0); @@ -761,11 +764,12 @@ class AttributedSpans { _log.fine('inserting "$markerAttribution" marker at end of copy region to maintain symmetry.'); cutAttributions.add(SpanMarker( attribution: markerAttribution, - offset: endOffset! - startOffset, + offset: effectiveEndOffset - startOffset, markerType: SpanMarkerType.end, )); } else if (count < 0 || count > 1) { - throw Exception('Found an unbalanced number of `start` and `end` markers after offset: $endOffset - $markers'); + throw Exception( + 'Found an unbalanced number of `start` and `end` markers after offset: $effectiveEndOffset - $markers'); } }); @@ -774,7 +778,7 @@ class AttributedSpans { _log.fine(' - $attribution'); } - return AttributedSpans(attributions: cutAttributions); + return AttributedSpans._fromMarkers(cutAttributions); } /// Changes all spans in this [AttributedSpans] by pushing @@ -866,8 +870,8 @@ class AttributedSpans { /// Returns a copy of this [AttributedSpans]. AttributedSpans copy() { - return AttributedSpans( - attributions: List.from(markers), + return AttributedSpans._fromMarkers( + List.from(markers), ); } @@ -892,11 +896,11 @@ class AttributedSpans { if (markers.isEmpty || markers.first.offset > contentLength - 1) { // There is content but no attributions that apply to it. - return [MultiAttributionSpan(attributions: {}, start: 0, end: contentLength - 1)]; + return [MultiAttributionSpan(attributions: {}, start: 0, end: contentLength)]; } final collapsedSpans = []; - var currentSpan = MultiAttributionSpan(attributions: {}, start: 0, end: contentLength - 1); + var currentSpan = MultiAttributionSpan(attributions: {}, start: 0, end: contentLength); _log.fine('walking list of markers to determine collapsed spans.'); for (final marker in markers) { @@ -916,9 +920,9 @@ class AttributedSpans { // Calculate the end of the current span. // - // If the current marker is an end marker, then the current span at that marker. Otherwise, if the - // marker is an start marker, the current span ends 1 unit before the marker. - final currentEnd = marker.isEnd ? marker.offset : marker.offset - 1; + // The conditional offset adjustments are to accounts for end indexes being inclusive in [SpanMarker] but + // exclusive in [MultiAttributionSpan]. + final currentEnd = marker.isEnd ? marker.offset + 1 : marker.offset; // Commit the completed span. collapsedSpans.add(currentSpan.copyWith(end: currentEnd)); @@ -930,7 +934,7 @@ class AttributedSpans { // marker is an end marker, the next span begins 1 unit after the marker. final nextStart = marker.isStart ? marker.offset : marker.offset + 1; - // Create the next span and continue consumeing markers + // Create the next span and continue consuming markers currentSpan = currentSpan.copyWith(start: nextStart); _log.fine('new current span is $currentSpan'); } @@ -948,7 +952,7 @@ class AttributedSpans { } } - if (collapsedSpans.isNotEmpty && collapsedSpans.last.end < contentLength - 1) { + if (collapsedSpans.isEmpty || collapsedSpans.last.end < contentLength) { // The last span committed during the loop does not reach the end of the requested content range. We either ran // out of markers or the remaining markers are outside the content range. In both cases the value in currentSpan // should already have the correct start, end, and attributions values to cover the remaining content. @@ -984,6 +988,7 @@ class AttributedSpans { /// /// The given [AttributionType] must implement equality for /// span management to work correctly. +@visibleForTesting class SpanMarker implements Comparable { /// Constructs a [SpanMarker] with the given [attribution], [offset] within /// some discrete content, and [markerType] of [start] or [end]. @@ -1052,12 +1057,13 @@ class SpanMarker implements Comparable { } /// The type of a marker within a span, either [start] or [end]. +@visibleForTesting enum SpanMarkerType { start, end, } -/// An [Attribution] span from [start] to [end], inclusive. +/// An [Attribution] span from [start] (inclusive) to [end] (exclusive). class AttributionSpan { const AttributionSpan({ required this.attribution, @@ -1066,9 +1072,22 @@ class AttributionSpan { }); final Attribution attribution; + + /// The start of the span, inclusive. + /// + /// Content at this index will have [attribution] applied. final int start; + + /// The end of the span, exclusive. + /// + /// Content at this index will _not_ have [attribution] applied. final int end; + /// The end of the span, inclusive. + /// + /// Content at this index will have [attribution] applied. + int get endIndex => end - 1; + AttributionSpan constrain({ required int start, required int end, @@ -1121,9 +1140,22 @@ class MultiAttributionSpan { }); final Set attributions; + + /// The start of the span, inclusive. + /// + /// Content at this index will have [attributions] applied. final int start; + + /// The end of the span, exclusive. + /// + /// Content at this index will _not_ have [attributions] applied. final int end; + /// The end of the span, inclusive. + /// + /// Content at this index will have [attributions] applied. + int get endIndex => end - 1; + MultiAttributionSpan copyWith({ Set? attributions, int? start, diff --git a/attributed_text/lib/src/attributed_text.dart b/attributed_text/lib/src/attributed_text.dart index 9eebed344..beb8f6dfe 100644 --- a/attributed_text/lib/src/attributed_text.dart +++ b/attributed_text/lib/src/attributed_text.dart @@ -63,7 +63,7 @@ class AttributedText { /// Returns true if this [AttributedText] contains at least one /// character with each of the given [attributions] within the - /// given [range] (inclusive). + /// given [range]. bool hasAttributionsWithin({ required Set attributions, required SpanRange range, @@ -76,12 +76,12 @@ class AttributedText { } /// Returns true if this [AttributedText] contains each of the - /// given [attributions] throughout the given [range] (inclusive). + /// given [attributions] throughout the given [range]. bool hasAttributionsThroughout({ required Set attributions, required SpanRange range, }) { - for (int i = range.start; i <= range.end; i += 1) { + for (int i = range.start; i < range.end; i += 1) { for (final attribution in attributions) { if (!spans.hasAttributionAt(i, attribution: attribution)) { return false; @@ -150,14 +150,14 @@ class AttributedText { } /// Adds the given [attribution] to all characters within the given - /// [range], inclusive. + /// [range]. void addAttribution(Attribution attribution, SpanRange range) { spans.addAttribution(newAttribution: attribution, start: range.start, end: range.end); _notifyListeners(); } /// Removes the given [attribution] from all characters within the - /// given [range], inclusive. + /// given [range]. void removeAttribution(Attribution attribution, SpanRange range) { spans.removeAttribution(attributionToRemove: attribution, start: range.start, end: range.end); _notifyListeners(); @@ -180,29 +180,27 @@ class AttributedText { } } - /// If ALL of the text in [range], inclusive, contains the given [attribution], - /// that [attribution] is removed from the text in [range], inclusive. - /// Otherwise, all of the text in [range], inclusive, is given the [attribution]. + /// If ALL of the text in [range], contains the given [attribution], + /// that [attribution] is removed from the text in [range]. + /// Otherwise, all of the text in [range] is given the [attribution]. void toggleAttribution(Attribution attribution, SpanRange range) { spans.toggleAttribution(attribution: attribution, start: range.start, end: range.end); _notifyListeners(); } - /// Copies all text and attributions from [startOffset] to - /// [endOffset], inclusive, and returns them as a new [AttributedText]. + /// Copies all text and attributions from [startOffset] (inclusive) to + /// [endOffset] (exclusive), and returns them as a new [AttributedText]. AttributedText copyText(int startOffset, [int? endOffset]) { _log.fine('start: $startOffset, end: $endOffset'); - // 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; int endCopyOffset; if (endOffset == startOffset) { endCopyOffset = startCopyOffset; } else if (endOffset != null) { - endCopyOffset = endOffset - 1; + endCopyOffset = endOffset; } else { - endCopyOffset = text.length - 1; + endCopyOffset = text.length; } _log.fine('offsets, start: $startCopyOffset, end: $endCopyOffset'); @@ -276,7 +274,7 @@ class AttributedText { final insertedText = AttributedText( text: textToInsert, ); - final insertTextRange = SpanRange(start: 0, end: textToInsert.length - 1); + final insertTextRange = SpanRange(start: 0, end: textToInsert.length); for (dynamic attribution in applyAttributions) { insertedText.addAttribution(attribution, insertTextRange); } @@ -422,7 +420,8 @@ class CallbackAttributionVisitor implements AttributionVisitor { } @override - void visitAttributions(AttributedText fullText, int index, Set startingAttributions, Set endingAttributions) { + void visitAttributions( + AttributedText fullText, int index, Set startingAttributions, Set endingAttributions) { _onVisitAttributions(fullText, index, startingAttributions, endingAttributions); } diff --git a/attributed_text/lib/src/span_range.dart b/attributed_text/lib/src/span_range.dart index 57e526588..2c12a86f3 100644 --- a/attributed_text/lib/src/span_range.dart +++ b/attributed_text/lib/src/span_range.dart @@ -40,6 +40,9 @@ class SpanRange { /// If [start] and [end] are both -1, the text range is empty. final int end; + /// The index of the last character in the range. + int get endIndex => end - 1; + /// Whether this range represents a valid position in the text. bool get isValid => start >= 0 && end >= 0; diff --git a/attributed_text/test/attributed_spans_test.dart b/attributed_text/test/attributed_spans_test.dart index adc5ce286..c76462074 100644 --- a/attributed_text/test/attributed_spans_test.dart +++ b/attributed_text/test/attributed_spans_test.dart @@ -161,10 +161,16 @@ void main() { test('returns the range of a single attribution for an offset in the middle of a span', () { final spans = AttributedSpans( attributions: [ - SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.italics, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.italics, offset: 10, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: ExpectedSpans.bold, + start: 4, + end: 9, + ), + AttributionSpan( + attribution: ExpectedSpans.italics, + start: 0, + end: 10, + ), ], ); @@ -175,10 +181,16 @@ void main() { test('returns the range of a single attribution for an offset at the beginning of a span', () { final spans = AttributedSpans( attributions: [ - SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.italics, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.italics, offset: 10, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: ExpectedSpans.bold, + start: 4, + end: 9, + ), + AttributionSpan( + attribution: ExpectedSpans.italics, + start: 0, + end: 10, + ), ], ); @@ -189,26 +201,41 @@ void main() { test('returns the range of a single attribution for an offset at the end of a span', () { final spans = AttributedSpans( attributions: [ - SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.italics, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.italics, offset: 10, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: ExpectedSpans.bold, + start: 4, + end: 9, + ), + AttributionSpan( + attribution: ExpectedSpans.italics, + start: 0, + end: 10, + ), ], ); - final range = spans.getAttributedRange({ExpectedSpans.bold}, 9); + final range = spans.getAttributedRange({ExpectedSpans.bold}, 8); expect(range, SpanRange(start: 4, end: 9)); }); test('returns the range for multiple attributions for an offset in the middle of the overlapping range', () { final spans = AttributedSpans( attributions: [ - SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.italics, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.strikethrough, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.strikethrough, offset: 10, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: ExpectedSpans.bold, + start: 4, + end: 9, + ), + AttributionSpan( + attribution: ExpectedSpans.italics, + start: 0, + end: 7, + ), + AttributionSpan( + attribution: ExpectedSpans.strikethrough, + start: 0, + end: 10, + ), ], ); @@ -219,12 +246,21 @@ void main() { test('returns the range for multiple attributions for an offset at the beginning of the overlapping range', () { final spans = AttributedSpans( attributions: [ - SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.italics, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.strikethrough, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.strikethrough, offset: 10, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: ExpectedSpans.bold, + start: 4, + end: 9, + ), + AttributionSpan( + attribution: ExpectedSpans.italics, + start: 0, + end: 7, + ), + AttributionSpan( + attribution: ExpectedSpans.strikethrough, + start: 0, + end: 10, + ), ], ); @@ -235,16 +271,25 @@ void main() { test('returns the range for multiple attributions for an offset at the end of the overlapping range', () { final spans = AttributedSpans( attributions: [ - SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.italics, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.strikethrough, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.strikethrough, offset: 10, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: ExpectedSpans.bold, + start: 4, + end: 9, + ), + AttributionSpan( + attribution: ExpectedSpans.italics, + start: 0, + end: 7, + ), + AttributionSpan( + attribution: ExpectedSpans.strikethrough, + start: 0, + end: 10, + ), ], ); - final range = spans.getAttributedRange({ExpectedSpans.bold, ExpectedSpans.italics}, 7); + final range = spans.getAttributedRange({ExpectedSpans.bold, ExpectedSpans.italics}, 6); expect(range, SpanRange(start: 4, end: 7)); }); @@ -257,8 +302,11 @@ void main() { test('throws when any attribution is not present at the given offset', () { final spans = AttributedSpans( attributions: [ - SpanMarker(attribution: ExpectedSpans.bold, offset: 6, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.bold, offset: 10, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: ExpectedSpans.bold, + start: 6, + end: 10, + ), ], ); @@ -267,6 +315,39 @@ void main() { }); }); + group('getMatchingAttributionsWithin', () { + final spans = AttributedSpans() + ..addAttribution(newAttribution: ExpectedSpans.bold, start: 3, end: 10) + ..addAttribution(newAttribution: ExpectedSpans.italics, start: 7, end: 15); + + test('query outside any spans', () { + final matching = spans.getMatchingAttributionsWithin(attributions: { + ExpectedSpans.bold, + ExpectedSpans.italics, + }, start: 0, end: 2); + expect(matching, isEmpty); + }); + + test('query a single span', () { + final matching = spans.getMatchingAttributionsWithin(attributions: { + ExpectedSpans.bold, + ExpectedSpans.italics, + }, start: 3, end: 5); + expect(matching, hasLength(1)); + expect(matching, contains(ExpectedSpans.bold)); + }); + + test('query within multiple spans', () { + final matching = spans.getMatchingAttributionsWithin(attributions: { + ExpectedSpans.bold, + ExpectedSpans.italics, + }, start: 6, end: 9); + expect(matching, hasLength(2)); + expect(matching, contains(ExpectedSpans.bold)); + expect(matching, contains(ExpectedSpans.italics)); + }); + }); + group('single attribution', () { test('applies attribution to full span', () { final spans = AttributedSpans()..addAttribution(newAttribution: ExpectedSpans.bold, start: 0, end: 16); @@ -303,9 +384,8 @@ void main() { test('removes attribution from full span', () { final spans = AttributedSpans( - attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 16, markerType: SpanMarkerType.end) + attributions: const [ + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 16), ], )..removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 0, end: 16); @@ -314,9 +394,8 @@ void main() { test('removes attribution from single unit', () { final spans = AttributedSpans( - attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 8, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 8, markerType: SpanMarkerType.end) + attributions: const [ + AttributionSpan(attribution: ExpectedSpans.bold, start: 8, end: 9), ], ); @@ -324,7 +403,7 @@ void main() { '________b_______', ]).expectSpans(spans); - spans.removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 8, end: 8); + spans.removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 8, end: 9); ExpectedSpans([ '________________', @@ -334,8 +413,7 @@ void main() { test('removes attribution from single unit at end of span', () { final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 8, markerType: SpanMarkerType.end) + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 8), ], ); @@ -343,7 +421,7 @@ void main() { 'bbbbbbbb_______', ]).expectSpans(spans); - spans.removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 8, end: 8); + spans.removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 8, end: 9); ExpectedSpans([ 'bbbbbbb_________', @@ -353,8 +431,7 @@ void main() { test('removes attribution from all units except the last', () { final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 8, markerType: SpanMarkerType.end) + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 9), ], ); @@ -372,8 +449,7 @@ void main() { test('removes attribution from single unit at start of span', () { final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 8, markerType: SpanMarkerType.end) + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 8), ], ); @@ -381,7 +457,7 @@ void main() { 'bbbbbbbb_______', ]).expectSpans(spans); - spans.removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 0, end: 0); + spans.removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 0, end: 1); ExpectedSpans([ '_bbbbbbb_______', @@ -391,8 +467,7 @@ void main() { test('removes attribution from all units except the first', () { final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 8, markerType: SpanMarkerType.end) + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 8), ], ); @@ -410,8 +485,7 @@ void main() { test('removes attribution from inner text span', () { final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 7, markerType: SpanMarkerType.end) + AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 7), ], )..removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 2, end: 7); @@ -421,8 +495,7 @@ void main() { test('removes attribution from partial beginning span', () { final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 7, markerType: SpanMarkerType.end) + AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 7), ], )..removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 2, end: 4); @@ -432,8 +505,7 @@ void main() { test('removes attribution from partial inner span', () { final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 7, markerType: SpanMarkerType.end) + AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 7), ], )..removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 4, end: 5); @@ -444,8 +516,7 @@ void main() { test('removes attribution from partial ending span', () { final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 7, markerType: SpanMarkerType.end) + AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 7), ], )..removeAttribution(attributionToRemove: ExpectedSpans.bold, start: 5, end: 7); @@ -455,8 +526,7 @@ void main() { test('applies attribution when mixed span is toggled', () { final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 8, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 16, markerType: SpanMarkerType.end) + AttributionSpan(attribution: ExpectedSpans.bold, start: 8, end: 16), ], )..toggleAttribution(attribution: ExpectedSpans.bold, start: 0, end: 16); @@ -466,8 +536,7 @@ void main() { test('removes attribution when contiguous span is toggled', () { final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 16, markerType: SpanMarkerType.end) + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 16), ], )..toggleAttribution(attribution: ExpectedSpans.bold, start: 0, end: 16); @@ -478,8 +547,8 @@ void main() { group('multiple attributions', () { test('full length overlap', () { final spans = AttributedSpans() - ..addAttribution(newAttribution: ExpectedSpans.bold, start: 0, end: 9) - ..addAttribution(newAttribution: ExpectedSpans.italics, start: 0, end: 9); + ..addAttribution(newAttribution: ExpectedSpans.bold, start: 0, end: 10) + ..addAttribution(newAttribution: ExpectedSpans.italics, start: 0, end: 10); ExpectedSpans([ 'bbbbbbbbbb', @@ -489,8 +558,8 @@ void main() { test('half and half', () { final spans = AttributedSpans() - ..addAttribution(newAttribution: ExpectedSpans.bold, start: 5, end: 9) - ..addAttribution(newAttribution: ExpectedSpans.italics, start: 0, end: 4); + ..addAttribution(newAttribution: ExpectedSpans.bold, start: 5, end: 10) + ..addAttribution(newAttribution: ExpectedSpans.italics, start: 0, end: 5); ExpectedSpans([ '_____bbbbb', @@ -500,8 +569,8 @@ void main() { test('two partial overlap', () { final spans = AttributedSpans() - ..addAttribution(newAttribution: ExpectedSpans.bold, start: 4, end: 8) - ..addAttribution(newAttribution: ExpectedSpans.italics, start: 1, end: 5); + ..addAttribution(newAttribution: ExpectedSpans.bold, start: 4, end: 9) + ..addAttribution(newAttribution: ExpectedSpans.italics, start: 1, end: 6); ExpectedSpans([ '____bbbbb_', @@ -511,9 +580,9 @@ void main() { test('three partial overlap', () { final spans = AttributedSpans() - ..addAttribution(newAttribution: ExpectedSpans.bold, start: 4, end: 8) - ..addAttribution(newAttribution: ExpectedSpans.italics, start: 1, end: 5) - ..addAttribution(newAttribution: ExpectedSpans.strikethrough, start: 5, end: 9); + ..addAttribution(newAttribution: ExpectedSpans.bold, start: 4, end: 9) + ..addAttribution(newAttribution: ExpectedSpans.italics, start: 1, end: 6) + ..addAttribution(newAttribution: ExpectedSpans.strikethrough, start: 5, end: 10); ExpectedSpans([ '____bbbbb_', @@ -524,11 +593,11 @@ void main() { test('many small segments', () { final spans = AttributedSpans() - ..addAttribution(newAttribution: ExpectedSpans.bold, start: 0, end: 1) - ..addAttribution(newAttribution: ExpectedSpans.italics, start: 2, end: 3) - ..addAttribution(newAttribution: ExpectedSpans.strikethrough, start: 4, end: 5) - ..addAttribution(newAttribution: ExpectedSpans.bold, start: 6, end: 7) - ..addAttribution(newAttribution: ExpectedSpans.italics, start: 8, end: 9); + ..addAttribution(newAttribution: ExpectedSpans.bold, start: 0, end: 2) + ..addAttribution(newAttribution: ExpectedSpans.italics, start: 2, end: 4) + ..addAttribution(newAttribution: ExpectedSpans.strikethrough, start: 4, end: 6) + ..addAttribution(newAttribution: ExpectedSpans.bold, start: 6, end: 8) + ..addAttribution(newAttribution: ExpectedSpans.italics, start: 8, end: 10); ExpectedSpans([ 'bb____bb__', @@ -544,7 +613,7 @@ void main() { spans.addAttribution( newAttribution: _LinkAttribution(url: 'https://flutter.dev'), start: 0, - end: 6, + end: 7, ); // Try to add a different link at the end but overlapping @@ -590,48 +659,72 @@ void main() { final collapsedSpans = AttributedSpans().collapseSpans(contentLength: 10); expect(collapsedSpans, hasLength(1)); expect(collapsedSpans.first.start, 0); - expect(collapsedSpans.first.end, 9); + expect(collapsedSpans.first.end, 10); expect(collapsedSpans.first.attributions, isEmpty); }); - test('single continuous attribution', () { - final collapsedSpans = AttributedSpans( + group('single continuous attribution', () { + final spans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 16, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 16), ], - ).collapseSpans(contentLength: 17); + ); - expect(collapsedSpans.length, 1); - expect(collapsedSpans.first.start, 0); - expect(collapsedSpans.first.end, 16); - expect(collapsedSpans.first.attributions.length, 1); - expect(collapsedSpans.first.attributions.first, ExpectedSpans.bold); + test('collapse at span end', () { + final collapsedSpans = spans.collapseSpans(contentLength: 16); + + expect(collapsedSpans, hasLength(1)); + expect(collapsedSpans.first.start, 0); + expect(collapsedSpans.first.end, 16); + expect(collapsedSpans.first.attributions.length, 1); + expect(collapsedSpans.first.attributions.first, ExpectedSpans.bold); + }); + + test('collapse before span end', () { + final collapsedSpans = spans.collapseSpans(contentLength: 10); + + expect(collapsedSpans.length, 1); + expect(collapsedSpans.first.start, 0); + expect(collapsedSpans.first.end, 10); + expect(collapsedSpans.first.attributions.length, 1); + expect(collapsedSpans.first.attributions.first, ExpectedSpans.bold); + }); + + test('collapse after span end', () { + final collapsedSpans = spans.collapseSpans(contentLength: 20); + + expect(collapsedSpans.length, 2); + expect(collapsedSpans[0].start, 0); + expect(collapsedSpans[0].end, 16); + expect(collapsedSpans[0].attributions.length, 1); + expect(collapsedSpans[0].attributions.first, ExpectedSpans.bold); + expect(collapsedSpans[1].start, 16); + expect(collapsedSpans[1].end, 20); + expect(collapsedSpans[1].attributions, isEmpty); + }); }); test('single fractured attribution', () { final collapsedSpans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 3, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 7, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 10, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 4), + AttributionSpan(attribution: ExpectedSpans.bold, start: 7, end: 10), ], - ).collapseSpans(contentLength: 17); + ).collapseSpans(contentLength: 16); expect(collapsedSpans.length, 4); expect(collapsedSpans[0].start, 0); - expect(collapsedSpans[0].end, 3); + expect(collapsedSpans[0].end, 4); expect(collapsedSpans[0].attributions.length, 1); expect(collapsedSpans[0].attributions.first, ExpectedSpans.bold); expect(collapsedSpans[1].start, 4); - expect(collapsedSpans[1].end, 6); + expect(collapsedSpans[1].end, 7); expect(collapsedSpans[1].attributions.length, 0); expect(collapsedSpans[2].start, 7); expect(collapsedSpans[2].end, 10); expect(collapsedSpans[2].attributions.length, 1); expect(collapsedSpans[2].attributions.first, ExpectedSpans.bold); - expect(collapsedSpans[3].start, 11); + expect(collapsedSpans[3].start, 10); expect(collapsedSpans[3].end, 16); expect(collapsedSpans[3].attributions.length, 0); }); @@ -639,67 +732,61 @@ void main() { test('adjacent non-overlapping attributions', () { final collapsedSpans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 5, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 9, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 5), + AttributionSpan(attribution: ExpectedSpans.italics, start: 5, end: 10), ], ).collapseSpans(contentLength: 10); expect(collapsedSpans, hasLength(2)); expect(collapsedSpans.first.start, 0); - expect(collapsedSpans.first.end, 4); + expect(collapsedSpans.first.end, 5); expect(collapsedSpans.last.start, 5); - expect(collapsedSpans.last.end, 9); + expect(collapsedSpans.last.end, 10); }); test('multiple non-overlapping attributions', () { final collapsedSpans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 3, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 10, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 4), + AttributionSpan(attribution: ExpectedSpans.italics, start: 7, end: 10), ], ).collapseSpans(contentLength: 17); expect(collapsedSpans.length, 4); expect(collapsedSpans[0].start, 0); - expect(collapsedSpans[0].end, 3); + expect(collapsedSpans[0].end, 4); expect(collapsedSpans[0].attributions.length, 1); expect(collapsedSpans[0].attributions.first, ExpectedSpans.bold); expect(collapsedSpans[1].start, 4); - expect(collapsedSpans[1].end, 6); + expect(collapsedSpans[1].end, 7); expect(collapsedSpans[1].attributions.length, 0); expect(collapsedSpans[2].start, 7); expect(collapsedSpans[2].end, 10); expect(collapsedSpans[2].attributions.length, 1); expect(collapsedSpans[2].attributions.first, ExpectedSpans.italics); - expect(collapsedSpans[3].start, 11); - expect(collapsedSpans[3].end, 16); + expect(collapsedSpans[3].start, 10); + expect(collapsedSpans[3].end, 17); expect(collapsedSpans[3].attributions.length, 0); }); test('multiple overlapping attributions', () { final collapsedSpans = AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 8, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 6, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 16, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 8), + AttributionSpan(attribution: ExpectedSpans.italics, start: 6, end: 16), ], - ).collapseSpans(contentLength: 17); + ).collapseSpans(contentLength: 16); expect(collapsedSpans.length, 3); expect(collapsedSpans[0].start, 0); - expect(collapsedSpans[0].end, 5); + expect(collapsedSpans[0].end, 6); expect(collapsedSpans[0].attributions.length, 1); expect(collapsedSpans[0].attributions.first, ExpectedSpans.bold); expect(collapsedSpans[1].start, 6); expect(collapsedSpans[1].end, 8); expect(collapsedSpans[1].attributions.length, 2); expect(collapsedSpans[1].attributions, equals({ExpectedSpans.bold, ExpectedSpans.italics})); - expect(collapsedSpans[2].start, 9); + expect(collapsedSpans[2].start, 8); expect(collapsedSpans[2].end, 16); expect(collapsedSpans[2].attributions.length, 1); expect(collapsedSpans[2].attributions.first, ExpectedSpans.italics); @@ -714,15 +801,15 @@ void main() { final italicEnd = italicStart.copyWith(markerType: SpanMarkerType.end, offset: 1); test('it is equal to another AttributedSpans with equivalent markers that are stored in the same order', () { - final span1 = AttributedSpans(attributions: [boldStart, italicStart, boldEnd, italicEnd]); - final span2 = AttributedSpans(attributions: [boldStart, italicStart, boldEnd, italicEnd]); + final span1 = AttributedSpans()..markers.addAll([boldStart, italicStart, boldEnd, italicEnd]); + final span2 = AttributedSpans()..markers.addAll([boldStart, italicStart, boldEnd, italicEnd]); expect(span1 == span2, isTrue); }); test('it is equal to another AttributedSpans with equivalent markers that are stored in a different order', () { - final boldBeforeitalicspan = AttributedSpans(attributions: [boldStart, italicStart, boldEnd, italicEnd]); - final italicsBeforeBoldSpan = AttributedSpans(attributions: [italicStart, boldStart, italicEnd, boldEnd]); + final boldBeforeitalicspan = AttributedSpans()..markers.addAll([boldStart, italicStart, boldEnd, italicEnd]); + final italicsBeforeBoldSpan = AttributedSpans()..markers.addAll([italicStart, boldStart, italicEnd, boldEnd]); expect(boldBeforeitalicspan == italicsBeforeBoldSpan, isTrue); }); @@ -735,8 +822,8 @@ void main() { }); test('it is NOT equal to another AttributedSpans with different markers', () { - final span1 = AttributedSpans(attributions: [boldStart, boldEnd]); - final span2 = AttributedSpans(attributions: [italicStart, italicEnd]); + final span1 = AttributedSpans()..markers.addAll([boldStart, boldEnd]); + final span2 = AttributedSpans()..markers.addAll([italicStart, italicEnd]); expect(span1 == span2, isFalse); }); @@ -760,7 +847,8 @@ class _LinkAttribution implements Attribution { } @override - bool operator ==(Object other) => identical(this, other) || other is _LinkAttribution && runtimeType == other.runtimeType && url == other.url; + bool operator ==(Object other) => + identical(this, other) || other is _LinkAttribution && runtimeType == other.runtimeType && url == other.url; @override int get hashCode => url.hashCode; diff --git a/attributed_text/test/attributed_text_test.dart b/attributed_text/test/attributed_text_test.dart index 3fa92637d..c194f2419 100644 --- a/attributed_text/test/attributed_text_test.dart +++ b/attributed_text/test/attributed_text_test.dart @@ -8,8 +8,7 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 9), ], ), ); @@ -31,8 +30,8 @@ void main() { test('combines overlapping spans when adding from left to right', () { // Note: span overlaps at the boundary had a bug that was filed in #582. final text = AttributedText(text: '01234567'); - text.addAttribution(ExpectedSpans.bold, SpanRange(start: 0, end: 4)); - text.addAttribution(ExpectedSpans.bold, SpanRange(start: 4, end: 8)); + text.addAttribution(ExpectedSpans.bold, SpanRange(start: 0, end: 5)); + text.addAttribution(ExpectedSpans.bold, SpanRange(start: 4, end: 9)); // Ensure that the spans were merged into a single span. expect(text.spans.markers.length, 2); @@ -46,10 +45,10 @@ void main() { ); }); - test('combines overlapping spans when adding from left to right', () { + test('combines overlapping spans when adding from right to left', () { final text = AttributedText(text: '01234567'); - text.addAttribution(ExpectedSpans.bold, SpanRange(start: 4, end: 8)); - text.addAttribution(ExpectedSpans.bold, SpanRange(start: 0, end: 4)); + text.addAttribution(ExpectedSpans.bold, SpanRange(start: 4, end: 9)); + text.addAttribution(ExpectedSpans.bold, SpanRange(start: 0, end: 5)); // Ensure that the spans were merged into a single span. expect(text.spans.markers.length, 2); @@ -65,8 +64,8 @@ void main() { test('combines back-to-back spans after addition', () { final text = AttributedText(text: 'ABCD'); - text.addAttribution(ExpectedSpans.bold, const SpanRange(start: 0, end: 1)); - text.addAttribution(ExpectedSpans.bold, const SpanRange(start: 2, end: 3)); + text.addAttribution(ExpectedSpans.bold, const SpanRange(start: 0, end: 2)); + text.addAttribution(ExpectedSpans.bold, const SpanRange(start: 2, end: 4)); // Ensure that we only have a single span expect(text.spans.markers.length, 2); @@ -101,10 +100,8 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 4, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 5, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 5), + AttributionSpan(attribution: ExpectedSpans.italics, start: 4, end: 7), ], ), ), @@ -113,10 +110,8 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 4, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 5, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 5), + AttributionSpan(attribution: ExpectedSpans.italics, start: 4, end: 7), ], ), ), @@ -130,10 +125,8 @@ void main() { text: 'jihgfedcba', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 4, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 5, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 5), + AttributionSpan(attribution: ExpectedSpans.italics, start: 4, end: 7), ], ), ) == @@ -141,10 +134,8 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 4, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 5, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 5), + AttributionSpan(attribution: ExpectedSpans.italics, start: 4, end: 7), ], ), ), @@ -158,10 +149,8 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 4, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 5, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 5), + AttributionSpan(attribution: ExpectedSpans.italics, start: 4, end: 7), ], ), ) == @@ -169,8 +158,7 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 5, markerType: SpanMarkerType.end), + AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 5), ], ), ), @@ -185,8 +173,11 @@ void main() { text: 'Hello world', spans: AttributedSpans( attributions: [ - SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: ExpectedSpans.bold, + start: 4, + end: 9, + ), ], ), ); @@ -200,12 +191,21 @@ void main() { text: 'Hello world', spans: AttributedSpans( attributions: [ - SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.italics, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.strikethrough, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.strikethrough, offset: 10, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: ExpectedSpans.bold, + start: 4, + end: 9, + ), + AttributionSpan( + attribution: ExpectedSpans.italics, + start: 0, + end: 7, + ), + AttributionSpan( + attribution: ExpectedSpans.strikethrough, + start: 0, + end: 10, + ), ], ), ); @@ -214,22 +214,34 @@ void main() { expect(range, SpanRange(start: 4, end: 7)); }); - test('finds all bold, italic and strikethrough text within a word that also includes a span with only bold and italics', () { + test( + 'finds all bold, italic and strikethrough text within a word that also includes a span with only bold and italics', + () { final attributedText = AttributedText( text: 'Hello world', spans: AttributedSpans( attributions: [ - SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.italics, offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.italics, offset: 4, markerType: SpanMarkerType.end), - SpanMarker(attribution: ExpectedSpans.strikethrough, offset: 1, markerType: SpanMarkerType.start), - SpanMarker(attribution: ExpectedSpans.strikethrough, offset: 3, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: ExpectedSpans.bold, + start: 0, + end: 4, + ), + AttributionSpan( + attribution: ExpectedSpans.italics, + start: 0, + end: 4, + ), + AttributionSpan( + attribution: ExpectedSpans.strikethrough, + start: 1, + end: 3, + ), ], ), ); - final range = attributedText.getAttributedRange({ExpectedSpans.bold, ExpectedSpans.italics, ExpectedSpans.strikethrough}, 2); + final range = attributedText + .getAttributedRange({ExpectedSpans.bold, ExpectedSpans.italics, ExpectedSpans.strikethrough}, 2); expect(range, SpanRange(start: 1, end: 3)); }); }); diff --git a/super_editor/example/lib/demos/components/demo_text_with_hint.dart b/super_editor/example/lib/demos/components/demo_text_with_hint.dart index fc1e7b01f..f40cef8ac 100644 --- a/super_editor/example/lib/demos/components/demo_text_with_hint.dart +++ b/super_editor/example/lib/demos/components/demo_text_with_hint.dart @@ -178,9 +178,8 @@ class HeaderWithHintComponentBuilder implements ComponentBuilder { hintText: AttributedText( text: 'header goes here...', spans: AttributedSpans( - attributions: [ - const SpanMarker(attribution: italicsAttribution, offset: 12, markerType: SpanMarkerType.start), - const SpanMarker(attribution: italicsAttribution, offset: 15, markerType: SpanMarkerType.end), + attributions: const [ + AttributionSpan(attribution: italicsAttribution, start: 12, end: 16), ], ), ), diff --git a/super_editor/example/lib/demos/demo_markdown_serialization.dart b/super_editor/example/lib/demos/demo_markdown_serialization.dart index 8c992a733..9d4956fc4 100644 --- a/super_editor/example/lib/demos/demo_markdown_serialization.dart +++ b/super_editor/example/lib/demos/demo_markdown_serialization.dart @@ -135,14 +135,11 @@ Document _createInitialDocument() { text: 'This is a 3rd list item, with a link', spans: AttributedSpans( attributions: [ - SpanMarker( - attribution: LinkAttribution(url: Uri.https('example.org', '')), - offset: 30, - markerType: SpanMarkerType.start), - SpanMarker( - attribution: LinkAttribution(url: Uri.https('example.org', '')), - offset: 35, - markerType: SpanMarkerType.end), + AttributionSpan( + attribution: LinkAttribution(url: Uri.https('example.org', '')), + start: 30, + end: 36, + ), ], )), ), diff --git a/super_editor/example/lib/demos/example_editor/_toolbar.dart b/super_editor/example/lib/demos/example_editor/_toolbar.dart index 373090365..3b01e933e 100644 --- a/super_editor/example/lib/demos/example_editor/_toolbar.dart +++ b/super_editor/example/lib/demos/example_editor/_toolbar.dart @@ -266,7 +266,7 @@ class _EditorToolbarState extends State { final extentOffset = (selection.extent.nodePosition as TextPosition).offset; final selectionStart = min(baseOffset, extentOffset); final selectionEnd = max(baseOffset, extentOffset); - final selectionRange = SpanRange(start: selectionStart, end: selectionEnd - 1); + final selectionRange = SpanRange(start: selectionStart, end: selectionEnd); final textNode = widget.editor!.document.getNodeById(selection.extent.nodeId) as TextNode; final text = textNode.text; @@ -287,7 +287,7 @@ class _EditorToolbarState extends State { final extentOffset = (selection.extent.nodePosition as TextPosition).offset; final selectionStart = min(baseOffset, extentOffset); final selectionEnd = max(baseOffset, extentOffset); - final selectionRange = SpanRange(start: selectionStart, end: selectionEnd - 1); + final selectionRange = SpanRange(start: selectionStart, end: selectionEnd); final textNode = widget.editor!.document.getNodeById(selection.extent.nodeId) as TextNode; final text = textNode.text; diff --git a/super_editor/example/lib/demos/supertextfield/_interactive_demo.dart b/super_editor/example/lib/demos/supertextfield/_interactive_demo.dart index 0fc41c8c6..aa7f7f401 100644 --- a/super_editor/example/lib/demos/supertextfield/_interactive_demo.dart +++ b/super_editor/example/lib/demos/supertextfield/_interactive_demo.dart @@ -14,11 +14,9 @@ class _InteractiveTextFieldDemoState extends State { final _textFieldController = AttributedTextEditingController( text: AttributedText( text: 'Super Editor is an open source text editor for Flutter projects.', - spans: AttributedSpans(attributions: [ - const SpanMarker(attribution: brandAttribution, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: brandAttribution, offset: 11, markerType: SpanMarkerType.end), - const SpanMarker(attribution: flutterAttribution, offset: 47, markerType: SpanMarkerType.start), - const SpanMarker(attribution: flutterAttribution, offset: 53, markerType: SpanMarkerType.end), + spans: AttributedSpans(attributions: const [ + AttributionSpan(attribution: brandAttribution, start: 0, end: 12), + AttributionSpan(attribution: flutterAttribution, start: 47, end: 54), ])), ); diff --git a/super_editor/lib/src/default_editor/common_editor_operations.dart b/super_editor/lib/src/default_editor/common_editor_operations.dart index 478d6fa27..4d1b104a6 100644 --- a/super_editor/lib/src/default_editor/common_editor_operations.dart +++ b/super_editor/lib/src/default_editor/common_editor_operations.dart @@ -2370,9 +2370,7 @@ class _PasteEditorCommand implements EditorCommand { final linkAttribution = LinkAttribution(url: link); final startOffset = wordBoundary.start; - // -1 because TextPosition's offset indexes the character after the - // selection, not the final character in the selection. - final endOffset = wordBoundary.end - 1; + final endOffset = wordBoundary.end; // Add link attribution. linkAttributionSpans.addAttribution( diff --git a/super_editor/lib/src/default_editor/text.dart b/super_editor/lib/src/default_editor/text.dart index 6c8119ff3..d115547f2 100644 --- a/super_editor/lib/src/default_editor/text.dart +++ b/super_editor/lib/src/default_editor/text.dart @@ -1047,28 +1047,21 @@ class ToggleTextAttributionsCommand implements EditorCommand { final extentOffset = (documentSelection.extent.nodePosition as TextPosition).offset; startOffset = baseOffset < extentOffset ? baseOffset : extentOffset; endOffset = baseOffset < extentOffset ? extentOffset : baseOffset; - - // -1 because TextPosition's offset indexes the character after the - // selection, not the final character in the selection. - endOffset -= 1; } else if (textNode == nodes.first) { // Handle partial node selection in first node. editorDocLog.info(' - selecting part of the first node: ${textNode.id}'); startOffset = (nodeRange.start.nodePosition as TextPosition).offset; - endOffset = max(textNode.text.text.length - 1, 0); + endOffset = max(textNode.text.text.length, 0); } else if (textNode == nodes.last) { // Handle partial node selection in last node. editorDocLog.info(' - toggling part of the last node: ${textNode.id}'); startOffset = 0; - - // -1 because TextPosition's offset indexes the character after the - // selection, not the final character in the selection. - endOffset = (nodeRange.end.nodePosition as TextPosition).offset - 1; + endOffset = (nodeRange.end.nodePosition as TextPosition).offset; } else { // Handle full node selection. editorDocLog.info(' - toggling full node: ${textNode.id}'); startOffset = 0; - endOffset = max(textNode.text.text.length - 1, 0); + endOffset = max(textNode.text.text.length, 0); } final selectionRange = SpanRange(start: startOffset, end: endOffset); diff --git a/super_editor/lib/src/infrastructure/attributed_text_styles.dart b/super_editor/lib/src/infrastructure/attributed_text_styles.dart index 7ff66c2b4..589db4e5a 100644 --- a/super_editor/lib/src/infrastructure/attributed_text_styles.dart +++ b/super_editor/lib/src/infrastructure/attributed_text_styles.dart @@ -32,7 +32,7 @@ extension ComputeTextSpan on AttributedText { final collapsedSpans = spans.collapseSpans(contentLength: text.length); final textSpans = collapsedSpans .map((attributedSpan) => TextSpan( - text: text.substring(attributedSpan.start, attributedSpan.end + 1), + text: text.substring(attributedSpan.start, attributedSpan.end), style: styleBuilder(attributedSpan.attributions), )) .toList(); diff --git a/super_editor/lib/src/infrastructure/super_textfield/infrastructure/attributed_text_editing_controller.dart b/super_editor/lib/src/infrastructure/super_textfield/infrastructure/attributed_text_editing_controller.dart index 0f18113e8..2dda65adb 100644 --- a/super_editor/lib/src/infrastructure/super_textfield/infrastructure/attributed_text_editing_controller.dart +++ b/super_editor/lib/src/infrastructure/super_textfield/infrastructure/attributed_text_editing_controller.dart @@ -119,7 +119,7 @@ class AttributedTextEditingController with ChangeNotifier { for (final attribution in attributions) { _text.toggleAttribution( attribution, - SpanRange(start: selection.start, end: selection.end - 1), + SpanRange(start: selection.start, end: selection.end), ); } @@ -133,7 +133,7 @@ class AttributedTextEditingController with ChangeNotifier { } _text.clearAttributions( - SpanRange(start: selection.start, end: selection.end - 1), + SpanRange(start: selection.start, end: selection.end), ); notifyListeners(); @@ -583,7 +583,7 @@ class AttributedTextEditingController with ChangeNotifier { text: updatedText, selection: updatedSelection, ); - + _updateComposingAttributions(); // TODO: do we need to implement composing region update behavior like selections? composingRegion = newComposingRegion ?? TextRange.empty; diff --git a/super_editor/test/src/attributed_text_styles_test.dart b/super_editor/test/src/attributed_text_styles_test.dart index e74106364..178cb65ae 100644 --- a/super_editor/test/src/attributed_text_styles_test.dart +++ b/super_editor/test/src/attributed_text_styles_test.dart @@ -20,8 +20,7 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 10), ], ), ); @@ -37,32 +36,7 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 1, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 1, markerType: SpanMarkerType.end), - ], - ), - ); - final textSpan = text.computeTextSpan(_styleBuilder); - - expect(textSpan.text, null); - expect(textSpan.children!.length, 3); - expect(textSpan.children![0].toPlainText(), 'a'); - expect(textSpan.children![1].toPlainText(), 'b'); - expect(textSpan.children![1].style!.fontWeight, FontWeight.bold); - expect(textSpan.children![2].toPlainText(), 'cdefghij'); - expect(textSpan.children![2].style!.fontWeight, null); - }); - - test('single character style - reverse order', () { - final text = AttributedText( - text: 'abcdefghij', - spans: AttributedSpans( - attributions: [ - // Notice that the markers are provided in reverse order: - // end then start. Order shouldn't matter within a single - // position index. This test ensures that. - const SpanMarker(attribution: ExpectedSpans.bold, offset: 1, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 1, markerType: SpanMarkerType.start), + const AttributionSpan(attribution: ExpectedSpans.bold, start: 1, end: 2), ], ), ); @@ -79,7 +53,7 @@ void main() { test('add single character style', () { final text = AttributedText(text: 'abcdefghij'); - text.addAttribution(ExpectedSpans.bold, const SpanRange(start: 1, end: 1)); + text.addAttribution(ExpectedSpans.bold, const SpanRange(start: 1, end: 2)); final textSpan = text.computeTextSpan(_styleBuilder); expect(textSpan.text, null); @@ -96,8 +70,7 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 7, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 8), ], ), ); @@ -116,8 +89,7 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 9, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: ExpectedSpans.bold, start: 9, end: 10), ], ), ); @@ -126,8 +98,7 @@ void main() { text: 'k', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 1), ], ), )); @@ -151,10 +122,8 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 4, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 5, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 9, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: ExpectedSpans.bold, start: 0, end: 5), + const AttributionSpan(attribution: ExpectedSpans.italics, start: 5, end: 10), ], ), ); @@ -175,10 +144,8 @@ void main() { text: 'abcdefghij', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: ExpectedSpans.bold, offset: 2, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 4, markerType: SpanMarkerType.start), - const SpanMarker(attribution: ExpectedSpans.bold, offset: 5, markerType: SpanMarkerType.end), - const SpanMarker(attribution: ExpectedSpans.italics, offset: 7, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: ExpectedSpans.bold, start: 2, end: 6), + const AttributionSpan(attribution: ExpectedSpans.italics, start: 4, end: 8), ], ), ); diff --git a/super_editor/test/src/default_editor/common_editor_operations_test.dart b/super_editor/test/src/default_editor/common_editor_operations_test.dart index 42e54a80a..f2dba5b0d 100644 --- a/super_editor/test/src/default_editor/common_editor_operations_test.dart +++ b/super_editor/test/src/default_editor/common_editor_operations_test.dart @@ -1,12 +1,11 @@ import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_test_robots/flutter_test_robots.dart'; import 'package:super_editor/super_editor.dart'; import 'package:super_editor/super_editor_test.dart'; -import '../_document_test_tools.dart'; -import 'package:flutter_test_robots/flutter_test_robots.dart'; - import '../../super_editor/document_test_tools.dart'; import '../../test_tools.dart'; +import '../_document_test_tools.dart'; void main() { group("Common editor operations", () { @@ -201,15 +200,12 @@ MutableDocument _singleParagraphWithLinkDoc() { text: "https://google.com", spans: AttributedSpans( attributions: [ - SpanMarker( - attribution: LinkAttribution(url: Uri.parse('https://google.com')), - offset: 0, - markerType: SpanMarkerType.start, - ), - SpanMarker( - attribution: LinkAttribution(url: Uri.parse('https://google.com')), - offset: 17, - markerType: SpanMarkerType.end, + AttributionSpan( + attribution: LinkAttribution( + url: Uri.parse('https://google.com'), + ), + start: 0, + end: 18, ), ], ), diff --git a/super_editor/test/src/default_editor/document_input_ime_test.dart b/super_editor/test/src/default_editor/document_input_ime_test.dart index 07dee6ef8..25dca70cd 100644 --- a/super_editor/test/src/default_editor/document_input_ime_test.dart +++ b/super_editor/test/src/default_editor/document_input_ime_test.dart @@ -6,9 +6,9 @@ import 'package:super_editor/super_editor.dart'; import 'package:super_editor/super_editor_test.dart'; import '../../super_editor/document_test_tools.dart'; +import '../../super_editor/test_documents.dart'; import '../../test_tools.dart'; import '../_document_test_tools.dart'; -import '../../super_editor/test_documents.dart'; void main() { group('IME input', () { @@ -359,15 +359,10 @@ MutableDocument _singleParagraphWithLinkDoc() { text: "https://google.com", spans: AttributedSpans( attributions: [ - SpanMarker( - attribution: LinkAttribution(url: Uri.parse('https://google.com')), - offset: 0, - markerType: SpanMarkerType.start, - ), - SpanMarker( + AttributionSpan( attribution: LinkAttribution(url: Uri.parse('https://google.com')), - offset: 17, - markerType: SpanMarkerType.end, + start: 0, + end: 18, ), ], ), diff --git a/super_editor/test/src/default_editor/document_keyboard_actions_test.dart b/super_editor/test/src/default_editor/document_keyboard_actions_test.dart index 41ee18916..ea23130d2 100644 --- a/super_editor/test/src/default_editor/document_keyboard_actions_test.dart +++ b/super_editor/test/src/default_editor/document_keyboard_actions_test.dart @@ -1046,15 +1046,10 @@ MutableDocument _singleParagraphWithLinkDoc() { text: "https://google.com", spans: AttributedSpans( attributions: [ - SpanMarker( + AttributionSpan( attribution: LinkAttribution(url: Uri.parse('https://google.com')), - offset: 0, - markerType: SpanMarkerType.start, - ), - SpanMarker( - attribution: LinkAttribution(url: Uri.parse('https://google.com')), - offset: 17, - markerType: SpanMarkerType.end, + start: 0, + end: 18, ), ], ), diff --git a/super_editor/test/src/default_editor/super_editor_content_conversion_test.dart b/super_editor/test/src/default_editor/super_editor_content_conversion_test.dart index 5fb7e0896..1a0cf05e2 100644 --- a/super_editor/test/src/default_editor/super_editor_content_conversion_test.dart +++ b/super_editor/test/src/default_editor/super_editor_content_conversion_test.dart @@ -218,15 +218,10 @@ MutableDocument _singleParagraphWithLinkDoc() { text: "https://google.com", spans: AttributedSpans( attributions: [ - SpanMarker( + AttributionSpan( attribution: LinkAttribution(url: Uri.parse('https://google.com')), - offset: 0, - markerType: SpanMarkerType.start, - ), - SpanMarker( - attribution: LinkAttribution(url: Uri.parse('https://google.com')), - offset: 17, - markerType: SpanMarkerType.end, + start: 0, + end: 18, ), ], ), diff --git a/super_editor/test/super_editor/supereditor_components_test.dart b/super_editor/test/super_editor/supereditor_components_test.dart index ffc7325cc..f63723153 100644 --- a/super_editor/test/super_editor/supereditor_components_test.dart +++ b/super_editor/test/super_editor/supereditor_components_test.dart @@ -114,8 +114,11 @@ class HintTextComponentBuilder implements ComponentBuilder { text: 'this is hint text...', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: italicsAttribution, offset: 12, markerType: SpanMarkerType.start), - const SpanMarker(attribution: italicsAttribution, offset: 15, markerType: SpanMarkerType.end), + const AttributionSpan( + attribution: italicsAttribution, + start: 12, + end: 16, + ), ], ), ), diff --git a/super_editor/test/super_textfield/attributed_text_editing_controller_test.dart b/super_editor/test/super_textfield/attributed_text_editing_controller_test.dart index afd318190..2f53f73ad 100644 --- a/super_editor/test/super_textfield/attributed_text_editing_controller_test.dart +++ b/super_editor/test/super_textfield/attributed_text_editing_controller_test.dart @@ -156,8 +156,7 @@ void main() { text: 'before [] after', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 7, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 7, end: 9), ], ), ), @@ -178,8 +177,7 @@ void main() { text: 'before [] after', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 7, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 7, end: 9), ], ), ), @@ -200,8 +198,7 @@ void main() { text: 'before [] after', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 7, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 7, end: 9), ], ), ), @@ -222,8 +219,7 @@ void main() { text: 'before [] after', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 7, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 7, end: 9), ], ), ), @@ -479,8 +475,7 @@ void main() { text: '[]:unstyled text', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 2, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 0, end: 3), ], ), ), @@ -500,8 +495,7 @@ void main() { text: '[]:unstyled text', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 2, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 0, end: 3), ], ), ), @@ -521,8 +515,7 @@ void main() { text: '[]:unstyled text', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 0, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 0, end: 1), ], ), ), @@ -617,8 +610,7 @@ void main() { text: '[replaceme]', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 10, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 0, end: 11), ], ), ), @@ -627,8 +619,7 @@ void main() { text: 'newtext', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: italicsAttribution, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: italicsAttribution, offset: 6, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: italicsAttribution, start: 0, end: 7), ], ), ); diff --git a/super_editor/test/super_textfield/ime_attributed_text_editing_controller_test.dart b/super_editor/test/super_textfield/ime_attributed_text_editing_controller_test.dart index 2764b8f85..35976bd5b 100644 --- a/super_editor/test/super_textfield/ime_attributed_text_editing_controller_test.dart +++ b/super_editor/test/super_textfield/ime_attributed_text_editing_controller_test.dart @@ -125,8 +125,7 @@ void main() { text: 'before [] after', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 7, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 7, end: 9), ], ), ), @@ -156,8 +155,7 @@ void main() { text: 'before [] after', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 7, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 7, end: 9), ], ), ), @@ -188,8 +186,7 @@ void main() { text: 'before [] after', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 7, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 7, end: 9), ], ), ), @@ -220,8 +217,7 @@ void main() { text: 'before [] after', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 7, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + const AttributionSpan(attribution: boldAttribution, start: 7, end: 9), ], ), ), diff --git a/super_editor_markdown/lib/src/markdown_to_document_parsing.dart b/super_editor_markdown/lib/src/markdown_to_document_parsing.dart index 1b8623dd8..3c04c2c9c 100644 --- a/super_editor_markdown/lib/src/markdown_to_document_parsing.dart +++ b/super_editor_markdown/lib/src/markdown_to_document_parsing.dart @@ -372,7 +372,7 @@ class _InlineMarkdownToDocument implements md.NodeVisitor { boldAttribution, SpanRange( start: 0, - end: styledText.text.length - 1, + end: styledText.text.length, ), ); } else if (element.tag == 'em') { @@ -380,7 +380,7 @@ class _InlineMarkdownToDocument implements md.NodeVisitor { italicsAttribution, SpanRange( start: 0, - end: styledText.text.length - 1, + end: styledText.text.length, ), ); } else if (element.tag == "del") { @@ -388,7 +388,7 @@ class _InlineMarkdownToDocument implements md.NodeVisitor { strikethroughAttribution, SpanRange( start: 0, - end: styledText.text.length - 1, + end: styledText.text.length, ), ); } else if (element.tag == "u") { @@ -396,7 +396,7 @@ class _InlineMarkdownToDocument implements md.NodeVisitor { underlineAttribution, SpanRange( start: 0, - end: styledText.text.length - 1, + end: styledText.text.length, ), ); } else if (element.tag == 'a') { @@ -404,7 +404,7 @@ class _InlineMarkdownToDocument implements md.NodeVisitor { LinkAttribution(url: Uri.parse(element.attributes['href']!)), SpanRange( start: 0, - end: styledText.text.length - 1, + end: styledText.text.length, ), ); } diff --git a/super_editor_markdown/test/attributed_text_markdown_test.dart b/super_editor_markdown/test/attributed_text_markdown_test.dart index 2a5541a88..02f780dc8 100644 --- a/super_editor_markdown/test/attributed_text_markdown_test.dart +++ b/super_editor_markdown/test/attributed_text_markdown_test.dart @@ -17,10 +17,8 @@ void main() { text: "This is single character styles.", spans: AttributedSpans( attributions: [ - SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.start), - SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), - SpanMarker(attribution: italicsAttribution, offset: 23, markerType: SpanMarkerType.start), - SpanMarker(attribution: italicsAttribution, offset: 23, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 8, end: 9), + AttributionSpan(attribution: italicsAttribution, start: 23, end: 24), ], ), ).toMarkdown(), @@ -34,8 +32,7 @@ void main() { text: "This is bold text.", spans: AttributedSpans( attributions: [ - SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.start), - SpanMarker(attribution: boldAttribution, offset: 11, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 8, end: 12), ], ), ).toMarkdown(), @@ -49,8 +46,7 @@ void main() { text: "This is italics text.", spans: AttributedSpans( attributions: [ - SpanMarker(attribution: italicsAttribution, offset: 8, markerType: SpanMarkerType.start), - SpanMarker(attribution: italicsAttribution, offset: 14, markerType: SpanMarkerType.end), + AttributionSpan(attribution: italicsAttribution, start: 8, end: 15), ], ), ).toMarkdown(), @@ -64,10 +60,8 @@ void main() { text: "This is multiple styled text.", spans: AttributedSpans( attributions: [ - SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.start), - SpanMarker(attribution: boldAttribution, offset: 22, markerType: SpanMarkerType.end), - SpanMarker(attribution: italicsAttribution, offset: 8, markerType: SpanMarkerType.start), - SpanMarker(attribution: italicsAttribution, offset: 22, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 8, end: 23), + AttributionSpan(attribution: italicsAttribution, start: 8, end: 23), ], ), ).toMarkdown(), @@ -80,11 +74,9 @@ void main() { AttributedText( text: "This is overlapping styles.", spans: AttributedSpans( - attributions: [ - SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.start), - SpanMarker(attribution: boldAttribution, offset: 13, markerType: SpanMarkerType.end), - SpanMarker(attribution: italicsAttribution, offset: 11, markerType: SpanMarkerType.start), - SpanMarker(attribution: italicsAttribution, offset: 18, markerType: SpanMarkerType.end), + attributions: const [ + AttributionSpan(attribution: boldAttribution, start: 8, end: 14), + AttributionSpan(attribution: italicsAttribution, start: 11, end: 19), ], ), ).toMarkdown(), diff --git a/super_editor_markdown/test/custom_block_serializer_test.dart b/super_editor_markdown/test/custom_block_serializer_test.dart index 4b83e041b..0cab6b20c 100644 --- a/super_editor_markdown/test/custom_block_serializer_test.dart +++ b/super_editor_markdown/test/custom_block_serializer_test.dart @@ -40,8 +40,11 @@ Paragraph 2''', text: "This is a callout!", spans: AttributedSpans( attributions: [ - SpanMarker(attribution: boldAttribution, offset: 10, markerType: SpanMarkerType.start), - SpanMarker(attribution: boldAttribution, offset: 17, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: boldAttribution, + start: 10, + end: 18, + ), ], ), ), diff --git a/super_editor_markdown/test/super_editor_markdown_test.dart b/super_editor_markdown/test/super_editor_markdown_test.dart index a7ba22b72..c25f68c39 100644 --- a/super_editor_markdown/test/super_editor_markdown_test.dart +++ b/super_editor_markdown/test/super_editor_markdown_test.dart @@ -1,5 +1,5 @@ -import 'package:super_editor/super_editor.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:super_editor/super_editor.dart'; import 'package:super_editor_markdown/super_editor_markdown.dart'; void main() { @@ -40,8 +40,7 @@ void main() { text: 'My Header', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 3, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 3, end: 9), ], ), ), @@ -72,8 +71,7 @@ void main() { text: 'This is a blockquote', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 10, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 19, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 10, end: 20), ], ), ), @@ -121,8 +119,7 @@ This is some code text: 'This is a paragraph.', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 5, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 5, end: 9), ], ), ), @@ -140,10 +137,8 @@ This is some code text: 'This is a paragraph.', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 5, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), - const SpanMarker(attribution: italicsAttribution, offset: 5, markerType: SpanMarkerType.start), - const SpanMarker(attribution: italicsAttribution, offset: 8, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 5, end: 9), + AttributionSpan(attribution: italicsAttribution, start: 5, end: 9), ], ), ), @@ -160,11 +155,9 @@ This is some code text: AttributedText( text: 'This is a paragraph.', spans: AttributedSpans( - attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 6, markerType: SpanMarkerType.end), - const SpanMarker(attribution: italicsAttribution, offset: 8, markerType: SpanMarkerType.start), - const SpanMarker(attribution: italicsAttribution, offset: 19, markerType: SpanMarkerType.end), + attributions: const [ + AttributionSpan(attribution: boldAttribution, start: 0, end: 7), + AttributionSpan(attribution: italicsAttribution, start: 8, end: 20), ], ), ), @@ -182,10 +175,8 @@ This is some code text: 'This is a paragraph.', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 5, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), - const SpanMarker(attribution: italicsAttribution, offset: 5, markerType: SpanMarkerType.start), - const SpanMarker(attribution: italicsAttribution, offset: 18, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 5, end: 9), + AttributionSpan(attribution: italicsAttribution, start: 5, end: 19), ], ), ), @@ -203,10 +194,8 @@ This is some code text: 'This is a paragraph.', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 5, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), - const SpanMarker(attribution: codeAttribution, offset: 5, markerType: SpanMarkerType.start), - const SpanMarker(attribution: codeAttribution, offset: 8, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 5, end: 9), + AttributionSpan(attribution: codeAttribution, start: 5, end: 9), ], ), ), @@ -224,14 +213,11 @@ This is some code text: 'This is a paragraph.', spans: AttributedSpans( attributions: [ - SpanMarker( - attribution: LinkAttribution(url: Uri.https('example.org', '')), - offset: 10, - markerType: SpanMarkerType.start), - SpanMarker( - attribution: LinkAttribution(url: Uri.https('example.org', '')), - offset: 18, - markerType: SpanMarkerType.end), + AttributionSpan( + attribution: LinkAttribution(url: Uri.https('example.org', '')), + start: 10, + end: 19, + ), ], ), ), @@ -249,16 +235,12 @@ This is some code text: 'This is a paragraph.', spans: AttributedSpans( attributions: [ - SpanMarker( - attribution: LinkAttribution(url: Uri.https('example.org', '')), - offset: 10, - markerType: SpanMarkerType.start), - SpanMarker( - attribution: LinkAttribution(url: Uri.https('example.org', '')), - offset: 18, - markerType: SpanMarkerType.end), - const SpanMarker(attribution: boldAttribution, offset: 10, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 18, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: LinkAttribution(url: Uri.https('example.org', '')), + start: 10, + end: 19, + ), + AttributionSpan(attribution: boldAttribution, start: 10, end: 19), ], ), ), @@ -276,16 +258,12 @@ This is some code text: 'This is a paragraph.', spans: AttributedSpans( attributions: [ - SpanMarker( - attribution: LinkAttribution(url: Uri.https('example.org', '')), - offset: 0, - markerType: SpanMarkerType.start), - SpanMarker( - attribution: LinkAttribution(url: Uri.https('example.org', '')), - offset: 18, - markerType: SpanMarkerType.end), - const SpanMarker(attribution: boldAttribution, offset: 5, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: LinkAttribution(url: Uri.https('example.org', '')), + start: 0, + end: 19, + ), + AttributionSpan(attribution: boldAttribution, start: 5, end: 9), ], ), ), @@ -303,8 +281,11 @@ This is some code text: 'This is a paragraph.', spans: AttributedSpans( attributions: [ - SpanMarker(attribution: underlineAttribution, offset: 10, markerType: SpanMarkerType.start), - SpanMarker(attribution: underlineAttribution, offset: 18, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: underlineAttribution, + start: 10, + end: 19, + ), ], ), ), @@ -322,8 +303,11 @@ This is some code text: 'This is a paragraph.', spans: AttributedSpans( attributions: [ - SpanMarker(attribution: strikethroughAttribution, offset: 10, markerType: SpanMarkerType.start), - SpanMarker(attribution: strikethroughAttribution, offset: 18, markerType: SpanMarkerType.end), + AttributionSpan( + attribution: strikethroughAttribution, + start: 10, + end: 19, + ), ], ), ), @@ -341,10 +325,8 @@ This is some code text: 'First LinkSecond Link', spans: AttributedSpans( attributions: [ - SpanMarker(attribution: LinkAttribution(url: Uri.https('example.org', '')), offset: 0, markerType: SpanMarkerType.start), - SpanMarker(attribution: LinkAttribution(url: Uri.https('example.org', '')), offset: 9, markerType: SpanMarkerType.end), - SpanMarker(attribution: LinkAttribution(url: Uri.https('github.com', '')), offset: 10, markerType: SpanMarkerType.start), - SpanMarker(attribution: LinkAttribution(url: Uri.https('github.com', '')), offset: 20, markerType: SpanMarkerType.end), + AttributionSpan(attribution: LinkAttribution(url: Uri.https('example.org', '')), start: 0, end: 10), + AttributionSpan(attribution: LinkAttribution(url: Uri.https('github.com', '')), start: 10, end: 21), ], ), ), @@ -563,8 +545,7 @@ Paragraph3"""); text: 'Unordered 1', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 8, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 0, end: 9), ], ), ), @@ -625,8 +606,7 @@ Paragraph3"""); text: 'Ordered 1', spans: AttributedSpans( attributions: [ - const SpanMarker(attribution: boldAttribution, offset: 0, markerType: SpanMarkerType.start), - const SpanMarker(attribution: boldAttribution, offset: 6, markerType: SpanMarkerType.end), + AttributionSpan(attribution: boldAttribution, start: 0, end: 7), ], ), ), @@ -1102,8 +1082,7 @@ Paragraph4"""; }); test('paragraph beginning with multiple blank lines', () { - final doc = - deserializeMarkdownToDocument(' \n \nFirst Paragraph.\n\nSecond Paragraph'); + final doc = deserializeMarkdownToDocument(' \n \nFirst Paragraph.\n\nSecond Paragraph'); expect(doc.nodes.length, 2); @@ -1113,7 +1092,7 @@ Paragraph4"""; expect(doc.nodes.last, isA()); expect((doc.nodes.last as ParagraphNode).text.text, 'Second Paragraph'); }); - + test('document ending with an empty paragraph', () { final doc = deserializeMarkdownToDocument(""" First Paragraph. diff --git a/website/lib/homepage/featured_editor.dart b/website/lib/homepage/featured_editor.dart index c11caa3fc..0ba9cfecd 100644 --- a/website/lib/homepage/featured_editor.dart +++ b/website/lib/homepage/featured_editor.dart @@ -247,15 +247,10 @@ MutableDocument _createInitialDocument() { text: 'The missing WYSIWYG editor for Flutter.', spans: AttributedSpans( attributions: [ - const SpanMarker( + const AttributionSpan( attribution: boldAttribution, - offset: 0, - markerType: SpanMarkerType.start, - ), - const SpanMarker( - attribution: boldAttribution, - offset: 25, - markerType: SpanMarkerType.end, + start: 0, + end: 26, ), ], ), @@ -268,15 +263,10 @@ MutableDocument _createInitialDocument() { 'Open source and written entirely in Dart. Comes with a modular architecture that allows you to customize it to your needs.', spans: AttributedSpans( attributions: [ - const SpanMarker( - attribution: _underlineAttribution, - offset: 16, - markerType: SpanMarkerType.start, - ), - const SpanMarker( + const AttributionSpan( attribution: _underlineAttribution, - offset: 40, - markerType: SpanMarkerType.end, + start: 16, + end: 41, ), ], ),