import './style.css' import 'github-markdown-css/github-markdown.css' import 'katex/dist/katex.min.css' import { StreamLanguage } from '@codemirror/language' import { EditorState } from '@codemirror/state' import { basicSetup, EditorView } from 'codemirror' import katex from 'katex' import { all, create } from 'mathjs' import getExpressions from './getExpressions' import { mathjsLang } from './mathjs-lang.js' const timeout = 250 // milliseconds const digits = 14 const math = create(all) const parser = math.parser() const editorDOM = document.querySelector('#editor') const resultsDOM = document.querySelector('#result') let processedExpressions let previousSelectedExpressionIndex let timer 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.lineWrapping, 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 => `${result}`, 'Help': result => `
${math.format(result)}
`, '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 when empty. // - All other results are visible. const visible = result === undefined ? false : (result.isResultSet && result.entries.length === 0) ? 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
 tag with appropriate styling based on its visibility.
*/
function resultsToHTML(results) {
  return results.map(el => {
    const elementStyle = el.visible ? '' : 'style="display:none"'
    return `
${el.outputs}
` } ).join('') } updateResults()