diff --git a/IDE/src/BeefConfig.bf b/IDE/src/BeefConfig.bf index bdfc0317..4b1369df 100644 --- a/IDE/src/BeefConfig.bf +++ b/IDE/src/BeefConfig.bf @@ -3,6 +3,8 @@ using System; using System.Collections; using System.IO; using Beefy.utils; +using System.Threading; +using System.Diagnostics; namespace IDE { @@ -14,6 +16,68 @@ namespace IDE public SemVer mVersion ~ delete _; public VerSpecRecord mLocation ~ delete _; public ConfigFile mConfigFile; + + public bool mParsedConfig; + } + + public class Registry + { + public List mEntries = new List() ~ DeleteContainerAndItems!(_); + public Monitor mMonitor = new .() ~ delete _; + public WaitEvent mEvent = new .() ~ delete _; + + public ~this() + { + mEvent.WaitFor(); + } + + public void ParseConfig(RegistryEntry entry) + { + entry.mParsedConfig = true; + + if (entry.mLocation.mVerSpec case .Path(let path)) + { + String configPath = scope String()..AppendF("{}/BeefProj.toml", path); + StructuredData sd = scope .(); + if (sd.Load(configPath) case .Ok) + { + using (sd.Open("Project")) + { + var projName = scope String(); + sd.GetString("Name", projName); + if (!projName.IsEmpty) + { + using (mMonitor.Enter()) + entry.mProjName.Set(projName); + } + } + } + } + } + + public void WaitFor() + { + mEvent.WaitFor(); + } + + void Resolve() + { + // NOTE: We allow a race condition where ParseConfig can possibly occur on multiple threads + // at the same time + for (var entry in mEntries) + { + if (!entry.mParsedConfig) + ParseConfig(entry); + } + + mEvent.Set(true); + } + + public void StartResolve() + { + var thread = new Thread(new => Resolve); + thread.Start(true); + } } public class LibDirectory @@ -29,9 +93,16 @@ namespace IDE } List mConfigFiles = new List() ~ DeleteContainerAndItems!(_); - public List mRegistry = new List() ~ DeleteContainerAndItems!(_); + public Registry mRegistry ~ delete _; List mConfigPathQueue = new List() ~ DeleteContainerAndItems!(_); List mLibDirectories = new List() ~ DeleteContainerAndItems!(_); + List mWatchers = new .() ~ DeleteContainerAndItems!(_); + public bool mLibsChanged; + + void LibsChanged() + { + mLibsChanged = true; + } Result Load(StringView path) { @@ -49,7 +120,7 @@ namespace IDE { RegistryEntry regEntry = new RegistryEntry(); regEntry.mProjName = new String(projName); - mRegistry.Add(regEntry); + mRegistry.mEntries.Add(regEntry); regEntry.mConfigFile = configFile; @@ -80,18 +151,15 @@ namespace IDE { RegistryEntry regEntry = new RegistryEntry(); regEntry.mProjName = new String(projName); - mRegistry.Add(regEntry); + mRegistry.mEntries.Add(regEntry); regEntry.mConfigFile = configFile; - var verString = scope String(); - data.GetString("Version", verString); regEntry.mVersion = new SemVer(); regEntry.mVersion.Parse("0.0.0"); regEntry.mLocation = new VerSpecRecord(); - using (data.Open("Location")) - regEntry.mLocation.SetPath(filePath); + regEntry.mLocation.SetPath(filePath); } else { @@ -116,6 +184,16 @@ namespace IDE Path.GetAbsolutePath(libDir.mPath, configFile.mConfigDir, absPath); AddFromLibraryPath(absPath); + absPath.Append(Path.DirectorySeparatorChar); + + FileSystemWatcher watcher = new FileSystemWatcher(absPath); + watcher.OnChanged.Add(new (fileName) => LibsChanged()); + watcher.OnCreated.Add(new (fileName) => LibsChanged()); + watcher.OnDeleted.Add(new (fileName) => LibsChanged()); + watcher.OnRenamed.Add(new (newName, oldName) => LibsChanged()); + watcher.OnError.Add(new () => LibsChanged()); + watcher.StartRaisingEvents(); + mWatchers.Add(watcher); } } @@ -163,14 +241,18 @@ namespace IDE public Result Load() { - ClearAndDeleteItems(mRegistry); + mLibsChanged = false; + delete mRegistry; + mRegistry = new Registry(); ClearAndDeleteItems(mConfigFiles); + ClearAndDeleteItems(mWatchers); for (int i = mConfigPathQueue.Count - 1; i >= 0; i--) { let path = mConfigPathQueue[i]; Try!(Load(path)); } + mRegistry.StartResolve(); return .Ok; } } diff --git a/IDE/src/IDEApp.bf b/IDE/src/IDEApp.bf index 5489fad6..5568dc57 100644 --- a/IDE/src/IDEApp.bf +++ b/IDE/src/IDEApp.bf @@ -2616,18 +2616,30 @@ namespace IDE if (let project = mWorkspace.FindProject(projectName)) return project; - + if (useVerSpecRecord.mVerSpec case .SemVer) { - for (int regEntryIdx = mBeefConfig.mRegistry.Count - 1; regEntryIdx >= 0; regEntryIdx--) + // First pass we just try to use the 'expected' project name + FindLoop: for (int pass < 2) { - var regEntry = mBeefConfig.mRegistry[regEntryIdx]; - - if (regEntry.mProjName == projectName) + using (mBeefConfig.mRegistry.mMonitor.Enter()) { - useVerSpecRecord = regEntry.mLocation; - verConfigDir = regEntry.mConfigFile.mConfigDir; + for (int regEntryIdx = mBeefConfig.mRegistry.mEntries.Count - 1; regEntryIdx >= 0; regEntryIdx--) + { + var regEntry = mBeefConfig.mRegistry.mEntries[regEntryIdx]; + + if ((regEntry.mProjName == projectName) && (!regEntry.mParsedConfig)) + mBeefConfig.mRegistry.ParseConfig(regEntry); + + if (regEntry.mProjName == projectName) + { + useVerSpecRecord = regEntry.mLocation; + verConfigDir = regEntry.mConfigFile.mConfigDir; + break FindLoop; + } + } } + mBeefConfig.mRegistry.WaitFor(); } } @@ -11072,6 +11084,12 @@ namespace IDE tabButton.Activate(); } + public void CheckLoadConfig() + { + if (mBeefConfig.mLibsChanged) + LoadConfig(); + } + void LoadConfig() { delete mBeefConfig; diff --git a/IDE/src/Project.bf b/IDE/src/Project.bf index e4b2d014..f7ecf435 100644 --- a/IDE/src/Project.bf +++ b/IDE/src/Project.bf @@ -1318,6 +1318,19 @@ namespace IDE options.mBuildOptions.mOtherLinkFlags.Set("$(LinkFlags)"); } + public static void GetSanitizedName(String projectName, String outName, bool allowDot = false) + { + for (let c in projectName.RawChars) + { + if ((c.IsLetterOrDigit) || (c == '_')) + outName.Append(c); + else if (c == '-') + outName.Append('_'); + else if ((c == '.') && (allowDot)) + outName.Append(c); + } + } + public this() { mRootFolder = new ProjectFolder(); @@ -1492,7 +1505,9 @@ namespace IDE data.Add("Name", mProjectName); data.ConditionalAdd("TargetType", mGeneralOptions.mTargetType, GetDefaultTargetType()); data.ConditionalAdd("StartupObject", mBeefGlobalOptions.mStartupObject, IsSingleFile ? "Program" : ""); - data.ConditionalAdd("DefaultNamespace", mBeefGlobalOptions.mDefaultNamespace, mProjectName); + var defaultNamespace = scope String(); + GetSanitizedName(mProjectName, defaultNamespace, true); + data.ConditionalAdd("DefaultNamespace", mBeefGlobalOptions.mDefaultNamespace, defaultNamespace); WriteStrings("Aliases", mGeneralOptions.mAliases); WriteStrings("ProcessorMacros", mBeefGlobalOptions.mPreprocessorMacros); WriteDistinctOptions(mBeefGlobalOptions.mDistinctBuildOptions); @@ -1807,7 +1822,9 @@ namespace IDE data.GetString("Name", mProjectName); ReadStrings("Aliases", mGeneralOptions.mAliases); data.GetString("StartupObject", mBeefGlobalOptions.mStartupObject, IsSingleFile ? "Program" : ""); - data.GetString("DefaultNamespace", mBeefGlobalOptions.mDefaultNamespace, mProjectName); + var defaultNamespace = scope String(); + GetSanitizedName(mProjectName, defaultNamespace, true); + data.GetString("DefaultNamespace", mBeefGlobalOptions.mDefaultNamespace, defaultNamespace); ReadStrings("ProcessorMacros", mBeefGlobalOptions.mPreprocessorMacros); for (data.Enumerate("DistinctOptions")) { @@ -2317,7 +2334,8 @@ namespace IDE public void SetupDefault(BeefGlobalOptions generalOptions) { - generalOptions.mDefaultNamespace.Set(mProjectName); + generalOptions.mDefaultNamespace.Clear(); + GetSanitizedName(mProjectName, generalOptions.mDefaultNamespace, true); generalOptions.mStartupObject.Set(scope String()..AppendF("{}.Program", generalOptions.mDefaultNamespace)); } diff --git a/IDE/src/ui/InstalledProjectDialog.bf b/IDE/src/ui/InstalledProjectDialog.bf index ad7aea68..b93d8e0d 100644 --- a/IDE/src/ui/InstalledProjectDialog.bf +++ b/IDE/src/ui/InstalledProjectDialog.bf @@ -63,7 +63,9 @@ namespace IDE.ui void FindProjects() { - for (let registryEntry in gApp.mBeefConfig.mRegistry) + gApp.CheckLoadConfig(); + gApp.mBeefConfig.mRegistry.WaitFor(); + for (let registryEntry in gApp.mBeefConfig.mRegistry.mEntries) { InstalledProject installedProject = new .(); installedProject.mName = new String(registryEntry.mProjName); diff --git a/IDE/src/ui/NewProjectDialog.bf b/IDE/src/ui/NewProjectDialog.bf index 59b18d79..8b84ceda 100644 --- a/IDE/src/ui/NewProjectDialog.bf +++ b/IDE/src/ui/NewProjectDialog.bf @@ -71,17 +71,11 @@ namespace IDE.ui Path.GetAbsolutePath(origDirectory, gApp.mWorkspace.mDir, projDirectory); } - bool isNameValid = projName.Length > 0; - for (int32 i = 0; i < projName.Length; i++) - { - char8 c = projName[i]; - if ((!c.IsLetterOrDigit) && (c != '-') && (c != ' ') && (c != '_')) - isNameValid = false; - } + bool isNameValid = projName.Length > 0; if (!isNameValid) { mNameEdit.SetFocus(); - app.Fail("Invalid project name. The project name can only consist of alphanumeric characters, spaces, dashes, and underscores."); + app.Fail("Invalid project name"); return false; } diff --git a/IDE/src/ui/ProjectPanel.bf b/IDE/src/ui/ProjectPanel.bf index 53caabe7..1873dde9 100644 --- a/IDE/src/ui/ProjectPanel.bf +++ b/IDE/src/ui/ProjectPanel.bf @@ -1863,6 +1863,8 @@ namespace IDE.ui return null; } + gApp.CheckLoadConfig(); + //TODO: Protect against importing an existing project Project proj = new Project(); String projFilePath = scope String(filePath);