mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-07-28 04:07:47 +00:00
update colorcode files
This commit is contained in:
+696
-30
@@ -6,17 +6,17 @@ var commonAtoms = ["And", "Or"];
|
||||
var commonKeywords = ["If", "Else", "Elseif", "Endif"];
|
||||
var commonCommands = ["AccessInfo", "Background", "Build", "ClearAccessBlock", "ClearRTCam", "Config", "ControllerDisable",
|
||||
"ControllerEnable", "DateTime", "Debug", "Dec", "DeepSleep", "DisablePriorityTask", "DNS", "DST", "EraseSDKWiFi", "ExecuteRules", "FactoryReset", "Gateway", "I2Cscanner", "Inc",
|
||||
"IP", "Let", "LetStr", "Load", "LogEntry", "LogPortStatus", "LoopTimerSet", "LoopTimerSet_ms", "LoopTimerSetAndRun", "LoopTimerSetAndRun_ms", "MemInfo", "MemInfoDetail", "Name", "Password", "PostToHTTP", "Publish", "PublishR",
|
||||
"Reboot", "Save", "SendTo", "SendToHTTP", "SendToUDP", "SendToUDPMix", "Settings", "Subnet", "Subscribe", "TaskClear", "TaskClearAll",
|
||||
"IP", "Latitude", "Let", "LetStr", "Load", "LogEntry", "LogPortStatus", "Longitude", "LoopTimerSet", "LoopTimerSet_ms", "LoopTimerSetAndRun", "LoopTimerSetAndRun_ms", "MemInfo", "MemInfoDetail", "Name", "Password", "PostToHTTP", "PostToHTTPS", "Publish", "PublishR",
|
||||
"PutToHTTP", "PutToHTTPS", "Reboot", "Save", "SendTo", "SendToHTTP", "SendToHTTPS", "SendToUDP", "SendToUDPMix", "Settings", "Subnet", "Subscribe", "TaskClear", "TaskClearAll",
|
||||
"TaskDisable", "TaskEnable", "TaskRun", "TaskValueSet", "TaskValueSetAndRun", "TaskValueSetDerived", "TaskValueSetPresentation", "TimerPause", "TimerResume", "TimerSet", "TimerSet_ms", "TimeZone",
|
||||
"UdpPort", "UdpTest", "Unit", "UseNTP", "WdConfig", "WdRead", "WiFi", "WiFiAllowAP", "WiFiAPMode", "WiFiConnect", "WiFiDisconnect", "WiFiKey",
|
||||
"WiFiKey2", "WiFiMode", "WiFiScan", "WiFiSSID", "WiFiSSID2", "WiFiSTAMode",
|
||||
"Event", "AsyncEvent",
|
||||
"GPIO", "GPIOToggle", "LongPulse", "LongPulse_mS", "Monitor", "Pulse", "PWM", "Servo", "Status", "Tone", "RTTTL", "UnMonitor",
|
||||
"Provision", "Provision,Config", "Provision,Security", "Provision,Notification", "Provision,Provision", "Provision,Rules,", "Provision,CustomCdnUrl", "Provision,Firmware,", ];
|
||||
"Provision", "Provision,Config", "Provision,Security", "Provision,Notification", "Provision,Provision", "Provision,Rules", "Provision,CustomCdnUrl", "Provision,Firmware"];
|
||||
var commonEvents = ["Clock#Time", "JsonReply", "JsonReply#", "Login#Failed", "MQTT#Connected", "MQTT#Disconnected", "MQTTimport#Connected", "MQTTimport#Disconnected","OpenMeteo#current","OpenMeteo#daily", "OpenMeteo#hourly", "Rules#Timer", "System#Boot",
|
||||
"System#BootMode", "System#Sleep", "System#Wake", "TaskExit#", "TaskInit#", "ThingspeakReply", "Time#Initialized", "Time#Set", "WiFi#APmodeDisabled", "WiFi#APmodeEnabled",
|
||||
"WiFi#ChangedAccesspoint", "WiFi#ChangedWiFichannel", "WiFi#Connected", "WiFi#Disconnected", "ProvisionFirmware",];
|
||||
"WiFi#ChangedAccesspoint", "WiFi#ChangedWiFichannel", "WiFi#Connected", "WiFi#Disconnected"];
|
||||
var commonPlugins = [
|
||||
//P003
|
||||
"ResetPulseCounter", "SetPulseCounterTotal", "LogPulseStatistic",
|
||||
@@ -167,7 +167,7 @@ var AnythingElse = [
|
||||
//System Variables
|
||||
"%eventvalue%", "%eventpar%", "%eventname%", "%sysname%", "%bootcause%", "%systime%", "%systm_hm%",
|
||||
"%systm_hm_0%", "%systm_hm_sp%", "%systime_am%", "%systime_am_0%", "%systime_am_sp%", "%systm_hm_am%", "%systm_hm_am_0%", "%systm_hm_am_sp%",
|
||||
"%lcltime%", "%sunrise%", "%s_sunrise%", "%m_sunrise%", "%sunset%", "%s_sunset%", "%m_sunset%", "%lcltime_am%",
|
||||
"%lcltime%", "%sunrise%", "%s_sunrise%", "%m_sunrise%", "%sunset%", "%s_sunset%", "%m_sunset%", "%lcltime_am%", "%latitude%", "%longitude%",
|
||||
"%syshour%", "%syshour_0%", "%sysmin%", "%sysmin_0%", "%syssec%", "%syssec_0%", "%sysday%", "%sysday_0%", "%sysmonth%",
|
||||
"%sysmonth_0%", "%systzoffset%", "%systzoffset_s%", "%sysyear%", "%sysyear_0%", "%sysyears%", "%sysweekday%", "%sysweekday_s%", "%unixtime%", "%unixtime_lcl%", "%uptime%", "%uptime_ms%",
|
||||
"%rssi%", "%ip%", "%unit%", "%unit_0%", "%ssid%", "%bssid%", "%wi_ch%", "%iswifi%", "%vcc%", "%mac%", "%mac_int%", "%isntp%", "%ismqtt%",
|
||||
@@ -179,7 +179,7 @@ var AnythingElse = [
|
||||
"%ethwifimode%", "%ethconnected%", "%ethduplex%", "%ethspeed%", "%ethstate%", "%ethspeedstate%",
|
||||
//Standard Conversions
|
||||
"%c_w_dir%", "%c_c2f%", "%c_ms2Bft%", "%c_dew_th%", "%c_alt_pres_sea%", "%c_sea_pres_alt%", "%c_cm2imp%", "%c_isnum%", "%c_mm2imp%",
|
||||
"%c_m2day%", "%c_m2dh%", "%c_m2dhm%", "%c_s2dhms%", "%c_ts2date%", "%c_ts2wday%", "%c_random%", "%c_2hex%", "%c_u2ip%", "%c_uname%", "%c_uage%", "%c_ubuild%", "%c_ubuildstr%",
|
||||
"%c_m2day%", "%c_m2dh%", "%c_m2dhm%", "%c_s2dhms%", "%c_ts2date%", "%c_ts2isodate%", "%c_ts2wday%", "%c_random%", "%c_2hex%", "%c_u2ip%", "%c_uname%", "%c_uage%", "%c_ubuild%", "%c_ubuildstr%",
|
||||
"%c_uload%", "%c_utype%", "%c_utypestr%", "%c_strf%",
|
||||
//Variables
|
||||
"var", "int", "str", "length"
|
||||
@@ -199,38 +199,704 @@ for (const element2 of pluginDispKind) {
|
||||
var EXTRAWORDS = commonAtoms.concat(commonPlugins, commonKeywords, commonCommands, commonEvents, commonTag, commonNumber, commonMath, commonWarning, taskSpecifics, AnythingElse);
|
||||
|
||||
var rEdit;
|
||||
var confirmR = true;
|
||||
var android = /Android/.test(navigator.userAgent);
|
||||
|
||||
function initCM() {
|
||||
CodeMirror.commands.autocomplete = function (cm) { cm.showHint({ hint: CodeMirror.hint.anyword }); }
|
||||
rEdit = CodeMirror.fromTextArea(document.getElementById('rules'), {
|
||||
tabSize: 2, indentWithTabs: false, lineNumbers: true, autoCloseBrackets: true,
|
||||
extraKeys: {
|
||||
'Ctrl-Space': 'autocomplete',
|
||||
Tab: (cm) => {
|
||||
if (cm.getMode().name === 'null') {
|
||||
cm.execCommand('insertTab');
|
||||
} else {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.execCommand('indentMore');
|
||||
if (android) {
|
||||
if (confirm("Do you want to enable colored rules on your Android device?\nThis feature hasn't been fully tested yet and may still have some issues.\nIt is currently expected to work with Chrome, Firefox, and Vivaldi.\nPlease report any problems you encounter.")) {
|
||||
confirmR = true
|
||||
} else {
|
||||
confirmR = false
|
||||
}
|
||||
}
|
||||
if (confirmR) {
|
||||
CodeMirror.commands.autocomplete = function (cm) { cm.showHint({ hint: CodeMirror.hint.anyword }); }
|
||||
rEdit = CodeMirror.fromTextArea(document.getElementById('rules'), {
|
||||
tabSize: 2, indentWithTabs: false, lineNumbers: true, autoCloseBrackets: true,
|
||||
extraKeys: {
|
||||
'Ctrl-Space': 'autocomplete',
|
||||
Tab: (cm) => {
|
||||
if (cm.getMode().name === 'null') {
|
||||
cm.execCommand('insertTab');
|
||||
} else {
|
||||
cm.execCommand('insertSoftTab');
|
||||
if (cm.somethingSelected()) {
|
||||
cm.execCommand('indentMore');
|
||||
} else {
|
||||
cm.execCommand('insertSoftTab');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'Shift-Tab': (cm) => cm.execCommand('indentLess'),
|
||||
},
|
||||
'Shift-Tab': (cm) => cm.execCommand('indentLess'),
|
||||
}
|
||||
});
|
||||
|
||||
rEdit.on('change', function () { rEdit.save() });
|
||||
|
||||
if (!android) {
|
||||
rEdit.on("inputRead", function (cm, event) {
|
||||
var letters = /[\w%,.]/; //characters for activation
|
||||
var cur = cm.getCursor();
|
||||
var token = cm.getTokenAt(cur);
|
||||
if (letters.test(event.text) && token.type != "comment") {
|
||||
cm.showHint({ completeSingle: false });
|
||||
};
|
||||
});
|
||||
}
|
||||
CodeMirror.keyMap.default["Ctrl-F"] = function (cm) {
|
||||
openFind(); // Inject your custom buttons
|
||||
};
|
||||
|
||||
CodeMirror.keyMap.default["Cmd-F"] = function (cm) {
|
||||
openFind();
|
||||
};
|
||||
// Disable search-related keys to prevent conflics with other shortcuts
|
||||
CodeMirror.keyMap.default["Ctrl-G"] = disableShortcut;
|
||||
CodeMirror.keyMap.default["Cmd-G"] = disableShortcut;
|
||||
CodeMirror.keyMap.default["Shift-Ctrl-G"] = disableShortcut;
|
||||
CodeMirror.keyMap.default["Shift-Cmd-G"] = disableShortcut;
|
||||
CodeMirror.keyMap.default["Ctrl-H"] = disableShortcut;
|
||||
CodeMirror.keyMap.default["Cmd-H"] = disableShortcut;
|
||||
CodeMirror.keyMap.default["Shift-Ctrl-F"] = disableShortcut;
|
||||
CodeMirror.keyMap.default["Shift-Cmd-F"] = disableShortcut;
|
||||
CodeMirror.keyMap.default["Ctrl-Shift-R"] = disableShortcut;
|
||||
CodeMirror.keyMap.default["Cmd-Shift-R"] = disableShortcut;
|
||||
}
|
||||
function disableShortcut(cm) {
|
||||
// do nothing to disable
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------- add search and formatting options
|
||||
|
||||
|
||||
function closeSearchDialog() {
|
||||
const dlg = document.querySelectorAll('.CodeMirror-dialog');
|
||||
if (dlg.length > 0) {
|
||||
dlg.forEach(d => d.remove());
|
||||
document.body.classList.remove('dialog-opened');
|
||||
}
|
||||
rEdit.execCommand('clearSearch');
|
||||
}
|
||||
|
||||
function removeHighlight() {
|
||||
requestAnimationFrame(() => {
|
||||
const highlights = document.querySelectorAll('.search-next-highlight');
|
||||
highlights.forEach(el => el.classList.remove('search-next-highlight'));
|
||||
});
|
||||
console.log('Removing highlights');
|
||||
}
|
||||
|
||||
let findDialogObserver = null; // Keep one observer
|
||||
|
||||
function openFind() {
|
||||
// Disconnect previous observer if it exists
|
||||
if (findDialogObserver) {
|
||||
document.querySelectorAll('.CodeMirror-dialog').forEach(d => d.remove());
|
||||
findDialogObserver.disconnect();
|
||||
findDialogObserver = null;
|
||||
}
|
||||
|
||||
// Create a new observer
|
||||
findDialogObserver = new MutationObserver(() => {
|
||||
if (!document.querySelector('.CodeMirror-dialog')) {
|
||||
removeHighlight();
|
||||
findDialogObserver.disconnect();
|
||||
findDialogObserver = null;
|
||||
}
|
||||
});
|
||||
rEdit.on('change', function () { rEdit.save() });
|
||||
//hinting on input
|
||||
rEdit.on("inputRead", function (cm, event) {
|
||||
var letters = /[\w%,.]/; //characters for activation
|
||||
var cur = cm.getCursor();
|
||||
var token = cm.getTokenAt(cur);
|
||||
if (letters.test(event.text) && token.type != "comment") {
|
||||
cm.showHint({ completeSingle: false });
|
||||
};
|
||||
|
||||
findDialogObserver.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
clearSearchNextHighlight(rEdit);
|
||||
rEdit.execCommand('findPersistent');
|
||||
addFindButtons();
|
||||
}
|
||||
|
||||
function clearSearchNextHighlight(cm) {
|
||||
removeHighlight();
|
||||
// Also clear any markText highlights if used
|
||||
if (cm.__searchNextHighlight) {
|
||||
cm.__searchNextHighlight.clear();
|
||||
cm.__searchNextHighlight = null;
|
||||
}
|
||||
}
|
||||
|
||||
function addFindButtons() {
|
||||
const element = document.querySelector('.CodeMirror-selected');
|
||||
const dialog = document.querySelector('.CodeMirror-dialog');
|
||||
if (!dialog || dialog.querySelector('.search-button-group')) return;
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
title: 'Find Previous',
|
||||
symbol: '▲',
|
||||
action: () => rEdit.execCommand('findPersistentPrev')
|
||||
},
|
||||
{
|
||||
title: 'Find Next',
|
||||
symbol: '▼',
|
||||
action: () => rEdit.execCommand('findPersistentNext')
|
||||
},
|
||||
{
|
||||
title: 'Replace',
|
||||
symbol: 'Replace',
|
||||
action: () => {
|
||||
closeSearchDialog();
|
||||
rEdit.execCommand('replace');
|
||||
addFindButtons(); // Re-add buttons after replacing
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Close',
|
||||
symbol: '❌',
|
||||
action: closeSearchDialog
|
||||
},
|
||||
{
|
||||
title: 'Help',
|
||||
symbol: '?',
|
||||
action: () => {
|
||||
alert(`Available shortcuts:
|
||||
• Ctrl+F / Cmd+F: Open search
|
||||
• Enter: Find next
|
||||
• Shift+Enter: Find previous
|
||||
• Use /re/ syntax for regex search`);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
buttons.forEach(({ title, symbol, action }) => {
|
||||
const btn = document.createElement('span');
|
||||
btn.title = title;
|
||||
btn.className = title.toLowerCase() === 'help' ? 'button help' : 'button';
|
||||
btn.innerHTML = symbol;
|
||||
btn.style.cssText = `
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
`;
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
action();
|
||||
});
|
||||
dialog.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
// Add Searchfield button
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.getElementById('rulesselect');
|
||||
|
||||
if (form) {
|
||||
if (confirmR) {
|
||||
// Add search help button to CodeMirror editor
|
||||
const btn3 = document.createElement('button');
|
||||
btn3.type = 'button'; // prevent form submission
|
||||
btn3.id = 'searchBtn';
|
||||
btn3.innerHTML = "🔎︎"; // magnifier
|
||||
btn3.style.padding = "2px 5px";
|
||||
btn3.className = 'button help'; // just the class, no inline style
|
||||
form.appendChild(btn3);
|
||||
|
||||
|
||||
btn3.addEventListener('click', () => {
|
||||
if (typeof rEdit !== 'undefined') {
|
||||
openFind();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add format document button
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button'; // prevent form submission
|
||||
btn.id = 'formatBtn';
|
||||
btn.textContent = 'Format';
|
||||
btn.className = 'button'; // just the class, no inline style
|
||||
form.appendChild(btn);
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
console.log('Format button clicked');
|
||||
triggerFormatting();
|
||||
});
|
||||
}
|
||||
|
||||
// Add hint button to CodeMirror editor
|
||||
// const form2 = document.getElementsByClassName('CodeMirror')[0];
|
||||
// if (form2) {
|
||||
// console.log('Form2 found', form2);
|
||||
// const btn2 = document.createElement('button');
|
||||
// btn2.type = 'button'; // prevent form submission
|
||||
// btn2.id = 'hintBtn';
|
||||
// btn2.innerHTML = "☰︎"; // ☰︎
|
||||
// btn2.className = 'button help';
|
||||
// btn2.style.position = 'absolute';
|
||||
// btn2.style.right = '0';
|
||||
// btn2.style.top = '0';
|
||||
|
||||
// form2.appendChild(btn2);
|
||||
|
||||
// btn2.addEventListener('click', () => {
|
||||
// if (typeof rEdit !== 'undefined') {
|
||||
// rEdit.focus(); // Optional: ensure focus
|
||||
// rEdit.execCommand('autocomplete'); // Show hint menu
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
let charBuffer = "";
|
||||
let findTimer = null;
|
||||
|
||||
// Global keydown handler
|
||||
document.addEventListener('keydown', function (e) {
|
||||
const key = e.key;
|
||||
|
||||
// disabled to prevent conflics with other shortcuts
|
||||
// const isCtrlShiftF = e.ctrlKey && e.shiftKey && key.toLowerCase() === 'f';
|
||||
|
||||
// // Ctrl + Shift + F triggers formatting
|
||||
// if (isCtrlShiftF) {
|
||||
// e.preventDefault();
|
||||
// console.log('Keyboard shortcut detected: Formatting...');
|
||||
// triggerFormatting();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Keys that cancel character buffering
|
||||
const nonCharKeys = [
|
||||
"Backspace", "Delete", "ArrowLeft", "ArrowRight",
|
||||
"ArrowUp", "ArrowDown", "Enter", "Tab", "Escape",
|
||||
"Shift", "Control", "Alt", "Meta"
|
||||
];
|
||||
|
||||
if (nonCharKeys.includes(key) || key.length !== 1) {
|
||||
charBuffer = "";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Workaround of showing hints for Android devices
|
||||
if (android) {
|
||||
var wasKeydown = false; // Flag to track if keydown was triggered
|
||||
|
||||
// Works on Android for some keys
|
||||
rEdit.on("keydown", (cm, e) => {
|
||||
if (["Enter", "Backspace", " "].includes(e.key)) {
|
||||
charBuffer = "";
|
||||
//alert("Buffer cleared via keydown:", e.key);
|
||||
}
|
||||
//alert("Keydown event detected: " + e.key);
|
||||
wasKeydown = true;
|
||||
});
|
||||
let lastCharBuffer = "";
|
||||
|
||||
const inputField = rEdit.getInputField();
|
||||
|
||||
function handleTypedChar(e, isBeforeInput = false) {
|
||||
if (!rEdit.hasFocus() || !rEdit || !e.data || !wasKeydown) return;
|
||||
wasKeydown = false;
|
||||
const data = e.data;
|
||||
const doc = rEdit.getDoc();
|
||||
const cursor = doc.getCursor(); // AFTER inserted char
|
||||
const letters = /[\w%,.]/;
|
||||
const token = rEdit.getTokenAt(cursor);
|
||||
|
||||
// Reset on space
|
||||
if (data === ' ') {
|
||||
console.log("Clearing buffer due to space");
|
||||
charBuffer = "";
|
||||
lastCharBuffer = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore unchanged input
|
||||
if (data === lastCharBuffer && charBuffer.length > 0) return;
|
||||
|
||||
if (letters.test(data) && token.type !== "comment") {
|
||||
const lastChar = cursor.ch <= 1 ? data.slice(-1) : data;
|
||||
charBuffer += lastChar;
|
||||
lastCharBuffer = charBuffer;
|
||||
|
||||
// Remove line number bleed-in
|
||||
if (charBuffer.startsWith(String(cursor.line + 1)) && cursor.ch === 0) {
|
||||
charBuffer = charBuffer.slice(String(cursor.line).length);
|
||||
}
|
||||
|
||||
console.log("Buffer updated:", charBuffer);
|
||||
|
||||
const insertPos = {
|
||||
line: cursor.line,
|
||||
ch: cursor.ch - charBuffer.length + 1
|
||||
};
|
||||
|
||||
const insertAndHint = () => {
|
||||
doc.replaceRange(charBuffer, insertPos, cursor);
|
||||
rEdit.setCursor({
|
||||
line: insertPos.line,
|
||||
ch: insertPos.ch + charBuffer.length
|
||||
});
|
||||
rEdit.showHint({ completeSingle: false });
|
||||
};
|
||||
|
||||
// For `input`, delay slightly to avoid race with native insert
|
||||
if (!isBeforeInput) {
|
||||
setTimeout(insertAndHint, 0);
|
||||
} else {
|
||||
insertAndHint();
|
||||
}
|
||||
|
||||
} else {
|
||||
charBuffer = "";
|
||||
}
|
||||
}
|
||||
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
const isFirefox = /firefox/.test(ua);
|
||||
const isChrome = /chrome/.test(ua) && !isFirefox;
|
||||
// Firefox (Android) – beforeinput preferred
|
||||
if (isFirefox) {
|
||||
inputField.addEventListener("beforeinput", (e) => {
|
||||
e.preventDefault();
|
||||
handleTypedChar(e, true);
|
||||
});
|
||||
} else if (isChrome) {
|
||||
document.addEventListener("input", (e) => {
|
||||
handleTypedChar(e, false);
|
||||
});
|
||||
}
|
||||
|
||||
rEdit.on('endCompletion', function () {
|
||||
setTimeout(() => {
|
||||
forceKeyboardOpen();
|
||||
}, 100); // small delay may help
|
||||
});
|
||||
}
|
||||
|
||||
function forceKeyboardOpen() {
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.style.position = "absolute";
|
||||
input.style.opacity = "0";
|
||||
input.style.height = "0";
|
||||
input.style.width = "0";
|
||||
input.style.border = "none";
|
||||
input.style.top = "0";
|
||||
input.style.left = "-9999";
|
||||
input.style.padding = "0";
|
||||
input.style.zIndex = "-1";
|
||||
input.style.fontSize = "16px"; // prevents zoom on iOS
|
||||
|
||||
document.body.appendChild(input);
|
||||
input.focus();
|
||||
|
||||
setTimeout(() => {
|
||||
input.remove();
|
||||
rEdit.focus(); // bring focus back to CodeMirror
|
||||
}, 10);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function triggerFormatting() {
|
||||
let scrollInfo, cursor, currentLine, lineText, textarea;
|
||||
|
||||
if (confirmR) {
|
||||
const doc = rEdit.getDoc();
|
||||
scrollInfo = rEdit.getScrollInfo();
|
||||
cursor = doc.getCursor();
|
||||
|
||||
currentLine = cursor.ch === 0 ? cursor.line - 1 : cursor.line;
|
||||
lineText = rEdit.getLine(currentLine) || "";
|
||||
|
||||
textarea = rEdit.getValue();
|
||||
} else {
|
||||
textarea = document.getElementById('rules').value;
|
||||
}
|
||||
|
||||
// Apply transformations
|
||||
textarea = initalAutocorrection(textarea);
|
||||
textarea = formatLogic(textarea);
|
||||
|
||||
if (confirmR) {
|
||||
rEdit.setValue(textarea);
|
||||
|
||||
// Restore cursor
|
||||
const newLine = currentLine;
|
||||
const newCh = cursor.ch === 0 && lineText.length > 0
|
||||
? lineText.length
|
||||
: cursor.ch;
|
||||
|
||||
rEdit.setCursor({ line: newLine, ch: newCh });
|
||||
|
||||
// Delay scroll restoration slightly to ensure rendering is complete
|
||||
setTimeout(() => {
|
||||
rEdit.scrollTo(scrollInfo.left, scrollInfo.top);
|
||||
rEdit.focus();
|
||||
}, 0);
|
||||
|
||||
rEdit.save();
|
||||
} else {
|
||||
document.getElementById('rules').value = textarea;
|
||||
}
|
||||
}
|
||||
|
||||
function initalAutocorrection(text) {
|
||||
for (const word of EXTRAWORDS) {
|
||||
if (word === "Do") {
|
||||
const pattern = /(^|\s)(do)(\s*)(\/\/.*)?$/gmi;
|
||||
text = text.replace(pattern, (match, p1, p2, p3, p4) => {
|
||||
return `${p1}Do${p3}${p4 ?? ""}`;
|
||||
});
|
||||
} else {
|
||||
const pattern = new RegExp(`^\\s*\\b${word}\\b`, "gmi");
|
||||
text = text.replace(pattern, (match) => {
|
||||
// Replace the matched word with its corrected casing
|
||||
return match.replace(new RegExp(word, 'i'), word);
|
||||
});
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function formatLogic(text) {
|
||||
const INDENT = ' ';
|
||||
const lines = text.split('\n').map(line => {
|
||||
const trimmed = line.trimStart(); // remove leading spaces only
|
||||
return trimmed.startsWith('//') ? line : trimmed;
|
||||
});
|
||||
const result = [];
|
||||
const errors = [];
|
||||
|
||||
let insideOnBlock = false;
|
||||
let onStartLine = null;
|
||||
let currentIfStack = [];
|
||||
let currentIfLines = [];
|
||||
|
||||
function isComment(line) {
|
||||
return line.trim().startsWith('//');
|
||||
}
|
||||
|
||||
function isEmpty(line) {
|
||||
return line.trim() === '';
|
||||
}
|
||||
|
||||
function isOnLine(line) {
|
||||
return line.trim().toLowerCase().startsWith('on');
|
||||
}
|
||||
|
||||
function hasDo(line) {
|
||||
return line.trim().toLowerCase().endsWith('do');
|
||||
}
|
||||
|
||||
function isEndonLine(line) {
|
||||
return line.trim().toLowerCase() === 'endon';
|
||||
}
|
||||
|
||||
function isIf(line) {
|
||||
return line.trim().toLowerCase().startsWith('if');
|
||||
}
|
||||
|
||||
function isElse(line) {
|
||||
return line.trim().toLowerCase() === 'else';
|
||||
}
|
||||
|
||||
function isElseif(line) {
|
||||
return line.trim().toLowerCase().startsWith('elseif');
|
||||
}
|
||||
|
||||
function isEndif(line) {
|
||||
return line.trim().toLowerCase() === 'endif';
|
||||
}
|
||||
|
||||
let indentLevel = 0;
|
||||
|
||||
function flushIfErrors() {
|
||||
if (currentIfStack.length > 0) {
|
||||
errors.push(`• Missing ${currentIfStack.length} Endif(s):`);
|
||||
errors.push(` - Unclosed If block(s) starting at line(s): ${currentIfLines.join(', ')}`);
|
||||
}
|
||||
currentIfStack = [];
|
||||
currentIfLines = [];
|
||||
}
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const trimmed = line.trim();
|
||||
|
||||
// Comments
|
||||
if (isComment(trimmed)) {
|
||||
result.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Empty
|
||||
if (isEmpty(trimmed)) {
|
||||
result.push('');
|
||||
continue;
|
||||
}
|
||||
|
||||
// === On Line ===
|
||||
if (isOnLine(trimmed)) {
|
||||
if (!hasDo(trimmed)) {
|
||||
errors.push(`• Line ${i + 1}: "On" statement must end with "Do"`);
|
||||
// still treat as valid On block so user can see rest of structure
|
||||
}
|
||||
|
||||
if (insideOnBlock) {
|
||||
errors.push(`• Line ${i + 1}: Found "On..." before previous "On...Do" (line ${onStartLine + 1}) was closed with "Endon"`);
|
||||
flushIfErrors();
|
||||
}
|
||||
|
||||
insideOnBlock = true;
|
||||
onStartLine = i;
|
||||
indentLevel = 1;
|
||||
result.push(trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// === Endon Line ===
|
||||
if (isEndonLine(trimmed)) {
|
||||
if (!insideOnBlock) {
|
||||
errors.push(`• Line ${i + 1}: Found "Endon" without a matching "On...Do"`);
|
||||
} else {
|
||||
flushIfErrors();
|
||||
insideOnBlock = false;
|
||||
onStartLine = null;
|
||||
indentLevel = 0;
|
||||
}
|
||||
|
||||
result.push(INDENT.repeat(indentLevel) + trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// === Inside On Block ===
|
||||
if (insideOnBlock) {
|
||||
if (isIf(trimmed)) {
|
||||
result.push(INDENT.repeat(indentLevel) + trimmed);
|
||||
currentIfStack.push('if');
|
||||
currentIfLines.push(i + 1);
|
||||
indentLevel++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isElse(trimmed)) {
|
||||
console.log("Else found:", trimmed);
|
||||
if (currentIfStack.length === 0) {
|
||||
errors.push(`• Line ${i + 1}: "Else" without matching "If"`);
|
||||
} else {
|
||||
indentLevel = Math.max(indentLevel - 1, 0);
|
||||
}
|
||||
|
||||
result.push(INDENT.repeat(indentLevel) + trimmed);
|
||||
indentLevel++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isElseif(trimmed)) {
|
||||
console.log("Elseif found:", trimmed);
|
||||
if (currentIfStack.length === 0) {
|
||||
errors.push(`• Line ${i + 1}: "Elseif" without matching "If"`);
|
||||
} else {
|
||||
indentLevel = Math.max(indentLevel - 1, 0);
|
||||
}
|
||||
|
||||
result.push(INDENT.repeat(indentLevel) + trimmed);
|
||||
indentLevel++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isEndif(trimmed)) {
|
||||
if (currentIfStack.length === 0) {
|
||||
errors.push(`• Line ${i + 1}: "Endif" without matching "If"`);
|
||||
} else {
|
||||
indentLevel = Math.max(indentLevel - 1, 0);
|
||||
currentIfStack.pop();
|
||||
currentIfLines.pop();
|
||||
}
|
||||
|
||||
result.push(INDENT.repeat(indentLevel) + trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Any other line inside a block
|
||||
result.push(INDENT.repeat(indentLevel) + trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// === Outside any block ===
|
||||
result.push(trimmed);
|
||||
}
|
||||
|
||||
// === Final checks ===
|
||||
if (insideOnBlock) {
|
||||
errors.push(`• Missing "Endon" for "On...Do" starting at line ${onStartLine + 1}`);
|
||||
flushIfErrors();
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
const firstErrorLine = extractFirstErrorLine(errors);
|
||||
alert("Errors found:\n" + errors.join('\n'));
|
||||
if (!isNaN(firstErrorLine)) {
|
||||
if (confirmR) {
|
||||
setTimeout(() => {
|
||||
jumpToLine(firstErrorLine);
|
||||
}, 50);
|
||||
} else {
|
||||
const textareaR = document.getElementById('rules');
|
||||
setTimeout(() => {
|
||||
jumpToLineInTextarea(textareaR, firstErrorLine);
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.join('\n');
|
||||
}
|
||||
|
||||
function jumpToLine(lineNumber) {
|
||||
const cmLine = Math.max(0, lineNumber - 1); // Convert to 0-based index
|
||||
rEdit.setCursor({ line: cmLine, ch: 0 }); // Place cursor at beginning of the line
|
||||
rEdit.focus(); // Ensure editor is focused
|
||||
rEdit.scrollIntoView({ line: cmLine, ch: 0 }, 100);
|
||||
}
|
||||
|
||||
function extractFirstErrorLine(errors) {
|
||||
for (const err of errors) {
|
||||
// Match: "• Line 5: ..."
|
||||
let match = err.match(/• Line (\d+)/);
|
||||
if (match) return parseInt(match[1]);
|
||||
|
||||
// Match: "starting at line 2"
|
||||
match = err.match(/starting at line (\d+)/);
|
||||
if (match) return parseInt(match[1]);
|
||||
|
||||
// Match: "starting at line(s): 4, 7, 9"
|
||||
match = err.match(/starting at line\(s\):\s*(\d+)/);
|
||||
if (match) return parseInt(match[1]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function jumpToLineInTextarea(textarea, lineNumber) {
|
||||
const lines = textarea.value.split('\n');
|
||||
const clampedLine = Math.max(1, Math.min(lineNumber, lines.length)); // Clamp to valid range
|
||||
|
||||
// Calculate the character offset to the start of the target line
|
||||
let offset = 0;
|
||||
for (let i = 0; i < clampedLine - 1; i++) {
|
||||
offset += lines[i].length + 1; // +1 for the newline character
|
||||
}
|
||||
|
||||
// Move cursor and scroll into view
|
||||
textarea.focus();
|
||||
textarea.selectionStart = textarea.selectionEnd = offset;
|
||||
|
||||
// Scroll to the selection
|
||||
textarea.scrollTop = textarea.scrollHeight; // Jump to bottom
|
||||
textarea.scrollTop = textarea.scrollTop - textarea.clientHeight / 2; // Center around selection
|
||||
}
|
||||
//--------------------------------------------------------------------------------- end of formatting option
|
||||
|
||||
(function (mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user