mediawiki-extensions-CodeMi.../resources/codemirror.mediawiki.bidiIsolation.js

128 lines
2.9 KiB
JavaScript
Raw Normal View History

const {
Decoration,
DecorationSet,
Direction,
EditorView,
PluginSpec,
Prec,
RangeSet,
RangeSetBuilder,
ViewPlugin,
ViewUpdate,
syntaxTree
} = require( 'ext.CodeMirror.v6.lib' );
const mwModeConfig = require( './codemirror.mediawiki.config.js' );
/**
* @type {Decoration}
* @private
*/
const isolate = Decoration.mark( {
class: 'cm-bidi-isolate',
bidiIsolate: Direction.LTR
} );
/**
* @param {EditorView} view
* @return {RangeSet}
* @private
*/
function computeIsolates( view ) {
const set = new RangeSetBuilder();
if ( view.editorAttrs.dir === 'rtl' ) {
for ( const { from, to } of view.visibleRanges ) {
let startPos = null;
syntaxTree( view.state ).iterate( {
from,
to,
enter( node ) {
// Determine if this is a bracket node (start or end of a tag).
const isBracket = node.name.split( '_' )
.some( ( tag ) => [
mwModeConfig.tags.htmlTagBracket,
mwModeConfig.tags.extTagBracket
].includes( tag ) );
if ( startPos === null && isBracket ) {
// If we find a bracket node, we keep track of the start position.
startPos = node.from;
} else if ( isBracket ) {
// When we find the closing bracket, add the isolate.
set.add( startPos, node.to, isolate );
startPos = null;
}
}
} );
}
}
return set.finish();
}
/**
* @private
*/
class CodeMirrorBidiIsolation {
/**
* @constructor
* @param {EditorView} view The editor view.
*/
constructor( view ) {
/** @type {DecorationSet} */
this.isolates = computeIsolates( view );
/** @type {Tree} */
this.tree = syntaxTree( view.state );
/** @type {Direction} */
this.dir = view.textDirection;
}
/**
* @param {ViewUpdate} update
*/
update( update ) {
if ( update.docChanged || update.viewportChanged ||
syntaxTree( update.state ) !== this.tree ||
update.view.textDirection !== this.dir
) {
this.isolates = computeIsolates( update.view );
this.tree = syntaxTree( update.state );
}
}
}
/**
* @type {PluginSpec}
* @private
*/
const bidiIsolationSpec = {
provide: ( plugin ) => {
/**
* @param {EditorView} view
* @return {DecorationSet}
*/
const access = ( view ) => view.plugin( plugin ) ?
( view.plugin( plugin ).isolates || Decoration.none ) :
Decoration.none;
// Use the lowest precedence to ensure that other decorations
// don't break up the isolating decorations.
return Prec.lowest( [
EditorView.decorations.of( access ),
EditorView.bidiIsolatedRanges.of( access )
] );
}
};
/**
* Bidirectional isolation plugin for CodeMirror for use on RTL pages.
* This ensures HTML and MediaWiki tags are always displayed left-to-right.
*
* Use this plugin by passing in `bidiIsolation: true` when instantiating
* a [CodeMirrorModeMediaWiki]{@link CodeMirrorModeMediaWiki} object.
*
* @module CodeMirrorBidiIsolation
* @see https://codemirror.net/examples/bidi/
*/
module.exports = ViewPlugin.fromClass( CodeMirrorBidiIsolation, bidiIsolationSpec );