").appendTo(el).addClass("red-ui-editor-text-container")[0];
- var editor = ace.edit(el);
- editor.setTheme("ace/theme/tomorrow");
- var session = editor.getSession();
- session.on("changeAnnotation", function () {
- var annotations = session.getAnnotations() || [];
- var i = annotations.length;
- var len = annotations.length;
- while (i--) {
- if (/doctype first\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
- else if (/Unexpected End of file\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
- }
- if (len > annotations.length) { session.setAnnotations(annotations); }
- });
- if (options.mode) {
- session.setMode(options.mode);
- }
- if (options.foldStyle) {
- session.setFoldStyle(options.foldStyle);
- } else {
- session.setFoldStyle('markbeginend');
- }
- if (options.options) {
- editor.setOptions(options.options);
- } else {
- editor.setOptions({
- enableBasicAutocompletion:true,
- enableSnippets:true,
- tooltipFollowsMouse: false
- });
- }
- if (options.readOnly) {
- editor.setOption('readOnly',options.readOnly);
- editor.container.classList.add("ace_read-only");
- }
- if (options.hasOwnProperty('lineNumbers')) {
- editor.renderer.setOption('showGutter',options.lineNumbers);
- }
- editor.$blockScrolling = Infinity;
- if (options.value) {
- session.setValue(options.value,-1);
- }
- if (options.globals) {
- setTimeout(function() {
- if (!!session.$worker) {
- session.$worker.send("setOptions", [{globals: options.globals, maxerr:1000}]);
- }
- },100);
- }
- if (options.mode === 'ace/mode/markdown') {
- $(el).addClass("red-ui-editor-text-container-toolbar");
- editor.toolbar = customEditTypes['_markdown'].buildToolbar(toolbarRow,editor);
- if (options.expandable !== false) {
- var expandButton = $('').appendTo(editor.toolbar);
- RED.popover.tooltip(expandButton, RED._("markdownEditor.expand"));
- expandButton.on("click", function(e) {
- e.preventDefault();
- var value = editor.getValue();
- RED.editor.editMarkdown({
- value: value,
- width: "Infinity",
- cursor: editor.getCursorPosition(),
- complete: function(v,cursor) {
- editor.setValue(v, -1);
- editor.gotoLine(cursor.row+1,cursor.column,false);
- setTimeout(function() {
- editor.focus();
- },300);
- }
- })
- });
- }
- var helpButton = $('').appendTo($(el).parent());
- RED.popover.create({
- target: helpButton,
- trigger: 'click',
- size: "small",
- direction: "left",
- content: RED._("markdownEditor.format"),
- autoClose: 50
- });
- session.setUseWrapMode(true);
- }
- return editor;
- }
+
return {
init: function() {
@@ -2862,6 +2776,8 @@ var buildingEditDialog = false;
$("#node-dialog-cancel").trigger("click");
$("#node-config-dialog-cancel").trigger("click");
});
+ //console.log("packages/node_modules/@node-red/editor-client/src/js/ui/editor.js ? init()") //TODO: Remove
+ RED.editor.codeEditor.init();
},
edit: showEditDialog,
editConfig: showEditConfigNodeDialog,
@@ -2908,9 +2824,14 @@ var buildingEditDialog = false;
/**
* Create a editor ui component
* @param {object} options - the editor options
- * @function
+ * @returs The code editor
* @memberof RED.editor
*/
- createEditor: createEditor
+ createEditor: function(options) {
+ return RED.editor.codeEditor.create(options);
+ },
+ get customEditTypes() {
+ return customEditTypes;
+ }
}
})();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js
new file mode 100644
index 000000000..13ed25611
--- /dev/null
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js
@@ -0,0 +1,107 @@
+/**
+ * Copyright JS Foundation and other contributors, http://js.foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+/**
+ * @namespace RED.editor.codeEditor
+ */
+ RED.editor.codeEditor = (function() {
+
+ const MONACO = "monaco";
+ const ACE = "ace";
+ const defaultEditor = ACE;
+ const DEFAULT_SETTINGS = { lib: defaultEditor, options: {} };
+ var selectedCodeEditor = null;
+ var initialised = false;
+
+ function init() {
+ var codeEditorSettings = RED.editor.codeEditor.settings;
+ var editorChoice = codeEditorSettings.lib === MONACO ? MONACO : ACE;
+ try {
+ var browser = RED.utils.getBrowserInfo();
+ selectedCodeEditor = RED.editor.codeEditor[editorChoice];
+ //fall back to default code editor if there are any issues
+ if (!selectedCodeEditor || (editorChoice === MONACO && (browser.ie || !window.monaco))) {
+ selectedCodeEditor = RED.editor.codeEditor[defaultEditor];
+ }
+ initialised = selectedCodeEditor.init();
+ } catch (error) {
+ selectedCodeEditor = null;
+ console.warn("Problem initialising '" + editorChoice + "' code editor", error);
+ }
+ if(!initialised) {
+ selectedCodeEditor = RED.editor.codeEditor[defaultEditor];
+ initialised = selectedCodeEditor.init();
+ }
+ }
+
+ function create(options) {
+ //TODO: (quandry - for consideration)
+ // Below, I had to create a hidden element if options.id || options.element is not in the DOM
+ // I have seen 1 node calling `this.editor = RED.editor.createEditor()` with an
+ // invalid (non existing html element selector) (e.g. node-red-contrib-components does this)
+ // This causes monaco to throw an error when attempting to hook up its events to the dom & the rest of the 'oneditperapre'
+ // code is thus skipped.
+ // In ACE mode, creating an ACE editor (with an invalid ID) allows the editor to be created (but obviously there is no UI)
+ // Because one (or more) contrib nodes have left this bad code in place, how would we handle this?
+ // For compatibility, I have decided to create a hidden element so that at least an editor is created & errors do not occur.
+ // IMO, we should warn and exit as it is a coding error by the contrib author.
+
+ if (!options) {
+ console.warn("createEditor() options are missing");
+ options = {};
+ }
+
+ if (this.editor.type === MONACO) {
+ // compatibility (see above note)
+ if (!options.element && !options.id) {
+ options.id = 'node-backwards-compatability-dummy-editor';
+ }
+ options.element = options.element || $("#" + options.id)[0];
+ if (!options.element) {
+ console.warn("createEditor() options.element or options.id is not valid", options);
+ $("#dialog-form").append('');
+ }
+ return this.editor.create(options);
+ } else {
+ return this.editor.create(options);//fallback to ACE
+ }
+ }
+
+ return {
+ init: init,
+ /**
+ * Get editor settings object
+ * @memberof RED.editor.codeEditor
+ */
+ get settings() {
+ return RED.settings.get('codeEditor') || DEFAULT_SETTINGS;
+ },
+ /**
+ * Get user selected code editor
+ * @return {string} Returns
+ * @memberof RED.editor.codeEditor
+ */
+ get editor() {
+ return selectedCodeEditor;
+ },
+ /**
+ * Create a editor ui component
+ * @param {object} options - the editor options
+ * @memberof RED.editor.codeEditor
+ */
+ create: create
+ }
+})();
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js
new file mode 100644
index 000000000..caec3006c
--- /dev/null
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/ace.js
@@ -0,0 +1,153 @@
+/*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+**/
+
+/**
+* @namespace RED.editor.codeEditor.ace
+*/
+RED.editor.codeEditor.ace = (function() {
+
+ const type = "ace";
+ var initialised = false;
+ var initOptions = {};
+
+ function init(options) {
+ initOptions = options || {};
+ initialised = true;
+ return initialised;
+ }
+
+ function create(options) {
+ var editorSettings = RED.editor.codeEditor.settings || {};
+ var el = options.element || $("#"+options.id)[0];
+ var toolbarRow = $("
").appendTo(el);
+ el = $("
").appendTo(el).addClass("red-ui-editor-text-container")[0];
+ var editor = window.ace.edit(el);
+ editor.setTheme(editorSettings.theme || initOptions.theme || "ace/theme/tomorrow");
+ var session = editor.getSession();
+ session.on("changeAnnotation", function () {
+ var annotations = session.getAnnotations() || [];
+ var i = annotations.length;
+ var len = annotations.length;
+ while (i--) {
+ if (/doctype first\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
+ else if (/Unexpected End of file\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
+ }
+ if (len > annotations.length) { session.setAnnotations(annotations); }
+ });
+ if (options.mode) {
+ session.setMode(options.mode);
+ }
+ if (options.foldStyle) {
+ session.setFoldStyle(options.foldStyle);
+ } else {
+ session.setFoldStyle('markbeginend');
+ }
+ if (options.options) {
+ editor.setOptions(options.options);
+ } else {
+ editor.setOptions({
+ enableBasicAutocompletion:true,
+ enableSnippets:true,
+ tooltipFollowsMouse: false
+ });
+ }
+ if (options.readOnly) {
+ editor.setOption('readOnly',options.readOnly);
+ editor.container.classList.add("ace_read-only");
+ }
+ if (options.hasOwnProperty('lineNumbers')) {
+ editor.renderer.setOption('showGutter',options.lineNumbers);
+ }
+ editor.$blockScrolling = Infinity;
+ if (options.value) {
+ session.setValue(options.value,-1);
+ }
+ if (options.globals) {
+ setTimeout(function() {
+ if (!!session.$worker) {
+ session.$worker.send("setOptions", [{globals: options.globals, maxerr:1000}]);
+ }
+ },100);
+ }
+ if (options.mode === 'ace/mode/markdown') {
+ $(el).addClass("red-ui-editor-text-container-toolbar");
+ editor.toolbar = RED.editor.customEditTypes['_markdown'].buildToolbar(toolbarRow,editor);
+ if (options.expandable !== false) {
+ var expandButton = $('').appendTo(editor.toolbar);
+ RED.popover.tooltip(expandButton, RED._("markdownEditor.expand"));
+ expandButton.on("click", function(e) {
+ e.preventDefault();
+ var value = editor.getValue();
+ RED.editor.editMarkdown({
+ value: value,
+ width: "Infinity",
+ cursor: editor.getCursorPosition(),
+ complete: function(v,cursor) {
+ editor.setValue(v, -1);
+ editor.gotoLine(cursor.row+1,cursor.column,false);
+ setTimeout(function() {
+ editor.focus();
+ },300);
+ }
+ })
+ });
+ }
+ var helpButton = $('').appendTo($(el).parent());
+ RED.popover.create({
+ target: helpButton,
+ trigger: 'click',
+ size: "small",
+ direction: "left",
+ content: RED._("markdownEditor.format"),
+ autoClose: 50
+ });
+ session.setUseWrapMode(true);
+ }
+ editor._destroy = editor.destroy;
+ editor.destroy = function() {
+ try {
+ this._destroy();
+ } catch (e) { }
+ $(el).remove();
+ $(toolbarRow).remove();
+ }
+ editor.type = type;
+ return editor;
+ }
+
+ return {
+ /**
+ * Editor type
+ * @memberof RED.editor.codeEditor.ace
+ */
+ get type() { return type; },
+ /**
+ * Editor initialised
+ * @memberof RED.editor.codeEditor.ace
+ */
+ get initialised() { return initialised; },
+ /**
+ * Initialise code editor
+ * @param {object} options - initialisation options
+ * @memberof RED.editor.codeEditor.ace
+ */
+ init: init,
+ /**
+ * Create a code editor
+ * @param {object} options - the editor options
+ * @memberof RED.editor.codeEditor.ace
+ */
+ create: create
+ }
+})();
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
new file mode 100644
index 000000000..372460824
--- /dev/null
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js
@@ -0,0 +1,1219 @@
+/*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+**/
+
+/**
+ * @namespace RED.editor.codeEditor.monaco
+*/
+
+/*
+ The code editor currenlty supports 2 functions init and create.
+ * Init() - setup the editor / must return true
+ * Create() - create an editor instance / returns an editor as generated by the editor lib
+ * To be compatable with the original ace lib (for contrib nodes using it), the object returned by create() must (at minimum) support the following...
+ property .selection = {};
+ function .selection.getRange();
+ property .session //the editor object
+ function .session.insert = function(position, text)
+ property .renderer = {};
+ function .renderer.updateFull()
+ function setMode(mode, cb)
+ function getRange();
+ function replace(range, text)
+ function getSelectedText()
+ function destroy()
+ function resize()
+ function getSession()
+ function getLength()
+ function scrollToLine(lineNumber, scrollType)
+ function moveCursorTo(lineNumber, colNumber)
+ function getAnnotations()
+ function gotoLine(row, col)
+ function getCursorPosition()
+ function setTheme(name)
+ function on(name, cb)
+ function getUndoManager()
+
+*/
+
+RED.editor.codeEditor.monaco = (function() {
+ var initialised = false;
+ const type = "monaco";
+ const monacoThemes = ["vs","vs-dark","hc-black"]; //TODO: consider setting hc-black autmatically based on acessability?
+
+ //TODO: get from externalModules.js For now this is enough for feature parity with ACE (and then some).
+ const knownModules = {
+ "assert": {package: "node", module: "assert", path: "node/assert.d.ts" },
+ "async_hooks": {package: "node", module: "async_hooks", path: "node/async_hooks.d.ts" },
+ "buffer": {package: "node", module: "buffer", path: "node/buffer.d.ts" },
+ "child_process": {package: "node", module: "child_process", path: "node/child_process.d.ts" },
+ "cluster": {package: "node", module: "cluster", path: "node/cluster.d.ts" },
+ "console": {package: "node", module: "console", path: "node/console.d.ts" },
+ "constants": {package: "node", module: "constants", path: "node/constants.d.ts" },
+ "crypto": {package: "node", module: "crypto", path: "node/crypto.d.ts" },
+ "dgram": {package: "node", module: "dgram", path: "node/dgram.d.ts" },
+ "dns": {package: "node", module: "dns", path: "node/dns.d.ts" },
+ "domain": {package: "node", module: "domain", path: "node/domain.d.ts" },
+ "events": {package: "node", module: "events", path: "node/events.d.ts" },
+ "fs": {package: "node", module: "fs", path: "node/fs.d.ts" },
+ "globals": {package: "node", module: "globals", path: "node/globals.d.ts" },
+ "http": {package: "node", module: "http", path: "node/http.d.ts" },
+ "http2": {package: "node", module: "http2", path: "node/http2.d.ts" },
+ "https": {package: "node", module: "https", path: "node/https.d.ts" },
+ "module": {package: "node", module: "module", path: "node/module.d.ts" },
+ "net": {package: "node", module: "net", path: "node/net.d.ts" },
+ "os": {package: "node", module: "os", path: "node/os.d.ts" },
+ "path": {package: "node", module: "path", path: "node/path.d.ts" },
+ "perf_hooks": {package: "node", module: "perf_hooks", path: "node/perf_hooks.d.ts" },
+ "process": {package: "node", module: "process", path: "node/process.d.ts" },
+ "querystring": {package: "node", module: "querystring", path: "node/querystring.d.ts" },
+ "readline": {package: "node", module: "readline", path: "node/readline.d.ts" },
+ "stream": {package: "node", module: "stream", path: "node/stream.d.ts" },
+ "string_decoder": {package: "node", module: "string_decoder", path: "node/string_decoder.d.ts" },
+ "timers": {package: "node", module: "timers", path: "node/timers.d.ts" },
+ "tls": {package: "node", module: "tls", path: "node/tls.d.ts" },
+ "trace_events": {package: "node", module: "trace_events", path: "node/trace_events.d.ts" },
+ "tty": {package: "node", module: "tty", path: "node/tty.d.ts" },
+ "url": {package: "node", module: "url", path: "node/url.d.ts" },
+ "util": {package: "node", module: "util", path: "node/util.d.ts" },
+ "v8": {package: "node", module: "v8", path: "node/v8.d.ts" },
+ "vm": {package: "node", module: "vm", path: "node/vm.d.ts" },
+ "wasi": {package: "node", module: "wasi", path: "node/wasi.d.ts" },
+ "worker_threads": {package: "node", module: "worker_threads", path: "node/worker_threads.d.ts" },
+ "zlib": {package: "node", module: "zlib", path: "node/zlib.d.ts" },
+ "node-red": {package: "node-red", module: "node-red", path: "node-red/index.d.ts" }, //only used if node-red types are concated by grunt.
+ "node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" },
+ "node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" },
+ }
+ const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] ];
+
+ var modulesCache = {};
+ window.modulesCache = modulesCache;//DEBUGGING
+ /**
+ * Helper function to load/reload types.
+ * @param {string} mod - type lib to load. Only known libs are currently supported.
+ * @param {boolean} preloadOnly - only cache the lib
+ * @param {object} loadedLibs - an object used to track loaded libs (needed to destroy them upon close)
+ */
+ function _loadModuleDTS(mod, preloadOnly, loadedLibs, cb) {
+ var _module;
+ if(typeof mod == "object") {
+ _module = mod;
+ } else {
+ _module = knownModules[mod];
+ }
+ if(_module) {
+ const libPackage = _module.package;
+ const libModule = _module.module;
+ const libPath = _module.path;
+ const def = modulesCache[libPath];
+ if( def ) {
+ if(!preloadOnly) {
+ loadedLibs.JS[libModule] = monaco.languages.typescript.javascriptDefaults.addExtraLib(def, `file://types/${libPackage}/${libModule}/index.d.ts`);
+ }
+ if(cb) {
+ setTimeout(function() {
+ cb(null, _module);
+ }, 5);
+ }
+ } else {
+ var typePath = "types/" + libPath;
+ $.get(typePath)
+ .done(function(data) {
+ modulesCache[libPath] = data;
+ if(!preloadOnly) {
+ loadedLibs.JS[libModule] = monaco.languages.typescript.javascriptDefaults.addExtraLib(data, `file://types/${libPackage}/${libModule}/index.d.ts`);
+ }
+ if(cb) { cb(null, _module) }
+ })
+ .fail(function(err) {
+ var warning = "Failed to load '" + typePath + "'";
+ modulesCache[libPath] = "/* " + warning + " */\n"; //populate the extraLibs cache to revent retries
+ if(cb) { cb(err, _module) }
+ console.warn(warning);
+ });
+ }
+ }
+ }
+
+
+ function init(options) {
+ options = options || {};
+ window.MonacoEnvironment = window.MonacoEnvironment || {};
+ window.MonacoEnvironment.getWorkerUrl = function (moduleId, label) {
+ if (label === 'json') { return './vendor/monaco/dist/json.worker.js'; }
+ if (label === 'css' || label === 'scss') { return './vendor/monaco/dist/css.worker.js'; }
+ if (label === 'html' || label === 'handlebars') { return './vendor/monaco/dist/html.worker.js'; }
+ if (label === 'typescript' || label === 'javascript') { return './vendor/monaco/dist/ts.worker.js'; }
+ return './vendor/monaco/dist/editor.worker.js';
+ };
+
+ var editorSettings = RED.editor.codeEditor.settings || {};
+ var editorOptions = editorSettings.options || {};
+
+ if (editorOptions.theme) {
+ if (!monacoThemes.includes(editorOptions.theme)) {
+ var customTheme = 'vendor/monaco/dist/theme/' + editorOptions.theme + '.json';
+ $.get(customTheme, function (theme) {
+ monacoThemes.push(editorOptions.theme);//add to list of loaded themes
+ if ((theme.rules && Array.isArray(theme.rules)) || theme.colors) {
+ monaco.editor.defineTheme(editorOptions.theme, theme);
+ monaco.editor.setTheme(editorOptions.theme);
+ }
+ });
+ }
+ }
+
+ //Helper function to simplify snippet setup
+ function createMonacoCompletionItem(label, insertText, documentation, range, kind) {
+ if (Array.isArray(documentation)) { documentation = documentation.join("\n"); }
+ return {
+ label: label,
+ kind: kind == null ? monaco.languages.CompletionItemKind.Snippet : kind,
+ documentation: { value: documentation },
+ insertText: insertText,
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
+ range: range
+ }
+ }
+
+ function setupJSONata(_monaco) {
+ // Register the language 'jsonata'
+ _monaco.languages.register({ id: 'jsonata' });
+
+ // Setup tokens for JSONata
+ _monaco.languages.setMonarchTokensProvider('jsonata',
+ {
+ // Set defaultToken to invalid to see what you do not tokenize yet
+ defaultToken: 'invalid',
+ tokenPostfix: '.js',
+ keywords: ["function", "true", "true", "null", "Infinity", "NaN", "undefined"].concat(Object.keys(jsonata.functions)),
+ // keywords: [
+ // "function", "$abs", "$append", "$assert", "$average",
+ // "$base64decode", "$base64encode", "$boolean", "$ceil", "$contains",
+ // "$count", "$decodeUrl", "$decodeUrlComponent", "$distinct", "$each", "$encodeUrl",
+ // "$encodeUrlComponent", "$env", "$error", "$eval", "$exists", "$filter", "$floor",
+ // "$flowContext", "$formatBase", "$formatInteger", "$formatNumber", "$fromMillis",
+ // "$globalContext", "$join", "$keys", "$length", "$lookup", "$lowercase", "$map",
+ // "$match", "$max", "$merge", "$millis", "$min", "$moment", "$not", "$now",
+ // "$number", "$pad", "$parseInteger", "$power", "$random", "$reduce", "$replace",
+ // "$reverse", "$round", "$shuffle", "$sift", "$single", "$sort", "$split",
+ // "$spread", "$sqrt", "$string", "$substring", "$substringAfter", "$substringBefore",
+ // "$sum", "$toMillis", "$trim", "$type", "$uppercase", "$zip"
+ // ],
+
+ operatorsKeywords: [ 'and', 'or', 'in' ],
+
+ operators: [
+ '<=', '>=', '!=', '==', '!=', '=>', '+', '-', '*', '/', '%',
+ ':=', '~>', '?', ':', '..', '@', '#', '|', '^', '*', '**',
+ ],
+
+ // we include these common regular expressions
+ symbols: /[=>](?!@symbols)/, '@brackets'],
+ [/(@symbols)|(\.\.)/, {
+ cases: {
+ '@operators': 'operator',
+ '@default': ''
+ }
+ }],
+
+ // numbers
+ [/(@digits)[eE]([\-+]?(@digits))?/, 'number.float'],
+ [/(@digits)\.(@digits)([eE][\-+]?(@digits))?/, 'number.float'],
+ [/0[xX](@hexdigits)/, 'number.hex'],
+ [/0[oO]?(@octaldigits)/, 'number.octal'],
+ [/0[bB](@binarydigits)/, 'number.binary'],
+ [/(@digits)/, 'number'],
+
+ // delimiter: after number because of .\d floats
+ [/[?:;,.]/, 'delimiter'],
+
+ // strings
+ [/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
+ [/'([^'\\]|\\.)*$/, 'string.invalid'], // non-teminated string
+ [/"/, 'string', '@string_double'],
+ [/'/, 'string', '@string_single'],
+ [/`/, 'string', '@string_backtick'],
+ ],
+
+ whitespace: [
+ [/[ \t\r\n]+/, ''],
+ [/\/\*\*(?!\/)/, 'comment.doc', '@jsdoc'],
+ [/\/\*/, 'comment', '@comment'],
+ [/\/\/.*$/, 'comment'],
+ ],
+
+ comment: [
+ [/[^\/*]+/, 'comment'],
+ [/\*\//, 'comment', '@pop'],
+ [/[\/*]/, 'comment']
+ ],
+
+ jsdoc: [
+ [/[^\/*]+/, 'comment.doc'],
+ [/\*\//, 'comment.doc', '@pop'],
+ [/[\/*]/, 'comment.doc']
+ ],
+
+ // We match regular expression quite precisely
+ regexp: [
+ [/(\{)(\d+(?:,\d*)?)(\})/, ['regexp.escape.control', 'regexp.escape.control', 'regexp.escape.control']],
+ [/(\[)(\^?)(?=(?:[^\]\\\/]|\\.)+)/, ['regexp.escape.control', { token: 'regexp.escape.control', next: '@regexrange' }]],
+ [/(\()(\?:|\?=|\?!)/, ['regexp.escape.control', 'regexp.escape.control']],
+ [/[()]/, 'regexp.escape.control'],
+ [/@regexpctl/, 'regexp.escape.control'],
+ [/[^\\\/]/, 'regexp'],
+ [/@regexpesc/, 'regexp.escape'],
+ [/\\\./, 'regexp.invalid'],
+ [/(\/)([gimsuy]*)/, [{ token: 'regexp', bracket: '@close', next: '@pop' }, 'keyword.other']],
+ ],
+
+ regexrange: [
+ [/-/, 'regexp.escape.control'],
+ [/\^/, 'regexp.invalid'],
+ [/@regexpesc/, 'regexp.escape'],
+ [/[^\]]/, 'regexp'],
+ [/\]/, { token: 'regexp.escape.control', next: '@pop', bracket: '@close' }],
+ ],
+
+ string_double: [
+ [/[^\\"]+/, 'string'],
+ [/@escapes/, 'string.escape'],
+ [/\\./, 'string.escape.invalid'],
+ [/"/, 'string', '@pop']
+ ],
+
+ string_single: [
+ [/[^\\']+/, 'string'],
+ [/@escapes/, 'string.escape'],
+ [/\\./, 'string.escape.invalid'],
+ [/'/, 'string', '@pop']
+ ],
+
+ string_backtick: [
+ [/\$\{/, { token: 'delimiter.bracket', next: '@bracketCounting' }],
+ [/[^\\`$]+/, 'string'],
+ [/@escapes/, 'string.escape'],
+ [/\\./, 'string.escape.invalid'],
+ [/`/, 'string', '@pop']
+ ],
+
+ bracketCounting: [
+ [/\{/, 'delimiter.bracket', '@bracketCounting'],
+ [/\}/, 'delimiter.bracket', '@pop'],
+ { include: 'common' }
+ ],
+ },
+ }
+
+ );
+
+ // Setup JSONata language config
+ _monaco.languages.setLanguageConfiguration('jsonata', {
+ comments: {
+ lineComment: '//',
+ blockComment: ['/*', '*/']
+ },
+ brackets: [
+ ['{', '}'],
+ ['[', ']'],
+ ['(', ')']
+ ],
+ autoClosingPairs: [
+ { open: '{', close: '}' },
+ { open: '[', close: ']' },
+ { open: '(', close: ')' },
+ { open: "'", close: "'", notIn: ['string', 'comment'] },
+ { open: '"', close: '"', notIn: ['string'] }
+ ],
+ surroundingPairs: [
+ { open: '{', close: '}' },
+ { open: '[', close: ']' },
+ { open: '(', close: ')' },
+ { open: '"', close: '"' },
+ { open: "'", close: "'" },
+ { open: '<', close: '>' }
+ ],
+ folding: {
+ markers: {
+ start: new RegExp('^\\s*//\\s*(?:(?:#?region\\b)|(?:))')
+ }
+ }
+ });
+
+ // Register a completion item provider for JSONata snippets
+ _monaco.languages.registerCompletionItemProvider('jsonata', {
+ provideCompletionItems: function (model, position) {
+ var _word = model.getWordUntilPosition(position);
+ if (!_word) { return; }
+ var startColumn = _word.startColumn;
+ var word = _word.word;
+ if (word[0] !== "$" && position.column > 1) { startColumn--; }
+ var range = {
+ startLineNumber: position.lineNumber,
+ endLineNumber: position.lineNumber,
+ startColumn: startColumn,
+ endColumn: _word.endColumn
+ };
+ var jsonataFunctions = Object.keys(jsonata.functions);
+ var jsonataSuggestions = jsonataFunctions.map(function (f) {
+ var args = RED._('jsonata:' + f + '.args', { defaultValue: '' });
+ var title = f + '(' + args + ')';
+ var body = RED._('jsonata:' + f + '.desc', { defaultValue: '' });
+ var insertText = (jsonata.getFunctionSnippet(f) + '').trim();
+ var documentation = { value: '`' + title + '`\n\n' + body };
+ return createMonacoCompletionItem(f, insertText, documentation, range, monaco.languages.CompletionItemKind.Function);
+ });
+ // sort in length order (long->short) otherwise substringAfter gets matched as substring etc.
+ jsonataFunctions.sort(function (A, B) {
+ return B.length - A.length;
+ });
+ // add snippets to suggestions
+ jsonataSuggestions.unshift(
+ createMonacoCompletionItem("randominteger", '(\n\t\\$minimum := ${1:1};\n\t\\$maximum := ${2:10};\n\t\\$round((\\$random() * (\\$maximum-\\$minimum)) + \\$minimum, 0)\n)', 'Random integer between 2 numbers', range)
+ );//TODO: add more JSONata snippets
+ return { suggestions: jsonataSuggestions };
+ }
+ });
+
+ // Register a hover provider for JSONata functions
+ _monaco.languages.registerHoverProvider('jsonata', {
+ provideHover: function (model, position) {
+ var w = model.getWordAtPosition(position);
+ var f = w && w.word;
+ if (!f) {return;}
+ if (f[0] !== "$" && position.column > 1) {
+ f = "$" + f;
+ } else {
+ return;
+ }
+ var args = RED._('jsonata:' + f + ".args", { defaultValue: '' });
+ if (!args) {return;}
+ var title = f + "(" + args + ")";
+ var body = RED._('jsonata:' + f + '.desc', { defaultValue: '' });
+
+ /** @type {monaco.Range} */
+ var r = new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column + w.word.length);
+ return {
+ range: r,
+ contents: [
+ { value: '**`' + title + '`**' },
+ // { value: '```html\n' + body + '\n```' },
+ { value: body },
+ ]
+ }
+ }
+ });
+ }
+
+ function setupJSON(_monaco) {
+ //Setup JSON options
+ try {
+ var diagnosticOptionsDefault = {validate: true};
+ var diagnosticOptions = RED.settings.get('codeEditor.monaco.languages.json.jsonDefaults.diagnosticOptions');
+ var modeConfiguration = RED.settings.get('codeEditor.monaco.languages.json.jsonDefaults.modeConfiguration');
+ diagnosticOptions = Object.assign({}, diagnosticOptionsDefault, (diagnosticOptions || {}));
+ _monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions);
+ if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); }
+ } catch (error) {
+ console.warn("monaco - Error setting up json options", err)
+ }
+ }
+
+ function setupHTML(_monaco) {
+ //Setup HTML / Handlebars options
+ try {
+ var htmlDefaults = RED.settings.get('codeEditor.monaco.languages.html.htmlDefaults.options');
+ var handlebarDefaults = RED.settings.get('codeEditor.monaco.languages.html.handlebarDefaults.options');
+ if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); }
+ if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); }
+ } catch (error) {
+ console.warn("monaco - Error setting up html options", err)
+ }
+ }
+
+ function setupCSS(_monaco) {
+ //Setup CSS/SCSS/LESS options
+ try {
+ var cssDefaults_diagnosticsOption = RED.settings.get('codeEditor.monaco.languages.css.cssDefaults.diagnosticsOptions');
+ var lessDefaults_diagnosticsOption = RED.settings.get('codeEditor.monaco.languages.css.lessDefaults.diagnosticsOption');
+ var scssDefaults_diagnosticsOption = RED.settings.get('codeEditor.monaco.languages.css.scssDefaults.diagnosticsOption');
+ var cssDefaults_modeConfiguration = RED.settings.get('codeEditor.monaco.languages.css.cssDefaults.modeConfiguration');
+ var lessDefaults_modeConfiguration = RED.settings.get('codeEditor.monaco.languages.css.lessDefaults.modeConfiguration');
+ var scssDefaults_modeConfiguration = RED.settings.get('codeEditor.monaco.languages.css.scssDefaults.modeConfiguration');
+ if(cssDefaults_diagnosticsOption) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(cssDefaults_diagnosticsOption); }
+ if(lessDefaults_diagnosticsOption) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_diagnosticsOption); }
+ if(scssDefaults_diagnosticsOption) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_diagnosticsOption); }
+ if(cssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(cssDefaults_modeConfiguration); }
+ if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); }
+ if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); }
+ } catch (error) {
+ console.warn("monaco - Error setting up CSS/SCSS/LESS options", err)
+ }
+ }
+
+ function setupJS(_monaco) {
+ var createJSSnippets = function(range) {
+ return [
+ //TODO: i18n for snippet descriptions?
+ createMonacoCompletionItem("dowhile", 'do {\n\t${2}\n} while (${1:condition});','Do-While Statement (JavaScript Language Basics)',range),
+ createMonacoCompletionItem("while", 'while (${1:condition}) {\n\t${2}\n}','While Statement (JavaScript Language Basics)',range),
+ createMonacoCompletionItem("switch", 'switch (${1:msg.topic}) {\n\tcase ${2:"value"}:\n\t\t${3}\n\t\tbreak;\n\tdefault:\n\t\t\n}','Switch Statement (JavaScript Language Basics)',range),
+ createMonacoCompletionItem("trycatch", 'try {\n\t${2}\n} catch (${1:error}) {\n\t\n};','Try-Catch Statement (JavaScript Language Basics)',range),
+ createMonacoCompletionItem("for (for loop)", 'for (let ${1:index} = 0; ${1:index} < ${2:array}.length; ${1:index}++) {\n\tconst element = ${2:array}[${1:index}];\n\t${3}\n}','for loop',range),
+ createMonacoCompletionItem("foreach", '${1:array}.forEach(function(${2:element}) {\n\t${3}\n});','forEach(callbackfn: (value: T, index: number, array: readonly T[]) => void, thisArg?: any): void\n\nA function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.',range),
+ createMonacoCompletionItem("forin", 'for (${1:prop} in ${2:obj}) {\n\tif (${2:obj}.hasOwnProperty(${1:prop})) {\n\t\t${3}\n\t}\n}','for in',range),
+ createMonacoCompletionItem("forof", 'for (const ${1:iterator} of ${2:object}) {\n\t${3}\n}','for of',range),
+ createMonacoCompletionItem("function", 'function ${1:methodName}(${2:arguments}) {\n\t${3}\n}','Function Declaration',range),
+ createMonacoCompletionItem("func (anonymous function)", 'var ${1:fn} = function(${2:arguments}) {\n\t${3}\n}','Function Expression',range),
+ createMonacoCompletionItem("pt (prototype)", '${1:ClassName}.prototype.${2:methodName} = function(${3:arguments}) {\n\t${4}\n}','prototype',range),
+ createMonacoCompletionItem("iife", '(function(${1:arg}) {\n\t${1}\n})(${1:arg});','immediately-invoked function expression',range),
+ createMonacoCompletionItem("call (function call)", '${1:methodName}.call(${2:context}, ${3:arguments})','function call',range),
+ createMonacoCompletionItem("apply (function apply)", '${1:methodName}.apply(${2:context}, [${3:arguments}])','function apply',range),
+ createMonacoCompletionItem("jsonparse", 'JSON.parse(${1:json});','JSON.parse',range),
+ createMonacoCompletionItem("jsonstringify", 'JSON.stringify(${1:obj});','JSON.stringify',range),
+ createMonacoCompletionItem("setinterval", 'setInterval(function() {\n\t${2}\n}, ${1:delay});','setInterval',range),
+ createMonacoCompletionItem("settimeout", 'setTimeout(function() {\n\t${2}\n}, ${1:delay});','setTimeout',range),
+ createMonacoCompletionItem("node.log", 'node.log(${1:"info"});','Write an info message to the console (not sent to sidebar)',range),
+ createMonacoCompletionItem("node.warn", 'node.warn(${1:"my warning"});','Write a warning to the console and debug sidebar',range),
+ createMonacoCompletionItem("node.error", 'node.error(${1:"my error message"}, ${2:msg});','Send an error to the console and debug sidebar. To trigger a Catch node on the same tab, the function should call `node.error` with the original message as a second argument',range),
+ createMonacoCompletionItem("node.send", 'node.send(${1:msg});','async send a msg to the next node',range),
+ createMonacoCompletionItem("node.send (multiple)", 'var ${1:msg1} = {payload:${2:1}};\nvar ${3:msg2} = {payload:${4:2}};\nnode.send([[${1:msg1}, ${3:msg2}]]);','send 1 or more messages out of 1 output',range),
+ createMonacoCompletionItem("node.send (multiple outputs)", 'var ${1:msg1} = {payload:${2:1}};\nvar ${3:msg2} = {payload:${4:2}};\nnode.send([${1:msg1}, ${3:msg2}]);','send more than 1 message out of multiple outputs',range),
+ createMonacoCompletionItem("node.status", 'node.status({fill:"${1:red}",shape:"${2:ring}",text:"${3:error}"});','Set the status icon and text underneath the function node',range),
+ createMonacoCompletionItem("get (node context)", 'context.get("${1:name}");','Get a value from node context',range),
+ createMonacoCompletionItem("set (node context)", 'context.set("${1:name}", ${1:value});','Set a value in node context',range),
+ createMonacoCompletionItem("get (flow context)", 'flow.get("${1:name}");','Get a value from flow context',range),
+ createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
+ createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
+ createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range),
+ createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
+ ["```typescript",
+ "RED.util.cloneMessage(msg: T): T",
+ "```",
+ "Safely clones a message object. This handles msg.req/msg.res objects that must not be cloned\n",
+ "*@param* `msg` — the msg object\n"],
+ range),
+ createMonacoCompletionItem("getObjectProperty (RED.util)", 'RED.util.getObjectProperty(${1:msg},${2:prop});',
+ ["```typescript",
+ "RED.util.getObjectProperty(msg: object, expr: string): any;",
+ "```",
+ "Gets a property of an object\n",
+ "*@param* `msg` — the msg object\n",
+ "*@param* `prop` — the msg object"],
+ range),
+ createMonacoCompletionItem("setObjectProperty (RED.util)", 'RED.util.setObjectProperty(${1:msg},${2:prop},${3:value},${4:createMissing});',
+ ["```typescript",
+ "RED.util.setObjectProperty(msg: object, prop: string, value: any, createMissing?: boolean): boolean",
+ "```",
+ "Sets a property of an object\n",
+ "`msg` — the object\n",
+ "`prop` — the property expression\n",
+ "`value` — the value to set\n",
+ "`createMissing` — whether to create missing parent properties"],
+ range),
+ createMonacoCompletionItem("getMessageProperty (RED.util)", 'RED.util.getMessageProperty(${1:msg},${2:prop});',
+ ["```typescript",
+ "RED.util.getMessageProperty(msg: object, expr: string): any;",
+ "```",
+ "Gets a property of an object\n",
+ "*@param* `msg` — the msg object\n",
+ "*@param* `prop` — the msg object"],
+ range),
+ createMonacoCompletionItem("setMessageProperty (RED.util)", 'RED.util.setMessageProperty(${1:msg},${2:prop},${3:value},${4:createMissing});',
+ ["```typescript",
+ "RED.util.setMessageProperty(msg: object, prop: string, value: any, createMissing?: boolean): boolean",
+ "```",
+ "Sets a property of an object\n",
+ "`msg` — the object\n",
+ "`prop` — the property expression\n",
+ "`value` — the value to set\n",
+ "`createMissing` — whether to create missing parent properties"],
+ range),
+ ];
+ }
+
+ //register snippets
+ _monaco.languages.registerCompletionItemProvider('javascript', {
+ provideCompletionItems: function(model, position) {
+ var word = model.getWordUntilPosition(position);
+ var range = {
+ startLineNumber: position.lineNumber,
+ endLineNumber: position.lineNumber,
+ startColumn: word.startColumn,
+ endColumn: word.endColumn
+ };
+ return {
+ suggestions: createJSSnippets(range)
+ };
+ }
+ });
+
+ //setup JS/TS compiler & diagnostic options (defaults)
+ try {
+ //prepare compiler options
+ var compilerOptions = {
+ allowJs: true,
+ checkJs: true,
+ allowNonTsExtensions: true,
+ target: monaco.languages.typescript.ScriptTarget.ESNext,
+ strictNullChecks: false,
+ strictPropertyInitialization: true,
+ strictFunctionTypes: true,
+ strictBindCallApply: true,
+ moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
+ module: monaco.languages.typescript.ModuleKind.CommonJS,
+ typeRoots: ["types"],
+ lib: ["esnext"] //dont load DOM by default,
+ }
+ //apply overrides from codeEditor.monaco.languages.typescript.javascriptDefaults.compilerOptions in settings.js
+ var settingsComilerOptions = RED.settings.get('codeEditor.monaco.languages.typescript.javascriptDefaults.compilerOptions') || {};
+ compilerOptions = Object.assign({}, compilerOptions, settingsComilerOptions);
+ /** @see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.typescript.languageservicedefaults.html#setcompileroptions */
+ _monaco.languages.typescript.javascriptDefaults.setCompilerOptions(compilerOptions);
+
+ //prepare diagnostic options (defaults)
+ var diagnosticOptions = {
+ noSemanticValidation: false,
+ noSyntaxValidation: false,
+ diagnosticCodesToIgnore: [
+ 1108, //return not inside function
+ //2304, //Cannot find name - this one is heavy handed and prevents user seeing stupid errors. Would provide better ACE feature parity (i.e. no need for declaration of vars) but misses lots of errors. Lets be bold & leave it out!
+ 2307, //Cannot find module 'xxx' or its corresponding type declarations
+ 2322, //Type 'unknown' is not assignable to type 'string'
+ 2339, //property does not exist on
+ 7043, //i forget what this one is,
+ 80001, //Convert to ES6 module
+ 80004, //JSDoc types may be moved to TypeScript types.
+ ]
+ };
+ //apply overrides from codeEditor.monaco.languages.typescript.javascriptDefaults.diagnosticsOptions settings.js
+ var settingsDiagnosticsOptions = RED.settings.get('codeEditor.monaco.languages.typescript.javascriptDefaults.diagnosticsOptions') || {};
+ diagnosticOptions = Object.assign({}, diagnosticOptions, settingsDiagnosticsOptions);
+ /** @see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.typescript.languageservicedefaults.html#setdiagnosticsoptions */
+ _monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions(diagnosticOptions);
+ } catch (error) {
+ console.warn("monaco - Error setting javascriptDefaults", error)
+ }
+ }
+
+ setupJS(monaco);
+ setupJSONata(monaco);
+ setupJSON(monaco);
+ setupCSS(monaco);
+ setupHTML(monaco);
+ defaultServerSideTypes.forEach(function(m) {
+ _loadModuleDTS(m, true)//pre-load common libs
+ })
+
+ initialised = true;
+ return initialised;
+ }
+
+ function create(options) {
+
+ var editorSettings = RED.editor.codeEditor.settings || {};
+ var loadedLibs = {JS:{}, TS:{}};//for tracking and later disposing of loaded type libs
+
+
+ var createThemeMenuOption = function (theme, keybinding) {
+ return {
+ // An unique identifier of the contributed action.
+ id: 'set-theme-' + theme,
+ // A label of the action that will be presented to the user.
+ label: RED._('monaco.setTheme') + ' ' + theme,
+ precondition: null,// A precondition for this action.
+ keybindingContext: keybinding || null,// A rule to evaluate on top of the precondition in order to dispatch the keybindings.
+
+ // Method that will be executed when the action is triggered.
+ // @param editor The editor instance is passed in as a convinience
+ run: function (ed) {
+ //monaco.editor.setTheme(theme)
+ ed.setTheme(theme)
+ return null;
+ }
+ }
+ }
+
+ var convertAceModeToMonacoLang = function (mode) {
+ if (typeof mode == "object" && mode.path) {
+ mode = mode.path;
+ }
+ if (mode) {
+ mode = mode.replace("ace/mode/", "");
+ } else {
+ mode = "javascript";
+ }
+ switch (mode) {
+ case "nrjavascript":
+ case "mjs":
+ mode = "javascript";
+ break;
+ case "vue":
+ mode = "html";
+ break;
+ //TODO: add other compatability types.
+ }
+ return mode;
+ }
+
+ var el = options.element || $("#"+options.id)[0];
+ var toolbarRow = $("
").appendTo(el);
+ el = $("
").appendTo(el).addClass("red-ui-editor-text-container")[0];
+
+ var editorOptions = $.extend({}, editorSettings.options, options);
+ editorOptions.language = convertAceModeToMonacoLang(options.mode);
+
+ //apply defaults
+ if (!editorOptions.minimap) {
+ editorOptions.minimap = {
+ enabled: true,
+ maxColumn: 50,
+ scale: 1,
+ showSlider: "mouseover",
+ renderCharacters: true
+ }
+ }
+
+ //common ACE flags - set equivelants in monaco
+ if(options.enableBasicAutocompletion === false) {
+ editorOptions.showSnippets = false;
+ editorOptions.quickSuggestions = false;
+ editorOptions.parameterHints = { enabled: false };
+ editorOptions.suggestOnTriggerCharacters = false;
+ editorOptions.acceptSuggestionOnEnter = "off";
+ editorOptions.tabCompletion = "off";
+ editorOptions.wordBasedSuggestions = false;
+ }
+ if (options.enableSnippets === false) { editorOptions.showSnippets = false; }
+ if (editorOptions.mouseWheelZoom == null) { editorOptions.mouseWheelZoom = true; }
+ if (editorOptions.suggestFontSize == null) { editorOptions.suggestFontSize = 12; }
+ if (editorOptions.formatOnPaste == null) { editorOptions.formatOnPaste = true; }
+ if (editorOptions.foldingHighlight == null) { editorOptions.foldingHighlight = true; }
+ if (editorOptions.foldStyle == null) { editorOptions.foldStyle = true; } //https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html#folding
+ if (editorOptions.readOnly != null) { editorOptions.readOnly = editorOptions.readOnly; }
+ if (editorOptions.lineNumbers === false) { editorOptions.lineNumbers = false; }
+ if (editorOptions.theme == null) { editorOptions.theme = monacoThemes[0]; }
+ if (editorOptions.mode == null) { editorOptions.mode = convertAceModeToMonacoLang(options.mode); }
+
+ if (options.foldStyle) {
+ switch (options.foldStyle) {
+ case "none":
+ editorOptions.foldStyle = false;
+ editorOptions.foldingHighlight = false
+ break;
+ default:
+ editorOptions.foldStyle = true;
+ editorOptions.foldingHighlight = true;
+ break;
+ }
+ } else {
+ editorOptions.foldStyle = true;
+ editorOptions.foldingHighlight = true;
+ }
+
+ //others
+ editorOptions.roundedSelection = editorOptions.roundedSelection === false ? false : true; //default to true
+ editorOptions.contextmenu = editorOptions.contextmenu === false ? false : true; //(context menu enable) default to true
+ editorOptions.snippetSuggestions = editorOptions.enableSnippets === false ? false : true; //default to true //https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html#snippetsuggestions
+
+ editorOptions.value = options.value || "";
+
+ //if wordSeparators are not supplied, override default ones for the "j" langs
+ if(!editorOptions.wordSeparators) {
+ if (editorOptions.language == "jsonata" || editorOptions.language == "json" || editorOptions.language == "javascript") {
+ editorOptions.wordSeparators = "`~!@#%^&*()-=+[{]}\|;:'\",.<>/?"; //dont use $ as separator
+ }
+ }
+
+ //fixedOverflowWidgets should probably never be set to false
+ //fixedOverflowWidgets allows hover tips to be above other parts of UI
+ editorOptions.fixedOverflowWidgets = editorOptions.fixedOverflowWidgets === false ? false : true;
+
+ //#region Detect mobile / tablet and reduce functionality for small screen and lower power
+ var browser = RED.utils.getBrowserInfo();
+ if (browser.mobile || browser.tablet) {
+ editorOptions.minimap = { enabled: false };
+ editorOptions.formatOnType = false; //try to prevent cursor issues
+ editorOptions.formatOnPaste = false; //try to prevent cursor issues
+ editorOptions.disableMonospaceOptimizations = true; //speed up
+ editorOptions.columnSelection = false; //try to prevent cursor issues
+ editorOptions.matchBrackets = "never"; //speed up
+ editorOptions.maxTokenizationLineLength = 10000; //speed up //internal default is 20000
+ editorOptions.stopRenderingLineAfter = 2000; //speed up //internal default is 10000
+ editorOptions.roundedSelection = false; //speed up rendering
+ editorOptions.trimAutoWhitespace = false; //try to prevent cursor issues
+ editorOptions.parameterHints = { enabled: false };
+ editorOptions.suggestOnTriggerCharacters = false;//limit suggestions, user can still use ctrl+space
+ editorOptions.wordBasedSuggestions = false;
+ editorOptions.suggest = { maxVisibleSuggestions: 6 };
+ // editorOptions.codeLens = false;//If Necessary, disable this useful feature?
+ // editorOptions.quickSuggestions = false;//If Necessary, disable this useful feature?
+ // editorOptions.showSnippets = false; //If Necessary, disable this useful feature?
+ // editorOptions.acceptSuggestionOnEnter = "off"; //If Necessary, disable this useful feature?
+ // editorOptions.tabCompletion = "off"; //If Necessary, disable this useful feature?
+ if (!editorOptions.accessibilitySupport && browser.android) {
+ editorOptions.accessibilitySupport = "off"; //ref https://github.com/microsoft/pxt/pull/7099/commits/35fd3e969b0d5b68ca1e35809f96cea81ef243bc
+ }
+ }
+ //#endregion
+
+ //#region Load types for intellisense
+
+ //Determine if this instance is for client side or server side...
+ //if clientSideSuggestions option is not specified, check see if
+ //requested mode is "nrjavascript" or if options.globals are specifying RED or Buffer
+ var serverSideSuggestions = false;
+ if (options.clientSideSuggestions == null) {
+ if ( ((options.mode + "").indexOf("nrjavascript") >= 0) || (options.globals && (options.globals.RED || options.globals.Buffer )) ) {
+ serverSideSuggestions = true;
+ }
+ }
+
+ // compiler options - enable / disable server-side/client-side suggestions
+ var compilerOptions = monaco.languages.typescript.javascriptDefaults.getCompilerOptions();
+ if (serverSideSuggestions) {
+ compilerOptions.lib = ["esnext"]; //dont include DOM
+ defaultServerSideTypes.forEach(function(m) {
+ _loadModuleDTS(m, false, loadedLibs); //load common node+node-red types
+ })
+ } else {
+ compilerOptions.lib = ["esnext", "dom"];
+ }
+
+ monaco.languages.typescript.javascriptDefaults.setCompilerOptions(compilerOptions);
+
+ //check if extraLibs are to be loaded (e.g. fs or os)
+ refreshModuleLibs(editorOptions.extraLibs)
+
+ function refreshModuleLibs(extraModuleLibs) {
+ var defs = [];
+ var imports = [];
+ const id = "extraModuleLibs/index.d.ts";
+ const file = 'file://types/extraModuleLibs/index.d.ts';
+ if(!extraModuleLibs || extraModuleLibs.length == 0) {
+ loadedLibs.JS[id] = monaco.languages.typescript.javascriptDefaults.addExtraLib(" ", file);
+ } else {
+ var loadList = [...extraModuleLibs];
+ var loadExtraModules = {};
+ for (let index = 0; index < extraModuleLibs.length; index++) {
+ const lib = extraModuleLibs[index];
+ const varName = lib.var;
+ const moduleName = lib.module;
+ if(varName && moduleName) {
+ imports.push("import " + varName + "_import = require('" + moduleName + "');\n");
+ defs.push("var " + varName + ": typeof " + varName + "_import;\n");
+ }
+ var km = knownModules[moduleName];
+ if(km) {
+ loadExtraModules[moduleName] = km;
+ } else {
+ loadExtraModules[moduleName] = {package: "other", module: moduleName, path: "other/" + moduleName + ".d.ts" };
+ }
+ }
+ Object.values(loadExtraModules).forEach(function(m) {
+ _loadModuleDTS(m, false, loadedLibs, function(err, extraLib) {
+ loadList = loadList.filter(e => e.module != extraLib.module );
+ if(loadList.length == 0) {
+ var _imports = imports.join("");
+ var _defs = "\ndeclare global {\n" + defs.join("") + "\n}";
+ var libSource = _imports + _defs;
+ setTimeout(function() {
+ loadedLibs.JS[id] = monaco.languages.typescript.javascriptDefaults.addExtraLib(libSource, file);
+ }, 500);
+ }
+ });
+ });
+ }
+ }
+
+ //#endregion
+
+ /*********** Create the monaco editor ***************/
+ var ed = monaco.editor.create(el, editorOptions);
+
+ ed.nodered = {
+ refreshModuleLibs: refreshModuleLibs //expose this for function node externalModules refresh
+ }
+
+ //add f1 menu items for changing theme
+ for (var themeIdx = 0; themeIdx < monacoThemes.length; themeIdx++) {
+ var themeName = monacoThemes[themeIdx];
+ ed.addAction(createThemeMenuOption(themeName));
+ }
+
+ //#region "ACE compatability"
+
+ ed.selection = {};
+ ed.session = ed;
+ ed.renderer = {};
+
+ ed.setMode = function(mode, cb) {
+ if (mode && typeof mode === "object") {
+ var options = mode;
+ mode = options.path;
+ } else {
+ mode = mode || "text";
+ }
+ mode = convertAceModeToMonacoLang(mode);
+ var oldModel = ed.getModel();
+ var oldValue = ed.getValue();
+ var newModel
+
+ if (oldModel) {
+ oldValue = oldModel.getValue() || "";
+ newModel = monaco.editor.createModel((oldValue || ""), mode);
+ ed.setModel(newModel);
+ oldModel.dispose();
+ } else {
+ newModel = monaco.editor.createModel((oldValue || ""), mode);
+ ed.setModel(newModel);
+ oldModel.dispose();
+ }
+ if (cb && typeof cb == "function") {
+ cb();
+ }
+ this.resize();//cause a call to layout()
+ }
+
+ ed.getRange = function getRange(){
+ var r = ed.getSelection();
+ r.start = {
+ row: r.selectionStartLineNumber-1,
+ column: r.selectionStartColumn-1
+ }
+ r.end = {
+ row: r.endLineNumber-1,
+ column: r.endColumn-1
+ }
+ return r;
+ }
+
+ ed.selection.getRange = ed.getRange;
+
+ ed.session.insert = function insert(position, text) {
+ //ACE position is zero based, monaco range is 1 based
+ var range = new monaco.Range(position.row+1, position.column+1, position.row+1, position.column+1);
+ var op = { range: range, text: text, forceMoveMarkers: true };
+ _executeEdits(this,[op]);
+ }
+
+ ed.session.replace = function replace(range, text) {
+ var op = { range: range, text: text, forceMoveMarkers: true };
+ _executeEdits(this,[op]);
+ }
+
+ function _executeEdits(editor, edits, endCursorState) {
+ var isReadOnly = editor.getOption(monaco.editor.EditorOptions.readOnly.id)
+ if (isReadOnly) {
+ editor.getModel().pushEditOperations(editor.getSelections(), edits, function() {
+ return endCursorState ? endCursorState : null;
+ });
+ } else {
+ editor.executeEdits("editor", edits);
+ }
+ }
+
+ ed.getSelectedText = function getSelectedText() {
+ return ed.getModel().getValueInRange(ed.getSelection());
+ }
+
+ ed.insertSnippet = function editer_insertSnippet(s) {
+ //https://github.com/microsoft/monaco-editor/issues/1112#issuecomment-429580604
+ //no great way of triggering snippets!
+ let contribution = ed.getContribution("snippetController2");
+ contribution.insert(s);
+ }
+
+ ed.destroy = function destroy() {
+ try {
+ //dispose serverside addExtraLib disposible we added - this is the only way (https://github.com/microsoft/monaco-editor/issues/1002#issuecomment-564123586)
+ if(Object.keys(loadedLibs.JS).length) {
+ let entries = Object.entries(loadedLibs.JS);
+ for (let i = 0; i < entries.length; i++) {
+ try {
+ const entry = entries[i];
+ let name = entry[0];
+ loadedLibs.JS[name].dispose();
+ loadedLibs.JS[name] = null;
+ delete loadedLibs.JS[name];
+ } catch (error) { }
+ }
+ }
+ if(Object.keys(loadedLibs.TS).length) {
+ let entries = Object.entries(loadedLibs.TS);
+ for (let i = 0; i < entries.length; i++) {
+ try {
+ const entry = entries[i];
+ let name = entry[0];
+ loadedLibs.TS[name].dispose();
+ loadedLibs.TS[name] = null;
+ delete loadedLibs.TS[name];
+ } catch (error) { }
+ }
+ }
+ } catch (error) { }
+ try {
+ this.setModel(null);
+ } catch (e) { }
+
+ $(el).remove();
+ $(toolbarRow).remove();
+ }
+
+ ed.resize = function resize() {
+ ed.layout();
+ }
+ ed.renderer.updateFull = ed.resize.bind(ed);//call resize on ed.renderer.updateFull
+
+ ed.getSession = function getSession() {
+ return ed;
+ }
+
+ ed.getLength = function getLength() {
+ var _model = ed.getModel();
+ if (_model !== null) {
+ return _model.getLineCount();
+ }
+ return 0;
+ }
+
+ /**
+ * Scroll vertically as necessary and reveal a line.
+ * @param {Number} lineNumber
+ * @param {ScrollType|Number} [scrollType] Immediate: = 1, Smooth: = 0
+ */
+ ed.scrollToLine = function scrollToLine(lineNumber, scrollType) {
+ ed.revealLine(lineNumber, scrollType);
+ }
+
+ ed.moveCursorTo = function moveCursorTo(lineNumber, colNumber) {
+ ed.setPosition({ lineNumber: lineNumber, column: colNumber });
+ }
+
+ // monaco.MarkerSeverity
+ // 1: "Hint"
+ // 2: "Info"
+ // 4: "Warning"
+ // 8: "Error"
+ // Hint: 1
+ // Info: 2
+ // Warning: 4
+ // Error: 8
+ ed.getAnnotations = function getAnnotations() {
+ var aceCompatibleMarkers = [];
+ try {
+ var _model = ed.getModel();
+ if (_model !== null) {
+ var id = _model.getModeId(); // e.g. javascript
+ var ra = _model._associatedResource.authority; //e.g. model
+ var rp = _model._associatedResource.path; //e.g. /18
+ var rs = _model._associatedResource.scheme; //e.g. inmemory
+ var modelMarkers = monaco.editor.getModelMarkers(_model) || [];
+ var thisEditorsMarkers = modelMarkers.filter(function (marker) {
+ var _ra = marker.resource.authority; //e.g. model
+ var _rp = marker.resource.path; //e.g. /18
+ var _rs = marker.resource.scheme; //e.g. inmemory
+ return marker.owner == id && _ra === ra && _rp === rp && _rs === rs;
+ })
+ aceCompatibleMarkers = thisEditorsMarkers.map(function (marker) {
+ return {
+ row: marker.startLineNumber, //ACE compatible
+ column: marker.startColumn, //future
+ endColumn: marker.endColumn, //future
+ endRow: marker.endLineNumber, //future
+ text: marker.message,//ACE compatible
+ type: monaco.MarkerSeverity[marker.severity] ? monaco.MarkerSeverity[marker.severity].toLowerCase() : marker.severity //ACE compatible
+ }
+ })
+ }
+ } catch (error) {
+ console.log("Failed to get editor Annotations", error);
+ }
+ return aceCompatibleMarkers || [];
+ }
+
+
+ //ACE row and col are zero based. Add 1 for monaco lineNumber and column
+ ed.gotoLine = function gotoLine(row, col) {
+ ed.setPosition({ lineNumber: row+1, column: col+1 });
+ }
+ //ACE row and col are zero based. Minus 1 from monaco lineNumber and column
+ ed.getCursorPosition = function getCursorPosition() {
+ var p = ed.getPosition();
+ return { row: p.lineNumber-1, column: p.column-1 };
+ }
+
+ ed.setTheme = monaco.editor.setTheme;
+
+ ed.on = function (name, cb) {
+ switch (name) {
+ case "change":
+ case "input":
+ name = "onDidChangeModelContent";
+ break;
+ case "focus":
+ name = "onDidFocusEditorWidget";
+ break;
+ case "blur":
+ name = "onDidBlurEditorWidget";
+ break;
+ case "paste":
+ name = "onDidPaste";
+ break;
+ default:
+ break;
+ }
+ var fn;
+ if (ed[name]) {
+ fn = ed[name]
+ } else if (monaco.editor[name]) {
+ fn = monaco.editor[name];
+ } else {
+ console.warn("monaco - unknown event: " + name)
+ return;
+ }
+ fn(cb);
+ }
+ ed.getUndoManager = function getUndoManager() {
+ var o = {};
+ function isClean() {
+ try {
+ return ed.getModel().canUndo() === false
+ } catch (error) {
+ return false
+ }
+
+ }
+ o.isClean = isClean.bind(ed);
+ return o;
+ }
+ //#endregion "ACE compatability"
+
+ //final setup
+ if (options.cursor) {
+ var row = options.cursor.row || options.cursor.lineNumber;
+ var col = options.cursor.column || options.cursor.col;
+ ed.gotoLine(row, col);
+ }
+ if (options.focus) {
+ ed.focus();
+ }
+ ed._mode = editorOptions.language;
+
+ if (editorOptions.language === 'markdown') {
+ $(el).addClass("red-ui-editor-text-container-toolbar");
+ ed.toolbar = RED.editor.customEditTypes['_markdown'].buildToolbar(toolbarRow, ed);
+ if (options.expandable !== false) {
+ var expandButton = $('').appendTo(ed.toolbar);
+ RED.popover.tooltip(expandButton, RED._("markdownEditor.expand"));
+ expandButton.on("click", function (e) {
+ e.preventDefault();
+ var value = ed.getValue();
+ RED.editor.editMarkdown({
+ value: value,
+ width: "Infinity",
+ cursor: ed.getCursorPosition(),
+ complete: function (v, cursor) {
+ ed.setValue(v, -1);
+ ed.gotoLine(cursor.row + 1, cursor.column, false);
+ setTimeout(function () {
+ ed.focus();
+ }, 300);
+ }
+ })
+ });
+ }
+ var helpButton = $('').appendTo($(el).parent());
+ RED.popover.create({
+ target: helpButton,
+ trigger: 'click',
+ size: "small",
+ direction: "left",
+ content: RED._("markdownEditor.format"),
+ autoClose: 50
+ });
+ }
+
+ ed.type = type;
+ return ed;
+ }
+
+ return {
+ /**
+ * Editor type
+ * @memberof RED.editor.codeEditor.monaco
+ */
+ get type() { return type; },
+ /**
+ * Editor initialised
+ * @memberof RED.editor.codeEditor.monaco
+ */
+ get initialised() { return initialised; },
+ /**
+ * Initialise code editor
+ * @param {object} options - initialisation options
+ * @memberof RED.editor.codeEditor.monaco
+ */
+ init: init,
+ /**
+ * Create a code editor
+ * @param {object} options - the editor options
+ * @memberof RED.editor.codeEditor.monaco
+ */
+ create: create
+ }
+})();
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/monaco/monaco-bootstrap.js b/packages/node_modules/@node-red/editor-client/src/vendor/monaco/monaco-bootstrap.js
new file mode 100644
index 000000000..f5573c1f6
--- /dev/null
+++ b/packages/node_modules/@node-red/editor-client/src/vendor/monaco/monaco-bootstrap.js
@@ -0,0 +1,24 @@
+(function() {
+ var _isIE = /MSIE \d|Trident.*rv:/.test(navigator.userAgent);
+ //dont load monaco if IE
+ if(_isIE === false) {
+ var defaultLanguage = 'en-gb';
+ var userLocale = (localStorage.getItem("editor-language") + "")
+ var browserLocale = typeof navigator === "undefined" ? "" : (navigator.language || navigator.userLanguage);
+ var cultureDists = {
+ "zh-cn":"zh-hans",
+ "zh-tw":"zh-hant",
+ "ja":"ja",
+ "ko":"ko",
+ "de":"de",
+ "fr":"fr",
+ "it":"it",
+ "es":"es",
+ "ru":"ru",
+ "en-us":"en-gb"
+ };
+ var uiLanguage = cultureDists[userLocale.toLowerCase()] || cultureDists[browserLocale.toLowerCase()] || defaultLanguage;
+ if(uiLanguage) document.write('
+{{#asset.vendorMonaco}}
+
+{{/asset.vendorMonaco}}
{{# page.scripts }}