1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-08 11:38:21 +02:00

Initial package management support

This commit is contained in:
Brian Fiete 2024-10-21 09:18:07 -04:00
parent 78138f5c5a
commit 4870c6fdd8
19 changed files with 2520 additions and 205 deletions

View file

@ -10,12 +10,22 @@ namespace BeefBuild
{ {
class BuildApp : IDEApp class BuildApp : IDEApp
{ {
public enum MainVerbState
{
None,
UpdateList,
End
}
const int cProgressSize = 30; const int cProgressSize = 30;
int mProgressIdx = 0; int mProgressIdx = 0;
public bool mIsTest; public bool mIsTest;
public bool mTestIncludeIgnored; public bool mTestIncludeIgnored;
public bool mDidRun; public bool mDidRun;
public bool mWantsGenerate = false; public bool mWantsGenerate = false;
public bool mHandledVerb;
public String mRunArgs ~ delete _;
MainVerbState mMainVerbState;
/*void Test() /*void Test()
{ {
@ -112,20 +122,33 @@ namespace BeefBuild
OutputErrorLine("The project '{}' is not empty, but '-generate' was specified.", mWorkspace.mStartupProject.mProjectName); OutputErrorLine("The project '{}' is not empty, but '-generate' was specified.", mWorkspace.mStartupProject.mProjectName);
} }
} }
if (!mFailed)
{
if (mIsTest)
{
RunTests(mTestIncludeIgnored, false);
}
else if (mVerb != .New)
Compile(.Normal, null);
}
} }
public override bool HandleCommandLineParam(String key, String value) public override bool HandleCommandLineParam(String key, String value)
{ {
if (mRunArgs != null)
{
if (!mRunArgs.IsEmpty)
mRunArgs.Append(" ");
if (value != null)
{
String qKey = scope .(key);
String qValue = scope .(value);
IDEApp.QuoteIfNeeded(qKey);
IDEApp.QuoteIfNeeded(qValue);
mRunArgs.Append(qKey);
mRunArgs.Append('=');
mRunArgs.Append(qValue);
}
else
{
String qKey = scope .(key);
IDEApp.QuoteIfNeeded(qKey);
mRunArgs.Append(qKey);
}
return true;
}
if (key.StartsWith("--")) if (key.StartsWith("--"))
key.Remove(0, 1); key.Remove(0, 1);
@ -133,6 +156,10 @@ namespace BeefBuild
{ {
switch (key) switch (key)
{ {
case "-args":
if (mRunArgs == null)
mRunArgs = new .();
return true;
case "-new": case "-new":
mVerb = .New; mVerb = .New;
return true; return true;
@ -157,12 +184,63 @@ namespace BeefBuild
case "-noir": case "-noir":
mConfig_NoIR = true; mConfig_NoIR = true;
return true; return true;
case "-update":
if (mWantUpdateVersionLocks == null)
mWantUpdateVersionLocks = new .();
return true;
case "-version": case "-version":
mVerb = .GetVersion; mVerb = .GetVersion;
return true; return true;
case "-crash": case "-crash":
Runtime.FatalError("-crash specified on command line"); Runtime.FatalError("-crash specified on command line");
} }
if (!key.StartsWith('-'))
{
switch (mMainVerbState)
{
case .None:
mMainVerbState = .End;
switch (key)
{
case "build":
mVerb = .None;
case "new":
mVerb = .New;
case "generate":
mWantsGenerate = true;
case "run":
if (mVerbosity == .Default)
mVerbosity = .Minimal;
mVerb = .Run;
case "test":
mIsTest = true;
case "testall":
mIsTest = true;
mTestIncludeIgnored = true;
case "clean":
mWantsClean = true;
case "version":
mVerb = .GetVersion;
case "crash":
Runtime.FatalError("-crash specified on command line");
case "update":
mVerb = .Update;
mWantUpdateVersionLocks = new .();
mMainVerbState = .UpdateList;
default:
mMainVerbState = .None;
}
if (mMainVerbState != .None)
return true;
case .UpdateList:
mWantUpdateVersionLocks.Add(new .(key));
return true;
case .End:
return false;
default:
}
}
} }
else else
{ {
@ -188,6 +266,11 @@ namespace BeefBuild
case "-platform": case "-platform":
mPlatformName.Set(value); mPlatformName.Set(value);
return true; return true;
case "-update":
if (mWantUpdateVersionLocks == null)
mWantUpdateVersionLocks = new .();
mWantUpdateVersionLocks.Add(new .(value));
return true;
case "-verbosity": case "-verbosity":
if (value == "quiet") if (value == "quiet")
mVerbosity = .Quiet; mVerbosity = .Quiet;
@ -281,33 +364,55 @@ namespace BeefBuild
{ {
base.Update(batchStart); base.Update(batchStart);
if (mCompilingBeef) if (mWorkspace.mProjectLoadState != .Loaded)
{ {
WriteProgress(mBfBuildCompiler.GetCompletionPercentage()); // Wait for workspace to complete loading
} }
else
if ((!IsCompiling) && (!AreTestsRunning()))
{ {
if ((mVerb == .Run) && (!mDidRun) && (!mFailed)) if ((!mFailed) && (!mHandledVerb))
{ {
let curPath = scope String(); mHandledVerb = true;
Directory.GetCurrentDirectory(curPath); if (mIsTest)
{
let workspaceOptions = gApp.GetCurWorkspaceOptions(); RunTests(mTestIncludeIgnored, false);
let options = gApp.GetCurProjectOptions(mWorkspace.mStartupProject); }
let targetPaths = scope List<String>(); else if (mVerb == .Update)
defer ClearAndDeleteItems(targetPaths); {
this.[Friend]GetTargetPaths(mWorkspace.mStartupProject, gApp.mPlatformName, workspaceOptions, options, targetPaths); // No-op here
if (targetPaths.IsEmpty) }
return; else if (mVerb != .New)
Compile(.Normal, null);
ExecutionQueueCmd executionCmd = QueueRun(targetPaths[0], "", curPath);
executionCmd.mIsTargetRun = true;
mDidRun = true;
return;
} }
Stop(); if (mCompilingBeef)
{
WriteProgress(mBfBuildCompiler.GetCompletionPercentage());
}
if ((!IsCompiling) && (!AreTestsRunning()))
{
if ((mVerb == .Run) && (!mDidRun) && (!mFailed))
{
let curPath = scope String();
Directory.GetCurrentDirectory(curPath);
let workspaceOptions = gApp.GetCurWorkspaceOptions();
let options = gApp.GetCurProjectOptions(mWorkspace.mStartupProject);
let targetPaths = scope List<String>();
defer ClearAndDeleteItems(targetPaths);
this.[Friend]GetTargetPaths(mWorkspace.mStartupProject, gApp.mPlatformName, workspaceOptions, options, targetPaths);
if (targetPaths.IsEmpty)
return;
ExecutionQueueCmd executionCmd = QueueRun(targetPaths[0], mRunArgs ?? "", curPath);
executionCmd.mIsTargetRun = true;
mDidRun = true;
return;
}
Stop();
}
} }
} }
} }

View file

@ -123,6 +123,7 @@ namespace IDE
List<String> mConfigPathQueue = new List<String>() ~ DeleteContainerAndItems!(_); List<String> mConfigPathQueue = new List<String>() ~ DeleteContainerAndItems!(_);
List<LibDirectory> mLibDirectories = new List<LibDirectory>() ~ DeleteContainerAndItems!(_); List<LibDirectory> mLibDirectories = new List<LibDirectory>() ~ DeleteContainerAndItems!(_);
List<FileSystemWatcher> mWatchers = new .() ~ DeleteContainerAndItems!(_); List<FileSystemWatcher> mWatchers = new .() ~ DeleteContainerAndItems!(_);
public String mManagedLibPath = new .() ~ delete _;
public bool mLibsChanged; public bool mLibsChanged;
void LibsChanged() void LibsChanged()
@ -221,6 +222,15 @@ namespace IDE
} }
} }
data.GetString("ManagedLibDir", mManagedLibPath);
if ((mManagedLibPath.IsEmpty) && (!mLibDirectories.IsEmpty))
{
var libPath = Path.GetAbsolutePath(mLibDirectories[0].mPath, gApp.mInstallDir, .. scope .());
var managedPath = Path.GetAbsolutePath("../BeefManaged", libPath, .. scope .());
if (Directory.Exists(managedPath))
mManagedLibPath.Set(managedPath);
}
mConfigFiles.Add(configFile); mConfigFiles.Add(configFile);
return .Ok; return .Ok;

View file

@ -76,6 +76,7 @@ namespace IDE
OpenOrNew, OpenOrNew,
Test, Test,
Run, Run,
Update,
GetVersion GetVersion
} }
@ -182,6 +183,7 @@ namespace IDE
public MainFrame mMainFrame; public MainFrame mMainFrame;
public GlobalUndoManager mGlobalUndoManager = new GlobalUndoManager() ~ delete _; public GlobalUndoManager mGlobalUndoManager = new GlobalUndoManager() ~ delete _;
public SourceControl mSourceControl = new SourceControl() ~ delete _; public SourceControl mSourceControl = new SourceControl() ~ delete _;
public GitManager mGitManager = new .() ~ delete _;
public WidgetWindow mPopupWindow; public WidgetWindow mPopupWindow;
public RecentFileSelector mRecentFileSelector; public RecentFileSelector mRecentFileSelector;
@ -223,6 +225,7 @@ namespace IDE
public WakaTime mWakaTime ~ delete _; public WakaTime mWakaTime ~ delete _;
public PackMan mPackMan = new PackMan() ~ delete _; public PackMan mPackMan = new PackMan() ~ delete _;
public HashSet<String> mWantUpdateVersionLocks ~ DeleteContainerAndItems!(_);
public Settings mSettings = new Settings() ~ delete _; public Settings mSettings = new Settings() ~ delete _;
public Workspace mWorkspace = new Workspace() ~ delete _; public Workspace mWorkspace = new Workspace() ~ delete _;
public FileWatcher mFileWatcher = new FileWatcher() ~ delete _; public FileWatcher mFileWatcher = new FileWatcher() ~ delete _;
@ -2145,6 +2148,91 @@ namespace IDE
return true; return true;
} }
bool SaveWorkspaceLockData(bool force = false)
{
if ((mWorkspace.mProjectLockMap.IsEmpty) && (!force))
return true;
StructuredData sd = scope StructuredData();
sd.CreateNew();
sd.Add("FileVersion", 1);
using (sd.CreateObject("Locks"))
{
List<String> projectNames = scope .(mWorkspace.mProjectLockMap.Keys);
projectNames.Sort();
for (var projectName in projectNames)
{
var lock = mWorkspace.mProjectLockMap[projectName];
switch (lock)
{
case .Git(let url, let tag, let hash):
using (sd.CreateObject(projectName))
{
using (sd.CreateObject("Git"))
{
sd.Add("URL", url);
sd.Add("Tag", tag);
sd.Add("Hash", hash);
}
}
default:
}
}
}
String jsonString = scope String();
sd.ToTOML(jsonString);
String lockFileName = scope String();
GetWorkspaceLockFileName(lockFileName);
if (lockFileName.IsEmpty)
return false;
return SafeWriteTextFile(lockFileName, jsonString);
}
bool LoadWorkspaceLockData()
{
String lockFilePath = scope String();
GetWorkspaceLockFileName(lockFilePath);
if (lockFilePath.IsEmpty)
return true;
var sd = scope StructuredData();
if (sd.Load(lockFilePath) case .Err)
return false;
for (var projectName in sd.Enumerate("Locks"))
{
Workspace.Lock lock = default;
if (sd.Contains("Git"))
{
using (sd.Open("Git"))
{
var url = sd.GetString("URL", .. new .());
var tag = sd.GetString("Tag", .. new .());
var hash = sd.GetString("Hash", .. new .());
lock = .Git(url, tag, hash);
}
}
mWorkspace.SetLock(projectName, lock);
}
return true;
}
bool GetWorkspaceLockFileName(String outResult)
{
if (mWorkspace.mDir == null)
return false;
if (mWorkspace.mCompositeFile != null)
outResult.Append(mWorkspace.mCompositeFile.mFilePath, ".bfuser");
else
outResult.Append(mWorkspace.mDir, "/BeefSpace_Lock.toml");
return true;
}
void GetDefaultLayoutDataFileName(String outResult) void GetDefaultLayoutDataFileName(String outResult)
{ {
outResult.Append(mInstallDir, "/DefaultLayout.toml"); outResult.Append(mInstallDir, "/DefaultLayout.toml");
@ -2377,6 +2465,17 @@ namespace IDE
project.mDependencies.Add(dep); project.mDependencies.Add(dep);
} }
public void ProjectCreated(Project project)
{
mProjectPanel.InitProject(project, mProjectPanel.GetSelectedWorkspaceFolder());
mProjectPanel.Sort();
mWorkspace.FixOptions();
mWorkspace.mHasChanged = true;
mWorkspace.ClearProjectNameCache();
CurrentWorkspaceConfigChanged();
}
public Project CreateProject(String projName, String projDir, Project.TargetType targetType) public Project CreateProject(String projName, String projDir, Project.TargetType targetType)
{ {
Project project = new Project(); Project project = new Project();
@ -2391,13 +2490,8 @@ namespace IDE
AddNewProjectToWorkspace(project); AddNewProjectToWorkspace(project);
project.FinishCreate(); project.FinishCreate();
mProjectPanel.InitProject(project, mProjectPanel.GetSelectedWorkspaceFolder()); ProjectCreated(project);
mProjectPanel.Sort();
mWorkspace.FixOptions();
mWorkspace.mHasChanged = true;
mWorkspace.ClearProjectNameCache();
CurrentWorkspaceConfigChanged();
return project; return project;
} }
@ -2517,6 +2611,8 @@ namespace IDE
mBookmarkManager.Clear(); mBookmarkManager.Clear();
mPackMan.CancelAll();
OutputLine("Workspace closed."); OutputLine("Workspace closed.");
} }
@ -2757,9 +2853,16 @@ namespace IDE
return .Err; return .Err;
} }
public void CheckDependenciesLoaded()
{
for (var project in mWorkspace.mProjects)
project.CheckDependenciesLoaded();
}
void FlushDeferredLoadProjects(bool addToUI = false) void FlushDeferredLoadProjects(bool addToUI = false)
{ {
bool hasDeferredProjects = false; bool hasDeferredProjects = false;
bool loadFailed = false;
while (true) while (true)
{ {
@ -2767,11 +2870,29 @@ namespace IDE
for (int projectIdx = 0; projectIdx < mWorkspace.mProjects.Count; projectIdx++) for (int projectIdx = 0; projectIdx < mWorkspace.mProjects.Count; projectIdx++)
{ {
var project = mWorkspace.mProjects[projectIdx]; var project = mWorkspace.mProjects[projectIdx];
if (project.mDeferState == .Searching)
{
if (mPackMan.mFailed)
{
// Just let it fail now
LoadFailed();
project.mDeferState = .None;
project.mFailed = true;
loadFailed = true;
}
else
{
hasDeferredProjects = true;
}
}
if ((project.mDeferState == .ReadyToLoad) || (project.mDeferState == .Pending)) if ((project.mDeferState == .ReadyToLoad) || (project.mDeferState == .Pending))
{ {
hadLoad = true; hadLoad = true;
var projectPath = project.mProjectPath; var projectPath = project.mProjectPath;
if (project.mDeferState == .Pending) if (project.mDeferState == .Pending)
{ {
hasDeferredProjects = true; hasDeferredProjects = true;
@ -2794,9 +2915,26 @@ namespace IDE
} }
if (hasDeferredProjects) if (hasDeferredProjects)
{
mWorkspace.mProjectLoadState = .Preparing; mWorkspace.mProjectLoadState = .Preparing;
}
else else
{
mWorkspace.mProjectLoadState = .Loaded; mWorkspace.mProjectLoadState = .Loaded;
SaveWorkspaceLockData();
CheckDependenciesLoaded();
}
if (loadFailed)
{
mProjectPanel.RebuildUI();
}
}
public void CancelWorkspaceLoading()
{
mPackMan.CancelAll();
FlushDeferredLoadProjects();
} }
protected void LoadWorkspace(BeefVerb verb) protected void LoadWorkspace(BeefVerb verb)
@ -2937,6 +3075,7 @@ namespace IDE
} }
else else
{ {
LoadWorkspaceLockData();
mWorkspace.mProjectFileEntries.Add(new .(workspaceFileName)); mWorkspace.mProjectFileEntries.Add(new .(workspaceFileName));
if (mVerb == .New) if (mVerb == .New)
@ -3044,9 +3183,10 @@ namespace IDE
outRelaunchCmd.Append(" -safe"); outRelaunchCmd.Append(" -safe");
} }
public void RetryProjectLoad(Project project) public void RetryProjectLoad(Project project, bool reloadConfig)
{ {
LoadConfig(); if (reloadConfig)
LoadConfig();
var projectPath = project.mProjectPath; var projectPath = project.mProjectPath;
if (!project.Load(projectPath)) if (!project.Load(projectPath))
@ -3054,6 +3194,8 @@ namespace IDE
Fail(scope String()..AppendF("Failed to load project '{0}' from '{1}'", project.mProjectName, projectPath)); Fail(scope String()..AppendF("Failed to load project '{0}' from '{1}'", project.mProjectName, projectPath));
LoadFailed(); LoadFailed();
project.mFailed = true; project.mFailed = true;
FlushDeferredLoadProjects();
mProjectPanel?.RebuildUI();
} }
else else
{ {
@ -3061,7 +3203,7 @@ namespace IDE
mWorkspace.FixOptions(); mWorkspace.FixOptions();
project.mFailed = false; project.mFailed = false;
mProjectPanel.RebuildUI(); mProjectPanel?.RebuildUI();
CurrentWorkspaceConfigChanged(); CurrentWorkspaceConfigChanged();
} }
} }
@ -3080,7 +3222,17 @@ namespace IDE
String verConfigDir = mWorkspace.mDir; String verConfigDir = mWorkspace.mDir;
if (let project = mWorkspace.FindProject(projectName)) if (let project = mWorkspace.FindProject(projectName))
{
switch (useVerSpec)
{
case .Git(let url, let ver):
if (ver != null)
mPackMan.UpdateGitConstraint(url, ver);
default:
}
return project; return project;
}
if (useVerSpec case .SemVer) if (useVerSpec case .SemVer)
{ {
@ -3153,27 +3305,24 @@ namespace IDE
case .SemVer(let semVer): case .SemVer(let semVer):
// //
case .Git(let url, let ver): case .Git(let url, let ver):
var verReference = new Project.VerReference();
verReference.mSrcProjectName = new String(projectName);
verReference.mVerSpec = _.Duplicate();
project.mVerReferences.Add(verReference);
var checkPath = scope String(); var checkPath = scope String();
if (mPackMan.CheckLock(projectName, checkPath)) if (mPackMan.CheckLock(projectName, checkPath))
{ {
projectFilePath = scope:: String(checkPath); projectFilePath = scope:: String(checkPath);
} }
else else
{
mPackMan.GetWithVersion(projectName, url, ver);
isDeferredLoad = true; isDeferredLoad = true;
}
default: default:
Fail("Invalid version specifier"); Fail("Invalid version specifier");
return .Err(.InvalidVersionSpec); return .Err(.InvalidVersionSpec);
} }
if ((projectFilePath == null) && (!isDeferredLoad)) if ((projectFilePath == null) && (!isDeferredLoad))
{
return .Err(.NotFound); return .Err(.NotFound);
}
if (isDeferredLoad) if (isDeferredLoad)
{ {
@ -3197,6 +3346,59 @@ namespace IDE
return .Ok(project); return .Ok(project);
} }
public void UpdateProjectVersionLocks(params Span<StringView> projectNames)
{
bool removedLock = false;
for (var projectName in projectNames)
{
if (var kv = gApp.mWorkspace.mProjectLockMap.GetAndRemoveAlt(projectName))
{
removedLock = true;
delete kv.key;
kv.value.Dispose();
}
}
if (removedLock)
{
if (SaveAll())
{
SaveWorkspaceLockData(true);
CloseOldBeefManaged();
ReloadWorkspace();
}
}
}
public void UpdateProjectVersionLocks(Span<String> projectNames)
{
List<StringView> svNames = scope .();
for (var name in projectNames)
svNames.Add(name);
UpdateProjectVersionLocks(params (Span<StringView>)svNames);
}
public void NotifyProjectVersionLocks(Span<String> projectNames)
{
if (projectNames.IsEmpty)
return;
String message = scope .();
message.Append((projectNames.Length == 1) ? "Project " : "Projects ");
for (var projectName in projectNames)
{
if (@projectName.Index > 0)
message.Append(", ");
message.AppendF($"'{projectName}'");
}
message.Append((projectNames.Length == 1) ? " has " : " have ");
message.AppendF("modified version constraints. Use 'Update Version Lock' in the project or workspace right-click menus to apply the new constraints.");
MessageDialog("Version Constraints Modified", message, DarkTheme.sDarkTheme.mIconWarning);
}
protected void WorkspaceLoaded() protected void WorkspaceLoaded()
{ {
scope AutoBeefPerf("IDE.WorkspaceLoaded"); scope AutoBeefPerf("IDE.WorkspaceLoaded");
@ -3848,9 +4050,9 @@ namespace IDE
return dialog; return dialog;
} }
public void MessageDialog(String title, String text) public void MessageDialog(String title, String text, Image icon = null)
{ {
Dialog dialog = ThemeFactory.mDefault.CreateDialog(title, text); Dialog dialog = ThemeFactory.mDefault.CreateDialog(title, text, icon);
dialog.mDefaultButton = dialog.AddButton("OK"); dialog.mDefaultButton = dialog.AddButton("OK");
dialog.mEscButton = dialog.mDefaultButton; dialog.mEscButton = dialog.mDefaultButton;
dialog.PopupWindow(mMainWindow); dialog.PopupWindow(mMainWindow);
@ -7433,6 +7635,37 @@ namespace IDE
CloseDocument(activeDocumentPanel); CloseDocument(activeDocumentPanel);
} }
public void CloseOldBeefManaged()
{
List<SourceViewPanel> pendingClosePanels = scope .();
WithSourceViewPanels(scope (sourceViewPanel) =>
{
if (sourceViewPanel.mProjectSource != null)
{
var checkHash = gApp.mPackMan.GetHashFromFilePath(sourceViewPanel.mFilePath, .. scope .());
if (!checkHash.IsEmpty)
{
bool foundHash = false;
if (gApp.mWorkspace.mProjectLockMap.TryGet(sourceViewPanel.mProjectSource.mProject.mProjectName, ?, var lock))
{
if (lock case .Git(let url, let tag, let hash))
{
if (hash == checkHash)
foundHash = true;
}
}
if (!foundHash)
pendingClosePanels.Add(sourceViewPanel);
}
}
});
for (var sourceViewPanel in pendingClosePanels)
CloseDocument(sourceViewPanel);
}
public SourceViewPanel ShowProjectItem(ProjectItem projectItem, bool showTemp = true, bool setFocus = true) public SourceViewPanel ShowProjectItem(ProjectItem projectItem, bool showTemp = true, bool setFocus = true)
{ {
if (projectItem is ProjectSource) if (projectItem is ProjectSource)
@ -7845,7 +8078,7 @@ namespace IDE
case "-autoshutdown": case "-autoshutdown":
mDebugAutoShutdownCounter = 200; mDebugAutoShutdownCounter = 200;
case "-new": case "-new":
mVerb = .New; mVerb = .Open;
case "-testNoExit": case "-testNoExit":
mExitWhenTestScriptDone = false; mExitWhenTestScriptDone = false;
case "-firstRun": case "-firstRun":
@ -8090,15 +8323,16 @@ namespace IDE
#endif #endif
} }
public virtual void OutputErrorLine(String format, params Object[] args) public virtual void OutputErrorLine(StringView format, params Object[] args)
{ {
mWantShowOutput = true; mWantShowOutput = true;
var errStr = scope String(); var errStr = scope String();
errStr.Append("ERROR: ", format); errStr.Append("ERROR: ");
errStr.Append(format);
OutputLineSmart(errStr, params args); OutputLineSmart(errStr, params args);
} }
public virtual void OutputWarnLine(String format, params Object[] args) public virtual void OutputWarnLine(StringView format, params Object[] args)
{ {
var warnStr = scope String(); var warnStr = scope String();
warnStr.AppendF(format, params args); warnStr.AppendF(format, params args);
@ -8113,7 +8347,7 @@ namespace IDE
OutputLine(outStr); OutputLine(outStr);
} }
public virtual void OutputLineSmart(String format, params Object[] args) public virtual void OutputLineSmart(StringView format, params Object[] args)
{ {
String outStr; String outStr;
if (args.Count > 0) if (args.Count > 0)
@ -10027,7 +10261,7 @@ namespace IDE
#endif #endif
} }
mWorkspace.ClearProjectNameCache(); mWorkspace.ClearProjectNameCache();
mProjectPanel.RehupProjects(); mProjectPanel?.RehupProjects();
} }
/*public string GetClangDepConfigName(Project project) /*public string GetClangDepConfigName(Project project)
@ -10064,8 +10298,8 @@ namespace IDE
RemoveProjectItems(project); RemoveProjectItems(project);
} }
mBfResolveCompiler.QueueDeferredResolveAll(); mBfResolveCompiler?.QueueDeferredResolveAll();
mBfResolveCompiler.QueueRefreshViewCommand(.FullRefresh); mBfResolveCompiler?.QueueRefreshViewCommand(.FullRefresh);
return; return;
} }
@ -10093,11 +10327,14 @@ namespace IDE
{ {
mWantsBeefClean = true; mWantsBeefClean = true;
var checkDeclName = (project.mProjectName !== project.mProjectNameDecl) && (mWorkspace.FindProject(project.mProjectNameDecl) == project);
for (var checkProject in mWorkspace.mProjects) for (var checkProject in mWorkspace.mProjects)
{ {
for (var dep in checkProject.mDependencies) for (var dep in checkProject.mDependencies)
{ {
if (dep.mProjectName == project.mProjectName) if ((dep.mProjectName == project.mProjectName) ||
((checkDeclName) && (dep.mProjectName == project.mProjectNameDecl)))
{ {
dep.mProjectName.Set(newName); dep.mProjectName.Set(newName);
checkProject.SetChanged(); checkProject.SetChanged();
@ -10115,14 +10352,27 @@ namespace IDE
} }
project.mProjectName.Set(newName); project.mProjectName.Set(newName);
if (project.mProjectNameDecl != project.mProjectName)
delete project.mProjectNameDecl;
project.mProjectNameDecl = project.mProjectName;
project.SetChanged(); project.SetChanged();
mWorkspace.ClearProjectNameCache(); mWorkspace.ClearProjectNameCache();
mProjectPanel.RebuildUI();
} }
public void RemoveProject(Project project) public void RemoveProject(Project project)
{ {
RemoveProjectItems(project); RemoveProjectItems(project);
if (mWorkspace.mProjectLockMap.GetAndRemove(project.mProjectName) case .Ok(let kv))
{
delete kv.key;
kv.value.Dispose();
if (mWorkspace.mProjectLockMap.IsEmpty)
SaveWorkspaceLockData(true);
}
project.mDeleted = true; project.mDeleted = true;
mWorkspace.SetChanged(); mWorkspace.SetChanged();
mWorkspace.mProjects.Remove(project); mWorkspace.mProjects.Remove(project);
@ -10911,7 +11161,7 @@ namespace IDE
} }
} }
static void QuoteIfNeeded(String str) protected static void QuoteIfNeeded(String str)
{ {
if (!str.Contains(' ')) if (!str.Contains(' '))
return; return;
@ -12398,6 +12648,8 @@ namespace IDE
base.Init(); base.Init();
mSettings.Apply(); mSettings.Apply();
mGitManager.Init();
//Yoop(); //Yoop();
/*for (int i = 0; i < 100*1024*1024; i++) /*for (int i = 0; i < 100*1024*1024; i++)
@ -14865,6 +15117,8 @@ namespace IDE
if (mLongUpdateProfileId != 0) if (mLongUpdateProfileId != 0)
DoLongUpdateCheck(); DoLongUpdateCheck();
mGitManager.Update();
mPackMan.Update();
if (mWakaTime != null) if (mWakaTime != null)
mWakaTime.Update(); mWakaTime.Update();
if (mFindResultsPanel != null) if (mFindResultsPanel != null)
@ -15053,7 +15307,6 @@ namespace IDE
[Import("user32.lib"), CLink, CallingConvention(.Stdcall)] [Import("user32.lib"), CLink, CallingConvention(.Stdcall)]
public static extern bool MessageBeep(MessageBeepType type); public static extern bool MessageBeep(MessageBeepType type);
#endif #endif
} }
static static

View file

@ -9,6 +9,7 @@ using Beefy;
using Beefy.gfx; using Beefy.gfx;
using Beefy.theme.dark; using Beefy.theme.dark;
using IDE.ui; using IDE.ui;
using System.Diagnostics;
namespace IDE namespace IDE
{ {
@ -128,6 +129,29 @@ namespace IDE
return true; return true;
} }
public static void SafeKill(int processId)
{
var beefConExe = scope $"{gApp.mInstallDir}/BeefCon.exe";
ProcessStartInfo procInfo = scope ProcessStartInfo();
procInfo.UseShellExecute = false;
procInfo.SetFileName(beefConExe);
procInfo.SetArguments(scope $"{processId} kill");
procInfo.ActivateWindow = false;
var process = scope SpawnedProcess();
process.Start(procInfo).IgnoreError();
}
public static void SafeKill(SpawnedProcess process)
{
if (process.WaitFor(0))
return;
SafeKill(process.ProcessId);
if (!process.WaitFor(2000))
process.Kill();
}
public static bool IsDirectoryEmpty(StringView dirPath) public static bool IsDirectoryEmpty(StringView dirPath)
{ {
for (let entry in Directory.Enumerate(scope String()..AppendF("{}/*.*", dirPath), .Directories | .Files)) for (let entry in Directory.Enumerate(scope String()..AppendF("{}/*.*", dirPath), .Directories | .Files))

View file

@ -1091,9 +1091,13 @@ namespace IDE
public class GeneralOptions public class GeneralOptions
{ {
[Reflect] [Reflect]
public String mProjectNameDecl; // Points to mProjectNameDecl in Project
[Reflect]
public TargetType mTargetType; public TargetType mTargetType;
[Reflect] [Reflect]
public List<String> mAliases = new .() ~ DeleteContainerAndItems!(_); public List<String> mAliases = new .() ~ DeleteContainerAndItems!(_);
[Reflect]
public SemVer mVersion = new SemVer("") ~ delete _;
} }
public class BeefGlobalOptions public class BeefGlobalOptions
@ -1321,6 +1325,7 @@ namespace IDE
{ {
public VerSpec mVerSpec ~ _.Dispose(); public VerSpec mVerSpec ~ _.Dispose();
public String mProjectName ~ delete _; public String mProjectName ~ delete _;
public bool mDependencyChecked;
} }
public enum DeferState public enum DeferState
@ -1337,13 +1342,20 @@ namespace IDE
public VerSpec mVerSpec ~ _.Dispose(); public VerSpec mVerSpec ~ _.Dispose();
} }
public class ManagedInfo
{
public SemVer mVersion = new .("") ~ delete _;
public String mInfo ~ delete _;
}
public Monitor mMonitor = new Monitor() ~ delete _; public Monitor mMonitor = new Monitor() ~ delete _;
public String mNamespace = new String() ~ delete _; public String mNamespace = new String() ~ delete _;
public String mProjectDir = new String() ~ delete _; public String mProjectDir = new String() ~ delete _;
public String mProjectName = new String() ~ delete _; public String mProjectName = new String() ~ delete _;
public String mProjectNameDecl = mProjectName ~ { if (mProjectNameDecl != mProjectName) delete _; }
public String mProjectPath = new String() ~ delete _; public String mProjectPath = new String() ~ delete _;
public ManagedInfo mManagedInfo ~ delete _;
public DeferState mDeferState; public DeferState mDeferState;
public List<VerReference> mVerReferences = new .() ~ DeleteContainerAndItems!(_);
//public String mLastImportDir = new String() ~ delete _; //public String mLastImportDir = new String() ~ delete _;
public bool mHasChanged; public bool mHasChanged;
@ -1410,6 +1422,16 @@ namespace IDE
} }
} }
public SemVer Version
{
get
{
if (mManagedInfo != null)
return mManagedInfo.mVersion;
return mGeneralOptions.mVersion;
}
}
void SetupDefaultOptions(Options options) void SetupDefaultOptions(Options options)
{ {
options.mBuildOptions.mOtherLinkFlags.Set("$(LinkFlags)"); options.mBuildOptions.mOtherLinkFlags.Set("$(LinkFlags)");
@ -1443,6 +1465,7 @@ namespace IDE
mGeneralOptions.mTargetType = .CustomBuild; mGeneralOptions.mTargetType = .CustomBuild;
SetupDefaultConfigs(); SetupDefaultConfigs();
mGeneralOptions.mProjectNameDecl = mProjectNameDecl;
mBeefGlobalOptions.mStartupObject.Set("Program"); mBeefGlobalOptions.mStartupObject.Set("Program");
} }
@ -1526,6 +1549,21 @@ namespace IDE
Path.GetDirectoryPath(mProjectPath, mProjectDir); Path.GetDirectoryPath(mProjectPath, mProjectDir);
if (structuredData.Load(ProjectFileName) case .Err) if (structuredData.Load(ProjectFileName) case .Err)
return false; return false;
String managedText = scope .();
if (File.ReadAllText(scope $"{mProjectDir}/BeefManaged.toml", managedText) case .Ok)
{
mManagedInfo = new .();
mManagedInfo.mInfo = new .(managedText);
StructuredData msd = scope .();
if (msd.LoadFromString(managedText) case .Ok)
{
if (mManagedInfo.mVersion.mVersion == null)
mManagedInfo.mVersion.mVersion = new .();
msd.GetString("Version", mManagedInfo.mVersion.mVersion);
}
}
} }
else else
{ {
@ -1615,7 +1653,8 @@ namespace IDE
using (data.CreateObject("Project")) using (data.CreateObject("Project"))
{ {
if (!IsSingleFile) if (!IsSingleFile)
data.Add("Name", mProjectName); data.Add("Name", mProjectNameDecl);
data.ConditionalAdd("Version", mGeneralOptions.mVersion.mVersion);
data.ConditionalAdd("TargetType", mGeneralOptions.mTargetType, GetDefaultTargetType()); data.ConditionalAdd("TargetType", mGeneralOptions.mTargetType, GetDefaultTargetType());
data.ConditionalAdd("StartupObject", mBeefGlobalOptions.mStartupObject, IsSingleFile ? "Program" : ""); data.ConditionalAdd("StartupObject", mBeefGlobalOptions.mStartupObject, IsSingleFile ? "Program" : "");
var defaultNamespace = scope String(); var defaultNamespace = scope String();
@ -1955,7 +1994,24 @@ namespace IDE
using (data.Open("Project")) using (data.Open("Project"))
{ {
if (!IsSingleFile) if (!IsSingleFile)
data.GetString("Name", mProjectName); {
var projectName = data.GetString("Name", .. scope .());
if ((!mProjectName.IsEmpty) && (projectName != mProjectName))
{
// If the name we specified clashes with the delclared project name in the config
if (mProjectNameDecl === mProjectName)
{
mProjectNameDecl = new .(projectName);
mGeneralOptions.mProjectNameDecl = mProjectNameDecl;
gApp.mWorkspace.ClearProjectNameCache();
}
else
mProjectNameDecl.Set(projectName);
}
else
mProjectName.Set(projectName);
}
data.GetString("Version", mGeneralOptions.mVersion.mVersion);
ReadStrings("Aliases", mGeneralOptions.mAliases); ReadStrings("Aliases", mGeneralOptions.mAliases);
data.GetString("StartupObject", mBeefGlobalOptions.mStartupObject, IsSingleFile ? "Program" : ""); data.GetString("StartupObject", mBeefGlobalOptions.mStartupObject, IsSingleFile ? "Program" : "");
var defaultNamespace = scope String(); var defaultNamespace = scope String();
@ -2029,7 +2085,7 @@ namespace IDE
{ {
case .Ok(let project): case .Ok(let project):
case .Err(let err): case .Err(let err):
gApp.OutputLineSmart("ERROR: Unable to load project '{0}' specified in project '{1}'", dep.mProjectName, mProjectName); // Give an error later
} }
} }
@ -2232,6 +2288,35 @@ namespace IDE
mRootFolder.StartWatching(); mRootFolder.StartWatching();
} }
public void CheckDependenciesLoaded()
{
for (var dep in mDependencies)
{
if (!dep.mDependencyChecked)
{
var project = gApp.mWorkspace.FindProject(dep.mProjectName);
if (project != null)
{
var projectVersion = project.Version;
if (!projectVersion.mVersion.IsEmpty)
{
if (dep.mVerSpec case .Git(let url, let ver))
{
if (!SemVer.IsVersionMatch(projectVersion, ver))
gApp.OutputLineSmart($"WARNING: Project '{mProjectName}' has version constraint '{ver}' for '{dep.mProjectName}' which is not satisfied by selected version '{projectVersion}'");
}
}
}
else
{
gApp.OutputLineSmart("ERROR: Unable to load project '{0}' specified in project '{1}'", dep.mProjectName, mProjectName);
}
dep.mDependencyChecked = true;
}
}
}
public void FinishCreate(bool allowCreateDir = true) public void FinishCreate(bool allowCreateDir = true)
{ {
if (!mRootFolder.mIsWatching) if (!mRootFolder.mIsWatching)
@ -2414,29 +2499,35 @@ namespace IDE
} }
} }
public bool HasDependency(String projectName, bool checkRecursively = true) public VerSpec* GetDependency(String projectName, bool checkRecursively = true)
{ {
HashSet<Project> checkedProject = scope .(); HashSet<Project> checkedProject = scope .();
bool CheckDependency(Project project) VerSpec* CheckDependency(Project project)
{ {
if (!checkedProject.Add(project)) if (!checkedProject.Add(project))
return false; return null;
for (var dependency in project.mDependencies) for (var dependency in project.mDependencies)
{ {
if (dependency.mProjectName == projectName) if (dependency.mProjectName == projectName)
return true; return &dependency.mVerSpec;
let depProject = gApp.mWorkspace.FindProject(dependency.mProjectName); let depProject = gApp.mWorkspace.FindProject(dependency.mProjectName);
if ((depProject != null) && (checkRecursively) && (CheckDependency(depProject))) if ((depProject != null) && (checkRecursively))
return true; {
var verSpec = CheckDependency(depProject);
if (verSpec != null)
return verSpec;
}
} }
return false; return null;
} }
return CheckDependency(this); return CheckDependency(this);
} }
public bool HasDependency(String projectName, bool checkRecursively = true) => GetDependency(projectName, checkRecursively) != null;
public void SetupDefault(Options options, String configName, String platformName) public void SetupDefault(Options options, String configName, String platformName)
{ {
bool isRelease = configName.Contains("Release"); bool isRelease = configName.Contains("Release");

View file

@ -223,7 +223,8 @@ namespace IDE
None, None,
Loaded, Loaded,
ReadyToLoad, ReadyToLoad,
Preparing Preparing,
Failed
} }
public class ConfigSelection : IHashable, IEquatable public class ConfigSelection : IHashable, IEquatable
@ -488,26 +489,25 @@ namespace IDE
public VerSpec mVerSpec ~ _.Dispose(); public VerSpec mVerSpec ~ _.Dispose();
} }
public class Lock public enum Lock
{ {
public enum Location case Cache;
{ case Local(String path);
case Cache; case Git(String url, String tag, String hash);
case Local(String path);
public void Dispose() public void Dispose()
{
switch (this)
{ {
switch (this) case .Cache:
{ case .Local(let path):
case .Cache: delete path;
case .Local(let path): case Git(var url, var tag, var hash):
delete path; delete url;
} delete tag;
delete hash;
} }
} }
public String mVersion ~ delete _;
public Location mLocation ~ _.Dispose();
} }
public Monitor mMonitor = new Monitor() ~ delete _; public Monitor mMonitor = new Monitor() ~ delete _;
@ -519,7 +519,15 @@ namespace IDE
public List<ProjectSpec> mProjectSpecs = new .() ~ DeleteContainerAndItems!(_); public List<ProjectSpec> mProjectSpecs = new .() ~ DeleteContainerAndItems!(_);
public List<ProjectFileEntry> mProjectFileEntries = new .() ~ DeleteContainerAndItems!(_); public List<ProjectFileEntry> mProjectFileEntries = new .() ~ DeleteContainerAndItems!(_);
public Dictionary<String, Project> mProjectNameMap = new .() ~ DeleteDictionaryAndKeys!(_); public Dictionary<String, Project> mProjectNameMap = new .() ~ DeleteDictionaryAndKeys!(_);
public Dictionary<String, Lock> mProjectLockMap = new .() ~ DeleteDictionaryAndKeysAndValues!(_); public Dictionary<String, Lock> mProjectLockMap = new .() ~
{
for (var kv in ref _)
{
delete kv.key;
kv.valueRef.Dispose();
}
delete _;
}
public Project mStartupProject; public Project mStartupProject;
public bool mLoading; public bool mLoading;
public bool mNeedsCreate; public bool mNeedsCreate;
@ -578,6 +586,20 @@ namespace IDE
ClearAndDeleteItems(mPlatforms); ClearAndDeleteItems(mPlatforms);
} }
public void SetLock(StringView projectName, Lock lock)
{
if (mProjectLockMap.TryAddAlt(projectName, var keyPtr, var valuePtr))
{
*keyPtr = new .(projectName);
*valuePtr = lock;
}
else
{
valuePtr.Dispose();
*valuePtr = lock;
}
}
public void GetPlatformList(List<String> outList) public void GetPlatformList(List<String> outList)
{ {
if (mPlatforms.IsEmpty) if (mPlatforms.IsEmpty)
@ -947,16 +969,33 @@ namespace IDE
{ {
using (mMonitor.Enter()) using (mMonitor.Enter())
{ {
int GetNamePriority(StringView name, Project project)
{
if (project.mProjectName == name)
return 2;
if (project.mProjectNameDecl == name)
return 1;
return 0;
}
void Add(String name, Project project) void Add(String name, Project project)
{ {
bool added = mProjectNameMap.TryAdd(name, var keyPtr, var valuePtr); if (mProjectNameMap.TryAdd(name, var keyPtr, var valuePtr))
if (!added) {
return; *keyPtr = new String(name);
*keyPtr = new String(name); *valuePtr = project;
*valuePtr = project; }
else
{
if (GetNamePriority(name, project) > GetNamePriority(*keyPtr, *valuePtr))
*valuePtr = project;
}
} }
Add(project.mProjectName, project); Add(project.mProjectName, project);
if (project.mProjectNameDecl !== project.mProjectName)
Add(project.mProjectNameDecl, project);
for (var alias in project.mGeneralOptions.mAliases) for (var alias in project.mGeneralOptions.mAliases)
Add(alias, project); Add(alias, project);
@ -979,7 +1018,11 @@ namespace IDE
} }
for (var project in mProjects) for (var project in mProjects)
{
Add(project.mProjectName, project); Add(project.mProjectName, project);
if (project.mProjectName != project.mProjectNameDecl)
Add(project.mProjectNameDecl, project);
}
for (var project in mProjects) for (var project in mProjects)
{ {

View file

@ -10,6 +10,87 @@ namespace IDE.ui
class BuildPropertiesDialog : TargetedPropertiesDialog class BuildPropertiesDialog : TargetedPropertiesDialog
{ {
protected class DependencyEntry : IEquatable, IMultiValued
{
public bool mUse;
public String mURL ~ delete _;
public String mVersion ~ delete _;
public this()
{
}
public ~this()
{
}
public this(DependencyEntry val)
{
mUse = val.mUse;
if (val.mURL != null)
mURL = new .(val.mURL);
if (val.mVersion != null)
mVersion = new .(val.mVersion);
}
public bool Equals(Object val)
{
if (var rhsDE = val as DependencyEntry)
{
return
(mUse == rhsDE.mUse) &&
(mURL == rhsDE.mURL) &&
(mVersion == rhsDE.mVersion);
}
return false;
}
public void GetValue(int idx, String outValue)
{
if ((idx == 1) && (mURL != null))
outValue.Set(mURL);
if ((idx == 2) && (mVersion != null))
outValue.Set(mVersion);
}
public bool SetValue(int idx, StringView value)
{
if (idx == 1)
{
if (value.IsEmpty)
{
DeleteAndNullify!(mURL);
}
else
{
String.NewOrSet!(mURL, value);
mURL.Trim();
}
}
if (idx == 2)
{
if (value.IsEmpty)
{
DeleteAndNullify!(mVersion);
}
else
{
String.NewOrSet!(mVersion, value);
mVersion.Trim();
}
}
return true;
}
public void Set(DependencyEntry value)
{
mUse = value.mUse;
SetValue(1, value.mURL);
SetValue(2, value.mVersion);
}
}
protected class DistinctOptionBuilder protected class DistinctOptionBuilder
{ {
BuildPropertiesDialog mDialog; BuildPropertiesDialog mDialog;

View file

@ -145,6 +145,7 @@ namespace IDE.ui
bool mImportFolderDeferred; bool mImportFolderDeferred;
bool mImportProjectDeferred; bool mImportProjectDeferred;
bool mImportInstalledDeferred; bool mImportInstalledDeferred;
bool mImportRemoteDeferred;
public Dictionary<ListViewItem, ProjectItem> mListViewToProjectMap = new .() ~ delete _; public Dictionary<ListViewItem, ProjectItem> mListViewToProjectMap = new .() ~ delete _;
public Dictionary<ProjectItem, ProjectListViewItem> mProjectToListViewMap = new .() ~ delete _; public Dictionary<ProjectItem, ProjectListViewItem> mProjectToListViewMap = new .() ~ delete _;
public Dictionary<ListViewItem, WorkspaceFolder> mListViewToWorkspaceFolderMap = new .() ~ delete _; public Dictionary<ListViewItem, WorkspaceFolder> mListViewToWorkspaceFolderMap = new .() ~ delete _;
@ -2904,6 +2905,15 @@ namespace IDE.ui
#endif #endif
} }
void ImportRemoteProject()
{
#if !CLI
RemoteProjectDialog dialog = new .();
dialog.Init();
dialog.PopupWindow(gApp.mMainWindow);
#endif
}
public void ShowProjectProperties(Project project) public void ShowProjectProperties(Project project)
{ {
var projectProperties = new ProjectProperties(project); var projectProperties = new ProjectProperties(project);
@ -3081,6 +3091,14 @@ namespace IDE.ui
}); });
if (gApp.IsCompiling) if (gApp.IsCompiling)
anItem.SetDisabled(true); anItem.SetDisabled(true);
anItem = menu.AddItem("Add From Remote...");
anItem.mOnMenuItemSelected.Add(new (item) => {
mImportRemoteDeferred = true;
});
if (gApp.IsCompiling)
anItem.SetDisabled(true);
anItem = menu.AddItem("New Folder"); anItem = menu.AddItem("New Folder");
anItem.mOnMenuItemSelected.Add(new (item) => { anItem.mOnMenuItemSelected.Add(new (item) => {
var workspaceFolder = GetSelectedWorkspaceFolder(); var workspaceFolder = GetSelectedWorkspaceFolder();
@ -3110,7 +3128,18 @@ namespace IDE.ui
} }
else if (gApp.mWorkspace.IsInitialized) else if (gApp.mWorkspace.IsInitialized)
{ {
var item = menu.AddItem("Update Version Locks");
item.mDisabled = gApp.mWorkspace.mProjectLockMap.IsEmpty;
item.mOnMenuItemSelected.Add(new (item) =>
{
List<StringView> projectNames = scope .();
for (var projectName in gApp.mWorkspace.mProjectLockMap.Keys)
projectNames.Add(projectName);
gApp.UpdateProjectVersionLocks(params (Span<StringView>)projectNames);
});
AddOpenContainingFolder(); AddOpenContainingFolder();
menu.AddItem(); menu.AddItem();
AddWorkspaceMenuItems(); AddWorkspaceMenuItems();
@ -3140,7 +3169,7 @@ namespace IDE.ui
{ {
var projectItem = GetSelectedProjectItem(); var projectItem = GetSelectedProjectItem();
if (projectItem != null) if (projectItem != null)
gApp.RetryProjectLoad(projectItem.mProject); gApp.RetryProjectLoad(projectItem.mProject, true);
}); });
menu.AddItem(); menu.AddItem();
//handled = true; //handled = true;
@ -3168,6 +3197,18 @@ namespace IDE.ui
SetAsStartupProject(projectItem.mProject); SetAsStartupProject(projectItem.mProject);
}); });
item = menu.AddItem("Update Version Lock");
item.mDisabled = (projectItem == null) || (!gApp.mWorkspace.mProjectLockMap.ContainsKey(projectItem.mProject.mProjectName));
item.mOnMenuItemSelected.Add(new (item) =>
{
var projectItem = GetSelectedProjectItem();
if (projectItem != null)
{
let project = projectItem.mProject;
gApp.UpdateProjectVersionLocks(project.mProjectName);
}
});
item = menu.AddItem("Lock Project"); item = menu.AddItem("Lock Project");
if (projectItem.mProject.mLocked) if (projectItem.mProject.mLocked)
item.mIconImage = DarkTheme.sDarkTheme.GetImage(.Check); item.mIconImage = DarkTheme.sDarkTheme.GetImage(.Check);
@ -3570,6 +3611,12 @@ namespace IDE.ui
ImportInstalledProject(); ImportInstalledProject();
} }
if (mImportRemoteDeferred)
{
mImportRemoteDeferred= false;
ImportRemoteProject();
}
ValidateCutClipboard(); ValidateCutClipboard();
} }

View file

@ -10,13 +10,12 @@ using Beefy.events;
using Beefy.theme.dark; using Beefy.theme.dark;
using Beefy.gfx; using Beefy.gfx;
using Beefy.geom; using Beefy.geom;
using IDE.Util;
namespace IDE.ui namespace IDE.ui
{ {
public class ProjectProperties : BuildPropertiesDialog public class ProjectProperties : BuildPropertiesDialog
{ {
ValueContainer<String> mVC;
enum CategoryType enum CategoryType
{ {
General, /// General, ///
@ -27,6 +26,7 @@ namespace IDE.ui
Dependencies, Dependencies,
Beef_Global, Beef_Global,
Platform, Platform,
Managed,
Targeted, /// Targeted, ///
Beef_Targeted, Beef_Targeted,
@ -36,10 +36,11 @@ namespace IDE.ui
COUNT COUNT
} }
public Project mProject; public Project mProject;
Dictionary<String, ValueContainer<bool>> mDependencyValuesMap ~ DeleteDictionaryAndKeysAndValues!(_); Dictionary<String, DependencyEntry> mDependencyValuesMap ~ DeleteDictionaryAndKeysAndValues!(_);
Project.Options[] mCurProjectOptions ~ delete _; Project.Options[] mCurProjectOptions ~ delete _;
List<String> mUpdateProjectLocks = new .() ~ DeleteContainerAndItems!(_);
float mLockFlashPct; float mLockFlashPct;
public int32 mNewDebugSessionCountdown; public int32 mNewDebugSessionCountdown;
@ -96,6 +97,7 @@ namespace IDE.ui
AddCategoryItem(globalItem, "Dependencies"); AddCategoryItem(globalItem, "Dependencies");
AddCategoryItem(globalItem, "Beef"); AddCategoryItem(globalItem, "Beef");
AddCategoryItem(globalItem, "Platform"); AddCategoryItem(globalItem, "Platform");
AddCategoryItem(globalItem, "Managed");
globalItem.Open(true, true); globalItem.Open(true, true);
var targetedItem = AddCategoryItem(root, "Targeted"); var targetedItem = AddCategoryItem(root, "Targeted");
@ -149,7 +151,8 @@ namespace IDE.ui
case .General, case .General,
.Project, .Project,
.Dependencies, .Dependencies,
.Beef_Global: .Beef_Global,
.Managed:
return .None; return .None;
case .Platform: case .Platform:
return .Platform; return .Platform;
@ -432,6 +435,7 @@ namespace IDE.ui
default: default:
} }
} }
case .Managed:
case .Build, .Debugging, .Beef_Targeted: case .Build, .Debugging, .Beef_Targeted:
DeleteDistinctBuildOptions(); DeleteDistinctBuildOptions();
DistinctBuildOptions defaultTypeOptions = scope:: .(); DistinctBuildOptions defaultTypeOptions = scope:: .();
@ -530,7 +534,9 @@ namespace IDE.ui
else else
{ {
mCurPropertiesTargets = new Object[1]; mCurPropertiesTargets = new Object[1];
if (categoryType == .Project) if (categoryType == .Managed)
mCurPropertiesTargets[0] = mProject.mManagedInfo;
else if (categoryType == .Project)
mCurPropertiesTargets[0] = mProject.mGeneralOptions; mCurPropertiesTargets[0] = mProject.mGeneralOptions;
else if (categoryType == .Beef_Global) else if (categoryType == .Beef_Global)
mCurPropertiesTargets[0] = mProject.mBeefGlobalOptions; mCurPropertiesTargets[0] = mProject.mBeefGlobalOptions;
@ -600,6 +606,8 @@ namespace IDE.ui
} }
} }
} }
else if (categoryType == CategoryType.Managed)
PopulateManagedOptions();
else if (categoryType == CategoryType.Build) else if (categoryType == CategoryType.Build)
PopulateBuildOptions(); PopulateBuildOptions();
else if (categoryType == CategoryType.Beef_Global ) else if (categoryType == CategoryType.Beef_Global )
@ -619,6 +627,8 @@ namespace IDE.ui
void PopulateGeneralOptions() void PopulateGeneralOptions()
{ {
var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot(); var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
var (listViewItem, propEntry) = AddPropertiesItem(root, "Project Name", "mProjectNameDecl");
AddPropertiesItem(root, "Project Name Aliases", "mAliases");
AddPropertiesItem(root, "Target Type", "mTargetType", scope String[] AddPropertiesItem(root, "Target Type", "mTargetType", scope String[]
( (
"Console Application", "Console Application",
@ -627,9 +637,21 @@ namespace IDE.ui
"Custom Build", "Custom Build",
"Test" "Test"
)); ));
AddPropertiesItem(root, "Project Name Aliases", "mAliases"); AddPropertiesItem(root, "Version", "mVersion.mVersion");
} }
void PopulateManagedOptions()
{
if (mCurPropertiesTargets[0] == null)
return;
var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
var (listViewItem, propEntry) = AddPropertiesItem(root, "Version", "mVersion.mVersion");
propEntry.mReadOnly = true;
(listViewItem, propEntry) = AddPropertiesItem(root, "Info", "mInfo");
propEntry.mAllowMultiline = true;
propEntry.mReadOnly = true;
}
void PopulateWindowsOptions() void PopulateWindowsOptions()
{ {
var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot(); var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
@ -696,7 +718,21 @@ namespace IDE.ui
void PopulateDependencyOptions() void PopulateDependencyOptions()
{ {
mDependencyValuesMap = new Dictionary<String, ValueContainer<bool>>(); mPropPage.mPropertiesListView.mColumns[0].Label = "Project";
mPropPage.mPropertiesListView.mColumns[0].mMinWidth = GS!(100);
mPropPage.mPropertiesListView.mColumns[0].mWidth = GS!(180);
mPropPage.mPropertiesListView.mColumns[1].Label = "";
mPropPage.mPropertiesListView.mColumns[1].mMinWidth = GS!(20);
mPropPage.mPropertiesListView.mColumns[1].mWidth = GS!(20);
mPropPage.mPropertiesListView.AddColumn(180, "Remote URL");
mPropPage.mPropertiesListView.mColumns[2].mMinWidth = GS!(100);
mPropPage.mPropertiesListView.AddColumn(180, "Ver Constraint");
mPropPage.mPropertiesListView.mColumns[3].mMinWidth = GS!(100);
mDependencyValuesMap = new .();
var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot(); var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
var category = root; var category = root;
@ -719,63 +755,184 @@ namespace IDE.ui
projectNames.Sort(scope (a, b) => String.Compare(a, b, true)); projectNames.Sort(scope (a, b) => String.Compare(a, b, true));
for (var projectName in projectNames) for (var projectName in projectNames)
{ {
var dependencyContainer = new ValueContainer<bool>(); var project = gApp.mWorkspace.FindProject(projectName);
dependencyContainer.mValue = mProject.HasDependency(projectName, false);
mDependencyValuesMap[new String(projectName)] = dependencyContainer; var dependencyEntry = new DependencyEntry();
var verSpec = mProject.GetDependency(projectName, false);
if (verSpec != null)
{
dependencyEntry.mUse = true;
if (verSpec case .Git(let url, let ver))
{
dependencyEntry.mURL = new .(url);
if (ver != null)
dependencyEntry.mVersion = new .(ver.mVersion);
}
}
mDependencyValuesMap[new String(projectName)] = dependencyEntry;
var (listViewItem, propItem) = AddPropertiesItem(category, projectName); var (listViewItem, propItem) = AddPropertiesItem(category, projectName);
if (IDEApp.sApp.mWorkspace.FindProject(projectName) == null) if (IDEApp.sApp.mWorkspace.FindProject(projectName) == null)
listViewItem.mTextColor = Color.Mult(DarkTheme.COLOR_TEXT, 0xFFFF6060); listViewItem.mTextColor = Color.Mult(DarkTheme.COLOR_TEXT, 0xFFFF6060);
var subItem = listViewItem.CreateSubItem(1); var subItem = (DarkListViewItem)listViewItem.CreateSubItem(1);
var checkbox = new DarkCheckBox(); var checkbox = new DarkCheckBox();
checkbox.Checked = dependencyContainer.mValue; checkbox.Checked = dependencyEntry.mUse;
checkbox.Resize(0, 0, DarkTheme.sUnitSize, DarkTheme.sUnitSize); checkbox.Resize(0, 0, DarkTheme.sUnitSize, DarkTheme.sUnitSize);
subItem.AddWidget(checkbox); subItem.AddWidget(checkbox);
PropEntry[] propEntries = new PropEntry[1]; PropEntry[] propEntries = new PropEntry[1];
PropEntry propEntry = new PropEntry(); PropEntry propEntry = new PropEntry();
propEntry.mTarget = dependencyContainer; propEntry.mTarget = dependencyEntry;
//propEntry.mFieldInfo = dependencyContainer.GetType().GetField("mValue").Value; propEntry.mOrigValue = Variant.Create(dependencyEntry);
propEntry.mOrigValue = Variant.Create(dependencyContainer.mValue); propEntry.mCurValue = Variant.Create(new DependencyEntry(dependencyEntry), true);
propEntry.mCurValue = propEntry.mOrigValue;
propEntry.mListViewItem = listViewItem; propEntry.mListViewItem = listViewItem;
propEntry.mCheckBox = checkbox; propEntry.mCheckBox = checkbox;
propEntry.mApplyAction = new () => propEntry.mApplyAction = new () =>
{ {
if (propEntry.mCurValue.Get<bool>()) bool updateProjectLock = false;
var dependencyEntry = propEntry.mCurValue.Get<DependencyEntry>();
if (dependencyEntry.mUse)
{ {
if (!mProject.HasDependency(listViewItem.mLabel)) VerSpec verSpec = default;
if (dependencyEntry.mURL != null)
verSpec = .Git(new .(dependencyEntry.mURL), (dependencyEntry.mVersion != null) ? new .(dependencyEntry.mVersion) : null);
else if (dependencyEntry.mVersion != null)
verSpec = .SemVer(new .(dependencyEntry.mVersion));
else
verSpec = .SemVer(new .("*"));
var verSpecPtr = mProject.GetDependency(listViewItem.mLabel);
if (verSpecPtr == null)
{ {
if (verSpec case .Git(let url, let ver))
updateProjectLock = true;
var dep = new Project.Dependency(); var dep = new Project.Dependency();
dep.mProjectName = new String(listViewItem.mLabel); dep.mProjectName = new String(listViewItem.mLabel);
dep.mVerSpec = .SemVer(new .("*")); dep.mVerSpec = verSpec;
mProject.mDependencies.Add(dep); mProject.mDependencies.Add(dep);
} }
else
{
if (*verSpecPtr != verSpec)
{
if ((*verSpecPtr case .Git) ||
(verSpecPtr case .Git))
updateProjectLock = true;
verSpecPtr.Dispose();
*verSpecPtr = verSpec;
}
}
} }
else else
{ {
int idx = mProject.mDependencies.FindIndex(scope (dep) => dep.mProjectName == listViewItem.mLabel); int idx = mProject.mDependencies.FindIndex(scope (dep) => dep.mProjectName == listViewItem.mLabel);
if (idx != -1) if (idx != -1)
{ {
var dep = mProject.mDependencies[idx];
if (dep.mVerSpec case .Git)
updateProjectLock = true;
delete mProject.mDependencies[idx]; delete mProject.mDependencies[idx];
mProject.mDependencies.RemoveAt(idx); mProject.mDependencies.RemoveAt(idx);
} }
} }
propEntry.mOrigValue = propEntry.mCurValue;
var origDependencyEntry = propEntry.mOrigValue.Get<DependencyEntry>();
origDependencyEntry.Set(dependencyEntry);
if (updateProjectLock)
mUpdateProjectLocks.Add(new .(listViewItem.Label));
}; };
checkbox.mOnMouseUp.Add(new (evt) => { PropEntry.DisposeVariant(ref propEntry.mCurValue); propEntry.mCurValue = Variant.Create(checkbox.Checked); }); checkbox.mOnMouseUp.Add(new (evt) =>
{
var dependencyEntry = propEntry.mCurValue.Get<DependencyEntry>();
dependencyEntry.mUse = !dependencyEntry.mUse;
if (dependencyEntry.mUse)
{
var projectName = listViewItem.Label;
for (var projectSpec in gApp.mWorkspace.mProjectSpecs)
{
if (projectSpec.mProjectName == projectName)
{
if (projectSpec.mVerSpec case .Git(let url, let ver))
{
dependencyEntry.SetValue(1, url);
dependencyEntry.SetValue(2, ver.mVersion);
}
}
}
var propEntries = mPropPage.mPropEntries[listViewItem];
UpdatePropertyValue(propEntries);
}
else
{
DeleteAndNullify!(dependencyEntry.mURL);
DeleteAndNullify!(dependencyEntry.mVersion);
var propEntries = mPropPage.mPropEntries[listViewItem];
UpdatePropertyValue(propEntries);
}
});
subItem = (.)listViewItem.GetOrCreateSubItem(2);
if (dependencyEntry.mURL != null)
subItem.Label = dependencyEntry.mURL;
subItem.mOnMouseDown.Add(new => DepPropValueClicked);
subItem = (.)listViewItem.GetOrCreateSubItem(3);
if (dependencyEntry.mVersion != null)
{
subItem.Label = dependencyEntry.mVersion;
if (project != null)
{
var version = project.Version;
if (!version.IsEmpty)
{
if (!SemVer.IsVersionMatch(version.mVersion, dependencyEntry.mVersion))
subItem.mTextColor = Color.Mult(DarkTheme.COLOR_TEXT, 0xFFFFFF60);
}
}
}
subItem.mOnMouseDown.Add(new => DepPropValueClicked);
propEntries[0] = propEntry; propEntries[0] = propEntry;
mPropPage.mPropEntries[listViewItem] = propEntries; mPropPage.mPropEntries[listViewItem] = propEntries;
} }
} }
protected void DepPropValueClicked(MouseEvent theEvent)
{
DarkListViewItem clickedItem = (DarkListViewItem)theEvent.mSender;
if (clickedItem.mColumnIdx == 0)
{
clickedItem.mListView.SetFocus();
clickedItem.mListView.GetRoot().SelectItemExclusively(clickedItem);
return;
}
if (theEvent.mX != -1)
{
clickedItem.mListView.GetRoot().SelectItemExclusively(null);
}
DarkListViewItem item = (DarkListViewItem)clickedItem;
DarkListViewItem rootItem = (DarkListViewItem)clickedItem.GetSubItem(0);
PropEntry[] propertyEntries = mPropPage.mPropEntries[rootItem];
if (propertyEntries[0].mDisabled)
return;
EditValue(item, propertyEntries, clickedItem.mColumnIdx - 1);
}
protected override Object[] PhysAddNewDistinctBuildOptions() protected override Object[] PhysAddNewDistinctBuildOptions()
{ {
if (mCurProjectOptions == null) if (mCurProjectOptions == null)
@ -985,6 +1142,8 @@ namespace IDE.ui
/*if (!AssertNotCompilingOrRunning()) /*if (!AssertNotCompilingOrRunning())
return false;*/ return false;*/
String newProjectName = scope .();
using (mProject.mMonitor.Enter()) using (mProject.mMonitor.Enter())
{ {
for (var targetedConfigData in mConfigDatas) for (var targetedConfigData in mConfigDatas)
@ -1000,9 +1159,18 @@ namespace IDE.ui
for (var propEntry in propEntries) for (var propEntry in propEntries)
{ {
if (propEntry.HasChanged()) if (propEntry.HasChanged())
{ {
configDataHadChange = true; if ((propEntry.mFieldInfo != default) && (propEntry.mFieldInfo.Name == "mProjectNameDecl"))
propEntry.ApplyValue(); {
var newName = propEntry.mCurValue.Get<String>();
newProjectName.Append(newName);
newProjectName.Trim();
}
else
{
configDataHadChange = true;
propEntry.ApplyValue();
}
} }
} }
if (propPage == mPropPage) if (propPage == mPropPage)
@ -1060,6 +1228,9 @@ namespace IDE.ui
ClearTargetedData(); ClearTargetedData();
} }
if (!newProjectName.IsEmpty)
gApp.RenameProject(mProject, newProjectName);
return true; return true;
} }
@ -1081,6 +1252,7 @@ namespace IDE.ui
{ {
base.Close(); base.Close();
SetWorkspaceData(false); SetWorkspaceData(false);
gApp.NotifyProjectVersionLocks(mUpdateProjectLocks);
} }
public override void PopupWindow(WidgetWindow parentWindow, float offsetX = 0, float offsetY = 0) public override void PopupWindow(WidgetWindow parentWindow, float offsetX = 0, float offsetY = 0)

View file

@ -114,6 +114,12 @@ namespace IDE.ui
public class PropertiesDialog : IDEDialog public class PropertiesDialog : IDEDialog
{ {
public interface IMultiValued
{
void GetValue(int idx, String outValue);
bool SetValue(int idx, StringView value);
}
class OwnedStringList : List<String> class OwnedStringList : List<String>
{ {
@ -215,6 +221,7 @@ namespace IDE.ui
public String mRelPath ~ delete _; public String mRelPath ~ delete _;
public bool mIsTypeWildcard; public bool mIsTypeWildcard;
public bool mAllowMultiline; public bool mAllowMultiline;
public bool mReadOnly;
public Insets mEditInsets ~ delete _; public Insets mEditInsets ~ delete _;
public ~this() public ~this()
@ -305,6 +312,18 @@ namespace IDE.ui
} }
return true; return true;
} }
else if (type.IsObject)
{
var lhsObj = lhs.Get<Object>();
var rhsObj = rhs.Get<Object>();
if ((var lhsEq = lhsObj as IEquatable) && (var rhsEq = rhsObj as IEquatable))
{
return lhsEq.Equals(rhsEq);
}
return false;
}
else // Could be an int or enum else // Could be an int or enum
return Variant.Equals!<int32>(lhs, rhs); return Variant.Equals!<int32>(lhs, rhs);
} }
@ -815,7 +834,7 @@ namespace IDE.ui
if (mPropEditWidget != null) if (mPropEditWidget != null)
{ {
DarkListViewItem editItem = (DarkListViewItem)mEditingListViewItem.GetSubItem(1); DarkListViewItem editItem = (DarkListViewItem)mEditingListViewItem;
let propEntry = mEditingProps[0]; let propEntry = mEditingProps[0];
float xPos; float xPos;
@ -871,7 +890,7 @@ namespace IDE.ui
editWidget.GetText(newValue); editWidget.GetText(newValue);
newValue.Trim(); newValue.Trim();
DarkListViewItem item = (DarkListViewItem)mEditingListViewItem; DarkListViewItem rootItem = (DarkListViewItem)mEditingListViewItem.GetSubItem(0);
//DarkListViewItem valueItem = (DarkListViewItem)item.GetSubItem(1); //DarkListViewItem valueItem = (DarkListViewItem)item.GetSubItem(1);
if (!editWidget.mEditWidgetContent.HasUndoData()) if (!editWidget.mEditWidgetContent.HasUndoData())
@ -920,14 +939,14 @@ namespace IDE.ui
{ {
// //
} }
else if (editingProp.mListViewItem != item) else if (editingProp.mListViewItem != rootItem)
{ {
List<String> curEntries = editingProp.mCurValue.Get<List<String>>(); List<String> curEntries = editingProp.mCurValue.Get<List<String>>();
List<String> entries = new List<String>(curEntries.GetEnumerator()); List<String> entries = new List<String>(curEntries.GetEnumerator());
for (int32 childIdx = 0; childIdx < editingProp.mListViewItem.GetChildCount(); childIdx++) for (int32 childIdx = 0; childIdx < editingProp.mListViewItem.GetChildCount(); childIdx++)
{ {
if (item == editingProp.mListViewItem.GetChildAtIndex(childIdx)) if (rootItem == editingProp.mListViewItem.GetChildAtIndex(childIdx))
{ {
if (childIdx >= entries.Count) if (childIdx >= entries.Count)
entries.Add(new String(newValue)); entries.Add(new String(newValue));
@ -1027,6 +1046,11 @@ namespace IDE.ui
setValue = false; setValue = false;
} }
} }
else if ((curVariantType.IsObject) && (var multiValue = prevValue.Get<Object>() as IMultiValued))
{
multiValue.SetValue(mEditingListViewItem.mColumnIdx - 1, newValue);
setValue = false;
}
else else
editingProp.mCurValue = Variant.Create(new String(newValue), true); editingProp.mCurValue = Variant.Create(new String(newValue), true);
@ -1247,37 +1271,49 @@ namespace IDE.ui
if (ewc.mIsMultiline) if (ewc.mIsMultiline)
editWidget.InitScrollbars(false, true); editWidget.InitScrollbars(false, true);
if (propEntry.mReadOnly)
editWidget.mEditWidgetContent.mIsReadOnly = true;
editWidget.mScrollContentInsets.Set(GS!(3), GS!(3), GS!(1), GS!(3)); editWidget.mScrollContentInsets.Set(GS!(3), GS!(3), GS!(1), GS!(3));
editWidget.Content.mTextInsets.Set(GS!(-3), GS!(2), 0, GS!(2)); editWidget.Content.mTextInsets.Set(GS!(-3), GS!(2), 0, GS!(2));
//editWidget.RehupSize(); //editWidget.RehupSize();
if (subValueIdx != -1) if (subValueIdx != -1)
{ {
List<String> stringList = propEntry.mCurValue.Get<List<String>>(); var obj = propEntry.mCurValue.Get<Object>();
if (subValueIdx < stringList.Count) if (var multiValued = obj as IMultiValued)
editWidget.SetText(stringList[subValueIdx]); {
var label = multiValued.GetValue(subValueIdx, .. scope .());
editWidget.SetText(label);
}
else
{
List<String> stringList = obj as List<String>;
if (subValueIdx < stringList.Count)
editWidget.SetText(stringList[subValueIdx]);
MoveItemWidget moveItemWidget; MoveItemWidget moveItemWidget;
if (subValueIdx > 0) if (subValueIdx > 0)
{ {
moveItemWidget = new MoveItemWidget(); moveItemWidget = new MoveItemWidget();
editWidget.AddWidget(moveItemWidget); editWidget.AddWidget(moveItemWidget);
moveItemWidget.Resize(6, editWidget.mY - GS!(16), GS!(20), GS!(20)); moveItemWidget.Resize(6, editWidget.mY - GS!(16), GS!(20), GS!(20));
moveItemWidget.mArrowDir = -1; moveItemWidget.mArrowDir = -1;
moveItemWidget.mOnMouseDown.Add(new (evt) => { MoveEditingItem(subValueIdx, -1); }); moveItemWidget.mOnMouseDown.Add(new (evt) => { MoveEditingItem(subValueIdx, -1); });
if (!ewc.mIsMultiline) if (!ewc.mIsMultiline)
editWidget.mOnKeyDown.Add(new (evt) => { if (evt.mKeyCode == KeyCode.Up) MoveEditingItem(subValueIdx, -1); }); editWidget.mOnKeyDown.Add(new (evt) => { if (evt.mKeyCode == KeyCode.Up) MoveEditingItem(subValueIdx, -1); });
} }
if (subValueIdx < stringList.Count - 1) if (subValueIdx < stringList.Count - 1)
{ {
moveItemWidget = new MoveItemWidget(); moveItemWidget = new MoveItemWidget();
editWidget.AddWidget(moveItemWidget); editWidget.AddWidget(moveItemWidget);
moveItemWidget.Resize(6, editWidget.mY + GS!(16), GS!(20), GS!(20)); moveItemWidget.Resize(6, editWidget.mY + GS!(16), GS!(20), GS!(20));
moveItemWidget.mArrowDir = 1; moveItemWidget.mArrowDir = 1;
moveItemWidget.mOnMouseDown.Add(new (evt) => { MoveEditingItem(subValueIdx, 1); }); moveItemWidget.mOnMouseDown.Add(new (evt) => { MoveEditingItem(subValueIdx, 1); });
if (!ewc.mIsMultiline) if (!ewc.mIsMultiline)
editWidget.mOnKeyDown.Add(new (evt) => { if (evt.mKeyCode == KeyCode.Down) MoveEditingItem(subValueIdx, 1); }); editWidget.mOnKeyDown.Add(new (evt) => { if (evt.mKeyCode == KeyCode.Down) MoveEditingItem(subValueIdx, 1); });
} }
}
} }
else else
{ {
@ -1363,8 +1399,8 @@ namespace IDE.ui
hasChanged = true; hasChanged = true;
} }
if (propEntry.mFieldInfo == default(FieldInfo)) /*if (propEntry.mFieldInfo == default(FieldInfo))
return; return;*/
var curVariantType = propEntry.mCurValue.VariantType; var curVariantType = propEntry.mCurValue.VariantType;
@ -1516,6 +1552,15 @@ namespace IDE.ui
valueItem.Label = allValues; valueItem.Label = allValues;
FixLabel(valueItem); FixLabel(valueItem);
} }
else if ((curVariantType.IsObject) && (var multiValue = propEntry.mCurValue.Get<Object>() as IMultiValued))
{
for (int columnIdx in 1..<propEntry.mListViewItem.mSubItems.Count)
{
var subItem = propEntry.mListViewItem.GetSubItem(columnIdx);
var label = multiValue.GetValue(columnIdx - 1, .. scope .());
subItem.Label = label;
}
}
else if (propEntry.mCheckBox != null) else if (propEntry.mCheckBox != null)
{ {
propEntry.mCheckBox.Checked = propEntry.mCurValue.Get<bool>(); propEntry.mCheckBox.Checked = propEntry.mCurValue.Get<bool>();
@ -2026,9 +2071,10 @@ namespace IDE.ui
clickedItem.mListView.GetRoot().SelectItemExclusively(null); clickedItem.mListView.GetRoot().SelectItemExclusively(null);
} }
DarkListViewItem item = (DarkListViewItem)clickedItem.GetSubItem(0); DarkListViewItem item = (DarkListViewItem)clickedItem;
DarkListViewItem rootItem = (DarkListViewItem)item.GetSubItem(0);
PropEntry[] propertyEntries = mPropPage.mPropEntries[item]; PropEntry[] propertyEntries = mPropPage.mPropEntries[rootItem];
if (propertyEntries[0].mDisabled) if (propertyEntries[0].mDisabled)
return; return;
EditValue(item, propertyEntries); EditValue(item, propertyEntries);
@ -2039,7 +2085,7 @@ namespace IDE.ui
var propEntry = propEntries[0]; var propEntry = propEntries[0];
DarkListViewItem parentItem = propEntry.mListViewItem; DarkListViewItem parentItem = propEntry.mListViewItem;
DarkListViewItem clickedItem = (DarkListViewItem)parentItem.GetChildAtIndex(idx); DarkListViewItem clickedItem = (DarkListViewItem)parentItem.GetChildAtIndex(idx);
DarkListViewItem item = (DarkListViewItem)clickedItem.GetSubItem(0); DarkListViewItem item = (DarkListViewItem)clickedItem.GetSubItem(1);
EditValue(item, propEntries, idx); EditValue(item, propEntries, idx);
} }

View file

@ -0,0 +1,179 @@
#pragma warning disable 168
using System;
using System.Collections;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Beefy;
using Beefy.gfx;
using Beefy.theme.dark;
using Beefy.widgets;
using Beefy.theme;
using IDE.Util;
namespace IDE.ui
{
public class RemoteProjectDialog : IDEDialog
{
public EditWidget mURLEdit;
public EditWidget mVersionEdit;
public DarkComboBox mTargetComboBox;
static String[1] sApplicationTypeNames =
.("Git");
public bool mNameChanged;
public String mDirBase ~ delete _;
public this()
{
mTitle = new String("Add Remote Project");
}
public override void CalcSize()
{
mWidth = GS!(320);
mHeight = GS!(200);
}
enum CreateFlags
{
None,
NonEmptyDirOkay = 1,
}
bool CreateProject(CreateFlags createFlags = .None)
{
var app = IDEApp.sApp;
String url = scope String();
mURLEdit.GetText(url);
url.Trim();
if (url.IsEmpty)
{
mURLEdit.SetFocus();
app.Fail("Invalid URL");
return false;
}
var projName = Path.GetFileName(url, .. scope .());
var version = mVersionEdit.GetText(.. scope .())..Trim();
var otherProject = app.mWorkspace.FindProject(projName);
if (otherProject != null)
{
mURLEdit.SetFocus();
app.Fail("A project with this name already exists in the workspace.");
return false;
}
VerSpec verSpec = .Git(url, scope .(version));
if (var project = gApp.AddProject(projName, verSpec))
{
//gApp.ProjectCreated(project);
app.mWorkspace.SetChanged();
gApp.[Friend]FlushDeferredLoadProjects(true);
//gApp.RetryProjectLoad(project, false);
//gApp.AddProjectToWorkspace(project);
var projectSpec = new Workspace.ProjectSpec();
projectSpec.mProjectName = new .(project.mProjectName);
projectSpec.mVerSpec = .Git(new .(url), new .(version));
gApp.mWorkspace.mProjectSpecs.Add(projectSpec);
}
return true;
}
public void UpdateProjectName()
{
if (!mNameChanged)
{
String path = scope .();
mURLEdit.GetText(path);
path.Trim();
if ((path.EndsWith('\\')) || (path.EndsWith('/')))
path.RemoveFromEnd(1);
String projName = scope .();
Path.GetFileName(path, projName);
mVersionEdit.SetText(projName);
}
}
public void Init()
{
mDefaultButton = AddButton("Create", new (evt) =>
{
if (!CreateProject()) evt.mCloseDialog = false;
});
mEscButton = AddButton("Cancel", new (evt) => Close());
if (gApp.mWorkspace.IsInitialized)
mDirBase = new String(gApp.mWorkspace.mDir);
else
mDirBase = new String();
mURLEdit = new DarkEditWidget();
AddEdit(mURLEdit);
mURLEdit.mOnContentChanged.Add(new (dlg) =>
{
});
mVersionEdit = AddEdit("");
mVersionEdit.mOnContentChanged.Add(new (dlg) =>
{
if (mVersionEdit.mHasFocus)
mNameChanged = true;
});
mTargetComboBox = new DarkComboBox();
mTargetComboBox.Label = sApplicationTypeNames[0];
mTargetComboBox.mPopulateMenuAction.Add(new (dlg) =>
{
for (var applicationTypeName in sApplicationTypeNames)
{
var item = dlg.AddItem(applicationTypeName);
item.mOnMenuItemSelected.Add(new (item) =>
{
mTargetComboBox.Label = item.mLabel;
MarkDirty();
});
}
});
AddWidget(mTargetComboBox);
mTabWidgets.Add(mTargetComboBox);
}
public override void PopupWindow(WidgetWindow parentWindow, float offsetX = 0, float offsetY = 0)
{
base.PopupWindow(parentWindow, offsetX, offsetY);
mURLEdit.SetFocus();
}
public override void ResizeComponents()
{
base.ResizeComponents();
float curY = mHeight - GS!(30) - mButtonBottomMargin;
mVersionEdit.Resize(GS!(16), curY - GS!(36), mWidth - GS!(16) * 2, GS!(24));
curY -= GS!(50);
mURLEdit.Resize(GS!(16), curY - GS!(36), mWidth - GS!(16) * 2, GS!(24));
curY -= GS!(60);
mTargetComboBox.Resize(GS!(16), curY - GS!(36), mWidth - GS!(16) * 2, GS!(28));
}
public override void Draw(Graphics g)
{
base.Draw(g);
g.DrawString("Remote Project URL", mURLEdit.mX, mURLEdit.mY - GS!(20));
g.DrawString("Version Constraint (Blank for HEAD)", mVersionEdit.mX, mVersionEdit.mY - GS!(20));
}
}
}

View file

@ -414,7 +414,8 @@ namespace IDE.ui
if (mGettingSymbolInfo) if (mGettingSymbolInfo)
{ {
gApp.Fail("Cannot rename symbols here"); if (gApp.mWorkspace.mProjectLoadState == .Loaded)
gApp.Fail("Cannot rename symbols here");
mGettingSymbolInfo = false; mGettingSymbolInfo = false;
return; return;
} }
@ -430,6 +431,12 @@ namespace IDE.ui
if ((mKind == Kind.ShowFileReferences) || (mResolveParams.mLocalId != -1)) if ((mKind == Kind.ShowFileReferences) || (mResolveParams.mLocalId != -1))
{ {
mParser = IDEApp.sApp.mBfResolveSystem.FindParser(mSourceViewPanel.mProjectSource); mParser = IDEApp.sApp.mBfResolveSystem.FindParser(mSourceViewPanel.mProjectSource);
if (mParser == null)
{
mGettingSymbolInfo = false;
return;
}
if ((mResolveParams != null) && (mResolveParams.mLocalId != -1)) if ((mResolveParams != null) && (mResolveParams.mLocalId != -1))
mParser.SetAutocomplete(mCursorPos); mParser.SetAutocomplete(mCursorPos);
else else

View file

@ -1932,7 +1932,7 @@ namespace IDE.ui
} }
else if (resolveType == ResolveType.GetCurrentLocation) else if (resolveType == ResolveType.GetCurrentLocation)
{ {
PrimaryNavigationBar.SetLocation(autocompleteInfo); PrimaryNavigationBar.SetLocation(autocompleteInfo ?? "");
} }
else if ((resolveType == .Autocomplete) || (resolveType == .GetFixits)) else if ((resolveType == .Autocomplete) || (resolveType == .GetFixits))
{ {

View file

@ -22,7 +22,7 @@ namespace IDE.ui
public DarkButton mSafeModeButton; public DarkButton mSafeModeButton;
public bool mWasCompiling; public bool mWasCompiling;
public int mEvalCount; public int mEvalCount;
public ImageWidget mCancelSymSrvButton; public ImageWidget mCancelButton;
public int mDirtyDelay; public int mDirtyDelay;
public int mStatusBoxUpdateCnt = -1; public int mStatusBoxUpdateCnt = -1;
@ -117,8 +117,8 @@ namespace IDE.ui
mConfigComboBox.Resize(mWidth - btnLeft, GS!(0), GS!(120), GS!(24)); mConfigComboBox.Resize(mWidth - btnLeft, GS!(0), GS!(120), GS!(24));
mPlatformComboBox.Resize(mWidth - btnLeft - GS!(120), GS!(0), GS!(120), GS!(24)); mPlatformComboBox.Resize(mWidth - btnLeft - GS!(120), GS!(0), GS!(120), GS!(24));
if (mCancelSymSrvButton != null) if (mCancelButton != null)
mCancelSymSrvButton.Resize(GS!(546), 0, GS!(20), GS!(20)); mCancelButton.Resize(GS!(546), 0, GS!(20), GS!(20));
if (mSafeModeButton != null) if (mSafeModeButton != null)
{ {
@ -182,19 +182,31 @@ namespace IDE.ui
else else
mEvalCount = 0; mEvalCount = 0;
void ShowCancelButton()
{
if (mCancelButton == null)
{
mCancelButton = new ImageWidget();
mCancelButton.mImage = DarkTheme.sDarkTheme.GetImage(.Close);
mCancelButton.mOverImage = DarkTheme.sDarkTheme.GetImage(.CloseOver);
mCancelButton.mOnMouseClick.Add(new (evt) =>
{
if (gApp.mWorkspace.mProjectLoadState == .Preparing)
{
gApp.CancelWorkspaceLoading();
}
else
gApp.mDebugger.CancelSymSrv();
});
AddWidget(mCancelButton);
ResizeComponents();
}
}
if (debugState == .SearchingSymSrv) if (debugState == .SearchingSymSrv)
{ {
MarkDirtyEx(); MarkDirtyEx();
ShowCancelButton();
if (mCancelSymSrvButton == null)
{
mCancelSymSrvButton = new ImageWidget();
mCancelSymSrvButton.mImage = DarkTheme.sDarkTheme.GetImage(.Close);
mCancelSymSrvButton.mOverImage = DarkTheme.sDarkTheme.GetImage(.CloseOver);
mCancelSymSrvButton.mOnMouseClick.Add(new (evt) => { gApp.mDebugger.CancelSymSrv(); });
AddWidget(mCancelSymSrvButton);
ResizeComponents();
}
float len = GS!(200); float len = GS!(200);
float x = GS!(350); float x = GS!(350);
@ -209,15 +221,45 @@ namespace IDE.ui
} }
} }
} }
else if (gApp.mWorkspace.mProjectLoadState == .Preparing)
{
MarkDirtyEx();
ShowCancelButton();
float len = GS!(200);
float x = GS!(350);
Rect completionRect = Rect(x, GS!(1), len, GS!(17));
String status = scope .();
for (var workItem in gApp.mPackMan.mWorkItems)
{
if (workItem.mGitInstance == null)
break;
//DrawCompletion(workItem.mGitInstance.mProgress);
status.AppendF($"Retrieving {workItem.mProjectName}: {(int)(workItem.mGitInstance.mProgress * 100)}%");
}
Point mousePos;
if (DarkTooltipManager.CheckMouseover(this, 25, out mousePos, true))
{
if (completionRect.Contains(mousePos.x, mousePos.y))
{
if (!status.IsEmpty)
DarkTooltipManager.ShowTooltip(status, this, mousePos.x, mousePos.y);
}
}
}
else else
{ {
if ((DarkTooltipManager.sTooltip != null) && (DarkTooltipManager.sTooltip.mRelWidget == this)) if ((DarkTooltipManager.sTooltip != null) && (DarkTooltipManager.sTooltip.mRelWidget == this))
DarkTooltipManager.sTooltip.Close(); DarkTooltipManager.sTooltip.Close();
if (mCancelSymSrvButton != null) if (mCancelButton != null)
{ {
RemoveAndDelete(mCancelSymSrvButton); RemoveAndDelete(mCancelButton);
mCancelSymSrvButton = null; mCancelButton = null;
} }
} }
@ -367,6 +409,16 @@ namespace IDE.ui
float statusLabelPos = (int)GS!(-1.3f); float statusLabelPos = (int)GS!(-1.3f);
void DrawCompletion(float pct)
{
Rect completionRect = Rect(GS!(200), GS!(2), GS!(120), GS!(15));
using (g.PushColor(0xFF000000))
g.FillRect(completionRect.mX, completionRect.mY, completionRect.mWidth, completionRect.mHeight);
completionRect.Inflate(GS!(-1), GS!(-1));
using (g.PushColor(0xFF00FF00))
g.FillRect(completionRect.mX, completionRect.mY, completionRect.mWidth * pct, completionRect.mHeight);
}
//completionPct = 0.4f; //completionPct = 0.4f;
if ((gApp.mDebugger?.mIsComptimeDebug == true) && if ((gApp.mDebugger?.mIsComptimeDebug == true) &&
((gApp.mDebugger.IsPaused()) || (debugState == .DebugEval))) ((gApp.mDebugger.IsPaused()) || (debugState == .DebugEval)))
@ -375,12 +427,7 @@ namespace IDE.ui
} }
else if (completionPct.HasValue) else if (completionPct.HasValue)
{ {
Rect completionRect = Rect(GS!(200), GS!(2), GS!(120), GS!(15)); DrawCompletion(completionPct.Value);
using (g.PushColor(0xFF000000))
g.FillRect(completionRect.mX, completionRect.mY, completionRect.mWidth, completionRect.mHeight);
completionRect.Inflate(GS!(-1), GS!(-1));
using (g.PushColor(0xFF00FF00))
g.FillRect(completionRect.mX, completionRect.mY, completionRect.mWidth * completionPct.Value, completionRect.mHeight);
} }
else if ((gApp.mDebugger.mIsRunning) && (gApp.HaveSourcesChanged())) else if ((gApp.mDebugger.mIsRunning) && (gApp.HaveSourcesChanged()))
{ {
@ -394,7 +441,7 @@ namespace IDE.ui
g.DrawString("Source Changed", GS!(200), statusLabelPos, FontAlign.Centered, GS!(120)); g.DrawString("Source Changed", GS!(200), statusLabelPos, FontAlign.Centered, GS!(120));
} }
void DrawStatusBox(StringView str, int32 updateCnt = -1) void DrawStatusBox(StringView str, int32 updateCnt = -1, bool showCancelButton = false)
{ {
if (mStatusBoxUpdateCnt == -1) if (mStatusBoxUpdateCnt == -1)
mStatusBoxUpdateCnt = 0; mStatusBoxUpdateCnt = 0;
@ -415,8 +462,18 @@ namespace IDE.ui
using (g.PushColor(Color.FromHSV(0.1f, 0.5f, (float)Math.Max(pulsePct * 0.15f + 0.3f, 0.3f)))) using (g.PushColor(Color.FromHSV(0.1f, 0.5f, (float)Math.Max(pulsePct * 0.15f + 0.3f, 0.3f))))
g.FillRect(completionRect.mX, completionRect.mY, completionRect.mWidth, completionRect.mHeight); g.FillRect(completionRect.mX, completionRect.mY, completionRect.mWidth, completionRect.mHeight);
if (mCancelSymSrvButton != null) if (mCancelButton != null)
mCancelSymSrvButton.mX = completionRect.Right - GS!(16); {
if (showCancelButton)
{
mCancelButton.SetVisible(true);
mCancelButton.mX = completionRect.Right - GS!(16);
}
else
{
mCancelButton.SetVisible(false);
}
}
using (g.PushColor(DarkTheme.COLOR_TEXT)) using (g.PushColor(DarkTheme.COLOR_TEXT))
g.DrawString(str, x, statusLabelPos, FontAlign.Centered, len); g.DrawString(str, x, statusLabelPos, FontAlign.Centered, len);
@ -429,10 +486,6 @@ namespace IDE.ui
chordState.Append(", <Awaiting Key>..."); chordState.Append(", <Awaiting Key>...");
DrawStatusBox(chordState); DrawStatusBox(chordState);
} }
else if (mCancelSymSrvButton != null)
{
DrawStatusBox("Retrieving Debug Symbols... ");
}
else if (mEvalCount > 20) else if (mEvalCount > 20)
{ {
DrawStatusBox("Evaluating Expression"); DrawStatusBox("Evaluating Expression");
@ -451,10 +504,16 @@ namespace IDE.ui
} }
else if (gApp.mWorkspace.mProjectLoadState == .Preparing) else if (gApp.mWorkspace.mProjectLoadState == .Preparing)
{ {
DrawStatusBox("Loading Projects"); DrawStatusBox("Loading Projects", -1, true);
}
else if (mCancelButton != null)
{
DrawStatusBox("Retrieving Debug Symbols... ", -1, true);
} }
else if (gApp.mDeferredShowSource != null) else if (gApp.mDeferredShowSource != null)
{
DrawStatusBox("Queued Showing Source"); DrawStatusBox("Queued Showing Source");
}
else else
mStatusBoxUpdateCnt = -1; mStatusBoxUpdateCnt = -1;

View file

@ -11,6 +11,7 @@ using Beefy.theme.dark;
using Beefy.theme; using Beefy.theme;
using Beefy.events; using Beefy.events;
using System.Diagnostics; using System.Diagnostics;
using IDE.Util;
//#define A //#define A
//#define B //#define B
@ -40,6 +41,7 @@ namespace IDE.ui
enum CategoryType enum CategoryType
{ {
General, General,
Dependencies,
Beef_Global, Beef_Global,
Targeted, Targeted,
@ -53,6 +55,7 @@ namespace IDE.ui
ConfigDataGroup mCurConfigDataGroup; ConfigDataGroup mCurConfigDataGroup;
Workspace.Options[] mCurWorkspaceOptions ~ delete _; Workspace.Options[] mCurWorkspaceOptions ~ delete _;
List<String> mUpdateProjectLocks = new .() ~ DeleteContainerAndItems!(_);
public this() public this()
{ {
@ -62,8 +65,9 @@ namespace IDE.ui
var root = (DarkListViewItem)mCategorySelector.GetRoot(); var root = (DarkListViewItem)mCategorySelector.GetRoot();
var globalItem = AddCategoryItem(root, "General"); var globalItem = AddCategoryItem(root, "General");
var item = AddCategoryItem(globalItem, "Beef"); var item = AddCategoryItem(globalItem, "Dependencies");
item.Focused = true; item.Focused = true;
AddCategoryItem(globalItem, "Beef");
globalItem.Open(true, true); globalItem.Open(true, true);
var targetedItem = AddCategoryItem(root, "Targeted"); var targetedItem = AddCategoryItem(root, "Targeted");
@ -124,6 +128,7 @@ namespace IDE.ui
{ {
case .General, case .General,
//.Targeted, //.Targeted,
.Dependencies,
.Beef_Global: .Beef_Global:
return .None; return .None;
default: default:
@ -454,7 +459,9 @@ namespace IDE.ui
mPropPage.mPropertiesListView.mShowColumnGrid = true; mPropPage.mPropertiesListView.mShowColumnGrid = true;
mPropPage.mPropertiesListView.mShowGridLines = true; mPropPage.mPropertiesListView.mShowGridLines = true;
if (categoryType == CategoryType.Beef_Global) if (categoryType == CategoryType.Dependencies)
PopulateDependencyOptions();
else if (categoryType == CategoryType.Beef_Global)
PopulateBeefGlobalOptions(); PopulateBeefGlobalOptions();
else if (categoryType == CategoryType.Build) else if (categoryType == CategoryType.Build)
PopulateBuildOptions(); PopulateBuildOptions();
@ -705,6 +712,230 @@ namespace IDE.ui
} }
} }
void PopulateDependencyOptions()
{
mPropPage.mPropertiesListView.mColumns[0].Label = "Project";
mPropPage.mPropertiesListView.mColumns[0].mMinWidth = GS!(100);
mPropPage.mPropertiesListView.mColumns[0].mWidth = GS!(180);
mPropPage.mPropertiesListView.mColumns[1].Label = "";
mPropPage.mPropertiesListView.mColumns[1].mMinWidth = GS!(20);
mPropPage.mPropertiesListView.mColumns[1].mWidth = GS!(20);
mPropPage.mPropertiesListView.AddColumn(180, "Remote URL");
mPropPage.mPropertiesListView.mColumns[2].mMinWidth = GS!(100);
mPropPage.mPropertiesListView.AddColumn(180, "Ver Constraint");
mPropPage.mPropertiesListView.mColumns[3].mMinWidth = GS!(100);
//mDependencyValuesMap = new .();
var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
var category = root;
List<String> projectNames = scope List<String>();
for (int32 projectIdx = 0; projectIdx < IDEApp.sApp.mWorkspace.mProjects.Count; projectIdx++)
{
var project = IDEApp.sApp.mWorkspace.mProjects[projectIdx];
/*if (project == mProject)
continue;*/
projectNames.Add(project.mProjectName);
}
/*for (var dep in mProject.mDependencies)
{
if (!projectNames.Contains(dep.mProjectName))
projectNames.Add(dep.mProjectName);
}*/
projectNames.Sort(scope (a, b) => String.Compare(a, b, true));
for (var projectName in projectNames)
{
var dependencyEntry = new DependencyEntry();
for (var projectSpec in gApp.mWorkspace.mProjectSpecs)
{
if (projectSpec.mProjectName == projectName)
{
dependencyEntry.mUse = true;
if (projectSpec.mVerSpec case .Git(let url, let ver))
{
dependencyEntry.mURL = new .(url);
if (ver != null)
dependencyEntry.mVersion = new .(ver.mVersion);
}
}
}
/*var verSpec = mProject.GetDependency(projectName, false);
if (verSpec != null)
{
dependencyEntry.mUse = true;
if (verSpec case .Git(let url, let ver))
{
dependencyEntry.mURL = new .(url);
if (ver != null)
dependencyEntry.mVersion = new .(ver.mVersion);
}
}
mDependencyValuesMap[new String(projectName)] = dependencyEntry;*/
var (listViewItem, propItem) = AddPropertiesItem(category, projectName);
if (IDEApp.sApp.mWorkspace.FindProject(projectName) == null)
listViewItem.mTextColor = Color.Mult(DarkTheme.COLOR_TEXT, 0xFFFF6060);
var subItem = (DarkListViewItem)listViewItem.CreateSubItem(1);
var checkbox = new DarkCheckBox();
checkbox.Checked = dependencyEntry.mUse;
checkbox.Resize(0, 0, DarkTheme.sUnitSize, DarkTheme.sUnitSize);
subItem.AddWidget(checkbox);
PropEntry[] propEntries = new PropEntry[1];
PropEntry propEntry = new PropEntry();
propEntry.mTarget = dependencyEntry;
propEntry.mOrigValue = Variant.Create(dependencyEntry, true);
propEntry.mCurValue = Variant.Create(new DependencyEntry(dependencyEntry), true);
propEntry.mListViewItem = listViewItem;
propEntry.mCheckBox = checkbox;
propEntry.mApplyAction = new () =>
{
bool updateProjectLock = false;
var dependencyEntry = propEntry.mCurValue.Get<DependencyEntry>();
VerSpec verSpec = default;
if (dependencyEntry.mUse)
{
if (dependencyEntry.mURL != null)
verSpec = .Git(new .(dependencyEntry.mURL), (dependencyEntry.mVersion != null) ? new .(dependencyEntry.mVersion) : null);
else if (dependencyEntry.mVersion != null)
verSpec = .SemVer(new .(dependencyEntry.mVersion));
else
verSpec = .SemVer(new .("*"));
}
FindBlock: do
{
for (var projectSpec in gApp.mWorkspace.mProjectSpecs)
{
if (projectSpec.mProjectName == projectName)
{
if (!dependencyEntry.mUse)
{
if (projectSpec.mVerSpec case .Git)
updateProjectLock = true;
@projectSpec.Remove();
delete projectSpec;
break FindBlock;
}
if (projectSpec.mVerSpec != verSpec)
{
if ((projectSpec.mVerSpec case .Git) ||
(verSpec case .Git))
updateProjectLock = true;
}
projectSpec.mVerSpec.Dispose();
projectSpec.mVerSpec = verSpec;
break FindBlock;
}
}
if (dependencyEntry.mUse)
{
Workspace.ProjectSpec projectSpec = new .();
projectSpec.mProjectName = new .(projectName);
projectSpec.mVerSpec = verSpec;
gApp.mWorkspace.mProjectSpecs.Add(projectSpec);
if (verSpec case .Git)
updateProjectLock = true;
var origDependencyEntry = propEntry.mOrigValue.Get<DependencyEntry>();
origDependencyEntry.Set(dependencyEntry);
}
}
if (updateProjectLock)
mUpdateProjectLocks.Add(new .(listViewItem.Label));
};
checkbox.mOnMouseUp.Add(new (evt) =>
{
var dependencyEntry = propEntry.mCurValue.Get<DependencyEntry>();
dependencyEntry.mUse = !dependencyEntry.mUse;
if (dependencyEntry.mUse)
{
var projectName = listViewItem.Label;
for (var projectSpec in gApp.mWorkspace.mProjectSpecs)
{
if (projectSpec.mProjectName == projectName)
{
if (projectSpec.mVerSpec case .Git(let url, let ver))
{
dependencyEntry.SetValue(1, url);
dependencyEntry.SetValue(2, ver.mVersion);
}
}
}
var propEntries = mPropPage.mPropEntries[listViewItem];
UpdatePropertyValue(propEntries);
}
else
{
DeleteAndNullify!(dependencyEntry.mURL);
DeleteAndNullify!(dependencyEntry.mVersion);
var propEntries = mPropPage.mPropEntries[listViewItem];
UpdatePropertyValue(propEntries);
}
});
subItem = (.)listViewItem.GetOrCreateSubItem(2);
if (dependencyEntry.mURL != null)
subItem.Label = dependencyEntry.mURL;
subItem.mOnMouseDown.Add(new => DepPropValueClicked);
subItem = (.)listViewItem.GetOrCreateSubItem(3);
if (dependencyEntry.mVersion != null)
subItem.Label = dependencyEntry.mVersion;
subItem.mOnMouseDown.Add(new => DepPropValueClicked);
propEntries[0] = propEntry;
mPropPage.mPropEntries[listViewItem] = propEntries;
}
}
protected void DepPropValueClicked(MouseEvent theEvent)
{
DarkListViewItem clickedItem = (DarkListViewItem)theEvent.mSender;
if (clickedItem.mColumnIdx == 0)
{
clickedItem.mListView.SetFocus();
clickedItem.mListView.GetRoot().SelectItemExclusively(clickedItem);
return;
}
if (theEvent.mX != -1)
{
clickedItem.mListView.GetRoot().SelectItemExclusively(null);
}
DarkListViewItem item = (DarkListViewItem)clickedItem;
DarkListViewItem rootItem = (DarkListViewItem)clickedItem.GetSubItem(0);
PropEntry[] propertyEntries = mPropPage.mPropEntries[rootItem];
if (propertyEntries[0].mDisabled)
return;
EditValue(item, propertyEntries, clickedItem.mColumnIdx - 1);
}
void PopulateBeefGlobalOptions() void PopulateBeefGlobalOptions()
{ {
var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot(); var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
@ -939,6 +1170,7 @@ namespace IDE.ui
{ {
base.Close(); base.Close();
SetWorkspaceData(false); SetWorkspaceData(false);
gApp.NotifyProjectVersionLocks(mUpdateProjectLocks);
} }
public override void CalcSize() public override void CalcSize()

326
IDE/src/util/GitManager.bf Normal file
View file

@ -0,0 +1,326 @@
#pragma warning disable 168
using System.Diagnostics;
using System;
using System.Threading;
using System.IO;
using System.Collections;
namespace IDE.util;
class GitManager
{
public enum Error
{
Unknown
}
public class GitInstance : RefCounted
{
public class TagInfo
{
public String mHash ~ delete _;
public String mTag ~ delete _;
}
public GitManager mGitManager;
public bool mFailed;
public bool mDone;
public bool mStarted;
public bool mRemoved;
public String mArgs ~ delete _;
public String mPath ~ delete _;
public float mProgress;
public float mProgressRecv;
public float mProgressDeltas;
public float mProgressFiles;
public Stopwatch mStopwatch = new .()..Start() ~ delete _;
public SpawnedProcess mProcess ~ delete _;
public Monitor mMonitor = new .() ~ delete _;
public List<String> mDeferredOutput = new .() ~ DeleteContainerAndItems!(_);
public List<TagInfo> mTagInfos = new .() ~ DeleteContainerAndItems!(_);
public Thread mOutputThread ~ delete _;
public Thread mErrorThread ~ delete _;
public this(GitManager gitManager)
{
mGitManager = gitManager;
}
public ~this()
{
IDEUtils.SafeKill(mProcess);
mOutputThread?.Join();
mErrorThread?.Join();
if (!mRemoved)
mGitManager.mGitInstances.Remove(this);
}
public void Init(StringView args, StringView path)
{
mArgs = new .(args);
if (path != default)
mPath = new .(path);
}
public void Start()
{
if (mStarted)
return;
mStarted = true;
ProcessStartInfo psi = scope ProcessStartInfo();
String gitPath = scope .();
#if BF_PLATFORM_WINDOWS
Path.GetAbsolutePath(gApp.mInstallDir, "git/cmd/git.exe", gitPath);
if (!File.Exists(gitPath))
gitPath.Clear();
if (gitPath.IsEmpty)
{
Path.GetAbsolutePath(gApp.mInstallDir, "../../bin/git/cmd/git.exe", gitPath);
if (!File.Exists(gitPath))
gitPath.Clear();
}
if (gitPath.IsEmpty)
{
Path.GetAbsolutePath(gApp.mInstallDir, "../../../bin/git/cmd/git.exe", gitPath);
if (!File.Exists(gitPath))
gitPath.Clear();
}
#endif
if (gitPath.IsEmpty)
gitPath.Set("git");
psi.SetFileName(gitPath);
psi.SetArguments(mArgs);
if (mPath != null)
psi.SetWorkingDirectory(mPath);
psi.UseShellExecute = false;
psi.RedirectStandardError = true;
psi.RedirectStandardOutput = true;
psi.CreateNoWindow = true;
mProcess = new SpawnedProcess();
if (mProcess.Start(psi) case .Err)
{
gApp.OutputErrorLine("Failed to execute Git");
mFailed = true;
return;
}
mOutputThread = new Thread(new => ReadOutputThread);
mOutputThread.Start(false);
mErrorThread = new Thread(new => ReadErrorThread);
mErrorThread.Start(false);
}
public void ReadOutputThread()
{
FileStream fileStream = scope FileStream();
if (mProcess.AttachStandardOutput(fileStream) case .Err)
return;
StreamReader streamReader = scope StreamReader(fileStream, null, false, 4096);
int count = 0;
while (true)
{
count++;
var buffer = scope String();
if (streamReader.ReadLine(buffer) case .Err)
break;
using (mMonitor.Enter())
{
mDeferredOutput.Add(new .(buffer));
}
}
}
public void ReadErrorThread()
{
FileStream fileStream = scope FileStream();
if (mProcess.AttachStandardError(fileStream) case .Err)
return;
StreamReader streamReader = scope StreamReader(fileStream, null, false, 4096);
while (true)
{
var buffer = scope String();
if (streamReader.ReadLine(buffer) case .Err)
break;
using (mMonitor.Enter())
{
//mDeferredOutput.Add(new $"{mStopwatch.ElapsedMilliseconds / 1000.0:0.0}: {buffer}");
mDeferredOutput.Add(new .(buffer));
}
}
}
public void Update()
{
using (mMonitor.Enter())
{
while (!mDeferredOutput.IsEmpty)
{
var line = mDeferredOutput.PopFront();
defer delete line;
//Debug.WriteLine($"GIT: {line}");
if (line.StartsWith("Cloning into "))
{
// May be starting a submodule
mProgressRecv = 0;
mProgressDeltas = 0;
mProgressFiles = 0;
}
if (line.StartsWith("remote: Counting objects"))
{
mProgressRecv = 0.001f;
}
if (line.StartsWith("Receiving objects: "))
{
var pctStr = line.Substring("Receiving objects: ".Length, 3)..Trim();
mProgressRecv = float.Parse(pctStr).GetValueOrDefault() / 100.0f;
}
if (line.StartsWith("Resolving deltas: "))
{
var pctStr = line.Substring("Resolving deltas: ".Length, 3)..Trim();
mProgressDeltas = float.Parse(pctStr).GetValueOrDefault() / 100.0f;
mProgressRecv = 1.0f;
}
if (line.StartsWith("Updating files: "))
{
var pctStr = line.Substring("Updating files: ".Length, 3)..Trim();
mProgressFiles = float.Parse(pctStr).GetValueOrDefault() / 100.0f;
mProgressRecv = 1.0f;
mProgressDeltas = 1.0f;
}
StringView version = default;
int refTagIdx = line.IndexOf("\trefs/tags/");
if (refTagIdx == 40)
version = line.Substring(40 + "\trefs/tags/".Length);
if ((line.Length == 45) && (line.EndsWith("HEAD")))
version = "HEAD";
if (!version.IsEmpty)
{
TagInfo tagInfo = new .();
tagInfo.mHash = new .(line, 0, 40);
tagInfo.mTag = new .(version);
mTagInfos.Add(tagInfo);
}
}
}
float pct = 0;
if (mProgressRecv > 0)
pct = 0.1f + (mProgressRecv * 0.3f) + (mProgressDeltas * 0.4f) + (mProgressFiles * 0.2f);
if (pct > mProgress)
{
mProgress = pct;
//Debug.WriteLine($"Completed Pct: {pct}");
}
if (mProcess.WaitFor(0))
{
if (mProcess.ExitCode != 0)
mFailed = true;
mDone = true;
}
}
public void Cancel()
{
if (!mProcess.WaitFor(0))
{
//Debug.WriteLine($"GitManager Cancel {mProcess.ProcessId}");
IDEUtils.SafeKill(mProcess);
}
}
}
public const int sMaxActiveGitInstances = 4;
public List<GitInstance> mGitInstances = new .() ~
{
for (var gitInstance in _)
gitInstance.ReleaseRef();
delete _;
};
public void Init()
{
//StartGit("-v");
//Repository repository = Clone("https://github.com/llvm/llvm-project", "c:/temp/__LLVM");
//Repository repository = Clone("https://github.com/Starpelly/raylib-beef", "c:/temp/__RAYLIB");
/*while (true)
{
Thread.Sleep(500);
Debug.WriteLine($"Repository {repository.mStatus} {repository.GetCompletedPct()}");
}*/
}
public GitInstance StartGit(StringView cmd, StringView path = default)
{
//Debug.WriteLine($"GIT STARTING: {cmd} in {path}");
GitInstance gitInst = new .(this);
gitInst.Init(cmd, path);
mGitInstances.Add(gitInst);
return gitInst;
}
public GitInstance Clone(StringView url, StringView path)
{
return StartGit(scope $"clone -v --progress --recurse-submodules {url} \"{path}\"");
}
public GitInstance Checkout(StringView path, StringView hash)
{
return StartGit(scope $"checkout -b BeefManaged {hash}", path);
}
public GitInstance GetTags(StringView url)
{
return StartGit(scope $"ls-remote {url}");
}
public void Update()
{
for (var gitInstance in mGitInstances)
{
if (@gitInstance.Index >= sMaxActiveGitInstances)
break;
if (!gitInstance.mStarted)
gitInstance.Start();
gitInstance.Update();
if (gitInstance.mDone)
{
@gitInstance.Remove();
gitInstance.mRemoved = true;
gitInstance.ReleaseRef();
}
}
}
}

View file

@ -1,35 +1,397 @@
#pragma warning disable 168
using System; using System;
using IDE.Util; using IDE.Util;
using System.Collections;
#if BF_PLATFORM_WINDOWS using System.Security.Cryptography;
using static Git.GitApi; using System.IO;
#define SUPPORT_GIT using Beefy.utils;
#endif using System.Threading;
namespace IDE.util namespace IDE.util
{ {
class PackMan class PackMan
{ {
class GitHelper public class WorkItem
{ {
static bool sInitialized; public enum Kind
public this()
{ {
if (!sInitialized) None,
{ FindVersion,
#if SUPPORT_GIT Clone,
#unwarn Checkout
var result = git_libgit2_init();
sInitialized = true;
#endif
}
} }
public Kind mKind;
public String mProjectName ~ delete _;
public String mURL ~ delete _;
public List<String> mConstraints ~ DeleteContainerAndItems!(_);
public String mTag ~ delete _;
public String mHash ~ delete _;
public String mPath ~ delete _;
public GitManager.GitInstance mGitInstance ~ _?.ReleaseRef();
public ~this()
{
mGitInstance?.Cancel();
}
}
public List<WorkItem> mWorkItems = new .() ~ DeleteContainerAndItems!(_);
public bool mInitialized;
public String mManagedPath ~ delete _;
public bool mFailed;
public void Fail(StringView error)
{
gApp.OutputErrorLine(error);
if (!mFailed)
{
mFailed = true;
gApp.[Friend]FlushDeferredLoadProjects();
}
}
public bool CheckInit()
{
if (mInitialized)
return true;
if (gApp.mBeefConfig.mManagedLibPath.IsEmpty)
return false;
mManagedPath = new .(gApp.mBeefConfig.mManagedLibPath);
mInitialized = true;
return true;
}
public void GetPath(StringView url, StringView hash, String outPath)
{
//var urlHash = SHA256.Hash(url.ToRawData()).ToString(.. scope .());
//outPath.AppendF($"{mManagedPath}/{urlHash}/{hash}");
outPath.AppendF($"{mManagedPath}/{hash}");
} }
public bool CheckLock(StringView projectName, String outPath) public bool CheckLock(StringView projectName, String outPath)
{ {
if (!CheckInit())
return false;
if (gApp.mWantUpdateVersionLocks != null)
{
if ((gApp.mWantUpdateVersionLocks.IsEmpty) || (gApp.mWantUpdateVersionLocks.ContainsAlt(projectName)))
return false;
}
if (!gApp.mWorkspace.mProjectLockMap.TryGetAlt(projectName, ?, var lock))
return false;
switch (lock)
{
case .Git(let url, let tag, let hash):
var path = GetPath(url, hash, .. scope .());
var managedFilePath = scope $"{path}/BeefManaged.toml";
if (File.Exists(managedFilePath))
{
outPath.Append(path);
outPath.Append("/BeefProj.toml");
return true;
}
default:
}
return false; return false;
} }
public void CloneCompleted(StringView projectName, StringView url, StringView tag, StringView hash, StringView path)
{
gApp.mWorkspace.SetLock(projectName, .Git(new .(url), new .(tag), new .(hash)));
StructuredData sd = scope .();
sd.CreateNew();
sd.Add("FileVersion", 1);
sd.Add("Version", tag);
sd.Add("GitURL", url);
sd.Add("GitTag", tag);
sd.Add("GitHash", hash);
var tomlText = sd.ToTOML(.. scope .());
var managedFilePath = scope $"{path}/BeefManaged.toml";
File.WriteAllText(managedFilePath, tomlText).IgnoreError();
}
public void GetWithHash(StringView projectName, StringView url, StringView tag, StringView hash)
{
if (!CheckInit())
return;
String destPath = GetPath(url, hash, .. scope .());
var urlPath = Path.GetDirectoryPath(destPath, .. scope .());
Directory.CreateDirectory(urlPath).IgnoreError();
if (Directory.Exists(destPath))
{
var managedFilePath = scope $"{destPath}/BeefManaged.toml";
if (File.Exists(managedFilePath))
{
if (gApp.mVerbosity >= .Normal)
{
if (tag.IsEmpty)
gApp.OutputLine($"Git selecting library '{projectName}' at {hash.Substring(0, 7)}");
else
gApp.OutputLine($"Git selecting library '{projectName}' tag '{tag}' at {hash.Substring(0, 7)}");
}
CloneCompleted(projectName, url, tag, hash, destPath);
ProjectReady(projectName, destPath);
return;
}
String tempDir = new $"{destPath}__{(int32)Internal.GetTickCountMicro():X}";
//if (Directory.DelTree(destPath) case .Err)
if (Directory.Move(destPath, tempDir) case .Err)
{
delete tempDir;
Fail(scope $"Failed to remove directory '{destPath}'");
return;
}
ThreadPool.QueueUserWorkItem(new () =>
{
Directory.DelTree(tempDir);
}
~
{
delete tempDir;
});
}
if (gApp.mVerbosity >= .Normal)
{
if (tag.IsEmpty)
gApp.OutputLine($"Git cloning library '{projectName}' at {hash.Substring(0, 7)}...");
else
gApp.OutputLine($"Git cloning library '{projectName}' tag '{tag}' at {hash.Substring(0, 7)}");
}
WorkItem workItem = new .();
workItem.mKind = .Clone;
workItem.mProjectName = new .(projectName);
workItem.mURL = new .(url);
workItem.mTag = new .(tag);
workItem.mHash = new .(hash);
workItem.mPath = new .(destPath);
mWorkItems.Add(workItem);
}
public void GetWithVersion(StringView projectName, StringView url, SemVer semVer)
{
if (!CheckInit())
return;
bool ignoreLock = false;
if (gApp.mWantUpdateVersionLocks != null)
{
if ((gApp.mWantUpdateVersionLocks.IsEmpty) || (gApp.mWantUpdateVersionLocks.ContainsAlt(projectName)))
ignoreLock = true;
}
if ((!ignoreLock) && (gApp.mWorkspace.mProjectLockMap.TryGetAlt(projectName, ?, var lock)))
{
switch (lock)
{
case .Git(let checkURL, let tag, let hash):
if (checkURL == url)
GetWithHash(projectName, url, tag, hash);
return;
default:
}
}
if (gApp.mVerbosity >= .Normal)
gApp.OutputLine($"Git retrieving version list for '{projectName}'");
WorkItem workItem = new .();
workItem.mKind = .FindVersion;
workItem.mProjectName = new .(projectName);
workItem.mURL = new .(url);
if (semVer != null)
workItem.mConstraints = new .() { new String(semVer.mVersion) };
mWorkItems.Add(workItem);
}
public void UpdateGitConstraint(StringView url, SemVer semVer)
{
for (var workItem in mWorkItems)
{
if ((workItem.mKind == .FindVersion) && (workItem.mURL == url))
{
if (workItem.mConstraints == null)
workItem.mConstraints = new .();
workItem.mConstraints.Add(new String(semVer.mVersion));
}
}
}
public void Checkout(StringView projectName, StringView url, StringView path, StringView tag, StringView hash)
{
if (!CheckInit())
return;
WorkItem workItem = new .();
workItem.mKind = .Checkout;
workItem.mProjectName = new .(projectName);
workItem.mURL = new .(url);
workItem.mTag = new .(tag);
workItem.mHash = new .(hash);
workItem.mPath = new .(path);
mWorkItems.Add(workItem);
}
public void ProjectReady(StringView projectName, StringView path)
{
if (var project = gApp.mWorkspace.FindProject(projectName))
{
String projectPath = scope $"{path}/BeefProj.toml";
project.mProjectPath.Set(projectPath);
gApp.RetryProjectLoad(project, false);
}
}
public void Update()
{
bool executingGit = false;
// First handle active git items
for (var workItem in mWorkItems)
{
if (workItem.mGitInstance == null)
continue;
if (!workItem.mGitInstance.mDone)
{
executingGit = true;
continue;
}
if (!workItem.mGitInstance.mFailed)
{
switch (workItem.mKind)
{
case .FindVersion:
gApp.CompilerLog("");
StringView bestTag = default;
StringView bestHash = default;
for (var tag in workItem.mGitInstance.mTagInfos)
{
if ((tag.mTag == "HEAD") && (workItem.mConstraints == null))
bestHash = tag.mHash;
else if (workItem.mConstraints != null)
{
bool hasMatch = false;
for (var constraint in workItem.mConstraints)
{
if (SemVer.IsVersionMatch(tag.mTag, constraint))
{
hasMatch = true;
break;
}
}
if (hasMatch)
{
if ((bestTag.IsEmpty) || (SemVer.Compare(tag.mTag, bestTag) > 0))
{
bestTag = tag.mTag;
bestHash = tag.mHash;
}
}
}
}
if (bestHash != default)
{
GetWithHash(workItem.mProjectName, workItem.mURL, bestTag, bestHash);
}
else
{
String constraints = scope .();
for (var constraint in workItem.mConstraints)
{
if (!constraints.IsEmpty)
constraints.Append(", ");
constraints.Append('\'');
constraints.Append(constraint);
constraints.Append('\'');
}
Fail(scope $"Failed to locate version for '{workItem.mProjectName}' with constraints '{constraints}'");
}
case .Clone:
Checkout(workItem.mProjectName, workItem.mURL, workItem.mPath, workItem.mTag, workItem.mHash);
case .Checkout:
CloneCompleted(workItem.mProjectName, workItem.mURL, workItem.mTag, workItem.mHash, workItem.mPath);
ProjectReady(workItem.mProjectName, workItem.mPath);
if (gApp.mVerbosity >= .Normal)
gApp.OutputLine($"Git cloning library '{workItem.mProjectName}' done.");
default:
}
}
@workItem.Remove();
delete workItem;
}
if (!executingGit)
{
// First handle active git items
for (var workItem in mWorkItems)
{
if (workItem.mGitInstance != null)
continue;
switch (workItem.mKind)
{
case .FindVersion:
workItem.mGitInstance = gApp.mGitManager.GetTags(workItem.mURL)..AddRef();
case .Checkout:
workItem.mGitInstance = gApp.mGitManager.Checkout(workItem.mPath, workItem.mHash)..AddRef();
case .Clone:
workItem.mGitInstance = gApp.mGitManager.Clone(workItem.mURL, workItem.mPath)..AddRef();
default:
}
}
}
}
public void GetHashFromFilePath(StringView filePath, String path)
{
if (mManagedPath == null)
return;
if (!filePath.StartsWith(mManagedPath))
return;
StringView hashPart = filePath.Substring(mManagedPath.Length);
if (hashPart.Length < 42)
return;
hashPart.RemoveFromStart(1);
hashPart.Length = 40;
path.Append(hashPart);
}
public void CancelAll()
{
if (mWorkItems.IsEmpty)
return;
Fail("Aborted project transfer");
mWorkItems.ClearAndDeleteItems();
}
} }
} }

View file

@ -2,10 +2,51 @@ using System;
namespace IDE.Util namespace IDE.Util
{ {
[Reflect]
class SemVer class SemVer
{ {
public struct Parts
{
public enum Kind
{
case Empty;
case Num(int32 val);
case Wild;
public int32 NumOrDefault
{
get
{
if (this case .Num(let val))
return val;
return 0;
}
}
}
public Kind[3] mPart;
public StringView mPreRelease;
public Kind Major => mPart[0];
public Kind Minor => mPart[1];
public Kind Patch => mPart[2];
}
enum CompareKind
{
Caret, // Default
Tilde,
Equal,
Gt,
Gte,
Lt,
Lte
}
public String mVersion ~ delete _; public String mVersion ~ delete _;
public bool IsEmpty => String.IsNullOrEmpty(mVersion);
public this() public this()
{ {
@ -27,5 +68,240 @@ namespace IDE.Util
mVersion = new String(ver); mVersion = new String(ver);
return .Ok; return .Ok;
} }
public static Result<Parts> GetParts(StringView version)
{
int startIdx = 0;
int partIdx = 0;
if (version.IsEmpty)
return .Err;
if (version.StartsWith("V", .OrdinalIgnoreCase))
startIdx++;
Parts parts = .();
Result<void> SetPart(Parts.Kind kind)
{
if (partIdx >= 3)
return .Err;
parts.mPart[partIdx] = kind;
partIdx++;
return .Ok;
}
Result<void> FlushPart(int i)
{
StringView partStr = version.Substring(startIdx, i - startIdx);
if (!partStr.IsEmpty)
{
int32 partNum = Try!(int32.Parse(partStr));
Try!(SetPart(.Num(partNum)));
}
return .Ok;
}
for (int i in startIdx ..< version.Length)
{
char8 c = version[i];
if (c.IsWhiteSpace)
return .Err;
if (c == '.')
{
Try!(FlushPart(i));
startIdx = i + 1;
continue;
}
else if (c.IsNumber)
{
continue;
}
else if (c == '-')
{
if (partIdx == 0)
return .Err;
parts.mPreRelease = version.Substring(i);
return .Ok(parts);
}
else if (c == '*')
{
Try!(SetPart(.Wild));
continue;
}
return .Err;
}
Try!(FlushPart(version.Length));
return parts;
}
public Result<Parts> GetParts()
{
return GetParts(mVersion);
}
public static bool IsVersionMatch(StringView fullVersion, StringView wildcard)
{
int commaPos = wildcard.IndexOf(',');
if (commaPos != -1)
return IsVersionMatch(fullVersion, wildcard.Substring(0, commaPos)..Trim()) && IsVersionMatch(fullVersion, wildcard.Substring(commaPos + 1)..Trim());
var wildcard;
wildcard.Trim();
CompareKind compareKind = .Caret;
if (wildcard.StartsWith('^'))
{
compareKind = .Caret;
wildcard.RemoveFromStart(1);
}
else if (wildcard.StartsWith('~'))
{
compareKind = .Tilde;
wildcard.RemoveFromStart(1);
}
else if (wildcard.StartsWith('='))
{
compareKind = .Equal;
wildcard.RemoveFromStart(1);
}
else if (wildcard.StartsWith('>'))
{
compareKind = .Gt;
wildcard.RemoveFromStart(1);
if (wildcard.StartsWith('='))
{
compareKind = .Gte;
wildcard.RemoveFromStart(1);
}
}
else if (wildcard.StartsWith('<'))
{
compareKind = .Lt;
wildcard.RemoveFromStart(1);
if (wildcard.StartsWith('='))
{
compareKind = .Lte;
wildcard.RemoveFromStart(1);
}
}
wildcard.Trim();
// Does we include equality?
if ((compareKind != .Gt) && (compareKind != .Lt))
{
if (fullVersion == wildcard)
return true;
}
Parts full;
if (!(GetParts(fullVersion) case .Ok(out full)))
return false;
Parts wild;
if (!(GetParts(wildcard) case .Ok(out wild)))
return false;
// Don't allow a general wildcard to match a pre-prelease
if ((!full.mPreRelease.IsEmpty) && (full.mPreRelease != wild.mPreRelease))
return false;
for (int partIdx < 3)
{
if (wild.mPart[partIdx] case .Wild)
return true;
int comp = full.mPart[partIdx].NumOrDefault <=> wild.mPart[partIdx].NumOrDefault;
switch (compareKind)
{
case .Caret:
if ((full.mPart[partIdx].NumOrDefault > 0) || (wild.mPart[partIdx].NumOrDefault > 0))
{
if (comp != 0)
return false;
// First number matches, now make sure we are at least a high enough version on the other numbers
compareKind = .Gte;
}
case .Tilde:
if (wild.mPart[partIdx] case .Empty)
return true;
if (partIdx == 2)
{
if (comp < 0)
return false;
}
else if (comp != 0)
return false;
case .Equal:
if (wild.mPart[partIdx] case .Empty)
return true;
if (comp != 0)
return false;
case .Gt:
if (comp > 0)
return true;
if (partIdx == 2)
return false;
if (comp < 0)
return false;
case .Gte:
if (comp < 0)
return false;
case .Lt:
if (comp < 0)
return true;
if (partIdx == 2)
return false;
if (comp > 0)
return false;
case .Lte:
if (comp > 0)
return false;
default:
}
}
return true;
}
public static bool IsVersionMatch(SemVer fullVersion, SemVer wildcard) => IsVersionMatch(fullVersion.mVersion, wildcard.mVersion);
public static Result<int> Compare(StringView lhs, StringView rhs)
{
Parts lhsParts;
if (!(GetParts(lhs) case .Ok(out lhsParts)))
return .Err;
Parts rhsParts;
if (!(GetParts(rhs) case .Ok(out rhsParts)))
return .Err;
int comp = 0;
for (int partIdx < 3)
{
comp = lhsParts.mPart[partIdx].NumOrDefault <=> rhsParts.mPart[partIdx].NumOrDefault;
if (comp != 0)
return comp;
}
// Don't allow a general wildcard to match a pre-prelease
if ((!lhsParts.mPreRelease.IsEmpty) || (!rhsParts.mPreRelease.IsEmpty))
{
if (lhsParts.mPreRelease.IsEmpty)
return 1;
if (rhsParts.mPreRelease.IsEmpty)
return -1;
return lhsParts.mPreRelease <=> rhsParts.mPreRelease;
}
return comp;
}
public override void ToString(String strBuffer)
{
strBuffer.Append(mVersion);
}
public static int operator<=>(Self lhs, Self rhs) => (lhs?.mVersion ?? "") <=> (rhs?.mVersion ?? "");
} }
} }

View file

@ -39,6 +39,8 @@ namespace IDE.Util
case .Path(let path): case .Path(let path):
return .Path(new String(path)); return .Path(new String(path));
case .Git(let url, let ver): case .Git(let url, let ver):
if (ver == null)
return .Git(new String(url), null);
return .Git(new String(url), new SemVer(ver)); return .Git(new String(url), new SemVer(ver));
} }
} }
@ -113,7 +115,7 @@ namespace IDE.Util
using (data.CreateObject(name)) using (data.CreateObject(name))
{ {
data.Add("Git", path); data.Add("Git", path);
if (ver != null) if ((ver != null) && (!ver.mVersion.IsEmpty))
data.Add("Version", ver.mVersion); data.Add("Version", ver.mVersion);
} }
case .SemVer(var ver): case .SemVer(var ver):