diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f0afb..0751020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Code Editor Package for Visual Studio +## [1.0.9] - 2019-03-05 + +Updated MonoDevelop support, to pass correct arguments, and not import VSTU plugin +Use release build of COMIntegration for Visual Studio + + +## [1.0.7] - 2019-04-30 + +Ensure asset database is refreshed when generating csproj and solution files. + +## [1.0.6] - 2019-04-27 + +Add support for generating all csproj files. + +## [1.0.5] - 2019-04-18 + +Fix relative package paths. +Fix opening editor on mac. + ## [1.0.4] - 2019-04-12 - Fixing null reference issue for callbacks to AssetPostProcessor. diff --git a/Editor/COMIntegration/Debug/COMIntegration.exe b/Editor/COMIntegration/Debug/COMIntegration.exe deleted file mode 100644 index a77822e..0000000 Binary files a/Editor/COMIntegration/Debug/COMIntegration.exe and /dev/null differ diff --git a/Editor/COMIntegration/Debug.meta b/Editor/COMIntegration/Release.meta similarity index 100% rename from Editor/COMIntegration/Debug.meta rename to Editor/COMIntegration/Release.meta diff --git a/Editor/Discovery.cs b/Editor/Discovery.cs index 8c6e46a..7a0c5b9 100644 --- a/Editor/Discovery.cs +++ b/Editor/Discovery.cs @@ -1,7 +1,12 @@ using System; +using System.IO; +using System.Collections.Generic; using System.Linq; using Unity.CodeEditor; using UnityEngine; +using UnityEditor; +using System.Diagnostics; +using Microsoft.Win32; namespace VisualStudioEditor { @@ -31,16 +36,239 @@ namespace VisualStudioEditor { try { - return VSEditor.GetInstalledVisualStudios().Select(pair => new CodeEditor.Installation + if (VSEditor.IsWindows) { - Path = pair.Value[0], - Name = VisualStudioVersionToNiceName(pair.Key) - }).ToArray(); + return GetInstalledVisualStudios().Select(pair => new CodeEditor.Installation + { + Path = pair.Value[0], + Name = VisualStudioVersionToNiceName(pair.Key) + }).ToArray(); + } + if (VSEditor.IsOSX) + { + var installationList = new List(); + AddIfDirectoryExists("Visual Studio", "/Applications/Visual Studio.app", installationList); + AddIfDirectoryExists("Visual Studio (Preview)", "/Applications/Visual Studio (Preview).app", installationList); + return installationList.ToArray(); + } } catch (Exception ex) { - Debug.Log($"Error detecting Visual Studio installations: {ex.Message}{Environment.NewLine}{ex.StackTrace}"); - return new CodeEditor.Installation[0]; + UnityEngine.Debug.Log($"Error detecting Visual Studio installations: {ex.Message}{Environment.NewLine}{ex.StackTrace}"); + } + return new CodeEditor.Installation[0]; + } + + void AddIfDirectoryExists(string name, string path, List installations) + { + if (Directory.Exists(path)) + { + installations.Add(new CodeEditor.Installation { Name = name, Path = path }); + } + } + + static string GetRegistryValue(string path, string key) + { + try + { + return Registry.GetValue(path, key, null) as string; + } + catch (Exception) + { + return ""; + } + } + + /// + /// Derives the Visual Studio installation path from the debugger path + /// + /// + /// The Visual Studio installation path (to devenv.exe) + /// + /// + /// The debugger path from the windows registry + /// + static string DeriveVisualStudioPath(string debuggerPath) + { + string startSentinel = DeriveProgramFilesSentinel(); + string endSentinel = "Common7"; + bool started = false; + string[] tokens = debuggerPath.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); + + string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + + // Walk directories in debugger path, chop out "Program Files\INSTALLATION\PATH\HERE\Common7" + foreach (var token in tokens) + { + if (!started && string.Equals(startSentinel, token, StringComparison.OrdinalIgnoreCase)) + { + started = true; + continue; + } + if (started) + { + path = Path.Combine(path, token); + if (string.Equals(endSentinel, token, StringComparison.OrdinalIgnoreCase)) + break; + } + } + + return Path.Combine(path, "IDE", "devenv.exe"); + } + + /// + /// Derives the program files sentinel for grabbing the VS installation path. + /// + /// + /// From a path like 'c:\Archivos de programa (x86)', returns 'Archivos de programa' + /// + static string DeriveProgramFilesSentinel() + { + string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + .Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + .LastOrDefault(); + + if (!string.IsNullOrEmpty(path)) + { + // This needs to be the "real" Program Files regardless of 64bitness + int index = path.LastIndexOf("(x86)"); + if (0 <= index) + path = path.Remove(index); + return path.TrimEnd(); + } + + return "Program Files"; + } + + public static void ParseRawDevEnvPaths(string[] rawDevEnvPaths, Dictionary versions) + { + if (rawDevEnvPaths == null) + { + return; + } + + var v2017 = rawDevEnvPaths.Where(path => path.Contains("2017")).ToArray(); + var v2019 = rawDevEnvPaths.Where(path => path.Contains("2019")).ToArray(); + + if (v2017.Length > 0) + { + versions[VisualStudioVersion.VisualStudio2017] = v2017; + } + + if (v2019.Length > 0) + { + versions[VisualStudioVersion.VisualStudio2019] = v2019; + } + } + + /// + /// Detects Visual Studio installations using the Windows registry + /// + /// + /// The detected Visual Studio installations + /// + public static Dictionary GetInstalledVisualStudios() + { + var versions = new Dictionary(); + + if (VSEditor.IsWindows) + { + foreach (VisualStudioVersion version in Enum.GetValues(typeof(VisualStudioVersion))) + { + if (version > VisualStudioVersion.VisualStudio2015) + continue; + + try + { + // Try COMNTOOLS environment variable first + FindLegacyVisualStudio(version, versions); + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"VS: {e.Message}"); + } + } + + var raw = FindVisualStudioDevEnvPaths(); + + ParseRawDevEnvPaths(raw.ToArray(), versions); + } + + return versions; + } + + static void FindLegacyVisualStudio(VisualStudioVersion version, Dictionary versions) + { + string key = Environment.GetEnvironmentVariable($"VS{(int)version}0COMNTOOLS"); + if (!string.IsNullOrEmpty(key)) + { + string path = Path.Combine(key, "..", "IDE", "devenv.exe"); + if (File.Exists(path)) + { + versions[version] = new[] { path }; + return; + } + } + + // Try the proper registry key + key = GetRegistryValue( + $@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\{(int)version}.0", "InstallDir"); + + // Try to fallback to the 32bits hive + if (string.IsNullOrEmpty(key)) + key = GetRegistryValue( + $@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\{(int)version}.0", "InstallDir"); + + if (!string.IsNullOrEmpty(key)) + { + string path = Path.Combine(key, "devenv.exe"); + if (File.Exists(path)) + { + versions[version] = new[] { path }; + return; + } + } + + // Fallback to debugger key + key = GetRegistryValue( + // VS uses this key for the local debugger path + $@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\{(int)version}.0\Debugger", "FEQARuntimeImplDll"); + if (!string.IsNullOrEmpty(key)) + { + string path = DeriveVisualStudioPath(key); + if (!string.IsNullOrEmpty(path) && File.Exists(path)) + versions[version] = new[] { DeriveVisualStudioPath(key) }; + } + } + + static IEnumerable FindVisualStudioDevEnvPaths() + { + string asset = AssetDatabase.FindAssets("VSWhere a:packages").Select(AssetDatabase.GUIDToAssetPath).FirstOrDefault(assetPath => assetPath.Contains("vswhere.exe")); + if (string.IsNullOrWhiteSpace(asset)) // This may be called too early where the asset database has not replicated this information yet. + { + yield break; + } + UnityEditor.PackageManager.PackageInfo packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(asset); + var progpath = packageInfo.resolvedPath + asset.Substring("Packages/com.unity.ide.visualstudio".Length); + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = progpath, + Arguments = "-prerelease -property productPath", + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + } + }; + + process.Start(); + process.WaitForExit(); + + while (!process.StandardOutput.EndOfStream) + { + yield return process.StandardOutput.ReadLine(); } } } diff --git a/Editor/Plugins.meta b/Editor/Plugins.meta new file mode 100644 index 0000000..6e01768 --- /dev/null +++ b/Editor/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1e5abb64fdd0542b38f4dc1b60343e8a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Plugins/AppleEventIntegrationPlugin.bundle.meta b/Editor/Plugins/AppleEventIntegrationPlugin.bundle.meta new file mode 100644 index 0000000..991b7e7 --- /dev/null +++ b/Editor/Plugins/AppleEventIntegrationPlugin.bundle.meta @@ -0,0 +1,28 @@ +fileFormatVersion: 2 +guid: dd66e4390e06fc14e92b9822744f2fb1 +folderAsset: yes +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents.meta b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents.meta new file mode 100644 index 0000000..1eeb0e2 --- /dev/null +++ b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 179209ff257e808409c755d32ecf1086 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/Info.plist b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/Info.plist new file mode 100644 index 0000000..50c0c33 --- /dev/null +++ b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/Info.plist @@ -0,0 +1,44 @@ + + + + + BuildMachineOSBuild + 18E226 + CFBundleDevelopmentRegion + en + CFBundleExecutable + AppleEventIntegrationPlugin + CFBundleIdentifier + com.unity.AppleEventIntegrationPlugin + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AppleEventIntegrationPlugin + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 10E1001 + DTPlatformVersion + GM + DTSDKBuild + 18E219 + DTSDKName + macosx10.14 + DTXcode + 1020 + DTXcodeBuild + 10E1001 + NSHumanReadableCopyright + Copyright © 2019 Unity. All rights reserved. + + diff --git a/Editor/COMIntegration/Debug/COMIntegration.exe.meta b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/Info.plist.meta similarity index 71% rename from Editor/COMIntegration/Debug/COMIntegration.exe.meta rename to Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/Info.plist.meta index e927985..4d47fc5 100644 --- a/Editor/COMIntegration/Debug/COMIntegration.exe.meta +++ b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/Info.plist.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: cb67edc1800c2ec4ba8dfb1cf0dfc84a +guid: d2694cce95579ec47a939a1a3efb8f33 DefaultImporter: externalObjects: {} userData: diff --git a/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/MacOS.meta b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/MacOS.meta new file mode 100644 index 0000000..773ad15 --- /dev/null +++ b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/MacOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a0f0c231a0e381e4ea37a97a8e7837a0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/MacOS/AppleEventIntegrationPlugin b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/MacOS/AppleEventIntegrationPlugin new file mode 100644 index 0000000..3f59902 Binary files /dev/null and b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/MacOS/AppleEventIntegrationPlugin differ diff --git a/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/MacOS/AppleEventIntegrationPlugin.meta b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/MacOS/AppleEventIntegrationPlugin.meta new file mode 100644 index 0000000..7ddbcb5 --- /dev/null +++ b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/MacOS/AppleEventIntegrationPlugin.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b94b55fbf62c6bd42a2f3edf2fd52f83 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/_CodeSignature.meta b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/_CodeSignature.meta new file mode 100644 index 0000000..10f6d91 --- /dev/null +++ b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/_CodeSignature.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 55aae143147983c4ca41af0f79695248 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/_CodeSignature/CodeResources b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/_CodeSignature/CodeResources new file mode 100644 index 0000000..06a6210 --- /dev/null +++ b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/_CodeSignature/CodeResources @@ -0,0 +1,115 @@ + + + + + files + + files2 + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/_CodeSignature/CodeResources.meta b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/_CodeSignature/CodeResources.meta new file mode 100644 index 0000000..7968517 --- /dev/null +++ b/Editor/Plugins/AppleEventIntegrationPlugin.bundle/Contents/_CodeSignature/CodeResources.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8529f5a60acb8cd4395bd1cec74495ae +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ProjectGeneration.cs b/Editor/ProjectGeneration.cs index 5a2a10e..e50aadc 100644 --- a/Editor/ProjectGeneration.cs +++ b/Editor/ProjectGeneration.cs @@ -22,6 +22,7 @@ namespace VisualStudioEditor bool HasSolutionBeenGenerated(); string SolutionFile(); string ProjectDirectory { get; } + void GenerateAll(bool generateAll); } public interface IAssemblyNameProvider @@ -29,6 +30,12 @@ namespace VisualStudioEditor string GetAssemblyNameFromScriptPath(string path); IEnumerable GetAllAssemblies(Func shouldFileBePartOfSolution); IEnumerable GetAllAssetPaths(); + UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath); + } + + public struct TestSettings { + public bool ShouldSync; + public Dictionary SyncPath; } class AssemblyNameProvider : IAssemblyNameProvider @@ -47,6 +54,11 @@ namespace VisualStudioEditor { return AssetDatabase.GetAllAssetPaths(); } + + public UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath) + { + return UnityEditor.PackageManager.PackageInfo.FindForAssetPath(assetPath); + } } public class ProjectGeneration : IGenerator @@ -94,26 +106,32 @@ namespace VisualStudioEditor string[] m_ProjectSupportedExtensions = new string[0]; public string ProjectDirectory { get; } + + public TestSettings Settings { get; set; } + readonly string m_ProjectName; readonly IAssemblyNameProvider m_AssemblyNameProvider; + bool m_ShouldGenerateAll; - public ProjectGeneration() + public ProjectGeneration() : this(Directory.GetParent(Application.dataPath).FullName, new AssemblyNameProvider()) { - var projectDirectory = Directory.GetParent(Application.dataPath).FullName; - ProjectDirectory = projectDirectory.Replace('\\', '/'); - m_ProjectName = Path.GetFileName(ProjectDirectory); - m_AssemblyNameProvider = new AssemblyNameProvider(); } public ProjectGeneration(string tempDirectory) : this(tempDirectory, new AssemblyNameProvider()) { } public ProjectGeneration(string tempDirectory, IAssemblyNameProvider assemblyNameProvider) { + Settings = new TestSettings { ShouldSync = true }; ProjectDirectory = tempDirectory.Replace('\\', '/'); m_ProjectName = Path.GetFileName(ProjectDirectory); m_AssemblyNameProvider = assemblyNameProvider; } + public void GenerateAll(bool generateAll) + { + m_ShouldGenerateAll = generateAll; + } + /// /// Syncs the scripting solution if any affected files are relevant. /// @@ -176,7 +194,7 @@ namespace VisualStudioEditor string extension = Path.GetExtension(file); // Exclude files coming from packages except if they are internalized. - if (IsInternalizedPackagePath(file)) + if (!m_ShouldGenerateAll && IsInternalizedPackagePath(file)) { return false; } @@ -296,7 +314,7 @@ namespace VisualStudioEditor foreach (string asset in m_AssemblyNameProvider.GetAllAssetPaths()) { // Exclude files coming from packages except if they are internalized. - if (IsInternalizedPackagePath(asset)) + if (!m_ShouldGenerateAll && IsInternalizedPackagePath(asset)) { continue; } @@ -306,8 +324,6 @@ namespace VisualStudioEditor { // Find assembly the asset belongs to by adding script extension and using compilation pipeline. var assemblyName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".cs"); - assemblyName = assemblyName ?? m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".js"); - assemblyName = assemblyName ?? m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset + ".boo"); if (string.IsNullOrEmpty(assemblyName)) { @@ -334,14 +350,14 @@ namespace VisualStudioEditor return result; } - static bool IsInternalizedPackagePath(string file) + bool IsInternalizedPackagePath(string file) { if (string.IsNullOrWhiteSpace(file)) { return false; } - var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(file); + var packageInfo = m_AssemblyNameProvider.FindForAssetPath(file); if (packageInfo == null) { return false; } @@ -358,7 +374,7 @@ namespace VisualStudioEditor SyncProjectFileIfNotChanged(ProjectFile(island), ProjectText(island, allAssetsProjectParts, responseFilesData, allProjectIslands)); } - static void SyncProjectFileIfNotChanged(string path, string newContents) + void SyncProjectFileIfNotChanged(string path, string newContents) { if (Path.GetExtension(path) == ".csproj") { @@ -368,7 +384,7 @@ namespace VisualStudioEditor SyncFileIfNotChanged(path, newContents); } - static void SyncSolutionFileIfNotChanged(string path, string newContents) + void SyncSolutionFileIfNotChanged(string path, string newContents) { newContents = OnGeneratedSlnSolution(path, newContents); @@ -455,7 +471,7 @@ namespace VisualStudioEditor return content; } - static void SyncFileIfNotChanged(string filename, string newContents) + void SyncFileIfNotChanged(string filename, string newContents) { if (File.Exists(filename) && newContents == File.ReadAllText(filename)) @@ -463,7 +479,16 @@ namespace VisualStudioEditor return; } - File.WriteAllText(filename, newContents, Encoding.UTF8); + if (Settings.ShouldSync) + { + File.WriteAllText(filename, newContents, Encoding.UTF8); + } + else + { + var utf8 = Encoding.UTF8; + byte[] utfBytes = utf8.GetBytes(newContents); + Settings.SyncPath[filename] = utf8.GetString(utfBytes, 0, utfBytes.Length); + } } string ProjectText(Assembly assembly, @@ -779,7 +804,7 @@ namespace VisualStudioEditor file = file.Replace('/', '\\'); var path = SkipPathPrefix(file, projectDir); - var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(path.Replace('\\', '/')); + var packageInfo = m_AssemblyNameProvider.FindForAssetPath(path.Replace('\\', '/')); if (packageInfo != null) { // We have to normalize the path, because the PackageManagerRemapper assumes // dir seperators will be os specific. @@ -792,7 +817,7 @@ namespace VisualStudioEditor static string SkipPathPrefix(string path, string prefix) { - if (path.StartsWith(prefix)) + if (path.Replace("\\","/").StartsWith($"{prefix}/")) return path.Substring(prefix.Length + 1); return path; } diff --git a/Editor/VSEditor.cs b/Editor/VSEditor.cs index 4246da5..598c20a 100644 --- a/Editor/VSEditor.cs +++ b/Editor/VSEditor.cs @@ -3,11 +3,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; using Microsoft.Win32; using UnityEditor; using UnityEngine; using Debug = System.Diagnostics.Debug; using Unity.CodeEditor; +using System.Runtime.InteropServices; + namespace VisualStudioEditor { @@ -32,42 +35,11 @@ namespace VisualStudioEditor "\n(This does work with Visual Studio Pro)" ); - static IEnumerable FindVisualStudioDevEnvPaths() - { - string asset = AssetDatabase.FindAssets("VSWhere a:packages").Select(AssetDatabase.GUIDToAssetPath).FirstOrDefault(assetPath => assetPath.Contains("vswhere.exe")); - if (string.IsNullOrWhiteSpace(asset)) // This may be called too early where the asset database has not replicated this information yet. - { - yield break; - } - UnityEditor.PackageManager.PackageInfo packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(asset); - var progpath = packageInfo.resolvedPath + asset.Substring("Packages/com.unity.ide.visualstudio".Length); - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = progpath, - Arguments = "-prerelease -property productPath", - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - } - }; - - process.Start(); - process.WaitForExit(); - - while (!process.StandardOutput.EndOfStream) - { - yield return process.StandardOutput.ReadLine(); - } - } - static VSEditor() { try { - InstalledVisualStudios = GetInstalledVisualStudios(); + InstalledVisualStudios = Discovery.GetInstalledVisualStudios(); } catch (Exception ex) { @@ -79,16 +51,19 @@ namespace VisualStudioEditor var current = CodeEditor.CurrentEditorInstallation; if (editor.TryGetInstallationForPath(current, out var installation)) { - editor.Initialize(current); + if (installation.Name != "MonoDevelop") + { + editor.Initialize(current); + } return; } } + const string unity_generate_all = "unity_generate_all_csproj"; IDiscovery m_Discoverability; IGenerator m_Generation; CodeEditor.Installation m_Installation; - VSInitializer m_Initiliazer = new VSInitializer(); - bool m_ExternalEditorSupportsUnityProj; + VSInitializer m_Initializer = new VSInitializer(); public VSEditor(IDiscovery discovery, IGenerator projectGeneration) { @@ -98,186 +73,11 @@ namespace VisualStudioEditor internal static Dictionary InstalledVisualStudios { get; private set; } - static bool IsOSX => Environment.OSVersion.Platform == PlatformID.Unix; - static bool IsWindows => !IsOSX && Path.DirectorySeparatorChar == '\\' && Environment.NewLine == "\r\n"; - static readonly GUIContent k_AddUnityProjeToSln = EditorGUIUtility.TrTextContent("Add .unityproj's to .sln"); - - static string GetRegistryValue(string path, string key) - { - try - { - return Registry.GetValue(path, key, null) as string; - } - catch (Exception) - { - return ""; - } - } - - /// - /// Derives the Visual Studio installation path from the debugger path - /// - /// - /// The Visual Studio installation path (to devenv.exe) - /// - /// - /// The debugger path from the windows registry - /// - static string DeriveVisualStudioPath(string debuggerPath) - { - string startSentinel = DeriveProgramFilesSentinel(); - string endSentinel = "Common7"; - bool started = false; - string[] tokens = debuggerPath.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); - - string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); - - // Walk directories in debugger path, chop out "Program Files\INSTALLATION\PATH\HERE\Common7" - foreach (var token in tokens) - { - if (!started && string.Equals(startSentinel, token, StringComparison.OrdinalIgnoreCase)) - { - started = true; - continue; - } - if (started) - { - path = Path.Combine(path, token); - if (string.Equals(endSentinel, token, StringComparison.OrdinalIgnoreCase)) - break; - } - } - - return Path.Combine(path, "IDE", "devenv.exe"); - } - - /// - /// Derives the program files sentinel for grabbing the VS installation path. - /// - /// - /// From a path like 'c:\Archivos de programa (x86)', returns 'Archivos de programa' - /// - static string DeriveProgramFilesSentinel() - { - string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) - .Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) - .LastOrDefault(); - - if (!string.IsNullOrEmpty(path)) - { - // This needs to be the "real" Program Files regardless of 64bitness - int index = path.LastIndexOf("(x86)"); - if (0 <= index) - path = path.Remove(index); - return path.TrimEnd(); - } - - return "Program Files"; - } + internal static bool IsOSX => Environment.OSVersion.Platform == PlatformID.Unix; + internal static bool IsWindows => !IsOSX && Path.DirectorySeparatorChar == '\\' && Environment.NewLine == "\r\n"; public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback(); - public static void ParseRawDevEnvPaths(string[] rawDevEnvPaths, Dictionary versions) - { - if (rawDevEnvPaths == null) - { - return; - } - - var v2017 = rawDevEnvPaths.Where(path => path.Contains("2017")).ToArray(); - var v2019 = rawDevEnvPaths.Where(path => path.Contains("2019")).ToArray(); - - if (v2017.Length > 0) - { - versions[VisualStudioVersion.VisualStudio2017] = v2017; - } - - if (v2019.Length > 0) - { - versions[VisualStudioVersion.VisualStudio2019] = v2019; - } - } - - /// - /// Detects Visual Studio installations using the Windows registry - /// - /// - /// The detected Visual Studio installations - /// - internal static Dictionary GetInstalledVisualStudios() - { - var versions = new Dictionary(); - - if (IsWindows) - { - foreach (VisualStudioVersion version in Enum.GetValues(typeof(VisualStudioVersion))) - { - if (version > VisualStudioVersion.VisualStudio2015) - continue; - - try - { - // Try COMNTOOLS environment variable first - FindLegacyVisualStudio(version, versions); - } - catch (Exception e) - { - UnityEngine.Debug.LogError($"VS: {e.Message}"); - } - } - - var raw = FindVisualStudioDevEnvPaths(); - - ParseRawDevEnvPaths(raw.ToArray(), versions); - } - - return versions; - } - - static void FindLegacyVisualStudio(VisualStudioVersion version, Dictionary versions) - { - string key = Environment.GetEnvironmentVariable($"VS{(int)version}0COMNTOOLS"); - if (!string.IsNullOrEmpty(key)) - { - string path = Path.Combine(key, "..", "IDE", "devenv.exe"); - if (File.Exists(path)) - { - versions[version] = new[] { path }; - return; - } - } - - // Try the proper registry key - key = GetRegistryValue( - $@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\{(int)version}.0", "InstallDir"); - - // Try to fallback to the 32bits hive - if (string.IsNullOrEmpty(key)) - key = GetRegistryValue( - $@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\{(int)version}.0", "InstallDir"); - - if (!string.IsNullOrEmpty(key)) - { - string path = Path.Combine(key, "devenv.exe"); - if (File.Exists(path)) - { - versions[version] = new[] { path }; - return; - } - } - - // Fallback to debugger key - key = GetRegistryValue( - // VS uses this key for the local debugger path - $@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\{(int)version}.0\Debugger", "FEQARuntimeImplDll"); - if (!string.IsNullOrEmpty(key)) - { - string path = DeriveVisualStudioPath(key); - if (!string.IsNullOrEmpty(path) && File.Exists(path)) - versions[version] = new[] { DeriveVisualStudioPath(key) }; - } - } - public void CreateIfDoesntExist() { if (!m_Generation.HasSolutionBeenGenerated()) @@ -338,12 +138,13 @@ namespace VisualStudioEditor GUILayout.EndHorizontal(); } - if (m_Installation.Name.Equals("MonoDevelop")) + var prevGenerate = EditorPrefs.GetBool(unity_generate_all, false); + var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate); + if (generateAll != prevGenerate) { - m_ExternalEditorSupportsUnityProj = EditorGUILayout.Toggle( - k_AddUnityProjeToSln, - m_ExternalEditorSupportsUnityProj); + EditorPrefs.SetBool(unity_generate_all, generateAll); } + m_Generation.GenerateAll(generateAll); } public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles) @@ -353,17 +154,37 @@ namespace VisualStudioEditor public void SyncAll() { + AssetDatabase.Refresh(); m_Generation.Sync(); } public void Initialize(string editorInstallationPath) { - m_Initiliazer.Initialize(editorInstallationPath, InstalledVisualStudios); + m_Initializer.Initialize(editorInstallationPath, InstalledVisualStudios); } public bool OpenProject(string path, int line, int column) { - var comAssetPath = AssetDatabase.FindAssets("COMIntegration a:packages").Select(AssetDatabase.GUIDToAssetPath).First(assetPath => assetPath.Contains("COMIntegration.exe")); + if (m_Installation.Name == "MonoDevelop") { + return OpenAppMonoDev(path, line, column); + } + + if (IsOSX) + { + return OpenOSXApp(path, line, column); + } + + if (IsWindows) + { + return OpenWindowsApp(path, line); + } + + return false; + } + + private bool OpenWindowsApp(string path, int line) + { + var comAssetPath = AssetDatabase.FindAssets("COMIntegration a:packages").Select(AssetDatabase.GUIDToAssetPath).First(assetPath => assetPath.Contains("COMIntegration.dom")); if (string.IsNullOrWhiteSpace(comAssetPath)) // This may be called too early where the asset database has not replicated this information yet. { return false; @@ -376,12 +197,8 @@ namespace VisualStudioEditor absolutePath = Path.GetFullPath(path); } - var solution = GetSolutionFile(path); - if (solution == "") - { - m_Generation.Sync(); - solution = GetSolutionFile(path); - } + + var solution = GetOrGenerateSolutionFile(path); solution = solution == "" ? "" : $"\"{solution}\""; var process = new Process { @@ -420,7 +237,72 @@ namespace VisualStudioEditor return result; } - private string GetSolutionFile(string path) + bool OpenAppMonoDev(string path, int line, int column) + { + string absolutePath = ""; + if (!string.IsNullOrWhiteSpace(path)) + { + absolutePath = Path.GetFullPath(path); + } + + var solution = GetOrGenerateSolutionFile(path); + solution = solution == "" ? "" : $"\"{solution}\""; + var pathArguments = path == "" ? "" : $"\"{path}\";{line}"; + var fileName = IsOSX ? "open" : CodeEditor.CurrentEditorInstallation; + var arguments = IsOSX + ? $"\"{CodeEditor.CurrentEditorInstallation}\" --args --nologo {solution} {pathArguments}" + : $"--nologo {solution} {pathArguments}"; + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + CreateNoWindow = true, + UseShellExecute = true, + } + }; + + process.Start(); + + return true; + } + + [DllImport ("AppleEventIntegrationPlugin")] + static extern void OpenVisualStudio(string appPath, string solutionPath, string filePath, int line, StringBuilder sb, int sbLength); + + bool OpenOSXApp(string path, int line, int column) + { + string absolutePath = ""; + if (!string.IsNullOrWhiteSpace(path)) + { + absolutePath = Path.GetFullPath(path); + } + + string solution = GetOrGenerateSolutionFile(path); + + StringBuilder sb = new StringBuilder(4096); + + OpenVisualStudio(CodeEditor.CurrentEditorInstallation, solution, absolutePath, line, sb, sb.Capacity); + + Console.WriteLine(sb.ToString()); + + return true; + } + + private string GetOrGenerateSolutionFile(string path) + { + var solution = GetSolutionFile(path); + if (solution == "") + { + m_Generation.Sync(); + solution = GetSolutionFile(path); + } + + return solution; + } + + string GetSolutionFile(string path) { if (UnityEditor.Unsupported.IsDeveloperBuild()) { @@ -443,7 +325,7 @@ namespace VisualStudioEditor return ""; } - private static string GetBaseUnityDeveloperFolder() + static string GetBaseUnityDeveloperFolder() { return Directory.GetParent(EditorApplication.applicationPath).Parent.Parent.FullName; } diff --git a/package.json b/package.json index 935fda8..466c688 100644 --- a/package.json +++ b/package.json @@ -2,16 +2,16 @@ "name": "com.unity.ide.visualstudio", "displayName": "Visual Studio Editor", "description": "Code editor integration for supporting Visual Studio as code editor for unity. Adds support for generating csproj files for intellisense purposes, auto discovery of installations, etc.", - "version": "1.0.4", + "version": "1.0.9", "unity": "2019.2", - "unityRelease": "0a7", + "unityRelease": "0a12", "dependencies": {}, "relatedPackages": { - "com.unity.ide.visualstudio.tests": "1.0.2" + "com.unity.ide.visualstudio.tests": "1.0.5" }, "repository": { "type": "git", "url": "git@github.cds.internal.unity3d.com:unity/com.unity.ide.visualstudio.git", - "revision": "89fbb2a14dd60b56c2eb64eac1e574a40a105822" + "revision": "e7d6cf461bbe2604836a0e783a1992651c28c535" } }