> ## Documentation Index
> Fetch the complete documentation index at: https://cometchat-22654f5b-docs-audit-content-webhooks.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Shortcut Formatter

> Implement shortcut formatting in CometChat Flutter UI Kit with CometChatTextFormatter, trigger characters, and message shortcuts.

## Introduction

The `ShortcutFormatter` class extends `CometChatTextFormatter` to handle shortcuts within messages. This guide walks you through implementing shortcut extensions in your CometChat V6 application.

## Setup

1. Create the ShortcutFormatter class by extending `CometChatTextFormatter`:

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    import 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart';

    class ShortcutFormatter extends CometChatTextFormatter {
      @override
      void init() {
        trackingCharacter ??= "!";
      }

      bool isShortcutTracking = false;

      void prepareShortcuts(TextEditingController textEditingController) {
        CometChat.callExtension('message-shortcuts', 'GET', '/v1/fetch', null,
          onSuccess: (map) {
            if (map.isNotEmpty) {
              Map<String, dynamic> data = map["data"];
              if (data.isNotEmpty) {
                Map<String, dynamic> shortcuts = data["shortcuts"];
                if (shortcuts.isNotEmpty) {
                  parseData(shortcuts: shortcuts, textEditingController: textEditingController);
                }
              }
            }
          },
          onError: (error) {},
        );
      }

      void parseData({Map<String, dynamic>? shortcuts, required TextEditingController textEditingController}) async {
        if (shortcuts == null || shortcuts.isEmpty) {
          suggestionListEventSink?.add([]);
          if (onSearch != null) onSearch!(null);
          CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
        } else {
          CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
          if (suggestionListEventSink != null && shortcuts.isNotEmpty) {
            List<SuggestionListItem> list = [];
            shortcuts.forEach((key, value) => list.add(SuggestionListItem(
              id: key,
              title: "$key → $value",
              onTap: () {
                int cursorPos = textEditingController.selection.base.offset;
                String left = textEditingController.text.substring(0, cursorPos - 1);
                String right = textEditingController.text.substring(cursorPos);
                textEditingController.text = "$left$value $right";
                updatePreviousText(textEditingController.text);
                textEditingController.selection = TextSelection(
                  baseOffset: cursorPos - 1 + "$value".length + 1,
                  extentOffset: cursorPos - 1 + "$value".length + 1,
                );
                resetMatchTracker();
                isShortcutTracking = false;
                CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
              },
            )));
            suggestionListEventSink?.add(list);
          }
        }
      }

      void updatePreviousText(String text) {
        previousTextEventSink?.add(text);
      }

      void resetMatchTracker() {
        suggestionListEventSink?.add([]);
        if (onSearch != null) onSearch!(null);
      }

      @override
      void onChange(TextEditingController textEditingController, String previousText) {
        var cursorPosition = textEditingController.selection.base.offset;
        if (textEditingController.text.isEmpty) {
          resetMatchTracker();
          return;
        }
        // Handle shortcut tracking logic
        String previousCharacter = cursorPosition == 0 ? "" : textEditingController.text[cursorPosition - 1];
        bool isSpace = cursorPosition == 1 || (textEditingController.text.length > 1 && cursorPosition > 1 &&
            (textEditingController.text[cursorPosition - 2] == " " || textEditingController.text[cursorPosition - 2] == "\n"));

        if (isShortcutTracking) {
          isShortcutTracking = false;
          if (onSearch != null) onSearch!(null);
          CometChatUIEvents.hidePanel(composerId, CustomUIPosition.composerPreview);
        } else if (previousCharacter == trackingCharacter && isSpace) {
          isShortcutTracking = true;
          if (onSearch != null) onSearch!(trackingCharacter);
          CometChatUIEvents.showPanel(composerId, CustomUIPosition.composerPreview,
              (context) => getLoadingIndicator(context, cometChatTheme));
          prepareShortcuts(textEditingController);
        }
      }

      @override
      TextStyle getMessageInputTextStyle(CometChatTheme theme) {
        throw UnimplementedError();
      }

      @override
      void handlePreMessageSend(BuildContext context, BaseMessage baseMessage) {}

      @override
      void onScrollToBottom(TextEditingController textEditingController) {}
    }
    ```
  </Tab>
</Tabs>

## Usage

Pass the `ShortcutFormatter` to the `textFormatters` property of `CometChatMessageComposer`:

<Tabs>
  <Tab title="Dart">
    ```dart theme={null}
    CometChatMessageComposer(
      user: user,
      textFormatters: [ShortcutFormatter()],
    )
    ```
  </Tab>
</Tabs>
