This commit is contained in:
2023-11-15 23:14:17 +03:00
commit 6f147ca8b7
16 changed files with 3465 additions and 0 deletions

56
src/App.tsx Normal file
View File

@@ -0,0 +1,56 @@
import { defaultKeymap, historyKeymap, history } from '@codemirror/commands'
import { EditorView, drawSelection, dropCursor, keymap } from '@codemirror/view'
import { defaultHighlightStyle, indentOnInput, syntaxHighlighting } from '@codemirror/language'
import { Extension } from '@codemirror/state'
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete'
import { searchKeymap } from '@codemirror/search'
import { useEffect, useRef, useState } from 'react'
import { getTextPlugin } from './getTextPlugin'
import { mathPlugin } from './mathPlugin'
const basicSetup: Extension = [
history(),
drawSelection(),
dropCursor(),
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
closeBrackets(),
keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...searchKeymap, ...historyKeymap]),
mathPlugin(),
]
export const App = () => {
const ref = useRef<HTMLDivElement>(null)
const view = useRef<EditorView>()
const [text, setText] = useState(testText)
useEffect(() => {
view.current = new EditorView({
doc: text,
extensions: [basicSetup, getTextPlugin(setText)],
parent: ref.current!,
})
return () => view.current?.destroy()
}, [])
return <div className="cm-editor-mount" ref={ref}></div>
}
export default App
const testText = `
2 + 2 * 2
sqrt(3^2 + 4^2)
2 inch to cm
cos(45 deg)
a = 25
b = a * 2
pow2(x) = x ^ 2
pow2(6)
2 * 2
last + 1
`

10
src/getTextPlugin.ts Normal file
View File

@@ -0,0 +1,10 @@
import { ViewPlugin, ViewUpdate } from '@codemirror/view'
export const getTextPlugin = (callback: (text: string) => void) =>
ViewPlugin.define(() => ({
update(update: ViewUpdate) {
if (update.docChanged) {
callback(update.state.doc.toString())
}
},
}))

22
src/index.css Normal file
View File

@@ -0,0 +1,22 @@
html {
line-height: 1.5;
tab-size: 4;
font-feature-settings: normal;
font-variation-settings: normal;
}
body {
margin: 0;
height: 100vh;
}
#root,
.cm-editor-mount,
.cm-editor {
height: 100%;
}
.math-result {
color: grey;
margin-left: 8px;
}

14
src/main.tsx Normal file
View File

@@ -0,0 +1,14 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
const root = document.getElementById('root')
if (root) {
ReactDOM.createRoot(root).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
}

79
src/mathPlugin.ts Normal file
View File

@@ -0,0 +1,79 @@
import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate, WidgetType } from '@codemirror/view'
import { create, all } from 'mathjs'
export const math = create(all, { number: 'BigNumber' })
export const mathPlugin = () =>
ViewPlugin.fromClass(
class {
decorations: DecorationSet
constructor(view: EditorView) {
this.decorations = createDecorations(view)
}
update(update: ViewUpdate) {
if (update.docChanged) {
this.decorations = createDecorations(update.view)
}
}
},
{
decorations: (v) => v.decorations,
},
)
const createDecorations = (view: EditorView) => {
let widgets: Range<Decoration>[] = []
const parser = math.parser()
for (let i = 1; i <= view.state.doc.lines; i++) {
const { text, to } = view.state.doc.line(i)
try {
const result = parser.evaluate(text)
if (result?.isBigNumber) {
parser.set('last', result)
const decoration = Decoration.widget({
widget: new MathResult(result.toPrecision()),
side: 1,
})
widgets.push(decoration.range(to))
}
} catch (e) {}
}
return Decoration.set(widgets)
}
class MathResult extends WidgetType {
constructor(readonly result: string) {
super()
}
eq(other: MathResult) {
return other.result == this.result
}
toDOM() {
let element = document.createElement('span')
element.className = 'math-result'
element.innerText = `= ${this.result}`
return element
}
ignoreEvent() {
return false
}
}
type Range<T> = {
readonly from: number
readonly to: number
readonly value: T
}

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />