diff --git a/BeefLibs/corlib/src/System/Diagnostics/FileVersionInfo.bf b/BeefLibs/corlib/src/System/Diagnostics/FileVersionInfo.bf new file mode 100644 index 00000000..8924ab08 --- /dev/null +++ b/BeefLibs/corlib/src/System/Diagnostics/FileVersionInfo.bf @@ -0,0 +1,593 @@ +// This file contains portions of code released by Microsoft under the MIT license as part +// of an open-sourcing initiative in 2014 of the C# core libraries. +// The original source was submitted to https://github.com/Microsoft/referencesource + +namespace System.Diagnostics +{ + using System.Text; + using System.IO; + using System.Security; + using System; + using System.Globalization; + + public class FileVersionInfo + { + public enum Error + { + FileNotFound, + NotSupported + } + + private String mFileName ~ delete _; + private String mCompanyName ~ delete _; + private String mFileDescription ~ delete _; + private String mFileVersion ~ delete _; + private String mInternalName ~ delete _; + private String mLegalCopyright ~ delete _; + private String mOriginalFilename ~ delete _; + private String mProductName ~ delete _; + private String mProductVersion ~ delete _; + private String mComments ~ delete _; + private String mLegalTrademarks ~ delete _; + private String mPrivateBuild ~ delete _; + private String mSpecialBuild ~ delete _; + private String mLanguage ~ delete _; + private int32 mFileMajor; + private int32 mFileMinor; + private int32 mFileBuild; + private int32 mFilePrivate; + private int32 mProductMajor; + private int32 mProductMinor; + private int32 mProductBuild; + private int32 mProductPrivate; + private int32 mFileFlags; + + public void Dispose() + { + DeleteAndNullify!(mFileName); + DeleteAndNullify!(mCompanyName); + DeleteAndNullify!(mFileDescription); + DeleteAndNullify!(mFileVersion); + DeleteAndNullify!(mInternalName); + DeleteAndNullify!(mLegalCopyright); + DeleteAndNullify!(mOriginalFilename); + DeleteAndNullify!(mProductName); + DeleteAndNullify!(mProductVersion); + DeleteAndNullify!(mComments); + DeleteAndNullify!(mLegalTrademarks); + DeleteAndNullify!(mPrivateBuild); + DeleteAndNullify!(mSpecialBuild); + DeleteAndNullify!(mLanguage); + mFileMajor = 0; + mFileMajor = 0; + mFileBuild = 0; + mFilePrivate = 0; + mProductMajor = 0; + mProductMinor = 0; + mProductBuild = 0; + mProductPrivate = 0; + mFileFlags = 0; + } + + /// Gets the comments associated with the file. + public StringView Comments + { + get + { + return mComments; + } + } + + /// >Gets the name of the company that produced the file. + public StringView CompanyName + { + get + { + return mCompanyName; + } + } + + /// Gets the build number of the file. + public int FileBuildPart + { + get + { + return mFileBuild; + } + } + + /// Gets the description of the file. + /// + public String FileDescription + { + get + { + return mFileDescription; + } + } + + /// + /// Gets the major part of the version number. + /// + public int FileMajorPart + { + get + { + return mFileMajor; + } + } + + /// + /// Gets the minor + /// part of the version number of the file. + /// + public int FileMinorPart + { + get + { + return mFileMinor; + } + } + + /// + /// Gets the name of the file that this instance describes. + /// + public String FileName + { + get + { + return mFileName; + } + } + + /// + /// Gets the file private part number. + /// + public int FilePrivatePart + { + get + { + return mFilePrivate; + } + } + + /// + /// Gets the file version number. + /// + public String FileVersion + { + get + { + return mFileVersion; + } + } + + /// + /// Gets the internal name of the file, if one exists. + /// + public String InternalName + { + get + { + return mInternalName; + } + } + + /// + /// Gets a value that specifies whether the file + /// contains debugging information or is compiled with debugging features enabled. + /// + public bool IsDebug + { + get + { +#if BF_PLATFORM_WINDOWS + return (mFileFlags & Windows.VS_FF_DEBUG) != 0; +#else + return false; +#endif + } + } + + /// + /// Gets a value that specifies whether the file has been modified and is not identical to + /// the original shipping file of the same version number. + /// + public bool IsPatched + { + get + { +#if BF_PLATFORM_WINDOWS + return (mFileFlags & Windows.VS_FF_PATCHED) != 0; +#else + return false; +#endif + } + } + + /// + /// Gets a value that specifies whether the file was built using standard release procedures. + /// + public bool IsPrivateBuild + { + get + { +#if BF_PLATFORM_WINDOWS + return (mFileFlags & Windows.VS_FF_PRIVATEBUILD) != 0; +#else + return false; +#endif + } + } + + /// + /// Gets a value that specifies whether the file + /// is a development version, rather than a commercially released product. + /// + public bool IsPreRelease + { + get + { +#if BF_PLATFORM_WINDOWS + return (mFileFlags & Windows.VS_FF_PRERELEASE) != 0; +#else + return false; +#endif + } + } + + /// + /// Gets a value that specifies whether the file is a special build. + /// + public bool IsSpecialBuild + { + get + { +#if BF_PLATFORM_WINDOWS + return (mFileFlags & Windows.VS_FF_SPECIALBUILD) != 0; +#else + return false; +#endif + } + } + + /// + /// + /// Gets the default language String for the version info block. + /// + /// + public String Language + { + get + { + return mLanguage; + } + } + + /// + /// Gets all copyright notices that apply to the specified file. + /// + public String LegalCopyright + { + get + { + return mLegalCopyright; + } + } + + /// + /// Gets the trademarks and registered trademarks that apply to the file. + /// + public String LegalTrademarks + { + get + { + return mLegalTrademarks; + } + } + + /// + /// Gets the name the file was created with. + /// + public String OriginalFilename + { + get + { + return mOriginalFilename; + } + } + + /// + /// Gets information about a private version of the file. + /// + public String PrivateBuild + { + get + { + return mPrivateBuild; + } + } + + /// + /// Gets the build number of the product this file is associated with. + /// + public int ProductBuildPart + { + get + { + return mProductBuild; + } + } + + /// + /// Gets the major part of the version number for the product this file is associated with. + /// + public int ProductMajorPart + { + get + { + return mProductMajor; + } + } + + /// + /// Gets the minor part of the version number for the product the file is associated with. + /// + public int ProductMinorPart + { + get + { + return mProductMinor; + } + } + + /// + /// Gets the name of the product this file is distributed with. + /// + public String ProductName + { + get + { + return mProductName; + } + } + + /// + /// Gets the private part number of the product this file is associated with. + /// + public int ProductPrivatePart + { + get + { + return mProductPrivate; + } + } + + /// + /// Gets the version of the product this file is distributed with. + /// + public String ProductVersion + { + get + { + return mProductVersion; + } + } + + /// + /// Gets the special build information for the file. + /// + public String SpecialBuild + { + get + { + return mSpecialBuild; + } + } + + private static void ConvertTo8DigitHex(int value, String s) + { + s.AppendF("{:X8}", value); + } + +#if BF_PLATFORM_WINDOWS + private static Windows.VS_FIXEDFILEINFO GetFixedFileInfo(void* memPtr) + { + void* memRef = null; + if (Windows.VerQueryValueW(memPtr, "\\".ToScopedNativeWChar!(), ref memRef, var memLen)) + return *(Windows.VS_FIXEDFILEINFO*)memRef; + return .(); + } + + private static String GetFileVersionLanguage(void* memPtr) + { + int langid = GetVarEntry(memPtr) >> 16; + + char16[256] langChars = .(0, ?); + Windows.VerLanguageNameW((uint32)langid, &langChars, (uint32)langChars.Count); + return new String()..Append(&langChars); + } + + private static String GetFileVersionString(void* memPtr, String name) + { + String str = new String(); + void* memRef = null; + if (Windows.VerQueryValueW(memPtr, name.ToScopedNativeWChar!(), ref memRef, var memLen)) + { + if (memRef != null) + { + str.Append((char16*)memRef); + } + } + return str; + } + + private static int32 GetVarEntry(void* memPtr) + { + void* memRef = null; + if (Windows.VerQueryValueW(memPtr, "\\VarFileInfo\\Translation".ToScopedNativeWChar!(), ref memRef, var memLen)) + { + return ((int32)(*(int16*)(memRef)) << 16) + *(int16*)((uint8*)memRef + 2); + } + return 0x040904E4; + } + + // + // This function tries to find version informaiton for a specific codepage. + // Returns true when version information is found. + // + private bool GetVersionInfoForCodePage(void* memIntPtr, String codepage) + { + StringView template = "\\\\StringFileInfo\\\\{0}\\\\{1}"; + + mCompanyName = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "CompanyName")); + mFileDescription = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "FileDescription")); + mFileVersion = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "FileVersion")); + mInternalName = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "InternalName")); + mLegalCopyright = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "LegalCopyright")); + mOriginalFilename = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "OriginalFilename")); + mProductName = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "ProductName")); + mProductVersion = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "ProductVersion")); + mComments = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "Comments")); + mLegalTrademarks = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "LegalTrademarks")); + mPrivateBuild = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "PrivateBuild")); + mSpecialBuild = GetFileVersionString(memIntPtr, scope String()..AppendF(CultureInfo.InvariantCulture, template, codepage, "SpecialBuild")); + + mLanguage = GetFileVersionLanguage(memIntPtr); + + Windows.VS_FIXEDFILEINFO ffi = GetFixedFileInfo(memIntPtr); + mFileMajor = HIWORD(ffi.dwFileVersionMS); + mFileMinor = LOWORD(ffi.dwFileVersionMS); + mFileBuild = HIWORD(ffi.dwFileVersionLS); + mFilePrivate = LOWORD(ffi.dwFileVersionLS); + mProductMajor = HIWORD(ffi.dwProductVersionMS); + mProductMinor = LOWORD(ffi.dwProductVersionMS); + mProductBuild = HIWORD(ffi.dwProductVersionLS); + mProductPrivate = LOWORD(ffi.dwProductVersionLS); + mFileFlags = (.)ffi.dwFileFlags; + + // fileVersion is chosen based on best guess. Other fields can be used if appropriate. + return (mFileVersion != string.Empty); + } + + /// + /// Returns a System.Windows.Forms.FileVersionInfo representing the version information associated with the specified file. + /// + public Result GetVersionInfo(StringView fileName) + { + if (mFileName != null) + { + Dispose(); + } + + // Check for the existence of the file. File.Exists returns false + // if Read permission is denied. + if (!File.Exists(fileName)) + { + // + // The previous version of this code in the success case would require + // one imperative Assert for PathDiscovery permission, one Demand for + // PathDiscovery permission (blocked by the Assert), and 2 demands for + // Read permission. It turns out that File.Exists does a demand for + // Read permission, so in the success case, we only need to do a single Demand. + // In the success case, this change increases the performance of this + // function dramatically. + // + // In the failure case, we want to remain backwardly compatible by throwing + // a SecurityException in the case where Read access is denied + // (it can be argued that this is less secure than throwing a FileNotFoundException, + // but perhaps not so much as to be worth a breaking change). + // File.Exists eats a SecurityException, so we need to Demand for it + // here. Since performance in the failure case is not crucial, as an + // exception will be thrown anyway, we do a Demand for Read access. + // If that does not throw an exception, then we will throw a FileNotFoundException. + // + // We also change the code to do a declarative Assert for PathDiscovery, + // as that performs much better than an imperative Assert. + // + return .Err(.FileNotFound); + } + + mFileName = new String(fileName); + char16* wcStr = fileName.ToScopedNativeWChar!(); + + uint32 handle; // This variable is not used, but we need an out variable. + uint32 infoSize = Windows.GetFileVersionInfoSizeW(wcStr, out handle); + + if (infoSize != 0) + { + uint8* memPtr = new:ScopedAlloc! uint8[infoSize]*; + if (Windows.GetFileVersionInfoW(wcStr, 0, infoSize, memPtr)) + { + int langid = GetVarEntry(memPtr); + String langStr = scope .(); + ConvertTo8DigitHex(langid, langStr); + if (!GetVersionInfoForCodePage(memPtr, langStr)) + { + // Some dlls might not contain correct codepage information. In this case we will fail during lookup. + // Explorer will take a few shots in dark by trying following ID: + // + // 040904B0 // US English + CP_UNICODE + // 040904E4 // US English + CP_USASCII + // 04090000 // US English + unknown codepage + // Explorer also randomly guess 041D04B0=Swedish+CP_UNICODE and 040704B0=German+CP_UNICODE) sometimes. + // We will try to simulate similiar behavior here. + int[] ids = scope .(0x040904B0, 0x040904E4, 0x04090000); + for (int id in ids) + { + if (id != langid) + { + String idStr = scope .(); + ConvertTo8DigitHex(id, idStr); + if (GetVersionInfoForCodePage(memPtr, idStr)) + { + break; + } + } + } + } + } + } + return .Ok; + } +#else + public Result GetVersionInfo(StringView fileName) + { + return .Err(.NotSupported); + } +#endif + + private static int32 HIWORD(uint32 val) + { + return (.)((val << 16) & 0xFFFF); + } + + private static int32 LOWORD(uint32 val) + { + return (.)(val & 0xFFFF); + } + + /// + /// Returns a partial list of properties in System.Windows.Forms.FileVersionInfo + /// and their values. + /// + public override void ToString(String str) + { + String nl = "\r\n"; + str.Append("File: "); str.Append(FileName); str.Append(nl); + str.Append("InternalName: "); str.Append(InternalName); str.Append(nl); + str.Append("OriginalFilename: "); str.Append(OriginalFilename); str.Append(nl); + str.Append("FileVersion: "); str.Append(FileVersion); str.Append(nl); + str.Append("FileDescription: "); str.Append(FileDescription); str.Append(nl); + str.Append("Product: "); str.Append(ProductName); str.Append(nl); + str.Append("ProductVersion: "); str.Append(ProductVersion); str.Append(nl); + str.Append("Debug: "); IsDebug.ToString(str); str.Append(nl); + str.Append("Patched: "); IsPatched.ToString(str); str.Append(nl); + str.Append("PreRelease: "); IsPreRelease.ToString(str); str.Append(nl); + str.Append("PrivateBuild: "); IsPrivateBuild.ToString(str); str.Append(nl); + str.Append("SpecialBuild: "); IsSpecialBuild.ToString(str); str.Append(nl); + str.Append("Language: "); str.Append(Language); str.Append(nl); + } + + } +} diff --git a/IDE/src/IDEApp.bf b/IDE/src/IDEApp.bf index 2cb6265b..1e2bf29e 100644 --- a/IDE/src/IDEApp.bf +++ b/IDE/src/IDEApp.bf @@ -122,6 +122,7 @@ namespace IDE public String mDbgVersionedCompileDir ~ delete _; public DateTime mDbgHighestTime; public bool mIsFirstRun; + public FileVersionInfo mVersionInfo ~ delete _; //public ToolboxPanel mToolboxPanel; public Monitor mMonitor = new Monitor() ~ delete _; @@ -157,7 +158,6 @@ namespace IDE public IDETabbedView mActiveDocumentsTabbedView; public static new IDEApp sApp; - //public IPCHostManager mIPCHostManager; public Image mTransparencyGridImage ~ delete _; public Image mSquiggleImage ~ delete _; public Image mCircleImage ~ delete _; @@ -676,9 +676,6 @@ namespace IDE public override void Shutdown() { - /*if (mIPCHostManager != null) - mIPCHostManager.Dispose();*/ - if ((mDebugger != null) && (mDebugger.mIsRunning)) { mDebugger.DisposeNativeBreakpoints(); @@ -758,26 +755,35 @@ namespace IDE #endif } + public void OpenWorkspace(StringView openFileName) + { + CloseWorkspace(); + + mWorkspace.mDir = new String(); + Path.GetDirectoryPath(openFileName, mWorkspace.mDir); + mWorkspace.mName = new String(); + Path.GetFileName(mWorkspace.mDir, mWorkspace.mName); + + LoadWorkspace(.OpenOrNew); + FinishShowingNewWorkspace(); + } + public void DoOpenWorkspace() { #if !CLI if (mDeferredOpenFileName != null) { - CloseWorkspace(); - - mWorkspace.mDir = new String(); - Path.GetDirectoryPath(mDeferredOpenFileName, mWorkspace.mDir); - mWorkspace.mName = new String(); - Path.GetFileName(mWorkspace.mDir, mWorkspace.mName); - - LoadWorkspace(.OpenOrNew); - FinishShowingNewWorkspace(); - + OpenWorkspace(mDeferredOpenFileName); DeleteAndNullify!(mDeferredOpenFileName); return; } var folderDialog = scope FolderBrowserDialog(); + var initialDir = scope String(); + if (mInstallDir.Length > 0) + Path.GetDirectoryPath(.(mInstallDir, 0, mInstallDir.Length - 1), initialDir); + initialDir.Concat(Path.DirectorySeparatorChar, "Samples"); + folderDialog.SelectedPath = initialDir; if (folderDialog.ShowDialog(GetActiveWindow()).GetValueOrDefault() == .OK) { var selectedPath = scope String..AppendF(folderDialog.SelectedPath); @@ -1073,6 +1079,7 @@ namespace IDE } ~ delete onSave); aDialog.AddButton("Don't Save", new (evt) => { + OutputLine("Changes not saved."); onNoSave(); } ~ delete onNoSave); @@ -2080,6 +2087,8 @@ namespace IDE delete mWorkspace; mWorkspace = new Workspace(); + + OutputLine("Workspace closed."); } void FinishShowingNewWorkspace(bool loadUserData = true) @@ -2233,6 +2242,7 @@ namespace IDE Project project = new Project(); mWorkspace.mProjects.Add(project); + project.mProjectPath.Set(projectPath); if (!project.Load(projectPath)) { project.mBeefGlobalOptions.mDefaultNamespace.Set(projectName); @@ -2243,7 +2253,6 @@ namespace IDE project.mNeedsCreate = true; project.mHasChanged = true; project.mProjectDir.Set(mWorkspace.mDir); - project.mProjectPath.Set(mWorkspace.mDir); Utils.GetDirWithSlash(project.mProjectPath); project.mProjectPath.Append("BeefProj.toml"); //project.FinishCreate(); @@ -2294,21 +2303,6 @@ namespace IDE mWorkspace.mNeedsCreate = true; OutputLine("{0}Use 'File\\Save All' to commit to disk.{1}", Font.EncodeColor(0xfffef860), Font.EncodePopColor()); } - - - /*for (int i < 10) - { - var testStr = scope String(); - - for (int j < 10) - { - Font.StrEncodeColor(0xfffebd57, testStr); - testStr.Append('A' + j); - Font.StrEncodePopColor(testStr); - } - - OutputLine(testStr); - }*/ } else { @@ -7752,8 +7746,8 @@ namespace IDE bfProject.ClearDependencies(); for (var depProject in depProjectList) { - var depBfProject = bfSystem.mProjectMap[depProject]; - bfProject.AddDependency(depBfProject); + if (bfSystem.mProjectMap.TryGetValue(depProject, var depBfProject)) + bfProject.AddDependency(depBfProject); } if (!bfCompiler.mIsResolveOnly) @@ -9546,6 +9540,27 @@ namespace IDE title.Append(" - "); } title.Append("Beef IDE"); + + String extraStr = scope .(); + + String exePath = scope .(); + Environment.GetExecutableFilePath(exePath); + if (exePath.Contains(@"\host\")) + extraStr.Append("host"); + + String exeFileName = scope .(); + Path.GetFileNameWithoutExtension(exePath, exeFileName); + int slashPos = exeFileName.IndexOf('_'); + if (slashPos != -1) + { + if (!extraStr.IsEmpty) + extraStr.Append(" "); + extraStr.Append(exeFileName, slashPos + 1); + } + + if (!extraStr.IsEmpty) + title.AppendF(" [{}]", extraStr); + mMainWindow.SetTitle(title); } @@ -9575,7 +9590,9 @@ namespace IDE Directory.GetCurrentDirectory(mInitialCWD); - mColorMatrix = Matrix4.Identity; +#if DEBUG + //mColorMatrix = Matrix4.Identity; +#endif base.Init(); mSettings.Apply(); @@ -9657,7 +9674,11 @@ namespace IDE mAutoCompletePanel = new AutoCompletePanel(); mAutoCompletePanel.mAutoDelete = false; - OutputLine("IDE Started. Build 1."); + String exeFilePath = scope .(); + Environment.GetExecutableFilePath(exeFilePath); + mVersionInfo = new .(); + mVersionInfo.GetVersionInfo(exeFilePath).IgnoreError(); + OutputLine("IDE Started. Version {}.", mVersionInfo.FileVersion); /*if (!mRunningTestScript) { @@ -9734,22 +9755,9 @@ namespace IDE UpdateRecentDisplayedFilesMenuItems(); if (mRecentlyDisplayedFiles.Count > 0) ShowRecentFile(0); - //mIPCHostManager = IPCHostManager.sIPCHostManager = new IPCHostManager(); - //mIPCHostManager.Init(); - - //TODO: Temporary - //mBfResolveCompiler.StartTiming(); - + mProjectPanel.RebuildUI(); - //mDebugger.LoadDebugVisualizers(scope String(mInstallDir, "BeefDbgVis.toml")); - - /*foreach (var project in mWorkspace.mProjects) - project.Save();*/ - - //OutputLine("IDE Started. Build 1."); - //mProjectPanel.ShowProjectProperties(mWorkspace.mProjects[0]); - if (mProcessAttachId != 0) { Process debugProcess = scope Process(); @@ -11581,7 +11589,8 @@ namespace IDE } #if !CLI - UpdateIPC(); + if (mSettings.mEnableDevMode) + UpdateIPC(); #endif if (mRunTest)