KeyMap for bold text in lang-markdown

Just starting out with CodeMirror6, I’m building a Markdown-Editor and want to add basic Markdown KeyMaps, starting with marking a selection as Bold. I got the first part down, but want it to behave like a toggle, so when a selection is already wrapped in **, eg. due to a previous Mod+b, I want to remove the **.

This is what I have so far.

  const insertBoldMarker: StateCommand = ({ state, dispatch }) => {
    const changes = state.changeByRange((range) => {
      return {
        changes: [
          {
            from: range.from,
            insert: Text.of(['**']),
          },
          {
            from: range.to,
            insert: Text.of(['**']),
          },
        ],
        range: EditorSelection.range(range.from + 2, range.to + 2),
      }
    })

    dispatch(
      state.update(changes, {
        scrollIntoView: true,
        annotations: Transaction.userEvent.of('input'),
      })
    )

    return true
  }

  const customKeyMap: KeyBinding[] = [
    {
      key: 'Mod-b',
      run: insertBoldMarker,
    },
  ]

How would I check whether eg (range.from - 2) are "**"? Especially keeping possible Multiselection (not yet enabled) in mind.

I just need some directions :slight_smile:

Update: probably with iterRange, gonna look into that!

1 Like

I think just doing state.sliceDoc(range.from - 2, range.from) == "**" should work here.

1 Like

Of course! I got it to work the way I want.

Really digging the API design of CM6! Thank you so much doing this! For anyone else who might land here via google, this is my result:

  const insertBoldMarker: StateCommand = ({ state, dispatch }) => {
    const changes = state.changeByRange((range) => {
      const isBoldBefore = state.sliceDoc(range.from - 2, range.from) === "**";
      const isBoldAfter = state.sliceDoc(range.to, range.to + 2) === "**";
      const changes = [];

      changes.push(isBoldBefore ? {
        from: range.from - 2,
        to: range.from,
        insert: Text.of([''])
      } : {
        from: range.from,
        insert: Text.of(['**']),
      })

      changes.push(isBoldAfter ? {
        from: range.to,
        to: range.to + 2,
        insert: Text.of([''])
      } : {
        from: range.to,
        insert: Text.of(['**']),
      })

      const extendBefore = isBoldBefore ? -2 : 2;
      const extendAfter = isBoldAfter ? -2 : 2;

      return {
        changes,
        range: EditorSelection.range(range.from + extendBefore, range.to + extendAfter),
      }
    })

    dispatch(
      state.update(changes, {
        scrollIntoView: true,
        annotations: Transaction.userEvent.of('input'),
      })
    )

    return true
  }

  const boldBinding: KeyBinding[] = [
    {
      key: 'Mod-b',
      run: insertBoldMarker,
    },
  ]

3 Likes

Hello, I’m trying to make a markdown editor, too. Thanks for your example code, and there are still two questions bothering me:

  1. what is the annotations: Transaction.userEvent.of('input') for?
  2. I’m trying to use EditorSelection.range(range.from, range.to + 4) to make marked-by-*-text selected, but it fail like this
    图片

BTW, EditorSelection.range(range.from + 2, range.to + 2) worked as expected.

图片

That marks a transaction as being user text input (which causes some extensions, such as autocompletion, to react differently).

Not sure what’s happening there.

Can It be reproduced, or it’s just my wrong usage in CM6?

I’m not sure how exactly you’re using that expression. A small, self-contained example might help.

Thanks for sharing it :slight_smile: