mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-07-28 04:07:47 +00:00
800 lines
30 KiB
JavaScript
800 lines
30 KiB
JavaScript
//espeasy.js written by chromoxdor based on shell.js
|
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
|
|
|
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",
|
|
"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,"];
|
|
var commonEvents = ["Clock#Time", "Login#Failed", "MQTT#Connected", "MQTT#Disconnected", "MQTTimport#Connected", "MQTTimport#Disconnected", "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"];
|
|
var commonPlugins = [
|
|
//P003
|
|
"ResetPulseCounter", "SetPulseCounterTotal", "LogPulseStatistic",
|
|
//P007
|
|
"analogout",
|
|
//P009
|
|
"MCPGPIO", "MCPGPIOToggle", "MCPLongPulse", "MCPLongPulse_ms", "MCPPulse", "Status,MCP", "Monitor,MCP", "MonitorRange,MCP",
|
|
"UnMonitorRange,MCP", "UnMonitor,MCP", "MCPGPIORange", "MCPGPIOPattern", "MCPMode", "MCPModeRange",
|
|
//P011
|
|
"ExtGpio", "ExtPwm", "ExtPulse", "ExtLongPulse", "Status,EXT,",
|
|
//P012
|
|
"LCDCmd", "LCD",
|
|
//P019
|
|
"PCFGPIO", "PCFGPIOToggle", "PCFLongPulse", "PCFLongPulse_ms", "PCFPulse", "Status,PCF", "Monitor,PCF",
|
|
"MonitorRange,PCF", "UnMonitorRange,PCF", "UnMonitor,PCF", "PCFGPIORange", "PCFGPIOpattern", "PCFMode", "PCFmodeRange",
|
|
//P020/P044
|
|
"SerialSend", "SerialSendMix", "Ser2NetClientSend", "SerialSend_test",
|
|
//P022
|
|
"pcapwm", "pcafrq", "mode2",
|
|
//P023
|
|
"OLED", "OLEDCMD", "OLEDCMD,on", "OLEDCMD,off", "OLEDCMD,clear",
|
|
//P035
|
|
"IRSEND", "IRSENDAC",
|
|
//P036
|
|
"OledFramedCmd", "OledFramedCmd,Display", "OledFramedCmd,low", "OledFramedCmd,med", "OledFramedCmd,high", /*"OledFramedCmd,user",*/ "OledFramedCmd,Frame", "OledFramedCmd,linecount", "OledFramedCmd,leftalign", "OledFramedCmd,align", "OledFramedCmd,userDef1", "OledFramedCmd,userDef2",
|
|
//P038
|
|
"NeoPixel", "NeoPixelAll", "NeoPixelLine", "NeoPixelHSV", "NeoPixelAllHSV", "NeoPixelLineHSV", "NeoPixelBright",
|
|
//P048
|
|
"MotorShieldCmd,DCMotor", "MotorShieldCmd,Stepper",
|
|
//P049
|
|
"MHZCalibrateZero", "MHZReset", "MHZABCEnable", "MHZABCDisable",
|
|
//P052
|
|
"Sensair_SetRelay",
|
|
//P053
|
|
"PMSX003", "PMSX003,Wake", "PMSX003,Sleep", "PMSX003,Reset",
|
|
//P059
|
|
"encwrite",
|
|
//P065
|
|
"Play", "Vol", "Eq", "Mode", "Repeat",
|
|
//P067
|
|
"tareChanA", "tareChanB",
|
|
//P073
|
|
"7dn", "7dst", "7dsd", "7dtext", "7ddt", "7dt", "7dtfont", "7dtbin", "7don", "7doff", "7output",
|
|
//P076
|
|
"HLWCalibrate", "HLWReset",
|
|
//P077
|
|
"csecalibrate", "cseclearpulses", "csereset",
|
|
//P079
|
|
"WemosMotorShieldCMD", "LolinMotorShieldCMD",
|
|
//P082
|
|
"GPS", "GPS,Sleep", "GPS,Wake", "GPS#GotFix", "GPS#LostFix", "GPS#Travelled",
|
|
//P086
|
|
"homieValueSet",
|
|
//P087
|
|
"SerialProxy_Write", "SerialProxy_WriteMix", "SerialProxy_Test",
|
|
//P088
|
|
"HeatPumpir",
|
|
//P093
|
|
"MitsubishiHP", "MitsubishiHP,temperature", "MitsubishiHP,power", "MitsubishiHP,mode", "MitsubishiHP,fan", "MitsubishiHP,vane", "MitsubishiHP,widevane",
|
|
//P094
|
|
"Culreader_Write",
|
|
//P099 & P123
|
|
"Touch", "Touch,Rot", "Touch,Flip", "Touch,Enable", "Touch,Disable", "Touch,On", "Touch,Off", "Touch,Toggle", "Touch,Setgrp", "Touch,Incgrp", "Touch,Decgrp", "Touch,Incpage", "Touch,Decpage", "Touch,Updatebutton",
|
|
//P101
|
|
"WakeOnLan",
|
|
//P104
|
|
"DotMatrix", "DotMatrix,clear", "DotMatrix,update", "DotMatrix,size", "DotMatrix,txt", "DotMatrix,settxt", "DotMatrix,content", "DotMatrix,alignment", "DotMatrix,anim.in", "DotMatrix,anim.out", "DotMatrix,speed", "DotMatrix,pause", "DotMatrix,font", "DotMatrix,layout", "DotMatrix,inverted", "DotMatrix,specialeffect", "DotMatrix,offset", "DotMatrix,brightness", "DotMatrix,repeat", "DotMatrix,setbar", "DotMatrix,bar",
|
|
//P109
|
|
"Thermo", "Thermo,Up", "Thermo,Down", "Thermo,Mode", "Thermo,ModeBtn", "Thermo,Setpoint",
|
|
//P115
|
|
"Max1704xclearalert",
|
|
//P116
|
|
//P117
|
|
"scdgetabc", "scdgetalt", "scdgettmp", "scdsetcalibration", "scdsetfrc", "scdgetinterval",
|
|
//P124
|
|
"multirelay", "multirelay,on", "multirelay,off", "multirelay,set", "multirelay,get", "multirelay,loop",
|
|
//P126
|
|
"ShiftOut", "ShiftOut,Set", "ShiftOut,SetNoUpdate", "ShiftOut,Update", "ShiftOut,SetAll", "ShiftOut,SetAllNoUpdate", "ShiftOut,SetAllLow", "ShiftOut,SetAllHigh", "ShiftOut,SetChipCount", "ShiftOut,SetHexBin",
|
|
//P127
|
|
"cdmrst",
|
|
//P128
|
|
"nfx", "nfx,off", "nfx,on", "nfx,dim", "nfx,line,", "nfx,hsvline,", "nfx,one,", "nfx,hsvone,", "nfx,all,", "nfx,rgb,", "nfx,fade,", "nfx,hsv,", "nfx,colorfade,", "nfx,rainbow", "nfx,kitt,", "nfx,comet,", "nfx,theatre,", "nfx,scan,", "nfx,dualscan,", "nfx,twinkle,", "nfx,twinklefade,", "nfx,sparkle,", "nfx,wipe,", "nfx,dualwipe", "nfx,fire", "nfx,fireflicker", "nfx,faketv", "nfx,simpleclock", "nfx,stop", "nfx,statusrequest", "nfx,fadetime,", "nfx,fadedelay,", "nfx,speed,", "nfx,count,", "nfx,bgcolor",
|
|
//P129
|
|
"ShiftIn", "ShiftIn,PinEvent", "ShiftIn,ChipEvent", "ShiftIn,SetChipCount", "ShiftIn,SampleFrequency", "ShiftIn,EventPerPin",
|
|
//P135
|
|
"scd4x", "scd4x,storesettings", "scd4x,facoryreset", "scd4x,selftest", "scd4x,setfrc,",
|
|
//P137
|
|
"axp", "axp,ldo2", "axp,ldo3", "axp,ldoio", "axp,gpio0", "axp,gpio1", "axp,gpio2", "axp,gpio3", "axp,gpio4", "axp,dcdc2", "axp,dcdc3", "axp,ldo2map", "axp,ldo3map", "axp,ldoiomap", "axp,dcdc2map", "axp,dcdc3map", "axp,ldo2perc", "axp,ldo3perc", "axp,ldoioperc", "axp,dcdc2perc", "axp,dcdc3perc",
|
|
//P143
|
|
"I2CEncoder", "I2CEncoder,bright", "I2CEncoder,led1", "I2CEncoder,led2", "I2CEncoder,gain", "I2CEncoder,set",
|
|
//P146
|
|
"cachereader", "cachereader,readpos", "cachereader,sendtaskinfo", "cachereader,flush",
|
|
//P148
|
|
"tm1621", "tm1621,write,", "tm1621,writerow,", "tm1621,voltamp,", "tm1621,energy,", "tm1621,celcius,", "tm1621,fahrenheit,", "tm1621,humidity,", "tm1621,raw,",
|
|
//P152
|
|
"dac", "dac,1", "dac,2",
|
|
//P153
|
|
"sht4x", "sht4x,startup",
|
|
//P159
|
|
"ld2410", "ld2410,factoryreset", "ld2410,logall",
|
|
//P162
|
|
"digipot", "digipot,reset", "digipot,shutdown", "digipot,",
|
|
//P165
|
|
"7dextra", "7dbefore", "7dgroup", "7digit", "7color", "7digitcolor", "7groupcolor",
|
|
//P166
|
|
"gp8403", "gp8403,volt,", "gp8403,mvolt,", "gp8403,range,", "gp8403,preset,", "gp8403,init,",
|
|
//P167
|
|
"sen5x", "sen5x,startclean", "sen5x,techlog,",
|
|
//P169
|
|
"as3935", "as3935,clearstats", "as3935,calibrate", "as3935,setgain,", "as3935,setnf,", "as3935,setwd,", "as3925,setsrej,",
|
|
//P175(P053)
|
|
/*"pmsx003", "pmsx003,wake", "pmsx003,sleep", "pmsx003,reset",*/
|
|
//P178
|
|
"lu9685", "lu9685,servo,", "lu9685,enable,", "lu9685,disable,", "lu9685,setrange,",
|
|
//P180
|
|
"geni2c", "geni2c,cmd,", "geni2c,exec,", "geni2c,log,",
|
|
];
|
|
var pluginDispKind = [
|
|
//P095
|
|
"tft", "ili9341", "ili9342", "ili9481", "ili9486", "ili9488",
|
|
//P096
|
|
"epd", "eink", "epaper", "il3897", "uc8151d", "ssd1680", "ws2in7", "ws1in54",
|
|
//P116
|
|
/*"tft",*/ "st77xx", "st7735", "st7789", "st7796",
|
|
//P131
|
|
"neomatrix", "neo",
|
|
//P141
|
|
/*"lcd",*/ "pcd8544",
|
|
];
|
|
var pluginDispCmd = [
|
|
"cmd,on", "cmd,off", "cmd,clear", "cmd,backlight", "cmd,bright", "cmd,deepsleep", "cmd,seq_start", "cmd,seq_end", "cmd,inv", "cmd,rot",
|
|
",clear", ",rot", ",tpm", ",txt", ",txp", ",txz", ",txc", ",txs", ",txtfull", ",asciitable", ",font",
|
|
",l", ",lh", ",lv", ",lm", ",lmr", ",r", ",rf", ",c", ",cf", ",rf", ",t", ",tf", ",rr", ",rrf", ",px", ",pxh", ",pxv", ",bmp", ",btn",
|
|
",win", ",defwin", ",delwin",
|
|
];
|
|
var commonTag = ["On", "Do", "Endon"];
|
|
var commonNumber = ["toBin", "toHex", "Constrain", "XOR", "AND:", "OR:", "Ord", "bitRead", "bitSet", "bitClear", "bitWrite", "urlencode"];
|
|
var commonMath = ["Log", "Ln", "Abs", "Exp", "Sqrt", "Sq", "Round", "Sin", "Cos", "Tan", "aSin", "aCos", "aTan", "Sin_d", "Cos_d", "Tan_d", "aSin_d", "aCos_d", "aTan_d", "map", "mapc"];
|
|
var commonWarning = ["delay", "Delay", "ResetFlashWriteCounter"];
|
|
var taskSpecifics = [
|
|
//Task settings
|
|
"settings.Enabled", "settings.Interval", "settings.ValueCount",
|
|
"settings.Controller1.Enabled", "settings.Controller2.Enabled", "settings.Controller3.Enabled",
|
|
"settings.Controller1.Idx", "settings.Controller2.Idx", "settings.Controller3.Idx"
|
|
];
|
|
//things that do not fit in any other catergory (for now)
|
|
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%",
|
|
"%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%",
|
|
"%dns%", "%dns1%", "%dns2%", "%flash_freq%", "%flash_size%", "%flash_chip_vendor%", "%flash_chip_model%", "%fs_free%", "%fs_size%",
|
|
"%cpu_id%", "%cpu_freq%", "%cpu_model%", "%cpu_rev%", "%cpu_cores%", "%board_name%", "%inttemp%", "%islimited_build%", "%isvar_double%",
|
|
//String Functions
|
|
"substring", "lookup", "indexOf", "indexOf_ci", "equals", "equals_ci", "strtol", "timeToMin", "timeToSec",
|
|
//Ethernet
|
|
"%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_uload%", "%c_utype%", "%c_utypestr%", "%c_strf%",
|
|
//Variables
|
|
"var", "int", "str", "length"
|
|
];
|
|
|
|
//merging displayspecific commands of P095,P096,P116,P131 into commonPlugins
|
|
for (const element2 of pluginDispKind) {
|
|
commonPlugins = commonPlugins.concat(element2);
|
|
}
|
|
for (const element2 of pluginDispKind) {
|
|
for (const element3 of pluginDispCmd) {
|
|
let mergedDisp = element2 + element3;
|
|
commonPlugins = commonPlugins.concat(mergedDisp);
|
|
}
|
|
}
|
|
|
|
var EXTRAWORDS = commonAtoms.concat(commonPlugins, commonKeywords, commonCommands, commonEvents, commonTag, commonNumber, commonMath, commonWarning, taskSpecifics, AnythingElse);
|
|
|
|
var rEdit;
|
|
function initCM() {
|
|
var confirmR = true
|
|
var android = /Android/.test(navigator.userAgent);
|
|
if (android) {
|
|
if (confirm("Do you want to enable colored rules? (There are some issues with the standard Android Keyboard causing it to fail!)")) {
|
|
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 {
|
|
if (cm.somethingSelected()) {
|
|
cm.execCommand('indentMore');
|
|
} else {
|
|
cm.execCommand('insertSoftTab');
|
|
}
|
|
}
|
|
},
|
|
'Shift-Tab': (cm) => cm.execCommand('indentLess'),
|
|
}
|
|
});
|
|
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 });
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------- add formatting option
|
|
document.addEventListener('keydown', function (e) {
|
|
// Ctrl + Shift + F to format
|
|
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'f') {
|
|
e.preventDefault();
|
|
console.log('Formatting...');
|
|
initalAutocorrection();
|
|
|
|
const textarea = document.getElementById('rules');
|
|
textarea.value = formatLogic(textarea.value);
|
|
|
|
// Clean up previous CodeMirror instances (if any)
|
|
document.querySelectorAll('div.cm-s-default').forEach(el => el.remove());
|
|
initCM();
|
|
}
|
|
});
|
|
|
|
function initalAutocorrection() {
|
|
const textarea = document.getElementById("rules");
|
|
let text = textarea.value;
|
|
|
|
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);
|
|
});
|
|
}
|
|
}
|
|
|
|
textarea.value = text;
|
|
}
|
|
|
|
unction formatLogic(text) {
|
|
const INDENT = ' '; // 2 spaces
|
|
const lines = text.split('\n').map(line => line.trim()); // remove all existing indentation
|
|
|
|
let indentLevel = 0;
|
|
const result = [];
|
|
const stack = [];
|
|
const errors = [];
|
|
|
|
let openOnDoLine = null;
|
|
let ifCount = 0;
|
|
let endifCount = 0;
|
|
let ifLines = [];
|
|
|
|
function startsWithKeyword(line, keywords) {
|
|
line = line.toLowerCase();
|
|
return keywords.some(k => line.startsWith(k));
|
|
}
|
|
|
|
const ON_START = 'on';
|
|
const ON_END = 'endon';
|
|
const IF_START = 'if';
|
|
const ELSE = 'else';
|
|
const IF_END = 'endif';
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
const low = line.toLowerCase();
|
|
|
|
// handle "On ... Do"
|
|
if (startsWithKeyword(low, [ON_START])) {
|
|
const hasDo = low.includes('do');
|
|
|
|
if (!hasDo) {
|
|
errors.push(`Line ${i + 1}: "On" without "Do"`);
|
|
} else {
|
|
if (openOnDoLine !== null) {
|
|
errors.push(`Line ${openOnDoLine + 1}: "On...Do" without closing "Endon"`);
|
|
}
|
|
openOnDoLine = i;
|
|
}
|
|
|
|
result.push(line);
|
|
indentLevel = 1;
|
|
stack.push(ON_START);
|
|
continue;
|
|
}
|
|
|
|
// Handle "Endon"
|
|
if (startsWithKeyword(low, [ON_END])) {
|
|
indentLevel = Math.max(indentLevel - 1, 0);
|
|
result.push(INDENT.repeat(indentLevel) + line);
|
|
|
|
if (stack.includes(ON_START)) {
|
|
// remove everything from the top of the stack until the last ON_START
|
|
while (stack.length) {
|
|
const popped = stack.pop();
|
|
if (popped === ON_START) {
|
|
openOnDoLine = null;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
errors.push(`Line ${i + 1}: "Endon" without matching "On...Do"`);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// If inside main block:
|
|
if (stack.includes(ON_START)) {
|
|
if (startsWithKeyword(low, [IF_START])) {
|
|
ifCount++;
|
|
ifLines.push(i + 1);
|
|
result.push(INDENT.repeat(indentLevel) + line);
|
|
stack.push(IF_START);
|
|
indentLevel++;
|
|
continue;
|
|
}
|
|
|
|
if (startsWithKeyword(low, [ELSE])) {
|
|
indentLevel = Math.max(indentLevel - 1, 0);
|
|
result.push(INDENT.repeat(indentLevel) + line);
|
|
indentLevel++;
|
|
continue;
|
|
}
|
|
|
|
if (startsWithKeyword(low, [IF_END])) {
|
|
endifCount++;
|
|
indentLevel = Math.max(indentLevel - 1, 0);
|
|
result.push(INDENT.repeat(indentLevel) + line);
|
|
|
|
if (stack.length && stack[stack.length - 1] === IF_START) {
|
|
stack.pop();
|
|
ifLines.pop();
|
|
} else {
|
|
errors.push(`Line ${i + 1}: "Endif" without matching "If"`);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// any normal line inside On block
|
|
result.push(INDENT.repeat(indentLevel) + line);
|
|
continue;
|
|
}
|
|
|
|
// Outside any block
|
|
result.push(line);
|
|
}
|
|
|
|
// Final checks
|
|
if (openOnDoLine !== null) {
|
|
errors.push(`Line ${openOnDoLine + 1}: "On...Do" without closing "Endon"`);
|
|
}
|
|
|
|
if (ifCount > endifCount) {
|
|
const diff = ifCount - endifCount;
|
|
errors.push(`Missing ${diff} Endif(s)`);
|
|
if (ifLines.length > 0) {
|
|
errors.push(`Unclosed If block(s) starting at line(s): ${ifLines.join(", ")}`);
|
|
}
|
|
} else if (endifCount > ifCount) {
|
|
errors.push(`Found ${endifCount - ifCount} Endif(s) without matching If(s)`);
|
|
}
|
|
|
|
if (errors.length > 0) {
|
|
alert("Errors found:\n" + errors.join("\n"));
|
|
}
|
|
|
|
return result.join('\n');
|
|
}
|
|
//--------------------------------------------------------------------------------- end of formatting option
|
|
|
|
(function (mod) {
|
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
mod(require("codemirror"));
|
|
else if (typeof define == "function" && define.amd) // AMD
|
|
define(["codemirror"], mod);
|
|
else // Plain browser env
|
|
mod(CodeMirror);
|
|
})(function (CodeMirror) {
|
|
"use strict";
|
|
|
|
CodeMirror.defineMode('espeasy', function () {
|
|
var words = {};
|
|
function define(style, dict) {
|
|
for (var i = 0; i < dict.length; i++) {
|
|
words[dict[i]] = style;
|
|
}
|
|
};
|
|
var lCcommonCommands = commonCommands.map(name => name.toLowerCase());
|
|
commonCommands = commonCommands.concat(lCcommonCommands);
|
|
|
|
var lCcommonString2 = commonEvents.map(name => name.toLowerCase());
|
|
commonEvents = commonEvents.concat(lCcommonString2);
|
|
|
|
var lCcommonPlugins = commonPlugins.map(name => name.toLowerCase());
|
|
commonPlugins = commonPlugins.concat(lCcommonPlugins);
|
|
|
|
var lCcommonAtoms = commonAtoms.map(name => name.toLowerCase());
|
|
commonAtoms = commonAtoms.concat(lCcommonAtoms);
|
|
|
|
var lCcommonKeywords = commonKeywords.map(name => name.toLowerCase());
|
|
commonKeywords = commonKeywords.concat(lCcommonKeywords);
|
|
|
|
var lCcommonTag = commonTag.map(name => name.toLowerCase());
|
|
commonTag = commonTag.concat(lCcommonTag);
|
|
|
|
var lCcommonNumber = commonNumber.map(name => name.toLowerCase());
|
|
commonNumber = commonNumber.concat(lCcommonNumber);
|
|
|
|
var lCcommonMath = commonMath.map(name => name.toLowerCase());
|
|
commonMath = commonMath.concat(lCcommonMath);
|
|
|
|
var lCAnythingElse = AnythingElse.map(name => name.toLowerCase());
|
|
AnythingElse = AnythingElse.concat(lCAnythingElse);
|
|
|
|
var lCtaskSpecifics = taskSpecifics.map(name => name.toLowerCase());
|
|
taskSpecifics = taskSpecifics.concat(lCtaskSpecifics);
|
|
|
|
define('atom', commonAtoms);
|
|
define('keyword', commonKeywords);
|
|
define('builtin', commonCommands);
|
|
define('events', commonEvents);
|
|
define('def', commonPlugins);
|
|
define('tag', commonTag);
|
|
define('number', commonNumber);
|
|
define('bracket', commonMath);
|
|
define('warning', commonWarning);
|
|
define('hr', AnythingElse);
|
|
define('comment', taskSpecifics);
|
|
|
|
function tokenBase(stream, state) {
|
|
if (stream.eatSpace()) return null;
|
|
|
|
var sol = stream.sol();
|
|
var ch = stream.next();
|
|
|
|
if (/\d/.test(ch)) {
|
|
if (ch == "0") {
|
|
if (stream.next() === 'x') {
|
|
stream.eatWhile(/\w/);
|
|
return 'number';
|
|
}
|
|
else {
|
|
stream.eatWhile(/\d|\./);
|
|
return 'number';
|
|
}
|
|
}
|
|
else {
|
|
stream.eatWhile(/\d|\./);
|
|
if (!stream.match("d") && !stream.match("output")) {
|
|
if (stream.eol() || /\D/.test(stream.peek())) {
|
|
return 'number';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (/\w/.test(ch)) {
|
|
for (const element of EXTRAWORDS) {
|
|
let WinDB = element.substring(1);
|
|
if ((element.includes(":") || element.includes(",") || element.includes(".")) && stream.match(WinDB)) void (0)
|
|
}
|
|
}
|
|
//P022 addition
|
|
if (/\w/.test(ch)) {
|
|
stream.eatWhile(/[\w]/);
|
|
if (stream.match(".gpio") || stream.match(".pulse") || stream.match(".frq") || stream.match(".pwm")) {
|
|
return 'def';
|
|
}
|
|
}
|
|
|
|
if (ch === '\\') {
|
|
stream.next();
|
|
return null;
|
|
}
|
|
|
|
if (ch === '(' || ch === ')') {
|
|
return "bracket";
|
|
}
|
|
|
|
if (ch === '{' || ch === '}' || ch === ':') {
|
|
return "number";
|
|
}
|
|
|
|
if (ch == "/") {
|
|
if (/\//.test(stream.peek())) {
|
|
stream.skipToEnd();
|
|
return "comment";
|
|
}
|
|
else {
|
|
return 'operator';
|
|
}
|
|
}
|
|
|
|
if (ch == "'") {
|
|
stream.eatWhile(/[^']/);
|
|
if (stream.match("'")) return 'attribute';
|
|
}
|
|
|
|
if (ch === '+' || ch === '=' || ch === '<' || ch === '>' || ch === '-' || ch === ',' || ch === '*' || ch === '!') {
|
|
return 'operator';
|
|
}
|
|
|
|
if (ch == "%") {
|
|
if (/\d/.test(stream.next())) { return 'number'; }
|
|
else {
|
|
stream.eatWhile(/[^\s\%]/);
|
|
if (stream.match("%")) return 'hr';
|
|
}
|
|
}
|
|
|
|
if (ch == "[") {
|
|
stream.eatWhile(/[^\s\]]/);
|
|
if (stream.eat("]")) return 'hr';
|
|
}
|
|
|
|
stream.eatWhile(/\w/);
|
|
var cur = stream.current();
|
|
|
|
if (/\w/.test(ch)) {
|
|
if (stream.match("#")) {
|
|
stream.eatWhile(/[\w.#]/);
|
|
return 'events';
|
|
}
|
|
}
|
|
|
|
if (ch === '#') {
|
|
stream.eatWhile(/\w/);
|
|
return 'number';
|
|
}
|
|
return words.hasOwnProperty(cur) ? words[cur] : null;
|
|
}
|
|
|
|
function tokenize(stream, state) {
|
|
return (state.tokens[0] || tokenBase)(stream, state);
|
|
};
|
|
|
|
return {
|
|
startState: function () { return { tokens: [] }; },
|
|
token: function (stream, state) {
|
|
return tokenize(stream, state);
|
|
},
|
|
//electricInput: /^\s*(\bendon\b)/i, //extra
|
|
closeBrackets: "[]{}''\"\"``()",
|
|
lineComment: '//',
|
|
fold: "brace"
|
|
};
|
|
});
|
|
});
|
|
|
|
//------------------------closebrackets.js------------
|
|
|
|
(function (closeBrackets) {
|
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
closeBrackets(require("../../lib/codemirror"));
|
|
else if (typeof define == "function" && define.amd) // AMD
|
|
define(["../../lib/codemirror"], mod);
|
|
else // Plain browser env
|
|
closeBrackets(CodeMirror);
|
|
})(function (CodeMirror) {
|
|
var defaults = {
|
|
pairs: "()[]{}''\"\"",
|
|
closeBefore: ")]}'\":;>",
|
|
triples: "",
|
|
explode: "[]{}"
|
|
};
|
|
|
|
var Pos = CodeMirror.Pos;
|
|
|
|
CodeMirror.defineOption("autoCloseBrackets", false, function (cm, val, old) {
|
|
if (old && old != CodeMirror.Init) {
|
|
cm.removeKeyMap(keyMap);
|
|
cm.state.closeBrackets = null;
|
|
}
|
|
if (val) {
|
|
ensureBound(getOption(val, "pairs"))
|
|
cm.state.closeBrackets = val;
|
|
cm.addKeyMap(keyMap);
|
|
}
|
|
});
|
|
|
|
function getOption(conf, name) {
|
|
if (name == "pairs" && typeof conf == "string") return conf;
|
|
if (typeof conf == "object" && conf[name] != null) return conf[name];
|
|
return defaults[name];
|
|
}
|
|
|
|
var keyMap = { Backspace: handleBackspace, Enter: handleEnter };
|
|
function ensureBound(chars) {
|
|
for (var i = 0; i < chars.length; i++) {
|
|
var ch = chars.charAt(i), key = "'" + ch + "'"
|
|
if (!keyMap[key]) keyMap[key] = handler(ch)
|
|
}
|
|
}
|
|
ensureBound(defaults.pairs + "`")
|
|
|
|
function handler(ch) {
|
|
return function (cm) { return handleChar(cm, ch); };
|
|
}
|
|
|
|
function getConfig(cm) {
|
|
var deflt = cm.state.closeBrackets;
|
|
if (!deflt || deflt.override) return deflt;
|
|
var mode = cm.getModeAt(cm.getCursor());
|
|
return mode.closeBrackets || deflt;
|
|
}
|
|
|
|
function handleBackspace(cm) {
|
|
var conf = getConfig(cm);
|
|
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
|
|
var pairs = getOption(conf, "pairs");
|
|
var ranges = cm.listSelections();
|
|
for (var i = 0; i < ranges.length; i++) {
|
|
if (!ranges[i].empty()) return CodeMirror.Pass;
|
|
var around = charsAround(cm, ranges[i].head);
|
|
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
|
}
|
|
for (var i = ranges.length - 1; i >= 0; i--) {
|
|
var cur = ranges[i].head;
|
|
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
|
|
}
|
|
}
|
|
|
|
function handleEnter(cm) {
|
|
var conf = getConfig(cm);
|
|
var explode = conf && getOption(conf, "explode");
|
|
if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
|
|
var ranges = cm.listSelections();
|
|
for (var i = 0; i < ranges.length; i++) {
|
|
if (!ranges[i].empty()) return CodeMirror.Pass;
|
|
var around = charsAround(cm, ranges[i].head);
|
|
if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
|
}
|
|
cm.operation(function () {
|
|
var linesep = cm.lineSeparator() || "\n";
|
|
cm.replaceSelection(linesep + linesep, null);
|
|
moveSel(cm, -1)
|
|
ranges = cm.listSelections();
|
|
for (var i = 0; i < ranges.length; i++) {
|
|
var line = ranges[i].head.line;
|
|
cm.indentLine(line, null, true);
|
|
cm.indentLine(line + 1, null, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
function moveSel(cm, dir) {
|
|
var newRanges = [], ranges = cm.listSelections(), primary = 0
|
|
for (var i = 0; i < ranges.length; i++) {
|
|
var range = ranges[i]
|
|
if (range.head == cm.getCursor()) primary = i
|
|
var pos = range.head.ch || dir > 0 ? { line: range.head.line, ch: range.head.ch + dir } : { line: range.head.line - 1 }
|
|
newRanges.push({ anchor: pos, head: pos })
|
|
}
|
|
cm.setSelections(newRanges, primary)
|
|
}
|
|
|
|
function contractSelection(sel) {
|
|
var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
|
|
return {
|
|
anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
|
|
head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))
|
|
};
|
|
}
|
|
|
|
function handleChar(cm, ch) {
|
|
var conf = getConfig(cm);
|
|
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
|
|
var pairs = getOption(conf, "pairs");
|
|
var pos = pairs.indexOf(ch);
|
|
if (pos == -1) return CodeMirror.Pass;
|
|
|
|
var closeBefore = getOption(conf, "closeBefore");
|
|
|
|
var triples = getOption(conf, "triples");
|
|
|
|
var identical = pairs.charAt(pos + 1) == ch;
|
|
var ranges = cm.listSelections();
|
|
var opening = pos % 2 == 0;
|
|
|
|
var type;
|
|
for (var i = 0; i < ranges.length; i++) {
|
|
var range = ranges[i], cur = range.head, curType;
|
|
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
|
|
if (opening && !range.empty()) {
|
|
curType = "surround";
|
|
} else if ((identical || !opening) && next == ch) {
|
|
if (identical && stringStartsAfter(cm, cur))
|
|
curType = "both";
|
|
else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
|
|
curType = "skipThree";
|
|
else
|
|
curType = "skip";
|
|
} else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
|
|
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
|
|
if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
|
|
curType = "addFour";
|
|
} else if (identical) {
|
|
var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
|
|
if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
|
|
else return CodeMirror.Pass;
|
|
} else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
|
|
curType = "both";
|
|
} else {
|
|
return CodeMirror.Pass;
|
|
}
|
|
if (!type) type = curType;
|
|
else if (type != curType) return CodeMirror.Pass;
|
|
}
|
|
|
|
var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
|
|
var right = pos % 2 ? ch : pairs.charAt(pos + 1);
|
|
cm.operation(function () {
|
|
if (type == "skip") {
|
|
moveSel(cm, 1)
|
|
} else if (type == "skipThree") {
|
|
moveSel(cm, 3)
|
|
} else if (type == "surround") {
|
|
var sels = cm.getSelections();
|
|
for (var i = 0; i < sels.length; i++)
|
|
sels[i] = left + sels[i] + right;
|
|
cm.replaceSelections(sels, "around");
|
|
sels = cm.listSelections().slice();
|
|
for (var i = 0; i < sels.length; i++)
|
|
sels[i] = contractSelection(sels[i]);
|
|
cm.setSelections(sels);
|
|
} else if (type == "both") {
|
|
cm.replaceSelection(left + right, null);
|
|
cm.triggerElectric(left + right);
|
|
moveSel(cm, -1)
|
|
} else if (type == "addFour") {
|
|
cm.replaceSelection(left + left + left + left, "before");
|
|
moveSel(cm, 1)
|
|
}
|
|
});
|
|
}
|
|
|
|
function charsAround(cm, pos) {
|
|
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
|
|
Pos(pos.line, pos.ch + 1));
|
|
return str.length == 2 ? str : null;
|
|
}
|
|
|
|
function stringStartsAfter(cm, pos) {
|
|
var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
|
|
return /\bstring/.test(token.type) && token.start == pos.ch &&
|
|
(pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
|
|
}
|
|
});
|
|
|