Created
June 18, 2023 21:11
-
-
Save nitzanhen/128cf1cc5797255282fea5d175806b0f to your computer and use it in GitHub Desktop.
dirty monkey-patch of CodeMirror's EditorView.bidiSpans() to force math blocks into LTR.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { BidiSpan, EditorView } from "@codemirror/view"; | |
import { Line } from "@codemirror/state"; | |
// Monkey patch the bidiSpans method to override the bidi spans for math blocks | |
/** Create a new BidiSpan */ | |
const bidiSpan = (from: number, to: number, dir: number) => | |
// @ts-expect-error - BidiSpan constructor is private | |
new BidiSpan(from, to, dir); | |
const mathRegex = /\$\$?.*?\$\$?/g; | |
/** Extract the math blocks as BidiSpans */ | |
function getMathRanges(text: string): BidiSpan[] { | |
const ranges: [number, number][] = []; | |
let match = mathRegex.exec(text); | |
while (match) { | |
ranges.push([match.index, match.index + match[0].length]); | |
match = mathRegex.exec(text) | |
} | |
return ranges.map(([from, to]) => bidiSpan(from, to, 2)); | |
} | |
const bidiSpans = EditorView.prototype.bidiSpans; | |
// This is inefficient - it's possible to override all spans in one pass. | |
function overrideSpans(spans: readonly BidiSpan[], newSpan: BidiSpan) { | |
const newSpans: BidiSpan[] = []; | |
let i = 0; | |
while (i < spans.length && spans[i].to < newSpan.from) { | |
newSpans.push(spans[i]); | |
i++; | |
} | |
if (i === spans.length) { | |
return newSpans; | |
} | |
const span = spans[i]; | |
const preSpan = bidiSpan(span.from, newSpan.from, span.dir); | |
newSpans.push(preSpan); | |
newSpans.push(newSpan); | |
i++; | |
while (i < spans.length && spans[i].to <= newSpan.to) { | |
i++; | |
} | |
if (i === spans.length) { | |
return newSpans; | |
} | |
const postSpan = bidiSpan(newSpan.to, spans[i].to, span.dir); | |
newSpans.push(postSpan); | |
i++; | |
while (i < spans.length) { | |
newSpans.push(spans[i]); | |
i++; | |
} | |
// Make sure last span is RTL | |
if (newSpans.length > 0 && newSpans[newSpans.length - 1].level === 2) { | |
newSpans.push( | |
bidiSpan(newSpans[newSpans.length - 1].to, newSpan.to, 1) | |
) | |
} | |
return newSpans; | |
} | |
export const overrideBidiSpans = () => { | |
EditorView.prototype.bidiSpans = function (line: Line): readonly BidiSpan[] { | |
let spans = bidiSpans.call(this, line); | |
const mathRanges = getMathRanges(line.text); | |
// Override bidi spans such that math blocks are treated as LTR | |
for (const mathRange of mathRanges) { | |
spans = overrideSpans(spans, mathRange); | |
} | |
return spans; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment