Are atomic multi line widgets possible?

Hello,
I am trying to create an atomic multiline widget but seems I can only do one or the other.

export function chatPrompt(state: EditorState) {
  const chatWidgets: Range<Decoration>[] = [];
  const doc = state.doc.toString();
  // get position of string between "[[" and "]]"
  const regex = /\[\[([\s\S]*?)\]\]/g;
  let match;
  while ((match = regex.exec(doc)) !== null) {
    const chat = match[1];
    const position = match.index;
    const end = match.index + match[0].length;
    const deco = Decoration.replace({
      widget: new ChatPromptWidget(chat),
    });
    const range = deco.range(position, end);
    chatWidgets.push(range);
  }
  return Decoration.set(chatWidgets);
}

export class ChatPromptWidget extends WidgetType {
  value: string;
  constructor(value: string) {
    super();
    this.value = value;
  }
  toDOM(): HTMLElement {
    const span = document.createElement("span");
    span.innerHTML = this.value;
    span.className = "cm-prompt";
    return span;
  }
}

Option 1: using ViewPlugin = but this gives me error -
RangeError: Decorations that replace line breaks may not be specified via plugins

export const chatPromptEffect = StateEffect.define<{
  decorations: DecorationSet;
}>({});
export const chatPromptPlugin = ViewPlugin.fromClass(
  class {
    chats: DecorationSet;
    constructor(view: EditorView) {
      this.chats = chatPrompt(view.state);
    }
    update(update: { state: EditorState }) {
      this.chats = chatPrompt(update.state);
    }
  },
  {
    decorations: (instance) => instance.chats,
    provide: (plugin) =>
      EditorView.atomicRanges.of((view) => {
        return view.state.field(charPromptExtension); 
      // or return view.plugin(plugin)?.chats || Decoration.none;
      }),
  },
);

Option 2: using StateField: but there is a type mismatch.

export const charPromptExtension = StateField.define({
  create() {
    return Decoration.none;
  },
  update(value, tr) {
    for (const effect of tr.effects) {
      if (effect.is(chatPromptEffect)) {
        value = effect.value?.decorations;
      }
    }
    return value;
  },
  provide(f) {
    return EditorView.atomicRanges.from(f);
      /* Error - TS2345: Argument of type StateField<DecorationSet> is not assignable to parameter of type
StateField<(view: EditorView) => RangeSet<any>>
Type DecorationSet is not assignable to type (view: EditorView) => RangeSet<any>
Type RangeSet<Decoration> provides no match for the signature (view: EditorView): RangeSet<any> */
  },
});

If this even possible? Any pointers would be very helpful.

That last error tells you that this facet expects a function from a view to a decoration set, not a direct set. So you want something like EditorView.atomicRanges.of(v => v.state.field(f)). But you’ll also need to return an instance of the decorations facet from that provide, or your decorations won’t show up.

Thanks @marijn that fixed the type error. But I don’t understand what you mean by

you’ll also need to return an instance of the decorations facet from that provide

How can I return EditorView.atomicRanges.of((v) => v.state.field(f)); and the decorations facet ?

do you mean a ViewPlugin like this? -

export const chatPromptPlugin = ViewPlugin.define(() => {
  return {
    update(update) {
      if (update.state.doc.toString() === update.startState.doc.toString())
        return;
      update.view.dispatch({
        effects: chatPromptEffect.of({
          decorations: chatPrompt(update.state),
        }),
      });
    },
  };
});

Put them in an array. Arrays of extensions can be provided wherever extensions are valid.