Add support for Math blocks

This commit is contained in:
Jonatan Heyman 2023-03-02 18:40:44 +01:00
parent cc6cda0217
commit 4d12404d77
10 changed files with 123 additions and 2 deletions

View File

@ -13,6 +13,19 @@ Welcome to Heynote!
[${modChar} + Up] Goto previous block [${modChar} + Up] Goto previous block
[${modChar} + A] Select all text in a note block. Press again to select the whole scratchpad [${modChar} + A] Select all text in a note block. Press again to select the whole scratchpad
[${modChar} + + Up/Down]  Add additional cursor above/below [${modChar} + + Up/Down]  Add additional cursor above/below
math
This is a Math block. Here, rows are evaluated as math expressions.
length = 10
radius = 5
volume = length * radius^2 * PI
sqrt(9)
It also supports some basic unit conversions:
13 inches in cm
time = 3900 seconds to minutes
time * 2
text-a text-a
` `
@ -27,6 +40,20 @@ Welcome to Heynote!
[${modChar} + Up] Goto previous block [${modChar} + Up] Goto previous block
[${modChar} + A] Select all text in a note block. Press again to select the whole scratchpad [${modChar} + A] Select all text in a note block. Press again to select the whole scratchpad
[${modChar} + + Up/Down]  Add additional cursor above/below [${modChar} + + Up/Down]  Add additional cursor above/below
math
This is a Math block. Here, rows are evaluated as math expressions.
length = 10
radius = 5
volume = length * radius^2 * PI
sqrt(9)
It also supports some basic unit conversions:
13 inches in cm
time = 3900 seconds to minutes
time * 2
text-a
python-a python-a
# hmm # hmm

View File

@ -16,6 +16,7 @@
<div id="app"></div> <div id="app"></div>
<script type="module" src="src/main.js"></script> <script type="module" src="src/main.js"></script>
<script src="math.js" type="text/javascript"></script>
<!--<div id="editor" class="editor"></div> <!--<div id="editor" class="editor"></div>
<script type="module" src="src/editor/index.js"></script>--> <script type="module" src="src/editor/index.js"></script>-->

3
public/math.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,7 @@ import { Note, Document, NoteDelimiter } from "../lang-heynote/parser.terms.js"
import { IterMode } from "@lezer/common"; import { IterMode } from "@lezer/common";
import { heynoteEvent, LANGUAGE_CHANGE } from "../annotation.js"; import { heynoteEvent, LANGUAGE_CHANGE } from "../annotation.js";
import { SelectionChangeEvent } from "../event.js" import { SelectionChangeEvent } from "../event.js"
import { mathBlock } from "./math.js"
// tracks the size of the first delimiter // tracks the size of the first delimiter
@ -310,5 +311,6 @@ export const noteBlockExtension = (editor) => {
preventFirstBlockFromBeingDeleted, preventFirstBlockFromBeingDeleted,
preventSelectionBeforeFirstBlock, preventSelectionBeforeFirstBlock,
emitCursorChange(editor), emitCursorChange(editor),
mathBlock,
] ]
} }

76
src/editor/block/math.js Normal file
View File

@ -0,0 +1,76 @@
import { ViewPlugin } from "@codemirror/view"
import { Decoration } from "@codemirror/view"
import { RangeSetBuilder } from "@codemirror/state"
import { WidgetType } from "@codemirror/view"
import { getNoteBlockFromPos } from "./block"
class MathResult extends WidgetType {
constructor(result) {
super()
this.result = result
}
eq(other) { return other.result == this.result }
toDOM() {
let wrap = document.createElement("span")
wrap.className = "heynote-math-result"
wrap.innerHTML = this.result
return wrap
}
ignoreEvent() { return false }
}
function mathDeco(view) {
let mathParsers = new WeakMap()
let builder = new RangeSetBuilder()
for (let { from, to } of view.visibleRanges) {
for (let pos = from; pos <= to;) {
let line = view.state.doc.lineAt(pos)
var block = getNoteBlockFromPos(view.state, pos)
if (block && block.language.name == "math") {
// get math.js parser and cache it for this block
let parser = mathParsers.get(block)
if (!parser) {
parser = math.parser()
mathParsers.set(block, parser)
}
// evaluate math line
let result
try {
result = parser.evaluate(line.text)
} catch (e) {
// suppress any errors
}
// if we got a result from math.js, add the result decoration
if (result !== undefined) {
builder.add(line.to, line.to, Decoration.widget({widget: new MathResult(math.format(result, 8)), side: 1}))
}
}
pos = line.to + 1
}
}
return builder.finish()
}
export const mathBlock = ViewPlugin.fromClass(class {
decorations
constructor(view) {
this.decorations = mathDeco(view)
}
update(update) {
if (update.docChanged || update.viewportChanged) {
this.decorations = mathDeco(update.view)
}
}
}, {
decorations: v => v.decorations
})

View File

@ -11,7 +11,7 @@ NoteDelimiter {
@tokens { @tokens {
noteDelimiterMark { "∞∞∞" } noteDelimiterMark { "∞∞∞" }
NoteLanguage { "text" | "javascript" | "json" | "python" | "html" | "sql" | "markdown" | "java" | "php" | "css" | "xml" | "cpp" | "rust" } NoteLanguage { "text" | "math" | "javascript" | "json" | "python" | "html" | "sql" | "markdown" | "java" | "php" | "css" | "xml" | "cpp" | "rust" }
Auto { "-a" } Auto { "-a" }
noteDelimiterEnter { "\n" } noteDelimiterEnter { "\n" }
//NoteContent { String } //NoteContent { String }

View File

@ -10,7 +10,7 @@ export const parser = LRParser.deserialize({
maxTerm: 10, maxTerm: 10,
skippedNodes: [0], skippedNodes: [0],
repeatNodeCount: 1, repeatNodeCount: 1,
tokenData: "&|~R[YZw}!O|#V#W!X#[#]!s#^#_#V#a#b$p#d#e%f#f#g%{#g#h&X#h#i&_#l#m!y%&x%&y&k~|OX~~!PP#T#U!S~!XOU~~![Q#d#e!b#g#h!m~!eP#d#e!h~!mOT~~!pP#g#h!h~!vP#h#i!y~!|P#a#b#P~#SP#`#a!h~#YQ#T#U#`#g#h$d~#cP#j#k#f~#iP#T#U#l~#qPT~#g#h#t~#wP#V#W#z~#}P#f#g$Q~$TP#]#^$W~$ZP#d#e$^~$aP#h#i!h~$gP#c#d$j~$mP#b#c!h~$sP#T#U$v~$yP#f#g$|~%PP#_#`%S~%VP#W#X%Y~%]P#c#d%`~%cP#k#l$j~%iQ#[#]!b#m#n%o~%rP#h#i%u~%xP#[#]$d~&OP#i#j&R~&UP#g#h$^~&[P#e#f#P~&bP#X#Y&e~&hP#l#m$^~&nP%&x%&y&q~&tP%&x%&y&w~&|OY~", tokenData: "'V~R[YZw}!O|#V#W!X#[#]!s#^#_#V#a#b$p#d#e%o#f#g&U#g#h&b#h#i&h#l#m!y%&x%&y&t~|OX~~!PP#T#U!S~!XOU~~![Q#d#e!b#g#h!m~!eP#d#e!h~!mOT~~!pP#g#h!h~!vP#h#i!y~!|P#a#b#P~#SP#`#a!h~#YQ#T#U#`#g#h$d~#cP#j#k#f~#iP#T#U#l~#qPT~#g#h#t~#wP#V#W#z~#}P#f#g$Q~$TP#]#^$W~$ZP#d#e$^~$aP#h#i!h~$gP#c#d$j~$mP#b#c!h~$sP#T#U$v~$yQ#f#g%P#h#i%i~%SP#_#`%V~%YP#W#X%]~%`P#c#d%c~%fP#k#l$j~%lP#[#]!h~%rQ#[#]!b#m#n%x~%{P#h#i&O~&RP#[#]$d~&XP#i#j&[~&_P#g#h$^~&eP#e#f#P~&kP#X#Y&n~&qP#l#m$^~&wP%&x%&y&z~&}P%&x%&y'Q~'VOY~",
tokenizers: [0, noteContent], tokenizers: [0, noteContent],
topRules: {"Document":[0,2]}, topRules: {"Document":[0,2]},
tokenPrec: 0 tokenPrec: 0

View File

@ -24,6 +24,7 @@ class Language {
export const LANGUAGES = [ export const LANGUAGES = [
new Language("text", "Plain Text", null, "plaintext"), new Language("text", "Plain Text", null, "plaintext"),
new Language("math", "Math", null, null),
new Language("javascript", "JavaScript", javascriptLanguage.parser, "javascript"), new Language("javascript", "JavaScript", javascriptLanguage.parser, "javascript"),
new Language("json", "JSON", jsonLanguage.parser, "json"), new Language("json", "JSON", jsonLanguage.parser, "json"),
new Language("python", "Python", pythonLanguage.parser, "python"), new Language("python", "Python", pythonLanguage.parser, "python"),

View File

@ -43,4 +43,11 @@ export const heynoteBase = EditorView.theme({
'.heynote-block-start.first': { '.heynote-block-start.first': {
height: '0px', height: '0px',
}, },
'.heynote-math-result': {
background: '#48b57e',
color: '#fff',
padding: '0px 4px',
borderRadius: '2px',
marginLeft: '12px',
},
}) })

View File

@ -111,6 +111,10 @@ const darkTheme = EditorView.theme({
background: "#213644", background: "#213644",
borderTop: "1px solid #1e222a", borderTop: "1px solid #1e222a",
}, },
".heynote-math-result": {
background: "#0e1217",
color: "#7fcba9",
},
}, { dark: true }); }, { dark: true });
/** /**