diff --git a/src/App.tsx b/src/App.tsx index 0349ef9..e42fab4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,49 +1,16 @@ -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(), -] +import { noop } from 'lodash' +import { Editor } from './Editor' export const App = () => { - const ref = useRef(null) - const view = useRef() - const [text, setText] = useState(testText) - - useEffect(() => { - view.current = new EditorView({ - doc: text, - extensions: [basicSetup, getTextPlugin(setText)], - parent: ref.current!, - }) - - return () => view.current?.destroy() - }, []) - - return
+ return } -export default App - const testText = ` 2 + 2 * 2 sqrt(3^2 + 4^2) -2 inch to cm +5cm + 0.2 m in inch cos(45 deg) +0.1 + 0.2 a = 25 b = a * 2 @@ -53,4 +20,13 @@ pow2(6) 2 * 2 last + 1 + +f(x) = (sin(x) + cos(x/2)) * 5 + +a = [1, 2, 3; 2+2, 5, 6] +a[2, 3] +a[1:2, 2] +b = [1, 2; 3, 4] +b * a +a[3, 1:3] = [7, 8, 9] ` diff --git a/src/Editor.tsx b/src/Editor.tsx new file mode 100644 index 0000000..0f5ac8c --- /dev/null +++ b/src/Editor.tsx @@ -0,0 +1,42 @@ +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 } from 'react' +import { textObserverPlugin } from './textObserverPlugin' +import { mathPlugin } from './mathPlugin' + +type Props = { + text: string + onChange: (text: string) => void +} + +export const Editor = ({ text, onChange }: Props) => { + const ref = useRef(null) + const view = useRef() + + useEffect(() => { + view.current = new EditorView({ + doc: text, + extensions: [basicSetup, textObserverPlugin(onChange)], + parent: ref.current!, + }) + + return () => view.current?.destroy() + }, []) + + return
+} + +const basicSetup: Extension = [ + history(), + drawSelection(), + dropCursor(), + indentOnInput(), + syntaxHighlighting(defaultHighlightStyle, { fallback: true }), + closeBrackets(), + keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...searchKeymap, ...historyKeymap]), + mathPlugin(), +] diff --git a/src/index.css b/src/index.css index 7746e58..332f420 100644 --- a/src/index.css +++ b/src/index.css @@ -17,6 +17,32 @@ body { } .math-result { - color: grey; + color: rgb(80, 80, 80); margin-left: 8px; -} \ No newline at end of file +} + +.matrix-wrapper { + display: flex; + margin-left: 8px; + margin-top: 4px; +} + +.matrix-result { + color: rgb(80, 80, 80); +} + +.matrix-wrapper::before { + min-width: 4px; + content: ' '; + border: 1px solid rgb(140, 140, 140); + border-right: 0; + margin-right: 6px; +} + +.matrix-wrapper::after { + min-width: 4px; + content: ' '; + border: 1px solid rgb(140, 140, 140); + border-left: 0; + margin-left: 6px; +} diff --git a/src/main.tsx b/src/main.tsx index e70585d..5e818a0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' -import App from './App.tsx' +import { App } from './App.tsx' import './index.css' const root = document.getElementById('root') diff --git a/src/math.ts b/src/math.ts new file mode 100644 index 0000000..ff37d72 --- /dev/null +++ b/src/math.ts @@ -0,0 +1,3 @@ +import { create, all } from 'mathjs' + +export const math = create(all, { number: 'BigNumber', precision: 16 }) diff --git a/src/mathPlugin.ts b/src/mathPlugin.ts index 579b17e..36b3d6a 100644 --- a/src/mathPlugin.ts +++ b/src/mathPlugin.ts @@ -1,7 +1,5 @@ import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate, WidgetType } from '@codemirror/view' -import { create, all } from 'mathjs' - -export const math = create(all, { number: 'BigNumber' }) +import { math } from './math' export const mathPlugin = () => ViewPlugin.fromClass( @@ -30,21 +28,53 @@ const createDecorations = (view: EditorView) => { for (let i = 1; i <= view.state.doc.lines; i++) { const { text, to } = view.state.doc.line(i) + let decoration: Decoration | null = null try { const result = parser.evaluate(text) - if (result?.isBigNumber) { + if (result?.isBigNumber || result?.type === 'Unit') { parser.set('last', result) - const decoration = Decoration.widget({ - widget: new MathResult(result.toPrecision()), + decoration = Decoration.widget({ + widget: new MathResult(result.toString()), side: 1, }) + } - widgets.push(decoration.range(to)) + if (result?.type === 'DenseMatrix') { + parser.set('last', result) + + const size = result.size() + + switch (size.length) { + case 1: + decoration = Decoration.widget({ + widget: new MathResult(result.toString()), + side: 1, + }) + break + + case 2: + decoration = Decoration.widget({ + widget: new Matrix(result), + side: 1, + }) + break + } + } + + if (typeof result === 'function') { + decoration = Decoration.widget({ + widget: new MathResult(result.syntax), + side: 1, + }) } } catch (e) {} + + if (decoration) { + widgets.push(decoration.range(to)) + } } return Decoration.set(widgets) @@ -72,6 +102,47 @@ class MathResult extends WidgetType { } } +class Matrix extends WidgetType { + constructor(readonly result: any) { + super() + } + + eq(other: MathResult) { + return other.result == this.result + } + + toDOM() { + const wrapper = document.createElement('div') + wrapper.className = 'matrix-wrapper' + + const table = document.createElement('table') + table.className = 'matrix-result' + wrapper.appendChild(table) + + const array = this.result.toArray() + for (const row of array) { + const tr = document.createElement('tr') + tr.className = 'matrix-row' + + for (const col of row) { + const td = document.createElement('td') + td.className = 'matrix-col' + td.innerText = col.toString() + + tr.appendChild(td) + } + + table.appendChild(tr) + } + + return wrapper + } + + ignoreEvent() { + return false + } +} + type Range = { readonly from: number readonly to: number diff --git a/src/getTextPlugin.ts b/src/textObserverPlugin.ts similarity index 77% rename from src/getTextPlugin.ts rename to src/textObserverPlugin.ts index 87c89ff..d4ac59a 100644 --- a/src/getTextPlugin.ts +++ b/src/textObserverPlugin.ts @@ -1,6 +1,6 @@ import { ViewPlugin, ViewUpdate } from '@codemirror/view' -export const getTextPlugin = (callback: (text: string) => void) => +export const textObserverPlugin = (callback: (text: string) => void) => ViewPlugin.define(() => ({ update(update: ViewUpdate) { if (update.docChanged) {