Overriding Bidi spans in RTL context

This set of changes introduces an EditorView.bidiIsolatedRanges facet, that can be used to inform the editor’s bidi algorithm to treat ranges in a given range set as isolating (using the bidiIsolate property on the decoration specs to determine the direction of the isolates).

I’ve set up an example of how to use it but can’t publish it to the website before this code is released in @codemirror/view. It looks like this:


import {EditorView, Direction, ViewPlugin, ViewUpdate,
        Decoration, DecorationSet} from "@codemirror/view"
import {Prec} from "@codemirror/state"
import {syntaxTree} from "@codemirror/language"
import {Tree} from "@lezer/common"

const htmlIsolates = ViewPlugin.fromClass(class {
  isolates: DecorationSet
  tree: Tree

  constructor(view: EditorView) {
    this.isolates = computeIsolates(view)
    this.tree = syntaxTree(view.state)
  }

  update(update: ViewUpdate) {
    if (update.docChanged || update.viewportChanged ||
        syntaxTree(update.state) != this.tree) {
      this.isolates = computeIsolates(update.view)
      this.tree = syntaxTree(update.state)
    }
  }
}, {
  provide: plugin => {
    function access(view: EditorView) {
      return view.plugin(plugin)?.isolates ?? Decoration.none
    }
    return Prec.lowest([EditorView.decorations.of(access),
                        EditorView.bidiIsolatedRanges.of(access)])
  }
})

import {RangeSetBuilder} from "@codemirror/state"

const isolate = Decoration.mark({
  attributes: {style: "direction: ltr; unicode-bidi: isolate"},
  bidiIsolate: Direction.LTR
})

function computeIsolates(view: EditorView) {
  let set = new RangeSetBuilder<Decoration>()
  for (let {from, to} of view.visibleRanges) {
    syntaxTree(view.state).iterate({
      from, to,
      enter(node) {
        if (node.name == "OpenTag" || node.name == "CloseTag" ||
            node.name == "SelfClosingTag")
          set.add(node.from, node.to, isolate)
      }
    })
  }
  return set.finish()
}
1 Like