Skip to content

Instantly share code, notes, and snippets.

@nitzanhen
Created June 18, 2023 21:11
Show Gist options
  • Save nitzanhen/128cf1cc5797255282fea5d175806b0f to your computer and use it in GitHub Desktop.
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.
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