"use strict"; require("ace/ext/rtl"); require("ace/multi_select"); require("./inline_editor"); var devUtil = require("./dev_util"); require("./file_drop"); var config = require("ace/config"); config.setLoader(function(moduleName, cb) { require([moduleName], function(module) { cb(null, module); }); }); var env = {}; var dom = require("ace/lib/dom"); var net = require("ace/lib/net"); var lang = require("ace/lib/lang"); var event = require("ace/lib/event"); var theme = require("ace/theme/textmate"); var EditSession = require("ace/edit_session").EditSession; var UndoManager = require("ace/undomanager").UndoManager; var HashHandler = require("ace/keyboard/hash_handler").HashHandler; var Renderer = require("ace/virtual_renderer").VirtualRenderer; var Editor = require("ace/editor").Editor; var Range = require("ace/range").Range; var whitespace = require("ace/ext/whitespace"); var doclist = require("./doclist"); var layout = require("./layout"); var util = require("./util"); var saveOption = util.saveOption; require("ace/ext/elastic_tabstops_lite"); require("ace/incremental_search"); var TokenTooltip = require("./token_tooltip").TokenTooltip; require("ace/config").defineOptions(Editor.prototype, "editor", { showTokenInfo: { set: function(val) { if (val) { this.tokenTooltip = this.tokenTooltip || new TokenTooltip(this); } else if (this.tokenTooltip) { this.tokenTooltip.destroy(); delete this.tokenTooltip; } }, get: function() { return !!this.tokenTooltip; }, handlesSet: true } }); require("ace/config").defineOptions(Editor.prototype, "editor", { useAceLinters: { set: function(val) { if (val && !window.languageProvider) { loadLanguageProvider(editor); } else if (val) { window.languageProvider.registerEditor(this); } else { // todo unregister } } } }); var {HoverTooltip} = require("ace/tooltip"); var MarkerGroup = require("ace/marker_group").MarkerGroup; var docTooltip = new HoverTooltip(); function loadLanguageProvider(editor) { require([ "https://mkslanc.github.io/ace-linters/build/ace-linters.js" ], function(m) { var languageProvider = m.LanguageProvider.fromCdn("https://mkslanc.github.io/ace-linters/build", { functionality: { hover: true, completion: { overwriteCompleters: true }, completionResolve: true, format: true, documentHighlights: true, signatureHelp: false } }); window.languageProvider = languageProvider; languageProvider.registerEditor(editor); // hack to replace tooltip implementation from ace-linters with hover tooltip // can be removed when ace-linters is updated to use MarkerGroup and HoverTooltip if (languageProvider.$descriptionTooltip) editor.off("mousemove", languageProvider.$descriptionTooltip.onMouseMove); languageProvider.$messageController.$worker.addEventListener("message", function(e) { var id = e.data.sessionId.split(".")[0]; var session = languageProvider.$getSessionLanguageProvider({id: id})?.session; if (e.data.type == 6) { // annotation message e.stopPropagation(); if (session) { showAnnotations(session, e.data.value); } } else if (e.data.type == 14) { // highlights message if (session) showOccurrenceMarkers(session, e.data.value); } }, true); function showOccurrenceMarkers(session, positions) { if (!session.state.occurrenceMarkers) { session.state.occurrenceMarkers = new MarkerGroup(session); } session.state.occurrenceMarkers.setMarkers(positions.map(function(el) { var r = el.range; return { range: new Range(r.start.line, r.start.character, r.end.line, r.end.character), // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentHighlightKind className: el.kind == 2 ? "language_highlight_read" : el.kind == 3 ? "language_highlight_write" : "language_highlight_text" }; })); } function showAnnotations(session, diagnostics) { session.clearAnnotations(); let annotations = diagnostics.map((el) => { return { row: el.range.start.line, column: el.range.start.character, text: el.message, type: el.severity === 1 ? "error" : el.severity === 2 ? "warning" : "info" }; }); if (annotations && annotations.length > 0) { session.setAnnotations(annotations); } if (!session.state) session.state = {} if (!session.state.diagnosticMarkers) { session.state.diagnosticMarkers = new MarkerGroup(session); } session.state.diagnosticMarkers.setMarkers(diagnostics.map(function(el) { var r = el.range; return { range: new Range(r.start.line, r.start.character, r.end.line, r.end.character), tooltipText: el.message, className: "language_highlight_error" }; })); }; docTooltip.setDataProvider(function(e, editor) { let session = editor.session; let docPos = e.getDocumentPosition(); languageProvider.doHover(session, docPos, function(hover) { var errorMarker = session.state?.diagnosticMarkers.getMarkerAtPosition(docPos); if (!errorMarker && !hover?.content) return; var range = hover?.range || errorMarker?.range; range = range ? Range.fromPoints(range.start, range.end) : session.getWordRange(docPos.row, docPos.column); var hoverNode = hover && dom.buildDom(["div", {}]); if (hoverNode) { // todo render markdown using ace markdown mode hoverNode.innerHTML = languageProvider.getTooltipText(hover); }; var domNode = dom.buildDom(["div", {}, errorMarker && ["div", {}, errorMarker.tooltipText.trim()], hoverNode ]); docTooltip.showForRange(editor, range, domNode, e); }); }); docTooltip.addToEditor(editor) }); } var workerModule = require("ace/worker/worker_client"); if (location.href.indexOf("noworker") !== -1) { workerModule.WorkerClient = workerModule.UIWorkerClient; } /*********** create editor ***************************/ var container = document.getElementById("editor-container"); // Splitting. var Split = require("ace/split").Split; var split = new Split(container, theme, 1); env.editor = split.getEditor(0); split.on("focus", function(editor) { env.editor = editor; updateUIEditorOptions(); }); env.split = split; window.env = env; var consoleEl = dom.createElement("div"); container.parentNode.appendChild(consoleEl); consoleEl.style.cssText = "position:fixed; bottom:1px; right:0;\ border:1px solid #baf; z-index:100"; var cmdLine = new layout.singleLineEditor(consoleEl); cmdLine.setOption("placeholder", "Enter a command..."); cmdLine.editor = env.editor; env.editor.cmdLine = cmdLine; env.editor.showCommandLine = function(val) { this.cmdLine.focus(); if (typeof val == "string") this.cmdLine.setValue(val, 1); }; /** * This demonstrates how you can define commands and bind shortcuts to them. */ env.editor.commands.addCommands([{ name: "snippet", bindKey: {win: "Alt-C", mac: "Command-Alt-C"}, exec: function(editor, needle) { if (typeof needle == "object") { editor.cmdLine.setValue("snippet ", 1); editor.cmdLine.focus(); return; } var s = snippetManager.getSnippetByName(needle, editor); if (s) snippetManager.insertSnippet(editor, s.content); }, readOnly: true }, { name: "focusCommandLine", bindKey: "shift-esc|ctrl-`", exec: function(editor, needle) { editor.cmdLine.focus(); }, readOnly: true }, { name: "nextFile", bindKey: "Ctrl-tab", exec: function(editor) { doclist.cycleOpen(editor, 1); }, readOnly: true }, { name: "previousFile", bindKey: "Ctrl-shift-tab", exec: function(editor) { doclist.cycleOpen(editor, -1); }, readOnly: true }, { name: "execute", bindKey: "ctrl+enter", exec: function(editor) { try { var r = window.eval(editor.getCopyText() || editor.getValue()); } catch(e) { r = e; } editor.cmdLine.setValue(r + ""); }, readOnly: true }, { name: "showKeyboardShortcuts", bindKey: {win: "Ctrl-Alt-h", mac: "Command-Alt-h"}, exec: function(editor) { config.loadModule("ace/ext/keybinding_menu", function(module) { module.init(editor); editor.showKeyboardShortcuts(); }); } }, { name: "increaseFontSize", bindKey: "Ctrl-=|Ctrl-+", exec: function(editor) { var size = parseInt(editor.getFontSize(), 10) || 12; editor.setFontSize(size + 1); } }, { name: "decreaseFontSize", bindKey: "Ctrl+-|Ctrl-_", exec: function(editor) { var size = parseInt(editor.getFontSize(), 10) || 12; editor.setFontSize(Math.max(size - 1 || 1)); } }, { name: "resetFontSize", bindKey: "Ctrl+0|Ctrl-Numpad0", exec: function(editor) { editor.setFontSize(12); } }]); env.editor.commands.addCommands(whitespace.commands); cmdLine.commands.bindKeys({ "Shift-Return|Ctrl-Return|Alt-Return": function(cmdLine) { cmdLine.insert("\n"); }, "Esc|Shift-Esc": function(cmdLine){ cmdLine.editor.focus(); }, "Return": function(cmdLine){ var command = cmdLine.getValue().split(/\s+/); var editor = cmdLine.editor; editor.commands.exec(command[0], editor, command[1]); editor.focus(); } }); cmdLine.commands.removeCommands(["find", "gotoline", "findall", "replace", "replaceall"]); var commands = env.editor.commands; commands.addCommand({ name: "save", bindKey: {win: "Ctrl-S", mac: "Command-S"}, exec: function(arg) { var session = env.editor.session; var name = session.name.match(/[^\/]+$/); localStorage.setItem( "saved_file:" + name, session.getValue() ); env.editor.cmdLine.setValue("saved "+ name); } }); commands.addCommand({ name: "load", bindKey: {win: "Ctrl-O", mac: "Command-O"}, exec: function(arg) { var session = env.editor.session; var name = session.name.match(/[^\/]+$/); var value = localStorage.getItem("saved_file:" + name); if (typeof value == "string") { session.setValue(value); env.editor.cmdLine.setValue("loaded "+ name); } else { env.editor.cmdLine.setValue("no previuos value saved for "+ name); } } }); /*********** manage layout ***************************/ function handleToggleActivate(target) { if (dom.hasCssClass(sidePanelContainer, "closed")) onResize(null, false); else if (dom.hasCssClass(target, "toggleButton")) onResize(null, true); }; var sidePanelContainer = document.getElementById("sidePanel"); sidePanelContainer.onclick = function(e) { handleToggleActivate(e.target); }; var optionToggle = document.getElementById("optionToggle"); optionToggle.onkeydown = function(e) { if (e.code === "Space" || e.code === "Enter") { handleToggleActivate(e.target); } }; var consoleHeight = 20; function onResize(e, closeSidePanel) { var left = 280; var width = document.documentElement.clientWidth; var height = document.documentElement.clientHeight; if (closeSidePanel == null) closeSidePanel = width < 2 * left; if (closeSidePanel) { left = 20; document.getElementById("optionToggle").setAttribute("aria-label", "Show Options"); } else document.getElementById("optionToggle").setAttribute("aria-label", "Hide Options"); width -= left; container.style.width = width + "px"; container.style.height = height - consoleHeight + "px"; container.style.left = left + "px"; env.split.resize(); consoleEl.style.width = width + "px"; consoleEl.style.left = left + "px"; cmdLine.resize(); sidePanel.style.width = left + "px"; sidePanel.style.height = height + "px"; dom.setCssClass(sidePanelContainer, "closed", closeSidePanel); } window.onresize = onResize; onResize(); /*********** options panel ***************************/ doclist.history = doclist.docs.map(function(doc) { return doc.name; }); doclist.history.index = 0; doclist.cycleOpen = function(editor, dir) { var h = this.history; h.index += dir; if (h.index >= h.length) h.index = 0; else if (h.index <= 0) h.index = h.length - 1; var s = h[h.index]; doclist.pickDocument(s); }; doclist.addToHistory = function(name) { var h = this.history; var i = h.indexOf(name); if (i != h.index) { if (i != -1) h.splice(i, 1); h.index = h.push(name); } }; doclist.pickDocument = function(name) { doclist.loadDoc(name, function(session) { if (!session) return; doclist.addToHistory(session.name); session = env.split.setSession(session); whitespace.detectIndentation(session); optionsPanel.render(); env.editor.focus(); }); }; var OptionPanel = require("ace/ext/options").OptionPanel; var optionsPanel = new OptionPanel(env.editor); var originalAutocompleteCommand = null; optionsPanel.add({ Main: { Document: { type: "select", path: "doc", items: doclist.all, position: -101, onchange: doclist.pickDocument, getValue: function() { return env.editor.session.name || "javascript"; } }, Split: { type: "buttonBar", path: "split", values: ["None", "Below", "Beside"], position: -100, onchange: function(value) { var sp = env.split; if (value == "Below" || value == "Beside") { var newEditor = (sp.getSplits() == 1); sp.setOrientation(value == "Below" ? sp.BELOW : sp.BESIDE); sp.setSplits(2); if (newEditor) { var session = sp.getEditor(0).session; var newSession = sp.setSession(session, 1); newSession.name = session.name; } } else { sp.setSplits(1); } }, getValue: function() { var sp = env.split; return sp.getSplits() == 1 ? "None" : sp.getOrientation() == sp.BELOW ? "Below" : "Beside"; } } }, More: { "RTL": { path: "rtl", position: 900 }, "Line based RTL switching": { path: "rtlText", position: 900 }, "Show token info": { path: "showTokenInfo", position: 2000 }, "Inline preview for autocomplete": { path: "inlineEnabledForAutocomplete", position: 2000, onchange: function(value) { var Autocomplete = require("ace/autocomplete").Autocomplete; if (value && !originalAutocompleteCommand) { originalAutocompleteCommand = Autocomplete.startCommand.exec; Autocomplete.startCommand.exec = function(editor) { var autocomplete = Autocomplete.for(editor); autocomplete.inlineEnabled = true; originalAutocompleteCommand(...arguments); } } else if (!value) { var autocomplete = Autocomplete.for(editor); autocomplete.destroy(); Autocomplete.startCommand.exec = originalAutocompleteCommand; originalAutocompleteCommand = null; } }, getValue: function() { return !!originalAutocompleteCommand; } }, "Use Ace Linters": { position: 3000, path: "useAceLinters" }, "Show Textarea Position": devUtil.textPositionDebugger, "Text Input Debugger": devUtil.textInputDebugger, } }); var optionsPanelContainer = document.getElementById("optionsPanel"); optionsPanel.render(); optionsPanelContainer.insertBefore(optionsPanel.container, optionsPanelContainer.firstChild); optionsPanel.container.style.width = "80%"; optionsPanel.on("setOption", function(e) { util.saveOption(e.name, e.value); }); function updateUIEditorOptions() { optionsPanel.editor = env.editor; optionsPanel.render(); } optionsPanel.setOption("doc", util.getOption("doc") || "JavaScript"); for (var i in optionsPanel.options) { var value = util.getOption(i); if (value != undefined) { if ((i == "mode" || i == "theme") && !/[/]/.test(value)) value = "ace/" + i + "/" + value; optionsPanel.setOption(i, value); } } function synchroniseScrolling() { var s1 = env.split.$editors[0].session; var s2 = env.split.$editors[1].session; s1.on('changeScrollTop', function(pos) {s2.setScrollTop(pos)}); s2.on('changeScrollTop', function(pos) {s1.setScrollTop(pos)}); s1.on('changeScrollLeft', function(pos) {s2.setScrollLeft(pos)}); s2.on('changeScrollLeft', function(pos) {s1.setScrollLeft(pos)}); } var StatusBar = require("ace/ext/statusbar").StatusBar; new StatusBar(env.editor, cmdLine.container); var Emmet = require("ace/ext/emmet"); net.loadScript("https://cloud9ide.github.io/emmet-core/emmet.js", function() { Emmet.setCore(window.emmet); env.editor.setOption("enableEmmet", true); }); require("ace/placeholder").PlaceHolder; var snippetManager = require("ace/snippets").snippetManager; env.editSnippets = function() { var sp = env.split; if (sp.getSplits() == 2) { sp.setSplits(1); return; } sp.setSplits(1); sp.setSplits(2); sp.setOrientation(sp.BESIDE); var editor = sp.$editors[1]; var id = sp.$editors[0].session.$mode.$id || ""; var m = snippetManager.files[id]; if (!doclist["snippets/" + id]) { var text = m.snippetText; var s = doclist.initDoc(text, "", {}); s.setMode("ace/mode/snippets"); doclist["snippets/" + id] = s; } editor.on("blur", function() { m.snippetText = editor.getValue(); snippetManager.unregister(m.snippets); m.snippets = snippetManager.parseSnippetFile(m.snippetText, m.scope); snippetManager.register(m.snippets); }); sp.$editors[0].once("changeMode", function() { sp.setSplits(1); }); editor.setSession(doclist["snippets/" + id], 1); editor.focus(); }; optionsPanelContainer.insertBefore( dom.buildDom(["div", {style: "text-align:right;width: 80%"}, ["div", {}, ["button", {onclick: env.editSnippets}, "Edit Snippets"]], ["div", {}, ["button", {onclick: function() { var info = navigator.platform + "\n" + navigator.userAgent; if (env.editor.getValue() == info) return env.editor.undo(); env.editor.setValue(info, -1); env.editor.setOption("wrap", 80); }}, "Show Browser Info"]], devUtil.getUI(), ["div", {}, "Open Dialog ", ["button", {onclick: openTestDialog.bind(null, false)}, "Scale"], ["button", {onclick: openTestDialog.bind(null, true)}, "Height"] ] ]), optionsPanelContainer.children[1] ); function openTestDialog(animateHeight) { if (window.dialogEditor) window.dialogEditor.destroy(); var editor = ace.edit(null, { value: "test editor", mode: "ace/mode/javascript" }); window.dialogEditor = editor; var dialog = dom.buildDom(["div", { style: "transition: all 1s; position: fixed; z-index: 100000;" + "background: darkblue; border: solid 1px black; display: flex; flex-direction: column" }, ["div", {}, "test dialog"], editor.container ], document.body); editor.container.style.flex = "1"; if (animateHeight) { dialog.style.width = "0vw"; dialog.style.height = "0vh"; dialog.style.left = "20vw"; dialog.style.top = "20vh"; setTimeout(function() { dialog.style.width = "80vw"; dialog.style.height = "80vh"; dialog.style.left = "10vw"; dialog.style.top = "10vh"; }, 0); } else { dialog.style.width = "80vw"; dialog.style.height = "80vh"; dialog.style.left = "10vw"; dialog.style.top = "10vh"; dialog.style.transform = "scale(0)"; setTimeout(function() { dialog.style.transform = "scale(1)" }, 0); } function close(e) { if (!e || !dialog.contains(e.target)) { if (animateHeight) { dialog.style.width = "0vw"; dialog.style.height = "0vh"; dialog.style.left = "80vw"; dialog.style.top = "80vh"; } else { dialog.style.transform = "scale(0)" } window.removeEventListener("mousedown", close); dialog.addEventListener("transitionend", function() { dialog.remove(); editor.destroy(); }); } } window.addEventListener("mousedown", close); editor.focus() editor.commands.bindKey("Esc", function() { close(); }); } require("ace/ext/language_tools"); require("ace/ext/inline_autocomplete"); env.editor.setOptions({ enableBasicAutocompletion: true, enableInlineAutocompletion: true, enableSnippets: true }); var beautify = require("ace/ext/beautify"); env.editor.commands.addCommands(beautify.commands); // global keybindings var KeyBinding = require("ace/keyboard/keybinding").KeyBinding; var CommandManager = require("ace/commands/command_manager").CommandManager; var commandManager = new CommandManager(); var kb = new KeyBinding({ commands: commandManager, fake: true }); event.addCommandKeyListener(document.documentElement, kb.onCommandKey.bind(kb)); event.addListener(document.documentElement, "keyup", function(e) { if (e.keyCode === 18) // do not trigger browser menu on windows e.preventDefault(); }); commandManager.addCommands([{ name: "window-left", bindKey: {win: "cmd-alt-left", mac: "ctrl-cmd-left"}, exec: function() { moveFocus(); } }, { name: "window-right", bindKey: {win: "cmd-alt-right", mac: "ctrl-cmd-right"}, exec: function() { moveFocus(); } }, { name: "window-up", bindKey: {win: "cmd-alt-up", mac: "ctrl-cmd-up"}, exec: function() { moveFocus(); } }, { name: "window-down", bindKey: {win: "cmd-alt-down", mac: "ctrl-cmd-down"}, exec: function() { moveFocus(); } }]); function moveFocus() { var el = document.activeElement; if (el == env.editor.textInput.getElement()) env.editor.cmdLine.focus(); else env.editor.focus(); }