Cursor to skip Decoration.replace

Hello, what would be the best way for cursor to skip ranges hidden through Decoration.replace?

Should I use transactionFilter(tr) that would update tr.selection if it gets into RangeSet that stores all Decoration.replace?

You may be looking for atomicRanges.

1 Like

Thanks a lot—this is exactly what’s needed!

Out of curiosity, if atomicRanges are included as core part of View why not to add them as additional parameter of Decoration.replace or Decoration.mark?

I made a quick plugin that links View.Plugin.atomicRanges with state.Facet(EditorView.decorations), but think it could be useful for others too.

import { Decoration, ViewPlugin, PluginField, EditorView } from '@codemirror/view'
import { RangeSet, Range } from '@codemirror/rangeset'

/** Decoration that hides the content and skips on selection move */
const rangeHide = Decoration.replace({})
/** Decoration that displays the content but skips on selection move */
const rangeSkip = Decoration.mark({})

/** ViewPlugin that enables the functionality
 * to hide a range provide RangeSet with either `rangeHide` or `rangeSkip` to ViewState facet
 */
const atomicRanges = ViewPlugin.define(
  (view) => ({ view }),
  {
    provide: PluginField.atomicRanges.from((val) => {
      const decorations = val.view.state.facet(EditorView.decorations)
      let ranges = []
      for (let iter = RangeSet.iter(decorations); iter.value !== null; iter.next())
        if (iter.value === rangeSkip || iter.value === rangeHide)
          ranges.push(new Range(iter.from, iter.to, iter.value))

      return RangeSet.of(ranges, true)
    })
  }
)

export { rangeHide, rangeSkip, atomicRanges }
2 Likes

Thank you @ilyakochik - this was very helpful to me.

I’ve been following the boolean toggle widget example but building a replace decoration instead of a widget and I want to make them atomic.

How would I apply your Facet in my case?

e.g.

const MyPluginClass = class {

	constructor(view){

		this.decorations = createsMyReplacementWidgets(update.view)

	}

	update(update){

		if(update.docChanged || update.viewportChanged){
			this.decorations = createsMyReplacementWidgets(update.view)
		}

	}

}

const myExtension =  [
	ViewPlugin.fromClass(
		MyPluginClass,
		{
			decorations: v => v.decorations
		}
	)	
]

I think this could work:

const MyPluginClass = class {
  constructor(view) {
    this.decorations = createsMyReplacementWidgets(view)
  }

  update(update) {
    if (update.docChanged || update.viewportChanged) {
      this.decorations = createsMyReplacementWidgets(update.view)
    }
  }
}

const myExtension = [
  ViewPlugin.fromClass(
    MyPluginClass,
    {
      decorations: v => v.decorations,
      // provide PluginField
      provide: PluginField.atomicRanges.from(val => val.decorations)
    }
  )
]
2 Likes

@ilyakochik yes this works perfectly - thank you very much