How to invoke extension(plugin) by transaction?

I have wrote an extension(pugin) for implementing custom highlight.

Now, I need to find the way to open a method to invoke the function, not only in the update function of the plugin. I know that transaction is a right way to do this, but when I invoke the view.dispath({ }), I don’t know how to invoke extension(plugin) by using transaction class.

Are there good suggestions for me to solve this problem? Thanks a lot!

What does ‘invoking’ your extension mean in this case?

It means I can call buildRegExpDecoration by transaction.

I’m writing a demo for matching and highlighting regex expression. When the doc changed, I use the plugin to change the part of highlight, but when outer regex expression changed, I’m confused about how to call this method(buildRegExpDecoration) manually. (or other better ideas?) :disappointed_relieved:

const RegExpHighlightPlugin = ViewPlugin.fromClass(
  class {
    decorations: DecorationSet = Decoration.none

    constructor(view: EditorView) {
      this.buildRegExpDecoration(view)
    }

    update(update: ViewUpdate) {
      const { docChanged, view } = update
      if (docChanged) {
        this.buildRegExpDecoration(view)
      }
    }

    buildRegExpDecoration(view: EditorView) {
      let builder = new RangeSetBuilder<Decoration>()
      let viewportLines = 'm'.includes(props.regExpFlags) ? view.viewportLineBlocks : [view.viewportLineBlocks[0]]
      viewportLines.forEach((line) => {
        let lineObj = view.state.doc.lineAt(line.from)
        try {
          let match = props.matchFn(lineObj.text)
          if (match && match[0]) {
            let startIndex = match.index
            let endIndex = startIndex + match[0].length
            builder.add(line.from + startIndex, line.from + endIndex, Decoration.mark({ class: 'cm-searchMatch' }))
          }
        } catch (e) {
          // ignore
        }
      })

      this.decorations = builder.finish()
    }
  },
  { decorations: (v) => v.decorations }
)

You basically want the if (docChanged) line to also check for expression changes. You could store the expression in a state field and see if that field changed compared to update.startState. Or you could define a state effect and fire a transaction with that effect attached whenever your expression changed.

I solve the problem using this method successfully. And your good advice was very much appreciated!

I hope one day I can be as strong as you!:grinning:

This is my code to solve the problem. I hope it can help people who encounter similar problems.

Example: Decorations also played a great role.

// === code for state field
const highlightMark: Decoration = Decoration.mark({ class: 'cm-searchMatch' })

const addHighlight = StateEffect.define<{ from: number; to: number }>()
const highlightField = StateField.define<DecorationSet>({
  create() {
    return Decoration.none
  },
  update(marks, tr) {
    marks = marks.map(tr.changes)
    let empty = true
    tr.effects.forEach((effect) => {
      if (effect.is(addHighlight)) {
        marks = marks.update({
          add: [highlightMark.range(effect.value.from, effect.value.to)],
        })
        empty = false
      }
    })
    if (empty) {
      return RangeSet.empty
    }
    return marks
  },
  provide: (field) => EditorView.decorations.from(field),
})

// === code for dispatch by the effects of transaction
function updateHighlightRange() {
  let viewportLines = 'm'.includes(props.regExpFlags)
    ? view.viewportLineBlocks
    : [view.viewportLineBlocks[0]]

  const effects: any = []
  viewportLines.forEach((line) => {
    let lineObj = view.state.doc.lineAt(line.from)

    try {
      let matches: RegExpMatchArray[] = props.matchFn(lineObj.text)
      matches.forEach((match) => {
        if (match && match[0]) {
          let startIndex = match.index || 0
          let endIndex = startIndex + match[0].length
          effects.push(addHighlight.of({ from: line.from + startIndex, to: line.from + endIndex }))
        }
      })
    } catch (e) {
      // ignore
    }
  })
  if (!view.state.field(highlightField, false)) {
    effects.push(StateEffect.appendConfig.of([highlightField]))
  }

  view.dispatch({ effects })
}