Skip to content

Commit

Permalink
fix for superlistapp#1882 and superlistapp#1863 + more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
BazinC committed Mar 12, 2024
1 parent bcba728 commit 8b56dd1
Show file tree
Hide file tree
Showing 3 changed files with 308 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,9 @@ class ComposingStableTag {

@override
int get hashCode => contentBounds.hashCode ^ token.hashCode;

@override
String toString() => 'ComposingStableTag{contentBounds: $contentBounds, token: $token}';
}

/// An [EditReaction] that prevents partial selection of a stable user tag.
Expand Down
71 changes: 32 additions & 39 deletions super_editor/lib/src/default_editor/text_tokenizing/tags.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:characters/characters.dart';
import 'package:super_editor/src/core/document.dart';
import 'package:super_editor/src/core/document_selection.dart';
import 'package:super_editor/src/default_editor/text.dart';
import 'package:super_editor/src/infrastructure/strings.dart';

/// A set of tools for finding tags within document text.
class TagFinder {
Expand All @@ -23,55 +22,51 @@ class TagFinder {
return null;
}

int tokenStartOffset = min(expansionPosition.offset - 1, rawText.length - 1);
tokenStartOffset = max(tokenStartOffset, 0);
if (tagRule.excludedCharacters.contains(rawText[tokenStartOffset])) {
int splitIndex = min(expansionPosition.offset, rawText.length);
splitIndex = max(splitIndex, 0);

// Create 2 splits of characters to navigate upstream and downstream the caret position.
// ex: "this is a very|long string"
// -> split around the caret into charactersBefore="this is a very" and charactersAfter="long string"
final charactersBefore = rawText.substring(0, splitIndex).characters;
final iteratorUpstream = charactersBefore.iteratorAtEnd;

final charactersAfter = rawText.substring(splitIndex).characters;
final iteratorDownstream = charactersAfter.iterator;

if (charactersBefore.isNotEmpty && tagRule.excludedCharacters.contains(charactersBefore.last)) {
// The character where we're supposed to begin our expansion is a
// character that's not allowed in a tag. Therefore, no tag exists
// around the search offset.
return null;
}

int tokenEndOffset = min(expansionPosition.offset - 1, rawText.length - 1);
tokenEndOffset = max(tokenEndOffset, 0);

if (rawText[tokenStartOffset] != tagRule.trigger) {
while (tokenStartOffset > 0) {
final upstreamCharacterIndex = rawText.moveOffsetUpstreamByCharacter(tokenStartOffset)!;
final upstreamCharacter = rawText[upstreamCharacterIndex];
if (tagRule.excludedCharacters.contains(upstreamCharacter)) {
// The upstream character isn't allowed to appear in a tag. Break before moving
// the starting character index any further upstream.
break;
}

// Move the starting character index upstream.
tokenStartOffset = upstreamCharacterIndex;
// Move upstream until we find the trigger character or an excluded character.
while (iteratorUpstream.moveBack()) {
final currentCharacter = iteratorUpstream.current;
if (tagRule.excludedCharacters.contains(currentCharacter)) {
// The upstream character isn't allowed to appear in a tag. end the search.
return null;
}

if (upstreamCharacter == tagRule.trigger) {
// The character we just added to the token bounds is the trigger.
// We don't want to move the start any further upstream.
break;
}
if (currentCharacter == tagRule.trigger) {
// The character we are reading is the trigger.
// We move the iteratorUpstream one last time to include the trigger in the tokenRange and stop looking any further upstream
iteratorUpstream.moveBack();
break;
}
}

while (tokenEndOffset < rawText.length - 1) {
final downstreamCharacterIndex = rawText.moveOffsetDownstreamByCharacter(tokenEndOffset)!;
final downstreamCharacter = rawText[downstreamCharacterIndex];
if (downstreamCharacter != tagRule.trigger && tagRule.excludedCharacters.contains(downstreamCharacter)) {
// Move downstream the caret position until we find excluded character or reach the end of the text.
while (iteratorDownstream.moveNext()) {
final current = iteratorDownstream.current;
if (current != tagRule.trigger && tagRule.excludedCharacters.contains(current)) {
break;
}

tokenEndOffset = downstreamCharacterIndex;
}
// Make end off exclusive.
tokenEndOffset += 1;

final tokenRange = SpanRange(tokenStartOffset, tokenEndOffset);
if (tokenRange.end - tokenRange.start <= 0) {
return null;
}
final tokenStartOffset = splitIndex - iteratorUpstream.stringAfterLength;
final tokenRange = SpanRange(tokenStartOffset, splitIndex + iteratorDownstream.stringBeforeLength);

final tagText = text.substringInRange(tokenRange);
if (!tagText.startsWith(tagRule.trigger)) {
Expand All @@ -83,16 +78,14 @@ class TagFinder {
return null;
}

final tagAroundPosition = TagAroundPosition(
return TagAroundPosition(
indexedTag: IndexedTag(
Tag(tagRule.trigger, tagText.substring(1)),
nodeId,
tokenStartOffset,
),
searchOffset: expansionPosition.offset,
);

return tagAroundPosition;
}

/// Finds and returns all tags in the given [textNode], which meet the given [rule].
Expand Down
Loading

0 comments on commit 8b56dd1

Please sign in to comment.