/* * 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) function .setReadOnly(readOnly) property .renderer = {}; function .renderer.updateFull() function setMode(mode, cb) function getRange(); function replace(range, text) function selectAll function clearSelection 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 setFontSize(size:Number) //Set a new font size (in pixels) for the editor text. 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"] ]; const modulesCache = {}; /** * 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) { //Handles orphaned models //ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed RED.events.on("editor:close",function() { let models = window.monaco ? monaco.editor.getModels() : null; if(models && models.length) { console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().") for (let index = 0; index < models.length; index++) { const model = models[index]; if(!model.isDisposed()) { model.dispose(); } } } }); 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,green,yellow,blue,grey|}",shape:"${2|ring,dot|}",text:"${3:message}"});','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("get (env)", 'env.get("${1:name}");','Get env variable value',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, useDefineForClassFields: true,//permit class static fields with private name to have initializer 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 2345, //Argument of type xxx is not assignable to parameter of type 'DateTimeFormatOptions' 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 watchTimer; 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 = "text"; } switch (mode) { case "nrjavascript": case "mjs": mode = "javascript"; break; case "vue": mode = "html"; break; case "appcache": mode = "shell"; 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); //by default, set javascript editors to text mode. //when element becomes visible, it will be (re) set to javascript mode //this is to ensure multiple editors sharing the model dont present its //consts & lets to each other if(editorOptions.language == "javascript") { editorOptions._language = editorOptions.language; editorOptions.language = "text" } //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 (editorOptions.automaticLayout == null) { editorOptions.automaticLayout = true; } 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 = []; Array.prototype.push.apply(loadList, extraModuleLibs);//Use this instead of spread operator to prevent IE syntax error 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(function(e) {return 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); //Unbind ctrl-Enter (default action is to insert a newline in editor) This permits the shortcut to close the tray. try { ed._standaloneKeybindingService.addDynamicKeybinding( '-editor.action.insertLineAfter', // command ID prefixed by '-' null, // keybinding () => {} // need to pass an empty handler ); } catch (error) { } 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, resize) { if(resize==null) { resize = true; } mode = convertAceModeToMonacoLang(mode); var oldModel = ed.getModel(); var oldValue = ed.getValue(); var newModel if (oldModel) { var oldScrollTop = ed.getScrollTop(); var oldScrollLeft = ed.getScrollLeft(); var oldSelections = ed.getSelections(); var oldPosition = ed.getPosition(); oldValue = oldModel.getValue() || ""; try { if(!oldModel.isDisposed()) { oldModel.dispose(); } } catch (error) { } ed.setModel(null); newModel = monaco.editor.createModel((oldValue || ""), mode); ed.setModel(newModel); ed.setScrollTop(oldScrollTop, 1/* immediate */); ed.setScrollLeft(oldScrollLeft, 1/* immediate */); ed.setPosition(oldPosition); ed.setSelections(oldSelections); } else { newModel = monaco.editor.createModel((oldValue || ""), mode); ed.setModel(newModel); } if (cb && typeof cb == "function") { cb(); } if(resize) { 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.setReadOnly = function setReadOnly(readOnly) { ed.updateOptions({ readOnly: readOnly }) } 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.selectAll = function selectAll() { const range = ed.getModel().getFullModelRange(); ed.setSelection(range); } ed.clearSelection = function clearSelection() { ed.setPosition({column:1,lineNumber:1}); } ed.getSelectedText = function getSelectedText() { return ed.getModel().getValueInRange(ed.getSelection()); } ed.insertSnippet = function 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() { if(watchTimer) { clearInterval(watchTimer); } 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 { var m = this.getModel(); if(m && !m.isDisposed()) { m.dispose(); } 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; } ed.setFontSize = function setFontSize(size) { ed.updateOptions({ fontSize: size }); } //#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; //as models are signleton, consts and let are avialable to other javascript instances //so when not focused, set editor mode to text temporarily to avoid multiple defs if(editorOptions._language) { ed._mode = editorOptions._language; ed._tempMode = editorOptions.language; } ed.onDidBlurEditorWidget(function() { if(isVisible(el) == false) { onVisibilityChange(false, 0, el); } }); ed.onDidFocusEditorWidget(function() { onVisibilityChange(true, 10, el); }); function visibilityWatcher(element, callback) { try { var options = { root: $(element).closest("div.red-ui-tray-content")[0] || document, attributes: true, childList: true, }; var observer = new IntersectionObserver(function(entries, observer) { entries.forEach(function(entry) { callback(entry.intersectionRatio > 0, 5, entry.target); }); }, options); observer.observe(element); } catch (e1) { //browser not supporting IntersectionObserver? then fall back to polling! try { let elVisibleMem = isVisible(el) watchTimer = setInterval(function() { let elVisible = isVisible(el); if(elVisible != elVisibleMem) { callback(elVisible, 5, element); } elVisibleMem = elVisible; }, 100); } catch (e2) { } } } function onVisibilityChange(visible, delay, element) { if(visible) { if(ed._mode == "javascript" && ed._tempMode == "text") { ed._tempMode = ""; setTimeout(function() { if(element.parentElement) { //ensure el is still in DOM ed.setMode('javascript', undefined, false); } }, delay || 50); } } else if(ed._mode == "javascript" && ed._tempMode != "text") { if(element.parentElement) { //ensure el is still in DOM ed.setMode('text', undefined, false); ed._tempMode = "text"; } } } visibilityWatcher(el, onVisibilityChange); 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; } function isVisible(el) { if(!el.offsetHeight && !el.offsetWidth) { return false; } if(getComputedStyle(el).visibility === 'hidden') { return false; } return true; } 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 } })();