mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-10 12:32:20 +02:00
2172 lines
49 KiB
Beef
2172 lines
49 KiB
Beef
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.IO;
|
|
using System.Diagnostics;
|
|
using IDE.ui;
|
|
using Beefy;
|
|
using Beefy.widgets;
|
|
using IDE.Debugger;
|
|
using Beefy.theme.dark;
|
|
using System.Threading;
|
|
using System.Security.Cryptography;
|
|
|
|
namespace IDE
|
|
{
|
|
class ScriptManager
|
|
{
|
|
public static ScriptManager sActiveManager;
|
|
|
|
class Target
|
|
{
|
|
public class Cmd
|
|
{
|
|
public MethodInfo mMethodInfo;
|
|
public Object mTargetObject;
|
|
}
|
|
|
|
public Dictionary<String, Target> mTargets = new .() ~ DeleteDictionyAndKeysAndItems!(_);
|
|
public Dictionary<String, Cmd> mCmds = new .() ~ DeleteDictionyAndKeysAndItems!(_);
|
|
}
|
|
|
|
public enum CmdFlags
|
|
{
|
|
None,
|
|
NoLines
|
|
}
|
|
|
|
public class QueuedCmd
|
|
{
|
|
public String mCondition ~ delete _;
|
|
|
|
public CmdFlags mFlags;
|
|
public bool mHandled = true;
|
|
public String mCmd ~ delete _;
|
|
public String mSrcFile ~ delete _;
|
|
public int mLineNum = -1;
|
|
|
|
public int mIntParam;
|
|
public int mExecIdx;
|
|
public bool mNoWait;
|
|
public Stopwatch mStopWatch ~ delete _;
|
|
}
|
|
|
|
ScriptHelper mScriptHelper = new ScriptHelper(this) ~ delete _;
|
|
Target mRoot = new Target() ~ delete _;
|
|
Dictionary<String, Variant> mVars = new .() ~
|
|
{
|
|
for (var kv in _)
|
|
{
|
|
delete kv.key;
|
|
kv.value.Dispose();
|
|
}
|
|
delete _;
|
|
};
|
|
List<QueuedCmd> mCmdList = new .() ~ DeleteContainerAndItems!(_);
|
|
public bool mFailed;
|
|
public bool mCancelled;
|
|
public QueuedCmd mCurCmd;
|
|
public Stopwatch mTimeoutStopwatch ~ delete _;
|
|
public int mTimeoutMS;
|
|
public String mExpectingError ~ delete _;
|
|
public bool mHadExpectingError;
|
|
public int mDoneTicks;
|
|
public bool mAllowCompiling;
|
|
public bool mSoftFail;
|
|
public Verbosity mVerbosity = .Quiet;
|
|
public String mProjectName ~ delete _;
|
|
|
|
public bool Failed
|
|
{
|
|
get
|
|
{
|
|
return mFailed;
|
|
}
|
|
}
|
|
|
|
public bool HasQueuedCommands
|
|
{
|
|
get
|
|
{
|
|
return !mCmdList.IsEmpty;
|
|
}
|
|
}
|
|
|
|
public this()
|
|
{
|
|
AddTarget(mScriptHelper);
|
|
|
|
//Exec("OutputLine(\"Hey bro!\", 2)");
|
|
}
|
|
|
|
public void MarkNotHandled()
|
|
{
|
|
if (mCurCmd != null)
|
|
mCurCmd.mHandled = false;
|
|
}
|
|
|
|
public void AddTarget(Object targetObject)
|
|
{
|
|
var targetType = targetObject.GetType();
|
|
for (var methodInfo in targetType.GetMethods(.Instance | .Public | .NonPublic))
|
|
{
|
|
var methodName = methodInfo.Name;
|
|
if (methodName.StartsWith("Cmd_"))
|
|
methodName = .(methodName, 4);
|
|
|
|
Target curTarget = mRoot;
|
|
|
|
while (true)
|
|
{
|
|
int splitPos = methodName.IndexOf("__");
|
|
if (splitPos != -1)
|
|
{
|
|
StringView cmdPart = .(methodName, 0, splitPos);
|
|
|
|
String* keyPtr;
|
|
Target* targetPtr;
|
|
if (curTarget.mTargets.TryAdd(scope String(cmdPart), out keyPtr, out targetPtr))
|
|
{
|
|
*keyPtr = new String(cmdPart);
|
|
*targetPtr = new Target();
|
|
}
|
|
curTarget = *targetPtr;
|
|
|
|
methodName.RemoveFromStart(splitPos + 2);
|
|
}
|
|
else
|
|
{
|
|
String* keyPtr;
|
|
Target.Cmd* cmdPtr;
|
|
if (curTarget.mCmds.TryAdd(scope String(methodName), out keyPtr, out cmdPtr))
|
|
{
|
|
*keyPtr = new String(methodName);
|
|
*cmdPtr = new .();
|
|
|
|
let cmd = *cmdPtr;
|
|
cmd.mMethodInfo = methodInfo;
|
|
cmd.mTargetObject = targetObject;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Fail(StringView err)
|
|
{
|
|
if (mFailed)
|
|
return;
|
|
|
|
mFailed = true;
|
|
var errStr = scope String(err);
|
|
if (mCurCmd != null)
|
|
{
|
|
if (mCurCmd.mFlags.HasFlag(.NoLines))
|
|
errStr.AppendF(" in {}\n\t{}", mCurCmd.mSrcFile, mCurCmd.mCmd);
|
|
else
|
|
errStr.AppendF(" at line {} in {}\n\t{}", mCurCmd.mLineNum + 1, mCurCmd.mSrcFile, mCurCmd.mCmd);
|
|
}
|
|
|
|
if (mSoftFail)
|
|
gApp.OutputErrorLine(errStr);
|
|
else
|
|
gApp.Fail(errStr);
|
|
|
|
//TODO:
|
|
//gApp.mRunningTestScript = false;
|
|
}
|
|
|
|
public bool IsErrorExpected(StringView err)
|
|
{
|
|
return (mExpectingError != null) && (err.Contains(mExpectingError));
|
|
}
|
|
|
|
public void Fail(StringView fmt, params Object[] args)
|
|
{
|
|
Fail(scope String()..AppendF(fmt, params args));
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
DeleteAndClearItems!(mCmdList);
|
|
mFailed = false;
|
|
mCurCmd = null;
|
|
}
|
|
|
|
public void QueueCommands(StreamReader streamReader, StringView filePath, CmdFlags flags)
|
|
{
|
|
int lineNum = 0;
|
|
|
|
for (var lineResult in streamReader.Lines)
|
|
Line:
|
|
{
|
|
switch (lineResult)
|
|
{
|
|
case .Ok(var line):
|
|
line.Trim();
|
|
if ((!line.IsEmpty) && (!line.StartsWith("#")))
|
|
{
|
|
QueuedCmd queuedCmd = new .();
|
|
queuedCmd.mFlags = flags;
|
|
queuedCmd.mSrcFile = new String(filePath);
|
|
queuedCmd.mLineNum = lineNum;
|
|
|
|
if (line.StartsWith("nowait "))
|
|
{
|
|
queuedCmd.mNoWait = true;
|
|
line.RemoveFromStart("no wait".Length);
|
|
}
|
|
|
|
if (line.StartsWith("if "))
|
|
{
|
|
int parenCount = 0;
|
|
int strPos = 2;
|
|
while (strPos < line.Length)
|
|
{
|
|
char8 c = line[strPos];
|
|
if (c == '(')
|
|
{
|
|
parenCount++;
|
|
}
|
|
else if (c == ')')
|
|
{
|
|
parenCount--;
|
|
if (parenCount == 0)
|
|
break;
|
|
}
|
|
strPos++;
|
|
}
|
|
|
|
queuedCmd.mCondition = new String(line, 4, strPos - 4);
|
|
line.RemoveFromStart(strPos + 1);
|
|
line.Trim();
|
|
}
|
|
|
|
queuedCmd.mCmd = new String(line);
|
|
mCmdList.Add(queuedCmd);
|
|
}
|
|
lineNum++;
|
|
case .Err:
|
|
Fail("Failed reading from file '{0}'", filePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void QueueCommands(StringView cmds, StringView filePath, CmdFlags flags)
|
|
{
|
|
StringStream strStream = scope .(cmds, .Reference);
|
|
StreamReader reader = scope .(strStream);
|
|
QueueCommands(reader, filePath, flags);
|
|
}
|
|
|
|
public void QueueCommandFile(StringView filePath)
|
|
{
|
|
let streamReader = scope StreamReader();
|
|
if (streamReader.Open(filePath) case .Err)
|
|
{
|
|
Fail("Unable to open command file '{0}'", filePath);
|
|
return;
|
|
}
|
|
|
|
QueueCommands(streamReader, filePath, .None);
|
|
}
|
|
|
|
public void SetTimeoutMS(int timeoutMS)
|
|
{
|
|
if (mTimeoutStopwatch == null)
|
|
{
|
|
mTimeoutStopwatch = new .();
|
|
mTimeoutStopwatch.Start();
|
|
}
|
|
mTimeoutMS = timeoutMS;
|
|
}
|
|
|
|
public void Exec(StringView cmd)
|
|
{
|
|
var cmd;
|
|
cmd.Trim();
|
|
|
|
if ((cmd.StartsWith("#")) || (cmd.IsEmpty))
|
|
return;
|
|
|
|
if (cmd.StartsWith("%exec "))
|
|
{
|
|
mScriptHelper.ExecuteRaw(scope String(cmd, "%exec ".Length));
|
|
return;
|
|
}
|
|
|
|
if (cmd.StartsWith("%targetComplete "))
|
|
{
|
|
let projectName = cmd.Substring("%targetComplete ".Length);
|
|
|
|
if (gApp.mExecutionQueue.IsEmpty)
|
|
return;
|
|
|
|
bool matched = false;
|
|
if (var targetCompleteCmd = gApp.mExecutionQueue[0] as IDEApp.TargetCompletedCmd)
|
|
{
|
|
if (targetCompleteCmd.mProject.mProjectName == projectName)
|
|
{
|
|
targetCompleteCmd.mIsReady = true;
|
|
matched = true;
|
|
}
|
|
}
|
|
|
|
if (!matched)
|
|
{
|
|
mCurCmd.mHandled = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (mCurCmd.mExecIdx == 0)
|
|
{
|
|
if (mVerbosity >= .Normal)
|
|
{
|
|
gApp.OutputLine("Executing Command: {}", cmd);
|
|
if (mVerbosity >= .Detailed)
|
|
{
|
|
mCurCmd.mStopWatch = new .(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
StringView varName = .();
|
|
|
|
/*int eqPos = cmdLineView.IndexOf('=');
|
|
if (eqPos != -1)
|
|
{
|
|
varName = StringView(cmdLineView, eqPos);
|
|
varName.Trim();
|
|
cmdLineView.RemoveFromStart(eqPos + 1);
|
|
cmdLineView.Clear();
|
|
}*/
|
|
|
|
StringView methodName;
|
|
List<Object> args = scope .();
|
|
|
|
int parenPos = cmd.IndexOf('(');
|
|
if (parenPos != -1)
|
|
{
|
|
methodName = cmd.Substring(0, parenPos);
|
|
methodName.Trim();
|
|
|
|
int endParenPos = cmd.LastIndexOf(')');
|
|
if (endParenPos == -1)
|
|
{
|
|
Fail("Missing argument end ')'");
|
|
return;
|
|
}
|
|
|
|
var postStr = StringView(cmd, endParenPos + 1);
|
|
postStr.Trim();
|
|
if ((!postStr.IsEmpty) && (!postStr.StartsWith("#")))
|
|
{
|
|
Fail("Invalid string following command");
|
|
return;
|
|
}
|
|
|
|
Workspace.Options workspaceOptions = null;
|
|
Project project = null;
|
|
Project.Options projectOptions = null;
|
|
|
|
bool inQuotes = false;
|
|
int startIdx = parenPos;
|
|
for (int idx = parenPos; idx <= endParenPos; idx++)
|
|
{
|
|
char8 c = cmd[idx];
|
|
if (c == '\\')
|
|
{
|
|
// Skip past slashed strings
|
|
idx++;
|
|
continue;
|
|
}
|
|
else if (c == '"')
|
|
{
|
|
inQuotes ^= true;
|
|
}
|
|
else if (((c == ',') || (c == ')')) && (!inQuotes))
|
|
{
|
|
StringView argView = cmd.Substring(startIdx + 1, idx - startIdx - 1);
|
|
|
|
argView.Trim();
|
|
if (argView.IsEmpty)
|
|
continue;
|
|
|
|
if (argView.StartsWith("\""))
|
|
{
|
|
var str = scope:: String();
|
|
if (argView.UnQuoteString(str) case .Err)
|
|
Fail("Failed to unquote string");
|
|
|
|
if (str.Contains('$'))
|
|
{
|
|
if (workspaceOptions == null)
|
|
{
|
|
workspaceOptions = gApp.GetCurWorkspaceOptions();
|
|
if (mProjectName != null)
|
|
{
|
|
project = gApp.mWorkspace.FindProject(mProjectName);
|
|
if (project != null)
|
|
projectOptions = gApp.GetCurProjectOptions(project);
|
|
}
|
|
}
|
|
|
|
String newStr = scope:: .();
|
|
String err = scope .();
|
|
if (!gApp.DoResolveConfigString(workspaceOptions, project, projectOptions, str, err, newStr))
|
|
{
|
|
Fail(scope String()..AppendF("Unknown macro string '{}' in '{}'", err, str));
|
|
}
|
|
str = newStr;
|
|
}
|
|
|
|
args.Add(str);
|
|
}
|
|
else if (argView.EndsWith('f'))
|
|
{
|
|
switch (float.Parse(argView))
|
|
{
|
|
case .Ok(let val):
|
|
args.Add(scope:: box val);
|
|
case .Err:
|
|
Fail("Failed to parse float");
|
|
return;
|
|
}
|
|
}
|
|
else if (argView.Contains('.'))
|
|
{
|
|
switch (double.Parse(argView))
|
|
{
|
|
case .Ok(let val):
|
|
args.Add(scope:: box val);
|
|
case .Err:
|
|
Fail("Failed to parse double");
|
|
return;
|
|
}
|
|
}
|
|
else // Integer
|
|
{
|
|
switch (int.Parse(argView))
|
|
{
|
|
case .Ok(let val):
|
|
args.Add(scope:: box val);
|
|
case .Err:
|
|
Fail("Failed to parse int");
|
|
return;
|
|
}
|
|
}
|
|
|
|
startIdx = idx;
|
|
}
|
|
}
|
|
|
|
/*for (var argView in cmdLineView.Substring(parenPos + 1, endParenPos - parenPos - 1).Split(','))
|
|
{
|
|
HandleArg(argView);
|
|
}*/
|
|
}
|
|
else
|
|
{
|
|
methodName = cmd;
|
|
}
|
|
|
|
if (mFailed)
|
|
return;
|
|
|
|
Target curTarget = mRoot;
|
|
for (var cmdPart in methodName.Split('.'))
|
|
{
|
|
if (@cmdPart.HasMore)
|
|
{
|
|
if (!curTarget.mTargets.TryGetValue(scope String(cmdPart), out curTarget))
|
|
{
|
|
Fail("Unable to find target '{0}'", cmdPart);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Target.Cmd cmd;
|
|
if (!curTarget.mCmds.TryGetValue(scope String(cmdPart), out cmd))
|
|
{
|
|
Fail("Unable to find command '{0}'", cmdPart);
|
|
return;
|
|
}
|
|
Object[] argsArr = scope Object[args.Count];
|
|
args.CopyTo(argsArr);
|
|
switch (cmd.mMethodInfo.Invoke(cmd.mTargetObject, params argsArr))
|
|
{
|
|
case .Err:
|
|
Fail("Failed to invoke command");
|
|
return;
|
|
case .Ok(var result):
|
|
if (!varName.IsEmpty)
|
|
{
|
|
String* keyPtr;
|
|
Variant* valuePtr;
|
|
if (mVars.TryAdd(scope String(varName), out keyPtr, out valuePtr))
|
|
*keyPtr = new String(varName);
|
|
else
|
|
valuePtr.Dispose();
|
|
*valuePtr = result;
|
|
}
|
|
else
|
|
result.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool CheckCondition(StringView condition)
|
|
{
|
|
StringView curStr = .();
|
|
BumpAllocator tempAlloc = scope .();
|
|
|
|
StringView NextToken()
|
|
{
|
|
if (curStr.IsEmpty)
|
|
return .();
|
|
|
|
char8* idStart = null;
|
|
while (true)
|
|
{
|
|
char8 c = 0;
|
|
if (!curStr.IsEmpty)
|
|
{
|
|
c = curStr[0];
|
|
curStr.RemoveFromStart(1);
|
|
}
|
|
if (c == ' ')
|
|
continue;
|
|
char8 nextC = 0;
|
|
if (!curStr.IsEmpty)
|
|
nextC = curStr[0];
|
|
|
|
if (idStart != null)
|
|
{
|
|
if (*idStart == '"')
|
|
{
|
|
if (c == '"')
|
|
return .(idStart, curStr.Ptr - idStart);
|
|
}
|
|
else if (!nextC.IsLetterOrDigit)
|
|
{
|
|
return .(idStart, curStr.Ptr - idStart);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (c == 0)
|
|
return .();
|
|
if ((c == '!') && (nextC == '='))
|
|
{
|
|
curStr.RemoveFromStart(1);
|
|
return StringView(curStr.Ptr - 2, 2);
|
|
}
|
|
if ((c == '=') && (nextC == '='))
|
|
{
|
|
curStr.RemoveFromStart(1);
|
|
return StringView(curStr.Ptr - 2, 2);
|
|
}
|
|
idStart = curStr.Ptr - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Object Evaluate(StringView token)
|
|
{
|
|
if (token.StartsWith("\""))
|
|
return new:tempAlloc String(token, 1, token.Length - 2);
|
|
if (token == "platform")
|
|
return gApp.mPlatformName;
|
|
else if (token == "config")
|
|
return gApp.mConfigName;
|
|
else if (token == "optlevel")
|
|
{
|
|
var workspaceOptions = gApp.GetCurWorkspaceOptions();
|
|
if (workspaceOptions != null)
|
|
{
|
|
String str = new:tempAlloc .();
|
|
workspaceOptions.mBfOptimizationLevel.ToString(str);
|
|
return str;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
bool Compare(Object obj, Object obj2)
|
|
{
|
|
if (obj is String)
|
|
{
|
|
return (String)obj == (String)obj2;
|
|
}
|
|
return obj == obj2;
|
|
}
|
|
|
|
Object Evaluate()
|
|
{
|
|
StringView tok = NextToken();
|
|
if (tok.IsEmpty)
|
|
return null;
|
|
Object val = Evaluate(tok);
|
|
if (val == null)
|
|
return val;
|
|
|
|
while (true)
|
|
{
|
|
StringView op = NextToken();
|
|
if (op.IsEmpty)
|
|
return val;
|
|
|
|
StringView rhsToken = NextToken();
|
|
Object rhs = Evaluate(rhsToken);
|
|
if (rhs == null)
|
|
return val;
|
|
|
|
if (op == "!=")
|
|
val = new:tempAlloc box !Compare(val, rhs);
|
|
else if (op == "==")
|
|
val = new:tempAlloc box Compare(val, rhs);
|
|
}
|
|
}
|
|
|
|
curStr = condition;
|
|
Object result = Evaluate();
|
|
if (result is bool)
|
|
{
|
|
bool success = (bool)result;
|
|
return success;
|
|
}
|
|
else
|
|
{
|
|
Fail("Invalid result from expression");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void Cancel()
|
|
{
|
|
mCancelled = true;
|
|
ClearAndDeleteItems(mCmdList);
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
if (mFailed)
|
|
return;
|
|
|
|
if ((mTimeoutMS > 0) && (gApp.mRunningTestScript))
|
|
{
|
|
if (mTimeoutStopwatch.ElapsedMilliseconds >= mTimeoutMS)
|
|
Fail("Script has timed out: {:0.00}s", mTimeoutStopwatch.ElapsedMilliseconds / 1000.0f);
|
|
}
|
|
|
|
ScriptManager.sActiveManager = this;
|
|
while ((!mCmdList.IsEmpty) && (!mFailed))
|
|
{
|
|
mCurCmd = mCmdList[0];
|
|
mCurCmd.mHandled = true;
|
|
if (!mCurCmd.mNoWait)
|
|
{
|
|
if (!mScriptHelper.IsPaused())
|
|
break;
|
|
// Only do a wait for the initial execution
|
|
// This is required for things like AssertEvalEquals that will be handled internally by repeated
|
|
// calls where 'mHandled = false' is set
|
|
mCurCmd.mNoWait = true;
|
|
}
|
|
|
|
bool doExec = true;
|
|
if (mCurCmd.mCondition != null)
|
|
doExec = CheckCondition(mCurCmd.mCondition);
|
|
if (doExec)
|
|
{
|
|
Exec(mCurCmd.mCmd);
|
|
mCurCmd?.mExecIdx++;
|
|
}
|
|
|
|
if (mCmdList.IsEmpty)
|
|
break;
|
|
|
|
if (!mCurCmd.mHandled)
|
|
break; // Try again next update
|
|
|
|
if (mCurCmd.mStopWatch != null)
|
|
{
|
|
mCurCmd.mStopWatch.Stop();
|
|
if (mCurCmd.mStopWatch.ElapsedMilliseconds > 10)
|
|
gApp.OutputLine("Command Time: {:0.00}s", mCurCmd.mStopWatch.ElapsedMilliseconds / 1000.0f);
|
|
}
|
|
|
|
mCmdList.RemoveAt(0);
|
|
delete mCurCmd;
|
|
mCurCmd = null;
|
|
}
|
|
ScriptManager.sActiveManager = null;
|
|
}
|
|
}
|
|
|
|
class ScriptHelper
|
|
{
|
|
public EditWidgetContent.LineAndColumn mMarkedPos;
|
|
public ScriptManager mScriptManager;
|
|
|
|
public this(ScriptManager scriptManager)
|
|
{
|
|
mScriptManager = scriptManager;
|
|
}
|
|
|
|
void FixFilePath(String filePath, ProjectFolder folder)
|
|
{
|
|
for (var entry in folder.mChildItems)
|
|
{
|
|
if (var projectSource = entry as ProjectSource)
|
|
{
|
|
var fullPath = scope String();
|
|
projectSource.GetFullImportPath(fullPath);
|
|
if (fullPath.EndsWith(filePath, .OrdinalIgnoreCase))
|
|
{
|
|
if (filePath.Length != fullPath.Length)
|
|
{
|
|
let prevC = fullPath[fullPath.Length - filePath.Length - 1];
|
|
if ((prevC != '\\') && (prevC != '/'))
|
|
continue; // Not a full path match
|
|
}
|
|
|
|
filePath.Set(fullPath); // Matched!
|
|
}
|
|
}
|
|
else if (var childFolder = entry as ProjectFolder)
|
|
{
|
|
FixFilePath(filePath, childFolder);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FixSrcPath(String fileName, String outFilePath)
|
|
{
|
|
outFilePath.Append(fileName);
|
|
IDEUtils.FixFilePath(outFilePath);
|
|
|
|
if (File.Exists(outFilePath))
|
|
return;
|
|
|
|
if (!File.Exists(outFilePath))
|
|
{
|
|
for (var project in gApp.mWorkspace.mProjects)
|
|
{
|
|
FixFilePath(outFilePath, project.mRootFolder);
|
|
}
|
|
}
|
|
|
|
if (!File.Exists(outFilePath))
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Unable to locate project file '{0}'", outFilePath);
|
|
}
|
|
}
|
|
|
|
void FixFilePath(String fileName, String outFilePath)
|
|
{
|
|
outFilePath.Append(fileName);
|
|
if (File.Exists(outFilePath))
|
|
return;
|
|
|
|
outFilePath.Clear();
|
|
Path.GetAbsolutePath(fileName, gApp.mInstallDir, outFilePath);
|
|
|
|
if (!File.Exists(outFilePath))
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Unable to locate file '{0}'", outFilePath);
|
|
}
|
|
}
|
|
|
|
SourceViewPanel GetActiveSourceViewPanel()
|
|
{
|
|
var sourceViewPanel = gApp.GetActiveSourceViewPanel();
|
|
if (sourceViewPanel == null)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("No active source view panel");
|
|
return null;
|
|
}
|
|
sourceViewPanel.EnsureReady();
|
|
return sourceViewPanel;
|
|
}
|
|
|
|
TextPanel GetActiveTextPanel()
|
|
{
|
|
var textPanel = gApp.GetActivePanel() as TextPanel;
|
|
if (textPanel == null)
|
|
ScriptManager.sActiveManager.Fail("No active text panel");
|
|
return textPanel;
|
|
}
|
|
|
|
public bool Evaluate(String evalStr, String outVal, DebugManager.EvalExpressionFlags expressionFlags = .AllowCalls | .AllowSideEffects)
|
|
{
|
|
var curCmd = ScriptManager.sActiveManager.mCurCmd;
|
|
|
|
if (curCmd.mIntParam == 1) // Pending
|
|
{
|
|
gApp.mDebugger.EvaluateContinue(outVal);
|
|
if (outVal.StartsWith("!Not paused"))
|
|
{
|
|
curCmd.mHandled = false;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gApp.mDebugger.Evaluate(evalStr, outVal, -1, (int)-1, expressionFlags);
|
|
}
|
|
|
|
if (outVal.StartsWith("!pending"))
|
|
{
|
|
curCmd.mIntParam = 1;
|
|
curCmd.mHandled = false;
|
|
return false;
|
|
}
|
|
|
|
if (outVal.StartsWith("!"))
|
|
{
|
|
int tabPos = outVal.LastIndexOf('\t');
|
|
if (tabPos != -1)
|
|
{
|
|
outVal.Remove(0, tabPos + 1);
|
|
outVal.Insert(0, "ERROR:'");
|
|
outVal.Append("'");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (expressionFlags.HasFlag(.MemoryWatch))
|
|
return true;
|
|
|
|
int splitPos = outVal.IndexOf('\n');
|
|
if (splitPos != -1)
|
|
outVal.RemoveToEnd(splitPos);
|
|
|
|
//int tabPos = outVal.IndexOf('\t');
|
|
//splitPos = (int)Math.Min((uint)tabPos, (uint)splitPos);
|
|
|
|
return true;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void OutputLine(Object obj)
|
|
{
|
|
gApp.OutputLine("SCRIPT: {0}", obj);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void Assert(bool val)
|
|
{
|
|
if (!val)
|
|
{
|
|
gApp.OutputLine("SCRIPT ASSERT FAILED");
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void Sleep(int length)
|
|
{
|
|
int wantTicks = length * gApp.RefreshRate / 1000;
|
|
|
|
var curCmd = ScriptManager.sActiveManager.mCurCmd;
|
|
if ((++curCmd.mIntParam <= wantTicks) || (length < 0)) // Negative is forever
|
|
curCmd.mHandled = false;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void SleepTicks(int length)
|
|
{
|
|
int wantTicks = length;
|
|
var curCmd = ScriptManager.sActiveManager.mCurCmd;
|
|
if ((++curCmd.mIntParam <= wantTicks) || (length < 0)) // Negative is forever
|
|
curCmd.mHandled = false;
|
|
}
|
|
|
|
bool mIsFirstBreak = true;
|
|
|
|
public bool IsPaused()
|
|
{
|
|
if (gApp.mLastActiveSourceViewPanel != null)
|
|
{
|
|
var sourceViewPanel = gApp.mLastActiveSourceViewPanel;
|
|
if (sourceViewPanel.HasFocus())
|
|
{
|
|
if (sourceViewPanel.[Friend]mOldVerLoadExecutionInstance != null)
|
|
return false;
|
|
if (!sourceViewPanel.mDeferredResolveResults.IsEmpty)
|
|
return false;
|
|
|
|
if (sourceViewPanel.[Friend]mWantsFastClassify)
|
|
return false;
|
|
if (sourceViewPanel.[Friend]mWantsFullClassify)
|
|
return false;
|
|
if (sourceViewPanel.[Friend]mWantsFullRefresh)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ((gApp.mBfResolveCompiler != null) && (gApp.mBfResolveCompiler.IsPerformingBackgroundOperation()))
|
|
return false;
|
|
if (gApp.[Friend]mDeferredOpen != .None)
|
|
return false;
|
|
|
|
if ((gApp.mExecutionPaused) && (gApp.mDebugger.IsPaused()))
|
|
{
|
|
if (gApp.mWantsRehupCallstack)
|
|
return false;
|
|
}
|
|
if (gApp.mWantsClean || gApp.mWantsBeefClean)
|
|
return false;
|
|
|
|
if (gApp.IsCompiling)
|
|
{
|
|
if (!ScriptManager.sActiveManager.mAllowCompiling)
|
|
return false;
|
|
}
|
|
|
|
if (!gApp.[Friend]mExecutionInstances.IsEmpty)
|
|
return false;
|
|
|
|
if (gApp.mDebugger == null)
|
|
return true;
|
|
|
|
if ((!gApp.AreTestsRunning()) && (!gApp.mDebugger.HasPendingDebugLoads()) &&
|
|
((gApp.mExecutionPaused) || (!gApp.mDebugger.mIsRunning)))
|
|
{
|
|
var runState = gApp.mDebugger.GetRunState();
|
|
if (runState == .Terminating)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (runState == .SearchingSymSrv)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (runState == .DebugEval)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (runState == .Running_ToTempBreakpoint)
|
|
return false;
|
|
|
|
Debug.Assert((runState == .NotStarted) || (runState == .Paused) || (runState == .Running_ToTempBreakpoint) ||
|
|
(runState == .Exception) || (runState == .Breakpoint) || (runState == .Terminated));
|
|
/*if (runState == .Paused)
|
|
{
|
|
NOP!();
|
|
}
|
|
else if ((runState == .Paused) || (runState == .Exception) || (runState == .Breakpoint))*/
|
|
{
|
|
if ((runState != .NotStarted) && (mIsFirstBreak))
|
|
{
|
|
//Debug.Assert((runState == .Breakpoint) || (gApp.IsCrashDump));
|
|
mIsFirstBreak = false;
|
|
}
|
|
|
|
//TEMPORARY TEST:
|
|
//Debug.Assert(runState == .Breakpoint);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void WaitForPaused()
|
|
{
|
|
var curCmd = ScriptManager.sActiveManager.mCurCmd;
|
|
curCmd.mHandled = IsPaused();
|
|
}
|
|
|
|
[IDECommand]
|
|
public void WaitForResolve()
|
|
{
|
|
var curCmd = ScriptManager.sActiveManager.mCurCmd;
|
|
curCmd.mHandled = IsPaused() && (!gApp.mBfResolveCompiler.IsPerformingBackgroundOperation());
|
|
}
|
|
|
|
[IDECommand]
|
|
public void OpenCrashDump(String fileName)
|
|
{
|
|
String filePath = scope String();
|
|
FixFilePath(fileName, filePath);
|
|
gApp.OpenCrashDump(filePath);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void SetSymSrvOptions(String symCacheDir, String symSrvStr, String flagsStr)
|
|
{
|
|
switch (Enum.Parse<DebugManager.SymSrvFlags>(flagsStr))
|
|
{
|
|
case .Ok(let flags):
|
|
gApp.mDebugger.SetSymSrvOptions(symCacheDir, symSrvStr, flags);
|
|
case .Err:
|
|
ScriptManager.sActiveManager.Fail("Failed to parse flags");
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void ShowFile(String fileName)
|
|
{
|
|
String filePath = scope String();
|
|
FixSrcPath(fileName, filePath);
|
|
|
|
gApp.ShowSourceFile(filePath);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void DelTree(String dirPath)
|
|
{
|
|
if (Utils.DelTree(dirPath) case .Err)
|
|
{
|
|
ScriptManager.sActiveManager.Fail(scope String()..AppendF("Failed to deltree '{}'", dirPath));
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void CreateFile(String path, String text)
|
|
{
|
|
let fileStream = scope FileStream();
|
|
if (fileStream.Create(path) case .Err)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Failed to create file '{}'", path);
|
|
return;
|
|
}
|
|
fileStream.Write(text);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void RenameFile(String origPath, String newPath)
|
|
{
|
|
if (File.Move(origPath, newPath) case .Err)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Failed to move file '{}' to '{}'", origPath, newPath);
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void DeleteFile(String path)
|
|
{
|
|
if (File.Delete(path) case .Err)
|
|
{
|
|
mScriptManager.Fail("Failed to delete file '{}'", path);
|
|
}
|
|
}
|
|
|
|
/*[IDECommand]
|
|
public void Copy(String srcPath, String destPath)
|
|
{
|
|
|
|
}*/
|
|
|
|
[IDECommand]
|
|
public void CopyFilesIfNewer(String srcPath, String destPath)
|
|
{
|
|
int copyCount = 0;
|
|
int foundCount = 0;
|
|
|
|
void Do(String srcPath, String destPath)
|
|
{
|
|
bool checkedDestDir = false;
|
|
|
|
for (var entry in Directory.Enumerate(srcPath, .Directories | .Files))
|
|
{
|
|
foundCount++;
|
|
|
|
if (mScriptManager.mFailed)
|
|
return;
|
|
|
|
String srcFilePath = scope .();
|
|
entry.GetFilePath(srcFilePath);
|
|
|
|
String srcFileName = scope .();
|
|
entry.GetFileName(srcFileName);
|
|
|
|
String destFilePath = scope .();
|
|
Path.GetAbsolutePath(srcFileName, destPath, destFilePath);
|
|
|
|
if (entry.IsDirectory)
|
|
{
|
|
Do(srcFilePath, destFilePath);
|
|
continue;
|
|
}
|
|
|
|
DateTime srcDate;
|
|
if (!(File.GetLastWriteTime(srcFilePath) case .Ok(out srcDate)))
|
|
continue;
|
|
|
|
bool wantCopy = true;
|
|
if (File.GetLastWriteTime(destFilePath) case .Ok(let destDate))
|
|
{
|
|
wantCopy = srcDate > destDate;
|
|
}
|
|
|
|
if (!wantCopy)
|
|
continue;
|
|
|
|
if (!checkedDestDir)
|
|
{
|
|
if (Directory.CreateDirectory(destPath) case .Err)
|
|
{
|
|
mScriptManager.Fail("Failed to create directory '{}'", destPath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (File.Copy(srcFilePath, destFilePath) case .Err)
|
|
{
|
|
mScriptManager.Fail("Failed to copy '{}' to '{}'", srcFilePath, destFilePath);
|
|
return;
|
|
}
|
|
|
|
copyCount++;
|
|
}
|
|
}
|
|
|
|
Do(srcPath, destPath);
|
|
|
|
if (foundCount == 0)
|
|
{
|
|
String srcDirPath = scope .();
|
|
Path.GetDirectoryPath(srcPath, srcDirPath);
|
|
if (!Directory.Exists(srcDirPath))
|
|
{
|
|
mScriptManager.Fail("Source directory does not exist: {}", srcDirPath);
|
|
}
|
|
else if ((!srcDirPath.Contains('*')) && (!srcDirPath.Contains('?')) && (!File.Exists(srcDirPath)))
|
|
{
|
|
mScriptManager.Fail("Source file does not exist: {}", srcPath);
|
|
}
|
|
}
|
|
|
|
if ((!mScriptManager.mFailed) && (copyCount > 0) && (mScriptManager.mVerbosity >= .Normal))
|
|
{
|
|
if (mScriptManager.mCurCmd.mStopWatch != null)
|
|
{
|
|
mScriptManager.mCurCmd.mStopWatch.Stop();
|
|
gApp.OutputLine("{} files copied from '{}' to '{}' in {:0.00}s", foundCount, srcPath, destPath, mScriptManager.mCurCmd.mStopWatch.ElapsedMilliseconds / 1000.0f);
|
|
DeleteAndNullify!(mScriptManager.mCurCmd.mStopWatch);
|
|
}
|
|
else
|
|
gApp.OutputLine("{} files copied from '{}' to '{}'", foundCount, srcPath, destPath);
|
|
}
|
|
}
|
|
|
|
public Project GetProject()
|
|
{
|
|
if (mScriptManager.mProjectName == null)
|
|
{
|
|
mScriptManager.Fail("Only usable in the context of a project");
|
|
return null;
|
|
}
|
|
|
|
let project = gApp.mWorkspace.FindProject(mScriptManager.mProjectName);
|
|
if (project == null)
|
|
{
|
|
mScriptManager.Fail("Unable to find project '{}'", mScriptManager.mProjectName);
|
|
return null;
|
|
}
|
|
return project;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void CopyToDependents(String srcPath)
|
|
{
|
|
let depProject = GetProject();
|
|
if (depProject == null)
|
|
return;
|
|
|
|
for (let checkProject in gApp.mWorkspace.mProjects)
|
|
{
|
|
if (checkProject.HasDependency(depProject.mProjectName))
|
|
{
|
|
List<String> targetPaths = scope .();
|
|
defer ClearAndDeleteItems(targetPaths);
|
|
|
|
let workspaceOptions = gApp.GetCurWorkspaceOptions();
|
|
let options = gApp.GetCurProjectOptions(checkProject);
|
|
gApp.[Friend]GetTargetPaths(checkProject, workspaceOptions, options, targetPaths);
|
|
|
|
if (!targetPaths.IsEmpty)
|
|
{
|
|
String targetDirPath = scope .();
|
|
Path.GetDirectoryPath(targetPaths[0], targetDirPath);
|
|
|
|
bool CopyFile(String srcPath)
|
|
{
|
|
String fileName = scope .();
|
|
Path.GetFileName(srcPath, fileName);
|
|
|
|
String destPath = scope .();
|
|
Path.GetAbsolutePath(fileName, targetDirPath, destPath);
|
|
|
|
if (File.CopyIfNewer(srcPath, destPath) case .Err)
|
|
{
|
|
mScriptManager.Fail("Failed to copy file '{}' to '{}'", srcPath, destPath);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (srcPath.Contains('*'))
|
|
{
|
|
String dirPath = scope .();
|
|
String wildcard = scope .();
|
|
Path.GetDirectoryPath(srcPath, dirPath);
|
|
Path.GetFileName(srcPath, wildcard);
|
|
|
|
for (let entry in Directory.EnumerateFiles(dirPath, wildcard))
|
|
{
|
|
String foundPath = scope .();
|
|
entry.GetFilePath(foundPath);
|
|
if (!CopyFile(foundPath))
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!CopyFile(srcPath))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void ExecuteRaw(String cmd)
|
|
{
|
|
var exePath = scope String();
|
|
int spacePos;
|
|
if (cmd.StartsWith("\""))
|
|
{
|
|
spacePos = cmd.IndexOf('"', 1) + 1;
|
|
if (spacePos != -1)
|
|
exePath.Append(cmd, 1, spacePos - 2);
|
|
}
|
|
else
|
|
{
|
|
spacePos = cmd.IndexOf(' ');
|
|
if (spacePos != -1)
|
|
exePath.Append(cmd, 0, spacePos);
|
|
}
|
|
|
|
if ((spacePos == -1) && (!cmd.IsEmpty))
|
|
{
|
|
mScriptManager.Fail("Invalid command '{0}' in '{1}'", cmd, mScriptManager.mCurCmd.mSrcFile);
|
|
return;
|
|
}
|
|
|
|
if (spacePos > 0)
|
|
{
|
|
var exeArgs = scope String();
|
|
exeArgs.Append(cmd, spacePos + 1);
|
|
gApp.DoRun(exePath, exeArgs, gApp.mInstallDir, .None);
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void Execute(String path)
|
|
{
|
|
ExecuteRaw(path);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void SetFileWatcherDelay(int32 delay)
|
|
{
|
|
FileWatcher.sDbgFileCreateDelay = delay;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void RenameFile_TempRenameDelete(String origPath, String newPath)
|
|
{
|
|
String content = scope .();
|
|
if (File.ReadAllText(origPath, content, true) case .Err)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Failed to open file '{}'", origPath);
|
|
return;
|
|
}
|
|
|
|
String tempPath = scope .();
|
|
|
|
while (true)
|
|
{
|
|
tempPath.Clear();
|
|
Path.GetDirectoryPath(origPath, tempPath);
|
|
tempPath.Append(Path.DirectorySeparatorChar);
|
|
tempPath.Append("_");
|
|
Rand.Int().ToString(tempPath);
|
|
tempPath.Append(".tmp");
|
|
if (!File.Exists(tempPath))
|
|
break;
|
|
}
|
|
|
|
FileStream tempStream = scope .();
|
|
if (tempStream.Create(tempPath) case .Err)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Failed to create temp file '{}'", tempPath);
|
|
return;
|
|
}
|
|
tempStream.Write(content);
|
|
tempStream.Close();
|
|
|
|
if (File.Move(tempPath, newPath) case .Err)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Failed to move file '{}' to '{}'", origPath, newPath);
|
|
return;
|
|
}
|
|
|
|
if (File.Delete(origPath) case .Err)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Failed to delete file '{}'", origPath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void OpenWorkspace(String dirPath)
|
|
{
|
|
gApp.[Friend]mDeferredOpen = .Workspace;
|
|
var selectedPath = scope String..AppendF(dirPath);
|
|
selectedPath.Append(Path.DirectorySeparatorChar);
|
|
selectedPath.Append("BeefSpace.toml");
|
|
IDEUtils.FixFilePath(selectedPath);
|
|
gApp.[Friend]mDeferredOpenFileName = new String(selectedPath);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void ClickPanelButton(String buttonName)
|
|
{
|
|
var sourceViewPanel = GetActiveSourceViewPanel();
|
|
if (sourceViewPanel == null)
|
|
return;
|
|
|
|
var panelHeader = sourceViewPanel.[Friend]mPanelHeader;
|
|
if (panelHeader == null)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("No panel present");
|
|
return;
|
|
}
|
|
|
|
for (var widget in panelHeader.mChildWidgets)
|
|
{
|
|
if (var button = widget as DarkButton)
|
|
{
|
|
if (button.Label == buttonName)
|
|
{
|
|
button.MouseClicked(0, 0, 0);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
ScriptManager.sActiveManager.Fail("Button '{0}' not found", buttonName);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void SelectLine(int lineNum)
|
|
{
|
|
var sourceViewPanel = GetActiveSourceViewPanel();
|
|
if (sourceViewPanel != null)
|
|
{
|
|
sourceViewPanel.ShowFileLocation(-1, lineNum - 1, 0, .None);
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void AssertEvalEquals(String evalStr, String evalResult)
|
|
{
|
|
String outVal = scope String();
|
|
if (!Evaluate(evalStr, outVal))
|
|
return;
|
|
|
|
if (outVal != evalResult)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Assert failed: {0} == {1}", outVal, evalResult);
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void AssertEvalContains(String evalStr, String evalResult)
|
|
{
|
|
String outVal = scope String();
|
|
if (!Evaluate(evalStr, outVal))
|
|
return;
|
|
|
|
if (!outVal.Contains(evalResult))
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Assert failed: {0} contains {1}", outVal, evalResult);
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void ImmediateEvaluate(String evalStr)
|
|
{
|
|
gApp.ShowImmediatePanel();
|
|
gApp.mImmediatePanel.[Friend]mImmediateWidget.mEditWidgetContent.InsertAtCursor(evalStr);
|
|
gApp.mImmediatePanel.[Friend]mImmediateWidget.KeyChar('\n');
|
|
}
|
|
|
|
[IDECommand]
|
|
public void SelectCallStackIdx(int selectIdx)
|
|
{
|
|
while (true)
|
|
{
|
|
int stackCount = gApp.mDebugger.GetCallStackCount();
|
|
if (selectIdx < stackCount)
|
|
break;
|
|
gApp.mDebugger.UpdateCallStack();
|
|
if (stackCount == gApp.mDebugger.GetCallStackCount())
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Stack idx '{0}' is out of range", selectIdx);
|
|
}
|
|
}
|
|
|
|
gApp.mDebugger.mActiveCallStackIdx = (.)selectIdx;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void SelectCallStackWithStr(String str)
|
|
{
|
|
int32 stackIdx = 0;
|
|
while (true)
|
|
{
|
|
int stackCount = gApp.mDebugger.GetCallStackCount();
|
|
if (stackIdx >= stackCount)
|
|
{
|
|
gApp.mDebugger.UpdateCallStack();
|
|
if (stackIdx >= gApp.mDebugger.GetCallStackCount())
|
|
break;
|
|
}
|
|
|
|
String file = scope .();
|
|
String stackFrameInfo = scope .();
|
|
gApp.mDebugger.GetStackFrameInfo(stackIdx, var addr, file, stackFrameInfo);
|
|
if (stackFrameInfo.Contains(str))
|
|
{
|
|
gApp.mDebugger.mActiveCallStackIdx = (.)stackIdx;
|
|
return;
|
|
}
|
|
stackIdx++;
|
|
}
|
|
|
|
ScriptManager.sActiveManager.Fail("Failed to find stack frame containing string '{}'", str);
|
|
}
|
|
|
|
public bool AssertRunning()
|
|
{
|
|
if (!gApp.mDebugger.mIsRunning)
|
|
{
|
|
mScriptManager.Fail("Expected target to be running");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool AssertDebuggerPaused()
|
|
{
|
|
if (!gApp.mDebugger.mIsRunning)
|
|
{
|
|
mScriptManager.Fail("Expected target to be running");
|
|
return false;
|
|
}
|
|
|
|
if (!gApp.mDebugger.IsPaused())
|
|
{
|
|
mScriptManager.Fail("Expected target to be paused");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void StepInto()
|
|
{
|
|
if (!AssertDebuggerPaused())
|
|
return;
|
|
gApp.[Friend]StepInto();
|
|
}
|
|
|
|
[IDECommand]
|
|
public void StepOver()
|
|
{
|
|
if (!AssertDebuggerPaused())
|
|
return;
|
|
gApp.[Friend]StepOver();
|
|
}
|
|
|
|
[IDECommand]
|
|
public void SelectThread(String threadName)
|
|
{
|
|
String threadInfo = scope .();
|
|
gApp.mDebugger.GetThreadInfo(threadInfo);
|
|
|
|
for (var infoLine in threadInfo.Split('\n'))
|
|
{
|
|
if (@infoLine.Pos == 0)
|
|
continue;
|
|
|
|
var infoSections = infoLine.Split('\t');
|
|
StringView id = infoSections.GetNext().GetValueOrDefault();
|
|
StringView name = infoSections.GetNext().GetValueOrDefault();
|
|
|
|
if ((threadName.IsEmpty) || (threadName == name) || (threadName == id))
|
|
{
|
|
gApp.mDebugger.SetActiveThread(int32.Parse(id).GetValueOrDefault());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void AssertCurrentMethod(String methodName)
|
|
{
|
|
int addr;
|
|
String fileName = scope String();
|
|
String stackframeInfo = scope String();
|
|
gApp.mDebugger.GetStackFrameInfo(gApp.mDebugger.mActiveCallStackIdx, out addr, fileName, stackframeInfo);
|
|
|
|
if (methodName != stackframeInfo)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Expect method name '{0}', got '{1}'", methodName, stackframeInfo);
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void CreateMemoryBreakpoint(String evalStr)
|
|
{
|
|
String outVal = scope String();
|
|
if (!Evaluate(evalStr, outVal, .AllowCalls | .AllowSideEffects | .MemoryWatch))
|
|
return;
|
|
|
|
var vals = scope List<StringView>(outVal.Split('\n'));
|
|
int64 addr = (int)int64.Parse(scope String(vals[0]), System.Globalization.NumberStyles.HexNumber);
|
|
int32 byteCount = int32.Parse(scope String(vals[1]));
|
|
String addrType = scope String(vals[2]);
|
|
#unwarn
|
|
var breakpoint = gApp.mDebugger.CreateMemoryBreakpoint(evalStr, addr, byteCount, addrType);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void Crash()
|
|
{
|
|
int* a = null;
|
|
*a = 123;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void Leak()
|
|
{
|
|
new String("This string leaked");
|
|
}
|
|
|
|
[IDECommand]
|
|
public void BreakpointSetCondition(String condition)
|
|
{
|
|
var lastBreakpoint = gApp.mDebugger.mBreakpointList.Back;
|
|
if (lastBreakpoint == null)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("No last breakpoint");
|
|
return;
|
|
}
|
|
lastBreakpoint.SetCondition(condition);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void BreakpointSetHitCountTarget(int hitCountTarget, String hitCountBreakKindStr)
|
|
{
|
|
var lastBreakpoint = gApp.mDebugger.mBreakpointList.Back;
|
|
if (lastBreakpoint == null)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("No last breakpoint");
|
|
return;
|
|
}
|
|
switch (Enum.Parse<Breakpoint.HitCountBreakKind>(hitCountBreakKindStr))
|
|
{
|
|
case .Err:
|
|
ScriptManager.sActiveManager.Fail("Invalid break kind: '{0}'", hitCountBreakKindStr);
|
|
case .Ok(let hitCountBreakKind):
|
|
lastBreakpoint.SetHitCountTarget(hitCountTarget, hitCountBreakKind);
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void InsertImmediateText(String text)
|
|
{
|
|
gApp.ShowImmediatePanel();
|
|
var ewc = (SourceEditWidgetContent)gApp.mImmediatePanel.EditWidget.mEditWidgetContent;
|
|
|
|
ewc.InsertAtCursor(text);
|
|
gApp.mImmediatePanel.Update();
|
|
ewc.mOnGenerateAutocomplete(0, .UserRequested);
|
|
}
|
|
|
|
AutoComplete GetAutocomplete()
|
|
{
|
|
var textPanel = GetActiveTextPanel();
|
|
if (textPanel == null)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("No text panel active");
|
|
return null;
|
|
}
|
|
|
|
var ewc = textPanel.EditWidget.mEditWidgetContent as SourceEditWidgetContent;
|
|
if (ewc == null)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Not an autocomplete text view");
|
|
return null;
|
|
}
|
|
|
|
if (ewc.mAutoComplete == null)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("No autocomplete content");
|
|
return null;
|
|
}
|
|
|
|
return ewc.mAutoComplete;
|
|
}
|
|
|
|
[IDECommand]
|
|
public bool AssertAutocompleteEntry(StringView wantEntry)
|
|
{
|
|
var wantEntry;
|
|
bool wantsFind = true;
|
|
if (wantEntry.StartsWith("!"))
|
|
{
|
|
wantsFind = false;
|
|
wantEntry.RemoveFromStart(1);
|
|
}
|
|
|
|
var autoComplete = GetAutocomplete();
|
|
if (autoComplete == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool found = false;
|
|
if (autoComplete.mAutoCompleteListWidget != null)
|
|
{
|
|
for (var entry in autoComplete.mAutoCompleteListWidget.mEntryList)
|
|
{
|
|
if (entry.mEntryDisplay == wantEntry)
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (found != wantsFind)
|
|
{
|
|
if (wantsFind)
|
|
ScriptManager.sActiveManager.Fail("Autocomplete entry '{0}' not found", wantEntry);
|
|
else
|
|
ScriptManager.sActiveManager.Fail("Autocomplete entry '{0}' found, but it shouldn't have been", wantEntry);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void AssertDbgAutocomplete(String text)
|
|
{
|
|
var lastActivePanel = gApp.mLastActivePanel;
|
|
InsertImmediateText(text);
|
|
for (let entry in text.Split('\n'))
|
|
{
|
|
if (!entry.IsEmpty)
|
|
{
|
|
if (!AssertAutocompleteEntry(entry))
|
|
break;
|
|
}
|
|
}
|
|
ClearImmediate();
|
|
gApp.mLastActivePanel = lastActivePanel;
|
|
}
|
|
|
|
|
|
[IDECommand]
|
|
public bool AssertAutocompleteEquals(String insertText, String wantsContents)
|
|
{
|
|
InsertImmediateText(insertText);
|
|
var autoComplete = GetAutocomplete();
|
|
if (autoComplete == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
String contents = scope .();
|
|
|
|
if (autoComplete.mAutoCompleteListWidget != null)
|
|
{
|
|
for (var entry in autoComplete.mAutoCompleteListWidget.mEntryList)
|
|
{
|
|
if (!contents.IsEmpty)
|
|
contents.Append("\n");
|
|
contents.Append(entry.mEntryDisplay);
|
|
}
|
|
}
|
|
|
|
ClearImmediate();
|
|
|
|
if (contents != wantsContents)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Autocomplete not showing expected values. Expected '{}', got '{}'.", wantsContents, contents);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void CloseAutocomplete()
|
|
{
|
|
var autoComplete = GetAutocomplete();
|
|
if (autoComplete == null)
|
|
return;
|
|
autoComplete.Close();
|
|
}
|
|
|
|
[IDECommand]
|
|
public void ClearImmediate()
|
|
{
|
|
gApp.mImmediatePanel.Clear();
|
|
}
|
|
|
|
[IDECommand]
|
|
public void AssertIsAt(String fileName, int lineNum)
|
|
{
|
|
String filePath = scope String();
|
|
FixSrcPath(fileName, filePath);
|
|
|
|
var sourceViewPanel = GetActiveSourceViewPanel();
|
|
if (sourceViewPanel == null)
|
|
return;
|
|
|
|
if (!Path.Equals(filePath, sourceViewPanel.mFilePath))
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Expected source file '{0}', got '{1}'", filePath, sourceViewPanel.mFilePath);
|
|
return;
|
|
}
|
|
|
|
let atLine = sourceViewPanel.mEditWidget.mEditWidgetContent.CursorLineAndColumn.mLine + 1;
|
|
if (atLine != lineNum)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Expected line '{0}', got '{1}'", lineNum, atLine);
|
|
return;
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void GotoTextSkip(String findText, int skipIdx)
|
|
{
|
|
var skipIdx;
|
|
|
|
var textPanel = GetActiveTextPanel();
|
|
if (textPanel == null)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("No active text panel");
|
|
return;
|
|
}
|
|
var ewc = textPanel.EditWidget.mEditWidgetContent;
|
|
|
|
for (int32 startIdx < ewc.mData.mTextLength)
|
|
{
|
|
bool isEqual = true;
|
|
for (int32 i = 0; i < findText.Length; i++)
|
|
{
|
|
if (ewc.mData.mText[i + startIdx].mChar != findText[i])
|
|
{
|
|
isEqual = false;
|
|
break;
|
|
}
|
|
}
|
|
if (isEqual)
|
|
{
|
|
if (skipIdx > 0)
|
|
{
|
|
skipIdx--;
|
|
}
|
|
else
|
|
{
|
|
ewc.CursorTextPos = startIdx;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
ScriptManager.sActiveManager.Fail("Unable to find text '{0}'", findText);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void GotoText(String findText)
|
|
{
|
|
GotoTextSkip(findText, 0);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void AssertLineContains(String findText)
|
|
{
|
|
var textPanel = GetActiveTextPanel();
|
|
if (textPanel == null)
|
|
return;
|
|
|
|
var ewc = textPanel.EditWidget.mEditWidgetContent;
|
|
int line = ewc.CursorLineAndColumn.mLine;
|
|
|
|
var lineText = scope String();
|
|
ewc.GetLineText(line, lineText);
|
|
|
|
if (!lineText.Contains(findText))
|
|
{
|
|
ScriptManager.sActiveManager.Fail("Lines does not contain text '{0}'", findText);
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void MoveCursor(int line, int column)
|
|
{
|
|
var textPanel = GetActiveTextPanel();
|
|
if (textPanel == null)
|
|
return;
|
|
|
|
var ewc = textPanel.EditWidget.mEditWidgetContent;
|
|
ewc.CursorLineAndColumn = .(line, column);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void AdjustCursor(int relColumn, int relLine)
|
|
{
|
|
var textPanel = GetActiveTextPanel();
|
|
if (textPanel == null)
|
|
return;
|
|
|
|
var ewc = textPanel.EditWidget.mEditWidgetContent;
|
|
var cursorPos = ewc.CursorLineAndColumn;
|
|
cursorPos.mLine += (.)relLine;
|
|
cursorPos.mColumn += (.)relColumn;
|
|
ewc.CursorLineAndColumn = cursorPos;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void InsertText(String text)
|
|
{
|
|
var textPanel = GetActiveTextPanel();
|
|
if (textPanel == null)
|
|
return;
|
|
textPanel.EditWidget.mEditWidgetContent.InsertAtCursor(text);
|
|
}
|
|
|
|
[IDECommand]
|
|
public void DeleteText()
|
|
{
|
|
var textPanel = GetActiveTextPanel();
|
|
if (textPanel == null)
|
|
return;
|
|
textPanel.EditWidget.mEditWidgetContent.DeleteSelection();
|
|
}
|
|
|
|
[IDECommand]
|
|
public void MarkPosition()
|
|
{
|
|
var textPanel = GetActiveTextPanel();
|
|
if (textPanel == null)
|
|
return;
|
|
|
|
var ewc = textPanel.EditWidget.mEditWidgetContent;
|
|
mMarkedPos = ewc.CursorLineAndColumn;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void SelectToMark()
|
|
{
|
|
var textPanel = GetActiveTextPanel();
|
|
if (textPanel == null)
|
|
return;
|
|
|
|
var ewc = textPanel.EditWidget.mEditWidgetContent;
|
|
var prevPos = ewc.CursorLineAndColumn;
|
|
|
|
EditSelection sel;
|
|
ewc.CursorLineAndColumn = mMarkedPos;
|
|
sel.mStartPos = (.)ewc.CursorTextPos;
|
|
ewc.CursorLineAndColumn = prevPos;
|
|
sel.mEndPos = (.)ewc.CursorTextPos;
|
|
ewc.mSelection = sel;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void RemoveSelection()
|
|
{
|
|
var textPanel = GetActiveTextPanel();
|
|
if (textPanel == null)
|
|
return;
|
|
|
|
var ewc = textPanel.EditWidget.mEditWidgetContent;
|
|
ewc.mSelection = null;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void ToggleCommentBetween(String textFrom, String textTo)
|
|
{
|
|
GotoText(textFrom);
|
|
AdjustCursor(1, 0);
|
|
MarkPosition();
|
|
GotoText(textTo);
|
|
SelectToMark();
|
|
gApp.[Friend]ToggleComment();
|
|
}
|
|
|
|
[IDECommand]
|
|
public void AssertCompileSucceeded()
|
|
{
|
|
if (gApp.IsCompiling)
|
|
{
|
|
ScriptManager.sActiveManager.mCurCmd.mHandled = false;
|
|
return;
|
|
}
|
|
|
|
if (gApp.mLastCompileFailed)
|
|
ScriptManager.sActiveManager.Fail("Compile failed");
|
|
}
|
|
|
|
[IDECommand]
|
|
public void ToggleCommentAt(String textFrom)
|
|
{
|
|
var sourceViewPanel = GetActiveSourceViewPanel();
|
|
if (sourceViewPanel == null)
|
|
return;
|
|
|
|
var ewc = sourceViewPanel.mEditWidget.mEditWidgetContent;
|
|
GotoText(textFrom);
|
|
|
|
if (ScriptManager.sActiveManager.Failed)
|
|
return;
|
|
|
|
|
|
char8 nextC = 0;
|
|
|
|
int32 checkIdx = (.)ewc.CursorTextPos;
|
|
while (checkIdx > 0)
|
|
{
|
|
if (ewc.mData.mText[checkIdx - 1].mDisplayTypeId != (.)SourceElementType.Comment)
|
|
break;
|
|
|
|
char8 c = ewc.mData.mText[checkIdx - 1].mChar;
|
|
|
|
// If we have comments back-to-back then we need to do this...
|
|
if ((c == '/') && (nextC == '*'))
|
|
{
|
|
checkIdx--;
|
|
if (checkIdx > 1)
|
|
{
|
|
char8 prevC = ewc.mData.mText[checkIdx - 1].mChar;
|
|
if (prevC == '/')
|
|
checkIdx--;
|
|
}
|
|
break;
|
|
}
|
|
nextC = c;
|
|
|
|
checkIdx--;
|
|
}
|
|
|
|
int32 startPos = checkIdx;
|
|
if (ewc.mData.mText[startPos + 1].mChar == '*')
|
|
{
|
|
ewc.CursorTextPos = startPos;
|
|
ewc.InsertAtCursor("/");
|
|
|
|
char8 prevC = 0;
|
|
|
|
int depth = 1;
|
|
|
|
checkIdx = (.)ewc.CursorTextPos + 2;
|
|
while (checkIdx < ewc.mData.mTextLength)
|
|
{
|
|
char8 c = ewc.mData.mText[checkIdx].mChar;
|
|
if (ewc.mData.mText[checkIdx].mDisplayTypeId != (.)SourceElementType.Comment)
|
|
break;
|
|
|
|
// If we have comments back-to-back then we need to do this...
|
|
if ((c == '/') && (prevC == '*'))
|
|
{
|
|
--depth;
|
|
if (depth == 0)
|
|
{
|
|
checkIdx++;
|
|
break;
|
|
}
|
|
}
|
|
else if ((c == '*') && (prevC == '/'))
|
|
depth++;
|
|
|
|
prevC = c;
|
|
checkIdx++;
|
|
}
|
|
ewc.CursorTextPos = checkIdx - 2;
|
|
ewc.InsertAtCursor("/*@");
|
|
}
|
|
else
|
|
{
|
|
var text = ewc.mData.mText;
|
|
|
|
ewc.mSelection = EditSelection(startPos, startPos + 1);
|
|
ewc.DeleteSelection();
|
|
|
|
checkIdx = (.)startPos;
|
|
while (checkIdx < ewc.mData.mTextLength - 4)
|
|
{
|
|
if (text[checkIdx].mDisplayTypeId == (.)SourceElementType.Comment)
|
|
{
|
|
if ((text[checkIdx + 0].mChar == '/') &&
|
|
(text[checkIdx + 1].mChar == '*') &&
|
|
(text[checkIdx + 2].mChar == '@') &&
|
|
(text[checkIdx + 3].mChar == '*') &&
|
|
(text[checkIdx + 4].mChar == '/'))
|
|
{
|
|
ewc.mSelection = EditSelection(checkIdx, checkIdx + 3);
|
|
ewc.DeleteSelection();
|
|
break;
|
|
}
|
|
}
|
|
checkIdx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void SetExpectError(String error)
|
|
{
|
|
DeleteAndNullify!(ScriptManager.sActiveManager.mExpectingError);
|
|
ScriptManager.sActiveManager.mExpectingError = new String(error);
|
|
ScriptManager.sActiveManager.mHadExpectingError = true;
|
|
}
|
|
|
|
[IDECommand]
|
|
public void ExpectError()
|
|
{
|
|
if (ScriptManager.sActiveManager.mExpectingError != null)
|
|
{
|
|
DeleteAndNullify!(ScriptManager.sActiveManager.mExpectingError);
|
|
ScriptManager.sActiveManager.Fail("Expected error did not occur");
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void AssertFileErrors()
|
|
{
|
|
var textPanel = GetActiveSourceViewPanel();
|
|
if (textPanel == null)
|
|
{
|
|
ScriptManager.sActiveManager.Fail("No active text panel");
|
|
return;
|
|
}
|
|
var ewc = textPanel.EditWidget.mEditWidgetContent;
|
|
|
|
String lineText = scope String();
|
|
for (int lineIdx = 0; lineIdx < ewc.GetLineCount(); lineIdx++)
|
|
{
|
|
lineText.Clear();
|
|
ewc.GetLineText(lineIdx, lineText);
|
|
|
|
ewc.GetLinePosition(lineIdx, var lineStart, var lineEnd);
|
|
bool hasError = false;
|
|
for (int i = lineStart; i < lineEnd; i++)
|
|
{
|
|
var flags = (SourceElementFlags)ewc.mData.mText[i].mDisplayFlags;
|
|
if (flags.HasFlag(.Error))
|
|
hasError = true;
|
|
}
|
|
|
|
bool expectedError = lineText.Contains("//FAIL");
|
|
if (hasError != expectedError)
|
|
{
|
|
if (hasError)
|
|
ScriptManager.sActiveManager.Fail("Unexpected error at line {0} in {1}\n\t", lineIdx + 1, textPanel.mFilePath);
|
|
else
|
|
ScriptManager.sActiveManager.Fail("Expected error at line {0} in {1} but didn't encounter one\n\t", lineIdx + 1, textPanel.mFilePath);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void Stop()
|
|
{
|
|
mScriptManager.Clear();
|
|
}
|
|
|
|
[IDECommand]
|
|
public void Exit()
|
|
{
|
|
gApp.Stop();
|
|
}
|
|
|
|
[IDECommand]
|
|
public void RestoreDebugFiles(String dbgPath)
|
|
{
|
|
for (var entry in Directory.EnumerateFiles(dbgPath))
|
|
{
|
|
String dbgFilePath = scope .();
|
|
entry.GetFilePath(dbgFilePath);
|
|
if (!dbgFilePath.EndsWith(".bf"))
|
|
continue;
|
|
|
|
String dbgContent = scope .();
|
|
File.ReadAllText(dbgFilePath, dbgContent, false);
|
|
if (!dbgContent.StartsWith("//@"))
|
|
continue;
|
|
int barPos = dbgContent.IndexOf('|');
|
|
|
|
String srcPath = scope .();
|
|
srcPath.Append(dbgContent, 3, barPos - 3);
|
|
|
|
int crPos = dbgContent.IndexOf('\n');
|
|
StringView dbgText = .(dbgContent.Ptr + crPos + 1, dbgContent.Length - crPos - 1);
|
|
let dbgHash = MD5.Hash(.((uint8*)dbgText.Ptr, dbgText.Length));
|
|
|
|
String srcContent = scope .();
|
|
File.ReadAllText(srcPath, srcContent, false);
|
|
let srcHash = MD5.Hash(.((uint8*)srcContent.Ptr, srcContent.Length));
|
|
|
|
if (dbgHash != srcHash)
|
|
{
|
|
String bkuPath = scope .();
|
|
bkuPath.Append(gApp.mInstallDir, "/bku/");
|
|
Path.GetFileNameWithoutExtension(dbgFilePath, bkuPath);
|
|
bkuPath.Append("_");
|
|
srcHash.ToString(bkuPath);
|
|
Path.GetExtension(dbgFilePath, bkuPath);
|
|
|
|
gApp.SafeWriteTextFile(bkuPath, srcContent);
|
|
gApp.SafeWriteTextFile(srcPath, dbgText);
|
|
}
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void RestoreDebugFilesSpan(String dbgPath, int fromIdx, int endIdx)
|
|
{
|
|
for (int i = fromIdx; i <= endIdx; i++)
|
|
{
|
|
String versionedPath = scope .();
|
|
versionedPath.AppendF("{}/{}/", dbgPath, i);
|
|
RestoreDebugFiles(versionedPath);
|
|
}
|
|
}
|
|
|
|
[IDECommand]
|
|
public void WaitDialog()
|
|
{
|
|
#if BF_PLATFORM_WINDOWS
|
|
Windows.MessageBoxA((Windows.HWnd)0, "Waiting for user input", "Beef IDE", 0);
|
|
#endif
|
|
}
|
|
}
|
|
}
|