Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TagFinder.findTagAroundPosition for better emoji support #1891

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
BazinC marked this conversation as resolved.
Show resolved Hide resolved
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)) {
BazinC marked this conversation as resolved.
Show resolved Hide resolved
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