2

I'm trying to create a mention feature, so I have extended the TextEditingController class for styling the entered text - change the color to blue and replace the entered string to the contact's name.

I'm facing a problem with the cursor when changing the text (setting a different text in TextSpan). If I enter a string with the exact length as the original one, the cursor acts normally, but if I enter a string with a smaller or longer length, the cursor doesn't follow the entered text, but the original one.

Example:
If the original text was @John and I have replaced it to John Snow, the cursor is located before S and not after W.

@override
  TextSpan buildTextSpan({TextStyle style, bool withComposing}) {
    var children = <InlineSpan>[];

    text.splitMapJoin(
      mentionPattern,
      onMatch: (Match match) {
        children.add(
          TextSpan(
            text: "Text replacer",
            style: style.merge(MainStyle.LinkSmallTextStyle),
          ),
        );
      },
      onNonMatch: (String text) {
        children.add(TextSpan(text: text, style: style));
        return '';
      },
    );

    return TextSpan(style: style, children: children);
  }
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Ro168
  • 163
  • 4
  • Will this answer: https://stackoverflow.com/a/59525664/11731883 resolve your issue? – Alexey Subbotin Feb 21 '21 at 16:59
  • I have tried it but it doesn’t work since Textspan doesn’t change the original text, but just display it differently. – Ro168 Feb 21 '21 at 17:22
  • @Ro168 Did you find another solution to this issue? I am able to reproduce the same error. Specifically on Android I get the following error when dragging the cursor to the end of my text: `E/flutter ( 6156): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: PlatformException(error, invalid selection start: 56, null, java.lang.IndexOutOfBoundsException: invalid selection start: 56` – bmonsalvatge Aug 03 '21 at 02:26
  • Facing similar issue, did you find any solution? – L.Keysoft Dec 30 '21 at 15:59

1 Answers1

0

I found this workaround / hack here: https://github.com/flutter/flutter/issues/107432#issuecomment-1580175805

As you said, the problem arises when the length of the strings are different. So the trick is to insert some zero width space characters (\u200b) to reach the same length.

In my case, I'm replacing some text with a WidgetSpan which gives me more control on the decoration. The WidgetSpan is always considered to have a length of 1. But if I suffix it with a TextSpan with some \u200b, then the cursor behaves as you would expect.

@override
TextSpan buildTextSpan({required context, style, required withComposing}) {
  final children = <InlineSpan>[];

  final color = Theme.of(context).primaryColor;
  text.splitMapJoin(
    regexp,
    onMatch: (match) {
      final replacement = '...'; // Insert what you want here.
      children.add(WidgetSpan(
        alignment: PlaceholderAlignment.middle,
        baseline: TextBaseline.ideographic,
        child: Container(
          padding: const EdgeInsets.all(4),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(4),
            border: Border.all(color: color, width: 1),
          ),
          child: Text(replacement),
        ),
      ));
      // We do -1 because the WidgetSpan already takes up one character.
      children.add(TextSpan(text: '\u200b' * (match[0]!.length - 1)));
      return '';
    },
    onNonMatch: (text) {
      children.add(TextSpan(text: text, style: style));
      return '';
    },
  );

  return TextSpan(style: style, children: children);
}
Gpack
  • 1,878
  • 3
  • 18
  • 45