Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CodeEditorExample #3027

Merged
merged 49 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9232a98
broadcasting
dvd101x Feb 7, 2023
c589ad5
Simplified broadcasting
Feb 8, 2023
fbfd332
Updated for broadcasting
Feb 8, 2023
8537fc9
Changed to camel case
Feb 8, 2023
099a7d8
Camel case and auto formating
Feb 8, 2023
3a0aa2d
Added comments
Feb 8, 2023
394c737
Skip if matrices have the same size
dvd101x Feb 9, 2023
772e1db
Fixed issue with undefined variable
dvd101x Feb 9, 2023
2cff0be
Implemented broadcasting in all functions
Feb 9, 2023
6508164
Merge branch 'develop' of https://github.com/dvd101x/mathjs into develop
Feb 9, 2023
3ab4ec9
Added helper functions
dvd101x Feb 12, 2023
899b785
Merge branch 'develop' into develop
dvd101x Feb 12, 2023
0d5fb32
Added function to check for broadcasting rules
dvd101x Feb 12, 2023
3b649ef
Tests for broadcasted arithmetic
Feb 15, 2023
7356dd3
Fixed issue with matrix the size of a vector
Feb 15, 2023
1e2138c
Documented and updated broadcasting
dvd101x Feb 18, 2023
ba8eda2
Included broadcast.test
dvd101x Feb 19, 2023
9a65882
Merge branch 'develop' into develop
josdejong Feb 23, 2023
a04770f
Merge branch 'josdejong:develop' into develop
dvd101x Feb 24, 2023
104310f
Included math to syntax when missing
dvd101x Apr 24, 2023
75e6c70
Merge branch 'develop' of https://github.com/dvd101x/mathjs into develop
dvd101x May 27, 2023
e2bdd4a
Merge branch 'develop' of https://github.com/dvd101x/mathjs into develop
dvd101x Aug 5, 2023
a7f6d40
Merge branch 'josdejong:develop' into develop
dvd101x Aug 24, 2023
446cb86
Add code editor example
dvd101x Sep 12, 2023
befa6c7
Vite mini project
dvd101x Oct 27, 2023
d052ad9
Initial example
dvd101x Oct 27, 2023
2444da3
added alpine debounce
dvd101x Oct 28, 2023
27b46d3
Fixed display
dvd101x Oct 28, 2023
4b72334
Added parser.clear
dvd101x Oct 29, 2023
3c3e4d3
Added mathjs-language
dvd101x Oct 29, 2023
db4855a
Made module to get expressions
dvd101x Oct 29, 2023
78b3df3
Added custom events
dvd101x Oct 30, 2023
02da088
Issue with help formatting
dvd101x Oct 31, 2023
f78bfd8
Simplified help format
dvd101x Oct 31, 2023
cdc2e6d
Restored package.json
dvd101x Nov 4, 2023
61e27be
Merge branch 'josdejong:develop' into CodeEditorExample
dvd101x Jan 2, 2024
5db2a70
removed unneded icons
dvd101x Jan 2, 2024
2c2b295
Merge branch 'develop' into CodeEditorExample
josdejong Jan 12, 2024
cdfae05
Added readme file
dvd101x Jan 13, 2024
40336fa
Fixed versions
dvd101x Jan 13, 2024
c6bab36
Commented getExpressions
dvd101x Jan 13, 2024
287949c
Documented main.js
dvd101x Jan 13, 2024
cac9f5b
Fixed title
dvd101x Jan 13, 2024
0474eed
Fixed alpine version
dvd101x Jan 13, 2024
340c427
Removed AlpineJS
dvd101x Jan 14, 2024
630db67
Added documentation and renamed variables for clarity
dvd101x Jan 14, 2024
28a01f9
Merge branch 'develop' into CodeEditorExample
josdejong Jan 15, 2024
0ad2273
Fixed naming errors
dvd101x Jan 15, 2024
5ba796e
Merge branch 'develop' into CodeEditorExample
josdejong Jan 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,9 @@ Evan Miller <[email protected]>
Timur <[email protected]>
Ari Markowitz <[email protected]>
Jay Wang <[email protected]>
David Contreras <[email protected]>
Jaeu Jeong <[email protected]>
cyavictor88 <[email protected]>
David Contreras <[email protected]>
Jakub Riegel <[email protected]>
Angus Comrie <[email protected]>
TMTron <[email protected]>
Expand Down
24 changes: 24 additions & 0 deletions examples/code editor/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
18 changes: 18 additions & 0 deletions examples/code editor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Code Editor Example

This is an example for using [mathjs](https://mathjs.org) with a code editor.

To run your own you need to install the dependancies with.
```
npm install
```

You can start development mode with:
```
npm run dev
```

Or build the project with:
```
npm run build
```
59 changes: 59 additions & 0 deletions examples/code editor/getExpressions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { parse } from 'mathjs'

/**
* Extracts parsable expressions from a multiline string.
*
* @param {string} str - The multiline string containing expressions.
* @returns {Array<{from: number, to: number, source: string}>} An array of objects,
* where each object represents a parsable expression and contains:
* - from: The starting line number of the expression within the original string.
* - to: The ending line number of the expression within the original string.
* - source: The actual string content of the expression.
*/
export default function getExpressions(str) {
const lines = str.split('\n');
let nextLineToParse = 0;
const result = [];

for (let lineID = 0; lineID < lines.length; lineID++) {
const linesToTest = lines.slice(nextLineToParse, lineID + 1).join('\n');
if (canBeParsed(linesToTest)) {
if (!isEmptyString(linesToTest)) {
result.push({ from: nextLineToParse, to: lineID, source: linesToTest });
}
// Start the next parsing attempt from the line after the successfully parsed expression.
nextLineToParse = lineID + 1;
}
}
// Handle any remaining lines that couldn't be parsed as expressions.
const linesToTest = lines.slice(nextLineToParse).join('\n');
if (!isEmptyString(linesToTest)) {
result.push({ from: nextLineToParse, to: lines.length - 1, source: linesToTest });
}
return result;
}

/**
* Determines whether a given expression can be successfully parsed.
*
* @param {string} expression - The expression to parse.
* @returns {boolean} True if the expression can be parsed, false otherwise.
*/
function canBeParsed(expression) {
try {
parse(expression)
return true
} catch (error) {
return false
}
}

/**
* Checks if a given string is empty or only contains whitespace characters.
*
* @param {string} str - The string to check.
* @returns {boolean} True if the string is empty or only contains whitespace, false otherwise.
*/
function isEmptyString(str) {
return str.trim() === ""
}
18 changes: 18 additions & 0 deletions examples/code editor/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>math.js | code editor</title>
</head>

<body>
<div id="app">
<div id="editor"></div>
<article id="result" class="markdown-body"></article>
</div>
<script type="module" src="/main.js"></script>
</body>

</html>
218 changes: 218 additions & 0 deletions examples/code editor/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import './style.css'
import 'github-markdown-css/github-markdown.css'

import 'katex/dist/katex.min.css'
import katex from 'katex'

import { mathjsLang } from './mathjs-lang.js'

import { EditorState } from "@codemirror/state"
import { EditorView, basicSetup } from "codemirror"
import { create, all } from 'mathjs'
import getExpressions from './getExpressions'

import {
StreamLanguage
} from '@codemirror/language'

let processedExpressions
let previousSelectedExpressionIndex
let timer
const timeout = 250 // milliseconds

const math = create(all)
const digits = 14
let parser = math.parser()
const editorDOM = document.querySelector('#editor')
const resultsDOM = document.querySelector('#result')

const doc = [
"round(e, 3)",
"atan2(3, -3) / pi",
"log(10000, 10)",
"sqrt(-4)",
"derivative('x^2 + x', 'x')",
"pow([[-1, 2], [3, 1]], 2)",
"# expressions",
"1.2 * (2 + 4.5)",
"12.7 cm to inch",
"sin(45 deg) ^ 2",
"9 / 3 + 2i",
"det([-1, 2; 3, 1])"
].join('\n')

let startState = EditorState.create({
doc,
extensions: [
basicSetup,
StreamLanguage.define(mathjsLang(math)),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
// if doc changed debounce and update results after a timeout
clearTimeout(timer)
timer = setTimeout(() => {
updateResults()
previousSelectedExpressionIndex = null
updateSelection()
}, timeout)
} else if (update.selectionSet) {
updateSelection()
}
})
],
})

let editor = new EditorView({
state: startState,
parent: editorDOM
})

/**
* Evaluates a given expression using a parser.
*
* @param {string} expression - The expression to evaluate.
* @returns {any} The result of the evaluation, or the error message if an error occurred.
*/
function calc(expression) {
let result
try {
result = parser.evaluate(expression)
} catch (error) {
result = error.toString()
}
return result
}

/**
* Formats result depending on the type of result
*
* @param {number, string, Help, any} result - The result to format
* @returns {string} The string in HTML with the formated result
*/
const formatResult = math.typed({
'number': result => math.format(result, { precision: digits }),
'string': result => `<code>${result}</code>`,
'Help': result => `<pre>${math.format(result)}</pre>`,
'any': math.typed.referTo(
'number',
fnumber => result => katex.renderToString(math.parse(fnumber(result)).toTex())
)
})

/**
* Processes an array of expressions by evaluating them, formatting the results,
* and determining their visibility.
*
* @param {Array<{from: number, to: number, source: string}>} expressions - An array of objects representing expressions,
* where each object has `from`, `to`, and `source` properties.
* @returns {Array<{from: number, to: number, source: string, outputs: any, visible: boolean}>} An array of processed expressions,
* where each object has additional `outputs` and `visible` properties.
*/
function processExpressions(expressions) {
parser.clear()
return expressions.map(expression => {
const result = calc(expression.source)
const outputs = formatResult(result)
// Determine visibility based on the result type:
// - Undefined results are hidden.
// - Results with an `isResultSet` property are hidden.
// - All other results are visible.
const visible = result === undefined ? false : result.isResultSet ? false : true
return ({
...expression,
outputs,
visible
})
})
}

/**
* Updates the displayed results based on the editor's current content.
*
* @function updateResults
* @requires getExpressions, processExpressions, resultsToHTML
*
* @description
* 1. Extracts expressions from the editor's content.
* 2. Evaluates and analyzes the expressions.
* 3. Generates HTML to display the processed results.
* 4. Renders the generated HTML in the designated results container.
*/
function updateResults() {
// Extract expressions from the editor's content.
const expressions = getExpressions(editor.state.doc.toString());

// Evaluate and analyze the expressions.
processedExpressions = processExpressions(expressions);

// Generate HTML to display the results.
const resultsHtml = resultsToHTML(processedExpressions);

// Render the generated HTML in the results container.
resultsDOM.innerHTML = resultsHtml;
}

/**
* Updates the visual highlighting of results based on the current line selection in the editor.
*
* @function updateSelection
* @requires editor, processedExpressions
*
* @description
* 1. Determines the current line number in the editor's selection.
* 2. Finds the corresponding result (processed expression) that matches the current line.
* 3. If a different result is selected than before:
* - Removes highlighting from the previously selected result.
* - Highlights the newly selected result.
* - Scrolls the newly selected result into view.
*/
function updateSelection() {
const selectedLine = editor.state.doc.lineAt(
editor.state.selection.ranges[editor.state.selection.mainIndex].from
).number - 1;

let selectedExpressionIndex;

processedExpressions.forEach((result, index) => {
if ((selectedLine >= result.from) && (selectedLine <= result.to)) {
selectedExpressionIndex = index;
}
});

if (selectedExpressionIndex !== previousSelectedExpressionIndex) {
const previouslyHighlightedResult = document.querySelector('#result').children[previousSelectedExpressionIndex];
if (previouslyHighlightedResult !== undefined) {
previouslyHighlightedResult.className = null;
}

const currentlySelectedResult = document.querySelector('#result').children[selectedExpressionIndex];
if (currentlySelectedResult !== undefined) {
currentlySelectedResult.className = 'highlighted';
currentlySelectedResult.scrollIntoView({ block: 'nearest', inline: 'start' });
}

previousSelectedExpressionIndex = selectedExpressionIndex;
}
}

/**
* Converts an array of processed results into HTML elements for display.
*
* @function resultsToHTML
* @param {Array<{from: number, to: number, source: string, outputs: any, visible: boolean}>} results - An array of processed results, where each object has:
* - from: The starting line number of the expression.
* - to: The ending line number of the expression.
* - source: The original expression string.
* - outputs: The formatted result of evaluating the expression.
* - visible: A boolean indicating whether the result should be displayed or hidden.
* @returns {string} A string of HTML elements representing the results, where each result is enclosed in a <pre> tag with appropriate styling based on its visibility.
*/
function resultsToHTML(results) {
return results.map(el => {
const elementStyle = el.visible ? '' : 'style="display:none"'
return `<pre ${elementStyle}>${el.outputs}</pre>`
}
).join('')
}

updateResults()
Loading