-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMathEvaluator.ts
More file actions
88 lines (78 loc) · 2.78 KB
/
Copy pathMathEvaluator.ts
File metadata and controls
88 lines (78 loc) · 2.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import type { EditorView } from '@codemirror/view'
import { getScope } from './VariableScope'
import { Parser, type Values } from 'expr-eval'
export function evaluateMath(
docStr: string,
scope: Record<string, unknown>
): { from: number; to: number; insert: string }[] {
const parser = new Parser()
const changes: { from: number; to: number; insert: string }[] = []
// 1. Evaluate new lines that end with '=' but don't have '\u200B' yet
const lines = docStr.split('\n')
let offset = 0
for (let i = 0; i < lines.length; i++) {
const text = lines[i]
const lineLen = text.length
if (!text.includes('\u200B') && text.trim().endsWith('=')) {
const expr = text.substring(0, text.lastIndexOf('=')).trim()
if (expr && !expr.startsWith('/var') && !expr.startsWith('/globvar')) {
try {
const result = String(parser.evaluate(expr, scope as Values))
changes.push({
from: offset + lineLen,
to: offset + lineLen,
insert: '\u200B' + result,
})
} catch (e) {
// eslint-disable-next-line no-console
console.error(`MathEvaluator new evaluation error for ${expr}:`, e)
}
}
}
offset += lineLen + 1 // +1 for '\n'
}
// 2. Re-evaluate existing calculations that already have '\u200B'
const reCalc = /^(.*?=\s*)\u200B(.*)$/gm
let calcMatch
while ((calcMatch = reCalc.exec(docStr)) !== null) {
const exprPart = calcMatch[1]
const oldResult = calcMatch[2]
const expr = exprPart.replace(/=\s*$/, '').trim()
if (expr) {
try {
const newResult = String(parser.evaluate(expr, scope as Values))
if (newResult !== oldResult) {
const startReplace = calcMatch.index + exprPart.length + 1 // +1 for \u200B
const endReplace = calcMatch.index + calcMatch[0].length
if (!changes.some((c) => c.from <= endReplace && c.to >= startReplace)) {
changes.push({
from: startReplace,
to: endReplace,
insert: newResult,
})
}
}
} catch (e) {
// eslint-disable-next-line no-console
console.error(`MathEvaluator old evaluation error for ${expr}:`, e)
}
}
}
return changes
}
export class MathEvaluator {
static evalTimeout: number | null = null
static triggerMathEvaluation(view: EditorView) {
if (this.evalTimeout) window.clearTimeout(this.evalTimeout)
this.evalTimeout = window.setTimeout(() => {
// Guard against the editor being unmounted during the timeout
if (!view.state) return
const docStr = view.state.doc.toString()
const scope = getScope()
const changes = evaluateMath(docStr, scope)
if (changes.length > 0) {
view.dispatch({ changes })
}
}, 300)
}
}