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
{
public enum MainVerbState
{
None,
UpdateList,
End
}
const int cProgressSize = 30;
int mProgressIdx = 0;
public bool mIsTest;
public bool mTestIncludeIgnored;
public bool mDidRun;
public bool mWantsGenerate = false;
public bool mHandledVerb;
public String mRunArgs ~ delete _;
MainVerbState mMainVerbState;
/*void Test()
{
@ -112,20 +122,33 @@ namespace BeefBuild
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)
{
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("--"))
key.Remove(0, 1);
@ -133,6 +156,10 @@ namespace BeefBuild
{
switch (key)
{
case "-args":
if (mRunArgs == null)
mRunArgs = new .();
return true;
case "-new":
mVerb = .New;
return true;
@ -157,12 +184,63 @@ namespace BeefBuild
case "-noir":
mConfig_NoIR = true;
return true;
case "-update":
if (mWantUpdateVersionLocks == null)
mWantUpdateVersionLocks = new .();
return true;
case "-version":
mVerb = .GetVersion;
return true;
case "-crash":
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
{
@ -188,6 +266,11 @@ namespace BeefBuild
case "-platform":
mPlatformName.Set(value);
return true;
case "-update":
if (mWantUpdateVersionLocks == null)
mWantUpdateVersionLocks = new .();
mWantUpdateVersionLocks.Add(new .(value));
return true;
case "-verbosity":
if (value == "quiet")
mVerbosity = .Quiet;
@ -281,6 +364,27 @@ namespace BeefBuild
{
base.Update(batchStart);
if (mWorkspace.mProjectLoadState != .Loaded)
{
// Wait for workspace to complete loading
}
else
{
if ((!mFailed) && (!mHandledVerb))
{
mHandledVerb = true;
if (mIsTest)
{
RunTests(mTestIncludeIgnored, false);
}
else if (mVerb == .Update)
{
// No-op here
}
else if (mVerb != .New)
Compile(.Normal, null);
}
if (mCompilingBeef)
{
WriteProgress(mBfBuildCompiler.GetCompletionPercentage());
@ -301,7 +405,7 @@ namespace BeefBuild
if (targetPaths.IsEmpty)
return;
ExecutionQueueCmd executionCmd = QueueRun(targetPaths[0], "", curPath);
ExecutionQueueCmd executionCmd = QueueRun(targetPaths[0], mRunArgs ?? "", curPath);
executionCmd.mIsTargetRun = true;
mDidRun = true;
return;
@ -312,3 +416,4 @@ namespace BeefBuild
}
}
}
}

View file

@ -123,6 +123,7 @@ namespace IDE
List<String> mConfigPathQueue = new List<String>() ~ DeleteContainerAndItems!(_);
List<LibDirectory> mLibDirectories = new List<LibDirectory>() ~ DeleteContainerAndItems!(_);
List<FileSystemWatcher> mWatchers = new .() ~ DeleteContainerAndItems!(_);
public String mManagedLibPath = new .() ~ delete _;
public bool mLibsChanged;
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);
return .Ok;

View file

@ -76,6 +76,7 @@ namespace IDE
OpenOrNew,
Test,
Run,
Update,
GetVersion
}
@ -182,6 +183,7 @@ namespace IDE
public MainFrame mMainFrame;
public GlobalUndoManager mGlobalUndoManager = new GlobalUndoManager() ~ delete _;
public SourceControl mSourceControl = new SourceControl() ~ delete _;
public GitManager mGitManager = new .() ~ delete _;
public WidgetWindow mPopupWindow;
public RecentFileSelector mRecentFileSelector;
@ -223,6 +225,7 @@ namespace IDE
public WakaTime mWakaTime ~ delete _;
public PackMan mPackMan = new PackMan() ~ delete _;
public HashSet<String> mWantUpdateVersionLocks ~ DeleteContainerAndItems!(_);
public Settings mSettings = new Settings() ~ delete _;
public Workspace mWorkspace = new Workspace() ~ delete _;
public FileWatcher mFileWatcher = new FileWatcher() ~ delete _;
@ -2145,6 +2148,91 @@ namespace IDE
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)
{
outResult.Append(mInstallDir, "/DefaultLayout.toml");
@ -2377,6 +2465,17 @@ namespace IDE
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)
{
Project project = new Project();
@ -2391,13 +2490,8 @@ namespace IDE
AddNewProjectToWorkspace(project);
project.FinishCreate();
mProjectPanel.InitProject(project, mProjectPanel.GetSelectedWorkspaceFolder());
mProjectPanel.Sort();
mWorkspace.FixOptions();
mWorkspace.mHasChanged = true;
ProjectCreated(project);
mWorkspace.ClearProjectNameCache();
CurrentWorkspaceConfigChanged();
return project;
}
@ -2517,6 +2611,8 @@ namespace IDE
mBookmarkManager.Clear();
mPackMan.CancelAll();
OutputLine("Workspace closed.");
}
@ -2757,9 +2853,16 @@ namespace IDE
return .Err;
}
public void CheckDependenciesLoaded()
{
for (var project in mWorkspace.mProjects)
project.CheckDependenciesLoaded();
}
void FlushDeferredLoadProjects(bool addToUI = false)
{
bool hasDeferredProjects = false;
bool loadFailed = false;
while (true)
{
@ -2767,11 +2870,29 @@ namespace IDE
for (int projectIdx = 0; projectIdx < mWorkspace.mProjects.Count; 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))
{
hadLoad = true;
var projectPath = project.mProjectPath;
if (project.mDeferState == .Pending)
{
hasDeferredProjects = true;
@ -2794,9 +2915,26 @@ namespace IDE
}
if (hasDeferredProjects)
{
mWorkspace.mProjectLoadState = .Preparing;
}
else
{
mWorkspace.mProjectLoadState = .Loaded;
SaveWorkspaceLockData();
CheckDependenciesLoaded();
}
if (loadFailed)
{
mProjectPanel.RebuildUI();
}
}
public void CancelWorkspaceLoading()
{
mPackMan.CancelAll();
FlushDeferredLoadProjects();
}
protected void LoadWorkspace(BeefVerb verb)
@ -2937,6 +3075,7 @@ namespace IDE
}
else
{
LoadWorkspaceLockData();
mWorkspace.mProjectFileEntries.Add(new .(workspaceFileName));
if (mVerb == .New)
@ -3044,8 +3183,9 @@ namespace IDE
outRelaunchCmd.Append(" -safe");
}
public void RetryProjectLoad(Project project)
public void RetryProjectLoad(Project project, bool reloadConfig)
{
if (reloadConfig)
LoadConfig();
var projectPath = project.mProjectPath;
@ -3054,6 +3194,8 @@ namespace IDE
Fail(scope String()..AppendF("Failed to load project '{0}' from '{1}'", project.mProjectName, projectPath));
LoadFailed();
project.mFailed = true;
FlushDeferredLoadProjects();
mProjectPanel?.RebuildUI();
}
else
{
@ -3061,7 +3203,7 @@ namespace IDE
mWorkspace.FixOptions();
project.mFailed = false;
mProjectPanel.RebuildUI();
mProjectPanel?.RebuildUI();
CurrentWorkspaceConfigChanged();
}
}
@ -3080,7 +3222,17 @@ namespace IDE
String verConfigDir = mWorkspace.mDir;
if (let project = mWorkspace.FindProject(projectName))
{
switch (useVerSpec)
{
case .Git(let url, let ver):
if (ver != null)
mPackMan.UpdateGitConstraint(url, ver);
default:
}
return project;
}
if (useVerSpec case .SemVer)
{
@ -3153,10 +3305,6 @@ namespace IDE
case .SemVer(let semVer):
//
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();
if (mPackMan.CheckLock(projectName, checkPath))
@ -3164,16 +3312,17 @@ namespace IDE
projectFilePath = scope:: String(checkPath);
}
else
{
mPackMan.GetWithVersion(projectName, url, ver);
isDeferredLoad = true;
}
default:
Fail("Invalid version specifier");
return .Err(.InvalidVersionSpec);
}
if ((projectFilePath == null) && (!isDeferredLoad))
{
return .Err(.NotFound);
}
if (isDeferredLoad)
{
@ -3197,6 +3346,59 @@ namespace IDE
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()
{
scope AutoBeefPerf("IDE.WorkspaceLoaded");
@ -3848,9 +4050,9 @@ namespace IDE
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.mEscButton = dialog.mDefaultButton;
dialog.PopupWindow(mMainWindow);
@ -7433,6 +7635,37 @@ namespace IDE
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)
{
if (projectItem is ProjectSource)
@ -7845,7 +8078,7 @@ namespace IDE
case "-autoshutdown":
mDebugAutoShutdownCounter = 200;
case "-new":
mVerb = .New;
mVerb = .Open;
case "-testNoExit":
mExitWhenTestScriptDone = false;
case "-firstRun":
@ -8090,15 +8323,16 @@ namespace IDE
#endif
}
public virtual void OutputErrorLine(String format, params Object[] args)
public virtual void OutputErrorLine(StringView format, params Object[] args)
{
mWantShowOutput = true;
var errStr = scope String();
errStr.Append("ERROR: ", format);
errStr.Append("ERROR: ");
errStr.Append(format);
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();
warnStr.AppendF(format, params args);
@ -8113,7 +8347,7 @@ namespace IDE
OutputLine(outStr);
}
public virtual void OutputLineSmart(String format, params Object[] args)
public virtual void OutputLineSmart(StringView format, params Object[] args)
{
String outStr;
if (args.Count > 0)
@ -10027,7 +10261,7 @@ namespace IDE
#endif
}
mWorkspace.ClearProjectNameCache();
mProjectPanel.RehupProjects();
mProjectPanel?.RehupProjects();
}
/*public string GetClangDepConfigName(Project project)
@ -10064,8 +10298,8 @@ namespace IDE
RemoveProjectItems(project);
}
mBfResolveCompiler.QueueDeferredResolveAll();
mBfResolveCompiler.QueueRefreshViewCommand(.FullRefresh);
mBfResolveCompiler?.QueueDeferredResolveAll();
mBfResolveCompiler?.QueueRefreshViewCommand(.FullRefresh);
return;
}
@ -10093,11 +10327,14 @@ namespace IDE
{
mWantsBeefClean = true;
var checkDeclName = (project.mProjectName !== project.mProjectNameDecl) && (mWorkspace.FindProject(project.mProjectNameDecl) == project);
for (var checkProject in mWorkspace.mProjects)
{
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);
checkProject.SetChanged();
@ -10115,14 +10352,27 @@ namespace IDE
}
project.mProjectName.Set(newName);
if (project.mProjectNameDecl != project.mProjectName)
delete project.mProjectNameDecl;
project.mProjectNameDecl = project.mProjectName;
project.SetChanged();
mWorkspace.ClearProjectNameCache();
mProjectPanel.RebuildUI();
}
public void RemoveProject(Project 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;
mWorkspace.SetChanged();
mWorkspace.mProjects.Remove(project);
@ -10911,7 +11161,7 @@ namespace IDE
}
}
static void QuoteIfNeeded(String str)
protected static void QuoteIfNeeded(String str)
{
if (!str.Contains(' '))
return;
@ -12398,6 +12648,8 @@ namespace IDE
base.Init();
mSettings.Apply();
mGitManager.Init();
//Yoop();
/*for (int i = 0; i < 100*1024*1024; i++)
@ -14865,6 +15117,8 @@ namespace IDE
if (mLongUpdateProfileId != 0)
DoLongUpdateCheck();
mGitManager.Update();
mPackMan.Update();
if (mWakaTime != null)
mWakaTime.Update();
if (mFindResultsPanel != null)
@ -15053,7 +15307,6 @@ namespace IDE
[Import("user32.lib"), CLink, CallingConvention(.Stdcall)]
public static extern bool MessageBeep(MessageBeepType type);
#endif
}
static

View file

@ -9,6 +9,7 @@ using Beefy;
using Beefy.gfx;
using Beefy.theme.dark;
using IDE.ui;
using System.Diagnostics;
namespace IDE
{
@ -128,6 +129,29 @@ namespace IDE
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)
{
for (let entry in Directory.Enumerate(scope String()..AppendF("{}/*.*", dirPath), .Directories | .Files))

View file

@ -1091,9 +1091,13 @@ namespace IDE
public class GeneralOptions
{
[Reflect]
public String mProjectNameDecl; // Points to mProjectNameDecl in Project
[Reflect]
public TargetType mTargetType;
[Reflect]
public List<String> mAliases = new .() ~ DeleteContainerAndItems!(_);
[Reflect]
public SemVer mVersion = new SemVer("") ~ delete _;
}
public class BeefGlobalOptions
@ -1321,6 +1325,7 @@ namespace IDE
{
public VerSpec mVerSpec ~ _.Dispose();
public String mProjectName ~ delete _;
public bool mDependencyChecked;
}
public enum DeferState
@ -1337,13 +1342,20 @@ namespace IDE
public VerSpec mVerSpec ~ _.Dispose();
}
public class ManagedInfo
{
public SemVer mVersion = new .("") ~ delete _;
public String mInfo ~ delete _;
}
public Monitor mMonitor = new Monitor() ~ delete _;
public String mNamespace = new String() ~ delete _;
public String mProjectDir = new String() ~ delete _;
public String mProjectName = new String() ~ delete _;
public String mProjectNameDecl = mProjectName ~ { if (mProjectNameDecl != mProjectName) delete _; }
public String mProjectPath = new String() ~ delete _;
public ManagedInfo mManagedInfo ~ delete _;
public DeferState mDeferState;
public List<VerReference> mVerReferences = new .() ~ DeleteContainerAndItems!(_);
//public String mLastImportDir = new String() ~ delete _;
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)
{
options.mBuildOptions.mOtherLinkFlags.Set("$(LinkFlags)");
@ -1443,6 +1465,7 @@ namespace IDE
mGeneralOptions.mTargetType = .CustomBuild;
SetupDefaultConfigs();
mGeneralOptions.mProjectNameDecl = mProjectNameDecl;
mBeefGlobalOptions.mStartupObject.Set("Program");
}
@ -1526,6 +1549,21 @@ namespace IDE
Path.GetDirectoryPath(mProjectPath, mProjectDir);
if (structuredData.Load(ProjectFileName) case .Err)
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
{
@ -1615,7 +1653,8 @@ namespace IDE
using (data.CreateObject("Project"))
{
if (!IsSingleFile)
data.Add("Name", mProjectName);
data.Add("Name", mProjectNameDecl);
data.ConditionalAdd("Version", mGeneralOptions.mVersion.mVersion);
data.ConditionalAdd("TargetType", mGeneralOptions.mTargetType, GetDefaultTargetType());
data.ConditionalAdd("StartupObject", mBeefGlobalOptions.mStartupObject, IsSingleFile ? "Program" : "");
var defaultNamespace = scope String();
@ -1955,7 +1994,24 @@ namespace IDE
using (data.Open("Project"))
{
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);
data.GetString("StartupObject", mBeefGlobalOptions.mStartupObject, IsSingleFile ? "Program" : "");
var defaultNamespace = scope String();
@ -2029,7 +2085,7 @@ namespace IDE
{
case .Ok(let project):
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();
}
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)
{
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 .();
bool CheckDependency(Project project)
VerSpec* CheckDependency(Project project)
{
if (!checkedProject.Add(project))
return false;
return null;
for (var dependency in project.mDependencies)
{
if (dependency.mProjectName == projectName)
return true;
return &dependency.mVerSpec;
let depProject = gApp.mWorkspace.FindProject(dependency.mProjectName);
if ((depProject != null) && (checkRecursively) && (CheckDependency(depProject)))
return true;
if ((depProject != null) && (checkRecursively))
{
var verSpec = CheckDependency(depProject);
if (verSpec != null)
return verSpec;
}
return false;
}
return null;
}
return CheckDependency(this);
}
public bool HasDependency(String projectName, bool checkRecursively = true) => GetDependency(projectName, checkRecursively) != null;
public void SetupDefault(Options options, String configName, String platformName)
{
bool isRelease = configName.Contains("Release");

View file

@ -223,7 +223,8 @@ namespace IDE
None,
Loaded,
ReadyToLoad,
Preparing
Preparing,
Failed
}
public class ConfigSelection : IHashable, IEquatable
@ -488,12 +489,11 @@ namespace IDE
public VerSpec mVerSpec ~ _.Dispose();
}
public class Lock
{
public enum Location
public enum Lock
{
case Cache;
case Local(String path);
case Git(String url, String tag, String hash);
public void Dispose()
{
@ -502,14 +502,14 @@ namespace IDE
case .Cache:
case .Local(let path):
delete path;
case Git(var url, var tag, var hash):
delete url;
delete tag;
delete hash;
}
}
}
public String mVersion ~ delete _;
public Location mLocation ~ _.Dispose();
}
public Monitor mMonitor = new Monitor() ~ delete _;
public String mName ~ delete _;
public String mDir ~ delete _;
@ -519,7 +519,15 @@ namespace IDE
public List<ProjectSpec> mProjectSpecs = new .() ~ DeleteContainerAndItems!(_);
public List<ProjectFileEntry> mProjectFileEntries = new .() ~ DeleteContainerAndItems!(_);
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 bool mLoading;
public bool mNeedsCreate;
@ -578,6 +586,20 @@ namespace IDE
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)
{
if (mPlatforms.IsEmpty)
@ -947,17 +969,34 @@ namespace IDE
{
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)
{
bool added = mProjectNameMap.TryAdd(name, var keyPtr, var valuePtr);
if (!added)
return;
if (mProjectNameMap.TryAdd(name, var keyPtr, var valuePtr))
{
*keyPtr = new String(name);
*valuePtr = project;
}
else
{
if (GetNamePriority(name, project) > GetNamePriority(*keyPtr, *valuePtr))
*valuePtr = project;
}
}
Add(project.mProjectName, project);
if (project.mProjectNameDecl !== project.mProjectName)
Add(project.mProjectNameDecl, project);
for (var alias in project.mGeneralOptions.mAliases)
Add(alias, project);
}
@ -979,7 +1018,11 @@ namespace IDE
}
for (var project in mProjects)
{
Add(project.mProjectName, project);
if (project.mProjectName != project.mProjectNameDecl)
Add(project.mProjectNameDecl, project);
}
for (var project in mProjects)
{

View file

@ -10,6 +10,87 @@ namespace IDE.ui
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
{
BuildPropertiesDialog mDialog;

View file

@ -145,6 +145,7 @@ namespace IDE.ui
bool mImportFolderDeferred;
bool mImportProjectDeferred;
bool mImportInstalledDeferred;
bool mImportRemoteDeferred;
public Dictionary<ListViewItem, ProjectItem> mListViewToProjectMap = new .() ~ delete _;
public Dictionary<ProjectItem, ProjectListViewItem> mProjectToListViewMap = new .() ~ delete _;
public Dictionary<ListViewItem, WorkspaceFolder> mListViewToWorkspaceFolderMap = new .() ~ delete _;
@ -2904,6 +2905,15 @@ namespace IDE.ui
#endif
}
void ImportRemoteProject()
{
#if !CLI
RemoteProjectDialog dialog = new .();
dialog.Init();
dialog.PopupWindow(gApp.mMainWindow);
#endif
}
public void ShowProjectProperties(Project project)
{
var projectProperties = new ProjectProperties(project);
@ -3081,6 +3091,14 @@ namespace IDE.ui
});
if (gApp.IsCompiling)
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.mOnMenuItemSelected.Add(new (item) => {
var workspaceFolder = GetSelectedWorkspaceFolder();
@ -3110,7 +3128,18 @@ namespace IDE.ui
}
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();
menu.AddItem();
AddWorkspaceMenuItems();
@ -3140,7 +3169,7 @@ namespace IDE.ui
{
var projectItem = GetSelectedProjectItem();
if (projectItem != null)
gApp.RetryProjectLoad(projectItem.mProject);
gApp.RetryProjectLoad(projectItem.mProject, true);
});
menu.AddItem();
//handled = true;
@ -3168,6 +3197,18 @@ namespace IDE.ui
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");
if (projectItem.mProject.mLocked)
item.mIconImage = DarkTheme.sDarkTheme.GetImage(.Check);
@ -3570,6 +3611,12 @@ namespace IDE.ui
ImportInstalledProject();
}
if (mImportRemoteDeferred)
{
mImportRemoteDeferred= false;
ImportRemoteProject();
}
ValidateCutClipboard();
}

View file

@ -10,13 +10,12 @@ using Beefy.events;
using Beefy.theme.dark;
using Beefy.gfx;
using Beefy.geom;
using IDE.Util;
namespace IDE.ui
{
public class ProjectProperties : BuildPropertiesDialog
{
ValueContainer<String> mVC;
enum CategoryType
{
General, ///
@ -27,6 +26,7 @@ namespace IDE.ui
Dependencies,
Beef_Global,
Platform,
Managed,
Targeted, ///
Beef_Targeted,
@ -38,8 +38,9 @@ namespace IDE.ui
}
public Project mProject;
Dictionary<String, ValueContainer<bool>> mDependencyValuesMap ~ DeleteDictionaryAndKeysAndValues!(_);
Dictionary<String, DependencyEntry> mDependencyValuesMap ~ DeleteDictionaryAndKeysAndValues!(_);
Project.Options[] mCurProjectOptions ~ delete _;
List<String> mUpdateProjectLocks = new .() ~ DeleteContainerAndItems!(_);
float mLockFlashPct;
public int32 mNewDebugSessionCountdown;
@ -96,6 +97,7 @@ namespace IDE.ui
AddCategoryItem(globalItem, "Dependencies");
AddCategoryItem(globalItem, "Beef");
AddCategoryItem(globalItem, "Platform");
AddCategoryItem(globalItem, "Managed");
globalItem.Open(true, true);
var targetedItem = AddCategoryItem(root, "Targeted");
@ -149,7 +151,8 @@ namespace IDE.ui
case .General,
.Project,
.Dependencies,
.Beef_Global:
.Beef_Global,
.Managed:
return .None;
case .Platform:
return .Platform;
@ -432,6 +435,7 @@ namespace IDE.ui
default:
}
}
case .Managed:
case .Build, .Debugging, .Beef_Targeted:
DeleteDistinctBuildOptions();
DistinctBuildOptions defaultTypeOptions = scope:: .();
@ -530,7 +534,9 @@ namespace IDE.ui
else
{
mCurPropertiesTargets = new Object[1];
if (categoryType == .Project)
if (categoryType == .Managed)
mCurPropertiesTargets[0] = mProject.mManagedInfo;
else if (categoryType == .Project)
mCurPropertiesTargets[0] = mProject.mGeneralOptions;
else if (categoryType == .Beef_Global)
mCurPropertiesTargets[0] = mProject.mBeefGlobalOptions;
@ -600,6 +606,8 @@ namespace IDE.ui
}
}
}
else if (categoryType == CategoryType.Managed)
PopulateManagedOptions();
else if (categoryType == CategoryType.Build)
PopulateBuildOptions();
else if (categoryType == CategoryType.Beef_Global )
@ -619,6 +627,8 @@ namespace IDE.ui
void PopulateGeneralOptions()
{
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[]
(
"Console Application",
@ -627,7 +637,19 @@ namespace IDE.ui
"Custom Build",
"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()
@ -696,7 +718,21 @@ namespace IDE.ui
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 category = root;
@ -720,62 +756,183 @@ namespace IDE.ui
for (var projectName in projectNames)
{
var dependencyContainer = new ValueContainer<bool>();
dependencyContainer.mValue = mProject.HasDependency(projectName, false);
mDependencyValuesMap[new String(projectName)] = dependencyContainer;
var project = gApp.mWorkspace.FindProject(projectName);
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);
if (IDEApp.sApp.mWorkspace.FindProject(projectName) == null)
listViewItem.mTextColor = Color.Mult(DarkTheme.COLOR_TEXT, 0xFFFF6060);
var subItem = listViewItem.CreateSubItem(1);
var subItem = (DarkListViewItem)listViewItem.CreateSubItem(1);
var checkbox = new DarkCheckBox();
checkbox.Checked = dependencyContainer.mValue;
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 = dependencyContainer;
//propEntry.mFieldInfo = dependencyContainer.GetType().GetField("mValue").Value;
propEntry.mOrigValue = Variant.Create(dependencyContainer.mValue);
propEntry.mCurValue = propEntry.mOrigValue;
propEntry.mTarget = dependencyEntry;
propEntry.mOrigValue = Variant.Create(dependencyEntry);
propEntry.mCurValue = Variant.Create(new DependencyEntry(dependencyEntry), true);
propEntry.mListViewItem = listViewItem;
propEntry.mCheckBox = checkbox;
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();
dep.mProjectName = new String(listViewItem.mLabel);
dep.mVerSpec = .SemVer(new .("*"));
dep.mVerSpec = verSpec;
mProject.mDependencies.Add(dep);
}
else
{
if (*verSpecPtr != verSpec)
{
if ((*verSpecPtr case .Git) ||
(verSpecPtr case .Git))
updateProjectLock = true;
verSpecPtr.Dispose();
*verSpecPtr = verSpec;
}
}
}
else
{
int idx = mProject.mDependencies.FindIndex(scope (dep) => dep.mProjectName == listViewItem.mLabel);
if (idx != -1)
{
var dep = mProject.mDependencies[idx];
if (dep.mVerSpec case .Git)
updateProjectLock = true;
delete mProject.mDependencies[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;
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()
{
if (mCurProjectOptions == null)
@ -985,6 +1142,8 @@ namespace IDE.ui
/*if (!AssertNotCompilingOrRunning())
return false;*/
String newProjectName = scope .();
using (mProject.mMonitor.Enter())
{
for (var targetedConfigData in mConfigDatas)
@ -1000,10 +1159,19 @@ namespace IDE.ui
for (var propEntry in propEntries)
{
if (propEntry.HasChanged())
{
if ((propEntry.mFieldInfo != default) && (propEntry.mFieldInfo.Name == "mProjectNameDecl"))
{
var newName = propEntry.mCurValue.Get<String>();
newProjectName.Append(newName);
newProjectName.Trim();
}
else
{
configDataHadChange = true;
propEntry.ApplyValue();
}
}
}
if (propPage == mPropPage)
UpdatePropertyValue(propEntries);
@ -1060,6 +1228,9 @@ namespace IDE.ui
ClearTargetedData();
}
if (!newProjectName.IsEmpty)
gApp.RenameProject(mProject, newProjectName);
return true;
}
@ -1081,6 +1252,7 @@ namespace IDE.ui
{
base.Close();
SetWorkspaceData(false);
gApp.NotifyProjectVersionLocks(mUpdateProjectLocks);
}
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 interface IMultiValued
{
void GetValue(int idx, String outValue);
bool SetValue(int idx, StringView value);
}
class OwnedStringList : List<String>
{
@ -215,6 +221,7 @@ namespace IDE.ui
public String mRelPath ~ delete _;
public bool mIsTypeWildcard;
public bool mAllowMultiline;
public bool mReadOnly;
public Insets mEditInsets ~ delete _;
public ~this()
@ -305,6 +312,18 @@ namespace IDE.ui
}
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
return Variant.Equals!<int32>(lhs, rhs);
}
@ -815,7 +834,7 @@ namespace IDE.ui
if (mPropEditWidget != null)
{
DarkListViewItem editItem = (DarkListViewItem)mEditingListViewItem.GetSubItem(1);
DarkListViewItem editItem = (DarkListViewItem)mEditingListViewItem;
let propEntry = mEditingProps[0];
float xPos;
@ -871,7 +890,7 @@ namespace IDE.ui
editWidget.GetText(newValue);
newValue.Trim();
DarkListViewItem item = (DarkListViewItem)mEditingListViewItem;
DarkListViewItem rootItem = (DarkListViewItem)mEditingListViewItem.GetSubItem(0);
//DarkListViewItem valueItem = (DarkListViewItem)item.GetSubItem(1);
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> entries = new List<String>(curEntries.GetEnumerator());
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)
entries.Add(new String(newValue));
@ -1027,6 +1046,11 @@ namespace IDE.ui
setValue = false;
}
}
else if ((curVariantType.IsObject) && (var multiValue = prevValue.Get<Object>() as IMultiValued))
{
multiValue.SetValue(mEditingListViewItem.mColumnIdx - 1, newValue);
setValue = false;
}
else
editingProp.mCurValue = Variant.Create(new String(newValue), true);
@ -1247,12 +1271,23 @@ namespace IDE.ui
if (ewc.mIsMultiline)
editWidget.InitScrollbars(false, true);
if (propEntry.mReadOnly)
editWidget.mEditWidgetContent.mIsReadOnly = true;
editWidget.mScrollContentInsets.Set(GS!(3), GS!(3), GS!(1), GS!(3));
editWidget.Content.mTextInsets.Set(GS!(-3), GS!(2), 0, GS!(2));
//editWidget.RehupSize();
if (subValueIdx != -1)
{
List<String> stringList = propEntry.mCurValue.Get<List<String>>();
var obj = propEntry.mCurValue.Get<Object>();
if (var multiValued = obj as IMultiValued)
{
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]);
@ -1279,6 +1314,7 @@ namespace IDE.ui
editWidget.mOnKeyDown.Add(new (evt) => { if (evt.mKeyCode == KeyCode.Down) MoveEditingItem(subValueIdx, 1); });
}
}
}
else
{
String label = valueItem.mLabel;
@ -1363,8 +1399,8 @@ namespace IDE.ui
hasChanged = true;
}
if (propEntry.mFieldInfo == default(FieldInfo))
return;
/*if (propEntry.mFieldInfo == default(FieldInfo))
return;*/
var curVariantType = propEntry.mCurValue.VariantType;
@ -1516,6 +1552,15 @@ namespace IDE.ui
valueItem.Label = allValues;
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)
{
propEntry.mCheckBox.Checked = propEntry.mCurValue.Get<bool>();
@ -2026,9 +2071,10 @@ namespace IDE.ui
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)
return;
EditValue(item, propertyEntries);
@ -2039,7 +2085,7 @@ namespace IDE.ui
var propEntry = propEntries[0];
DarkListViewItem parentItem = propEntry.mListViewItem;
DarkListViewItem clickedItem = (DarkListViewItem)parentItem.GetChildAtIndex(idx);
DarkListViewItem item = (DarkListViewItem)clickedItem.GetSubItem(0);
DarkListViewItem item = (DarkListViewItem)clickedItem.GetSubItem(1);
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,6 +414,7 @@ namespace IDE.ui
if (mGettingSymbolInfo)
{
if (gApp.mWorkspace.mProjectLoadState == .Loaded)
gApp.Fail("Cannot rename symbols here");
mGettingSymbolInfo = false;
return;
@ -430,6 +431,12 @@ namespace IDE.ui
if ((mKind == Kind.ShowFileReferences) || (mResolveParams.mLocalId != -1))
{
mParser = IDEApp.sApp.mBfResolveSystem.FindParser(mSourceViewPanel.mProjectSource);
if (mParser == null)
{
mGettingSymbolInfo = false;
return;
}
if ((mResolveParams != null) && (mResolveParams.mLocalId != -1))
mParser.SetAutocomplete(mCursorPos);
else

View file

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

View file

@ -22,7 +22,7 @@ namespace IDE.ui
public DarkButton mSafeModeButton;
public bool mWasCompiling;
public int mEvalCount;
public ImageWidget mCancelSymSrvButton;
public ImageWidget mCancelButton;
public int mDirtyDelay;
public int mStatusBoxUpdateCnt = -1;
@ -117,8 +117,8 @@ namespace IDE.ui
mConfigComboBox.Resize(mWidth - btnLeft, GS!(0), GS!(120), GS!(24));
mPlatformComboBox.Resize(mWidth - btnLeft - GS!(120), GS!(0), GS!(120), GS!(24));
if (mCancelSymSrvButton != null)
mCancelSymSrvButton.Resize(GS!(546), 0, GS!(20), GS!(20));
if (mCancelButton != null)
mCancelButton.Resize(GS!(546), 0, GS!(20), GS!(20));
if (mSafeModeButton != null)
{
@ -182,19 +182,31 @@ namespace IDE.ui
else
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)
{
MarkDirtyEx();
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();
}
ShowCancelButton();
float len = GS!(200);
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
{
if ((DarkTooltipManager.sTooltip != null) && (DarkTooltipManager.sTooltip.mRelWidget == this))
DarkTooltipManager.sTooltip.Close();
if (mCancelSymSrvButton != null)
if (mCancelButton != null)
{
RemoveAndDelete(mCancelSymSrvButton);
mCancelSymSrvButton = null;
RemoveAndDelete(mCancelButton);
mCancelButton = null;
}
}
@ -367,6 +409,16 @@ namespace IDE.ui
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;
if ((gApp.mDebugger?.mIsComptimeDebug == true) &&
((gApp.mDebugger.IsPaused()) || (debugState == .DebugEval)))
@ -375,12 +427,7 @@ namespace IDE.ui
}
else if (completionPct.HasValue)
{
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 * completionPct.Value, completionRect.mHeight);
DrawCompletion(completionPct.Value);
}
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));
}
void DrawStatusBox(StringView str, int32 updateCnt = -1)
void DrawStatusBox(StringView str, int32 updateCnt = -1, bool showCancelButton = false)
{
if (mStatusBoxUpdateCnt == -1)
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))))
g.FillRect(completionRect.mX, completionRect.mY, completionRect.mWidth, completionRect.mHeight);
if (mCancelSymSrvButton != null)
mCancelSymSrvButton.mX = completionRect.Right - GS!(16);
if (mCancelButton != null)
{
if (showCancelButton)
{
mCancelButton.SetVisible(true);
mCancelButton.mX = completionRect.Right - GS!(16);
}
else
{
mCancelButton.SetVisible(false);
}
}
using (g.PushColor(DarkTheme.COLOR_TEXT))
g.DrawString(str, x, statusLabelPos, FontAlign.Centered, len);
@ -429,10 +486,6 @@ namespace IDE.ui
chordState.Append(", <Awaiting Key>...");
DrawStatusBox(chordState);
}
else if (mCancelSymSrvButton != null)
{
DrawStatusBox("Retrieving Debug Symbols... ");
}
else if (mEvalCount > 20)
{
DrawStatusBox("Evaluating Expression");
@ -451,10 +504,16 @@ namespace IDE.ui
}
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)
{
DrawStatusBox("Queued Showing Source");
}
else
mStatusBoxUpdateCnt = -1;

View file

@ -11,6 +11,7 @@ using Beefy.theme.dark;
using Beefy.theme;
using Beefy.events;
using System.Diagnostics;
using IDE.Util;
//#define A
//#define B
@ -40,6 +41,7 @@ namespace IDE.ui
enum CategoryType
{
General,
Dependencies,
Beef_Global,
Targeted,
@ -53,6 +55,7 @@ namespace IDE.ui
ConfigDataGroup mCurConfigDataGroup;
Workspace.Options[] mCurWorkspaceOptions ~ delete _;
List<String> mUpdateProjectLocks = new .() ~ DeleteContainerAndItems!(_);
public this()
{
@ -62,8 +65,9 @@ namespace IDE.ui
var root = (DarkListViewItem)mCategorySelector.GetRoot();
var globalItem = AddCategoryItem(root, "General");
var item = AddCategoryItem(globalItem, "Beef");
var item = AddCategoryItem(globalItem, "Dependencies");
item.Focused = true;
AddCategoryItem(globalItem, "Beef");
globalItem.Open(true, true);
var targetedItem = AddCategoryItem(root, "Targeted");
@ -124,6 +128,7 @@ namespace IDE.ui
{
case .General,
//.Targeted,
.Dependencies,
.Beef_Global:
return .None;
default:
@ -454,7 +459,9 @@ namespace IDE.ui
mPropPage.mPropertiesListView.mShowColumnGrid = true;
mPropPage.mPropertiesListView.mShowGridLines = true;
if (categoryType == CategoryType.Beef_Global)
if (categoryType == CategoryType.Dependencies)
PopulateDependencyOptions();
else if (categoryType == CategoryType.Beef_Global)
PopulateBeefGlobalOptions();
else if (categoryType == CategoryType.Build)
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()
{
var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
@ -939,6 +1170,7 @@ namespace IDE.ui
{
base.Close();
SetWorkspaceData(false);
gApp.NotifyProjectVersionLocks(mUpdateProjectLocks);
}
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 IDE.Util;
#if BF_PLATFORM_WINDOWS
using static Git.GitApi;
#define SUPPORT_GIT
#endif
using System.Collections;
using System.Security.Cryptography;
using System.IO;
using Beefy.utils;
using System.Threading;
namespace IDE.util
{
class PackMan
{
class GitHelper
public class WorkItem
{
static bool sInitialized;
public enum Kind
{
None,
FindVersion,
Clone,
Checkout
}
public this()
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()
{
if (!sInitialized)
{
#if SUPPORT_GIT
#unwarn
var result = git_libgit2_init();
sInitialized = true;
#endif
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)
{
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;
}
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
{
[Reflect]
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 bool IsEmpty => String.IsNullOrEmpty(mVersion);
public this()
{
@ -27,5 +68,240 @@ namespace IDE.Util
mVersion = new String(ver);
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):
return .Path(new String(path));
case .Git(let url, let ver):
if (ver == null)
return .Git(new String(url), null);
return .Git(new String(url), new SemVer(ver));
}
}
@ -113,7 +115,7 @@ namespace IDE.Util
using (data.CreateObject(name))
{
data.Add("Git", path);
if (ver != null)
if ((ver != null) && (!ver.mVersion.IsEmpty))
data.Add("Version", ver.mVersion);
}
case .SemVer(var ver):