UNPKG

priceless-mathematica

Version:

Advanced Mathematica mode for CodeMirror 6

308 lines (264 loc) 7.36 kB
import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap, snippetCompletion } from "@codemirror/autocomplete"; import { keymap } from "@codemirror/view"; import { EditorSelection } from "@codemirror/state"; import { syntaxTree, IndentContext, getIndentUnit, indentUnit, indentString, getIndentation, matchBrackets } from "@codemirror/language"; import { StreamLanguage } from "@codemirror/language"; import { defaultFunctions } from "./functions"; function newESC() { return ({ state, dispatch }) => { if (state.readOnly) return false; let changes = state.changeByRange((range) => { let { from, to } = range; //if (atEof) from = to = (to <= line.to ? line : state.doc.lineAt(to)).to return { changes: { from, to, insert: "EC" }, range: EditorSelection.cursor(from + 2) }; }); dispatch( state.update(changes, { scrollIntoView: true, userEvent: "input" }) ); return true; }; } function Completions(context, defaultFunctions) { let word = context.matchBefore(/\w*/); if (word.from === word.to && !context.explicit) return null; return { from: word.from, options: defaultFunctions }; } // used pattern building blocks var Identifier = "[a-zA-Z\\$][a-zA-Z0-9\\$]*"; var pBase = "(?:\\d+)"; var pFloat = "(?:\\.\\d+|\\d+\\.\\d*|\\d+)"; var pFloatBase = "(?:\\.\\w+|\\w+\\.\\w*|\\w+)"; var pPrecision = "(?:`(?:`?" + pFloat + ")?)"; // regular expressions var reBaseForm = new RegExp( "(?:" + pBase + "(?:\\^\\^" + pFloatBase + pPrecision + "?(?:\\*\\^[+-]?\\d+)?))" ); var reFloatForm = new RegExp( "(?:" + pFloat + pPrecision + "?(?:\\*\\^[+-]?\\d+)?)" ); var reIdInContext = new RegExp( "(?:`?)(?:" + Identifier + ")(?:`(?:" + Identifier + "))*(?:`?)" ); let builtins = defaultFunctions.map((e) => e.label); const builtinsSpecial = [ "True", "False", "All", "None", "Null", "Full", "$Failed", "$Aborted" ]; let localVariables = {}; function tokenBase(stream, state) { let localVars = {}; var ch; // get next character ch = stream.next(); // string if (ch === '"') { state.tokenize = tokenString; return state.tokenize(stream, state); } // comment if (ch === "(") { if (stream.eat("*")) { state.commentLevel++; state.tokenize = tokenComment; return state.tokenize(stream, state); } } // go back one character stream.backUp(1); // look for numbers // Numbers in a baseform if (stream.match(reBaseForm, true, false)) { return "number"; } // Mathematica numbers. Floats (1.2, .2, 1.) can have optionally a precision (`float) or an accuracy definition // (``float). Note: while 1.2` is possible 1.2`` is not. At the end an exponent (float*^+12) can follow. if (stream.match(reFloatForm, true, false)) { return "number"; } // usage if ( stream.match( /([a-zA-Z\$][a-zA-Z0-9\$]*(?:`[a-zA-Z0-9\$]+)*::usage)/, true, false ) ) { return "meta"; } // message if ( stream.match( /([a-zA-Z\$][a-zA-Z0-9\$]*(?:`[a-zA-Z0-9\$]+)*::[a-zA-Z\$][a-zA-Z0-9\$]*):?/, true, false ) ) { return "string.special"; } // this makes a look-ahead match for something like variable:{_Integer} // the match is then forwarded to the mma-patterns tokenizer. if ( stream.match( /([a-zA-Z\$][a-zA-Z0-9\$]*\s*:)(?:(?:[a-zA-Z\$][a-zA-Z0-9\$]*)|(?:[^:=>~@\^\&\*\)\[\]'\?,\|])).*/, true, false ) ) { return "variableName.special"; } // catch variables which are used together with Blank (_), BlankSequence (__) or BlankNullSequence (___) // Cannot start with a number, but can have numbers at any other position. Examples // blub__Integer, a1_, b34_Integer32 if ( stream.match( /[a-zA-Z\$][a-zA-Z0-9\$]*_+[a-zA-Z\$][a-zA-Z0-9\$]*/, true, false ) ) { return "variableName.special"; } if (stream.match(/[a-zA-Z\$][a-zA-Z0-9\$]*_+/, true, false)) { return "variableName.special"; } if (stream.match(/_+[a-zA-Z\$][a-zA-Z0-9\$]*/, true, false)) { return "variableName.special"; } // Named characters in Mathematica, like \[Gamma]. if (stream.match(/\\\[[a-zA-Z\$][a-zA-Z0-9\$]*\]/, true, false)) { return "character"; } // Match all braces separately if (stream.match(/(?:\[|\]|{|}|\(|\))/, true, false)) { return "bracket"; } // Catch Slots (#, ##, #3, ##9 and the V10 named slots #name). I have never seen someone using more than one digit after #, so we match // only one. if (stream.match(/(?:#[a-zA-Z\$][a-zA-Z0-9\$]*|#+[0-9]?)/, true, false)) { return "variableName.constant"; } // Literals like variables, keywords, functions if (stream.match(reIdInContext, true, false)) { if (builtinsSpecial.indexOf(stream.current()) > -1) return "number"; if (builtins.indexOf(stream.current()) > -1) return "keyword"; if (stream.current() in state.localVars) return "atom"; state.localVars[stream.current()] = true; return "function"; } // operators. Note that operators like @@ or /; are matched separately for each symbol. if ( stream.match( /(?:\\|\+|\-|\*|\/|,|;|\.|:|@|~|=|>|<|&|\||_|`|'|\^|\?|!|%)/, true, false ) ) { return "operator"; } // everything else is an error stream.next(); // advance the stream. return "error"; } function tokenString(stream, state) { var next, end = false, escaped = false; while ((next = stream.next()) != null) { if (next === '"' && !escaped) { end = true; break; } escaped = !escaped && next === "\\"; } if (end && !escaped) { state.tokenize = tokenBase; } return "string"; } function tokenComment(stream, state) { var prev, next; while (state.commentLevel > 0 && (next = stream.next()) != null) { if (prev === "(" && next === "*") state.commentLevel++; if (prev === "*" && next === ")") state.commentLevel--; prev = next; } if (state.commentLevel <= 0) { state.tokenize = tokenBase; } return "comment"; } var refToGlobalVars = {}; const mathematica = { name: "mathematica", extendVariables: function (symbol) { //not implemented }, startState: function () { //.log("tocken string"); return { tokenize: tokenBase, commentLevel: 0, localVars: {} }; }, token: function (stream, state) { if (stream.eatSpace()) return null; return state.tokenize(stream, state); }, languageData: { commentTokens: { block: { open: "(*", close: "*)" } } } }; export let wolframLanguage; wolframLanguage = [ StreamLanguage.define(mathematica), autocompletion({ override: [ async (ctx) => Completions(ctx, defaultFunctions) //snippetCompletion('mySnippet(${one}, ${two})', {label: 'mySnippet'}) ] }), keymap.of([{ key: "Escape", run: newESC() }]) ]; wolframLanguage.of = (vocabulary) => { return [ StreamLanguage.define(mathematica), autocompletion({ override: [ async (ctx) => Completions(ctx, vocabulary) //snippetCompletion('mySnippet(${one}, ${two})', {label: 'mySnippet'}) ] }), keymap.of([{ key: "Escape", run: newESC() }]) ]; } wolframLanguage.reBuild = (vocabulary) => { builtins = vocabulary.map((e) => e.label) }