From 74aac68dc0cb32d42120d5ad756b7b96e78a2c08 Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Sat, 16 Aug 2025 16:14:57 -0300 Subject: [PATCH 1/2] [SuperEditor] Don't activate an action tag when placing the caret at an existing tag pattern. (Resolves #2392) --- .../text_tokenizing/action_tags.dart | 7 +++++ .../text_entry/tagging/action_tags_test.dart | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/super_editor/lib/src/default_editor/text_tokenizing/action_tags.dart b/super_editor/lib/src/default_editor/text_tokenizing/action_tags.dart index 7156b52ad3..a0119a9001 100644 --- a/super_editor/lib/src/default_editor/text_tokenizing/action_tags.dart +++ b/super_editor/lib/src/default_editor/text_tokenizing/action_tags.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:attributed_text/attributed_text.dart'; import 'package:characters/characters.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:super_editor/src/core/document.dart'; @@ -326,6 +327,12 @@ class ActionTagComposingReaction extends EditReaction { return; } + if (changeList.none((event) => event is DocumentEdit)) { + // Action tags are composed while the user is typing. Since the + // are no edits, the user is not typing. + return; + } + _updateComposingTag(requestDispatcher, tagAroundPosition.indexedTag); editorContext.composingActionTag.value = tagAroundPosition.indexedTag; _onUpdateComposingActionTag(tagAroundPosition.indexedTag); diff --git a/super_editor/test/super_editor/text_entry/tagging/action_tags_test.dart b/super_editor/test/super_editor/text_entry/tagging/action_tags_test.dart index 61d1a178d3..3da93c2e83 100644 --- a/super_editor/test/super_editor/text_entry/tagging/action_tags_test.dart +++ b/super_editor/test/super_editor/text_entry/tagging/action_tags_test.dart @@ -768,6 +768,34 @@ void main() { // Ensure that we received a notification when the tag was cancelled. expect(tagNotificationCount, 7); }); + + testWidgetsOnAllPlatforms("does not start composing when placing the caret at an existing tag pattern", + (tester) async { + await _pumpTestEditor( + tester, + MutableDocument( + nodes: [ + ParagraphNode( + id: "1", + text: AttributedText("This is origin/main branch"), + ), + ], + ), + ); + + // Place the caret at "mai|n" + await tester.placeCaretInParagraph("1", 18); + + // Ensure that we are not composing a tag. + final text = SuperEditorInspector.findTextInComponent("1"); + expect( + text.getAttributionSpansInRange( + attributionFilter: (attribution) => attribution == actionTagComposingAttribution, + range: const SpanRange(0, 26), + ), + isEmpty, + ); + }); }); group("submissions >", () { From ea8b89db572de3468afb9be40745d4bef468bb77 Mon Sep 17 00:00:00 2001 From: Angelo Silvestre Date: Sat, 16 Aug 2025 16:47:48 -0300 Subject: [PATCH 2/2] Update tag when moving the caret --- .../text_tokenizing/action_tags.dart | 11 +++++-- .../text_entry/tagging/action_tags_test.dart | 31 ++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/super_editor/lib/src/default_editor/text_tokenizing/action_tags.dart b/super_editor/lib/src/default_editor/text_tokenizing/action_tags.dart index a0119a9001..014c3c71a0 100644 --- a/super_editor/lib/src/default_editor/text_tokenizing/action_tags.dart +++ b/super_editor/lib/src/default_editor/text_tokenizing/action_tags.dart @@ -327,9 +327,14 @@ class ActionTagComposingReaction extends EditReaction { return; } - if (changeList.none((event) => event is DocumentEdit)) { - // Action tags are composed while the user is typing. Since the - // are no edits, the user is not typing. + final hasComposingTagAttribution = textNode!.text + .getAttributionSpansInRange( + attributionFilter: (attribution) => attribution == actionTagComposingAttribution, + range: SpanRange(tagAroundPosition.indexedTag.startOffset, tagAroundPosition.indexedTag.endOffset), + ) + .isNotEmpty; + if (changeList.none((event) => event is DocumentEdit) && !hasComposingTagAttribution) { + // The user is neither typing nor moving the caret within an existing composing tag. return; } diff --git a/super_editor/test/super_editor/text_entry/tagging/action_tags_test.dart b/super_editor/test/super_editor/text_entry/tagging/action_tags_test.dart index 3da93c2e83..9772843b5e 100644 --- a/super_editor/test/super_editor/text_entry/tagging/action_tags_test.dart +++ b/super_editor/test/super_editor/text_entry/tagging/action_tags_test.dart @@ -783,7 +783,7 @@ void main() { ), ); - // Place the caret at "mai|n" + // Place the caret at "mai|n". await tester.placeCaretInParagraph("1", 18); // Ensure that we are not composing a tag. @@ -796,6 +796,35 @@ void main() { isEmpty, ); }); + + testWidgetsOnAllPlatforms("updates composing when moving the caret within an existing composing tag", + (tester) async { + await _pumpTestEditor( + tester, + singleParagraphEmptyDoc(), + ); + await tester.placeCaretInParagraph("1", 0); + + // Compose an action tag. + await tester.typeImeText("/header"); + + // Ensure that the tag has a composing attribution. + final textBefore = SuperEditorInspector.findTextInComponent("1"); + expect( + textBefore.getAttributedRange({actionTagComposingAttribution}, 0), + const SpanRange(0, 6), + ); + + // Press the left arrow to move the caret within the tag. + await tester.pressLeftArrow(); + + // Ensure that the tag was updated. + final textAfter = SuperEditorInspector.findTextInComponent("1"); + expect( + textAfter.getAttributedRange({actionTagComposingAttribution}, 0), + const SpanRange(0, 5), + ); + }); }); group("submissions >", () {