2025-06-16 18:28:08 +05:00

753 lines
24 KiB
JavaScript
Executable File

"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();
}