com.unity.ide.visualstudio@2.0.0

## [2.0.0] - 2019-11-06

- Improved Visual Studio and Visual Studio for Mac automatic discovery
- Added support for the VSTU messaging system (start/stop features from Visual Studio)
- Added support for solution roundtrip (preserves references to external projects and solution properties)
- Added support for VSTU Analyzers (requires Visual Studio 2019 16.3, Visual Studio for Mac 8.3)
- Added a warning when using legacy pdb symbol files.
- Fixed issues while Opening Visual Studio on Windows
- Fixed issues while Opening Visual Studio on Mac

## [1.1.1] - 2019-05-29

Fix Bridge assembly loading with non VS2017 editors

## [1.1.0] - 2019-05-27

Move internal extension handling to package.
This commit is contained in:
Unity Technologies
2019-11-06 00:00:00 +00:00
parent d6ef04ed17
commit fb1154211c
74 changed files with 2944 additions and 2306 deletions

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 36422aa067e092e45b9820da2ee3e728
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 36422aa067e092e45b9820da2ee3e728
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 41b2d972bdac29e4a89ef08b3b52c248
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 41b2d972bdac29e4a89ef08b3b52c248
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: cb67edc1800c2ec4ba8dfb1cf0dfc84a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: cb67edc1800c2ec4ba8dfb1cf0dfc84a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,275 +1,165 @@
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
{
public interface IDiscovery {
CodeEditor.Installation[] PathCallback();
}
public class Discovery : IDiscovery {
internal static string VisualStudioVersionToNiceName(VisualStudioVersion version)
{
switch (version)
{
case VisualStudioVersion.Invalid: return "Invalid Version";
case VisualStudioVersion.VisualStudio2008: return "Visual Studio 2008";
case VisualStudioVersion.VisualStudio2010: return "Visual Studio 2010";
case VisualStudioVersion.VisualStudio2012: return "Visual Studio 2012";
case VisualStudioVersion.VisualStudio2013: return "Visual Studio 2013";
case VisualStudioVersion.VisualStudio2015: return "Visual Studio 2015";
case VisualStudioVersion.VisualStudio2017: return "Visual Studio 2017";
case VisualStudioVersion.VisualStudio2019: return "Visual Studio 2019";
default:
throw new ArgumentOutOfRangeException(nameof(version), version, null);
}
}
public CodeEditor.Installation[] PathCallback()
{
try
{
if (VSEditor.IsWindows)
{
return GetInstalledVisualStudios().Select(pair => new CodeEditor.Installation
{
Path = pair.Value[0],
Name = VisualStudioVersionToNiceName(pair.Key)
}).ToArray();
}
if (VSEditor.IsOSX)
{
var installationList = new List<CodeEditor.Installation>();
AddIfDirectoryExists("Visual Studio", "/Applications/Visual Studio.app", installationList);
AddIfDirectoryExists("Visual Studio (Preview)", "/Applications/Visual Studio (Preview).app", installationList);
return installationList.ToArray();
}
}
catch (Exception ex)
{
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<CodeEditor.Installation> 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 "";
}
}
/// <summary>
/// Derives the Visual Studio installation path from the debugger path
/// </summary>
/// <returns>
/// The Visual Studio installation path (to devenv.exe)
/// </returns>
/// <param name='debuggerPath'>
/// The debugger path from the windows registry
/// </param>
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");
}
/// <summary>
/// Derives the program files sentinel for grabbing the VS installation path.
/// </summary>
/// <remarks>
/// From a path like 'c:\Archivos de programa (x86)', returns 'Archivos de programa'
/// </remarks>
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<VisualStudioVersion, string[]> 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;
}
}
/// <summary>
/// Detects Visual Studio installations using the Windows registry
/// </summary>
/// <returns>
/// The detected Visual Studio installations
/// </returns>
public static Dictionary<VisualStudioVersion, string[]> GetInstalledVisualStudios()
{
var versions = new Dictionary<VisualStudioVersion, string[]>();
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<VisualStudioVersion, string[]> 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<string> 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();
}
}
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Unity Technologies.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Linq;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class Discovery
{
public static IEnumerable<VisualStudioInstallation> GetVisualStudioInstallations()
{
if (VisualStudioEditor.IsWindows)
{
foreach (var installation in QueryVsWhere())
yield return installation;
}
if (VisualStudioEditor.IsOSX)
{
var candidates = Directory.EnumerateDirectories("/Applications", "*.app");
foreach (var candidate in candidates)
{
if (TryDiscoverInstallation(candidate, out var installation))
yield return installation;
}
}
}
private static bool IsCandidateToDiscovery(string path)
{
if (File.Exists(path) && VisualStudioEditor.IsWindows && Regex.IsMatch(path, "devenv.exe$", RegexOptions.IgnoreCase))
return true;
if (Directory.Exists(path) && VisualStudioEditor.IsOSX && Regex.IsMatch(path, "Visual\\s?Studio(?!.*Code.*).*.app$", RegexOptions.IgnoreCase))
return true;
return false;
}
public static bool TryDiscoverInstallation(string editorPath, out VisualStudioInstallation installation)
{
installation = null;
if (string.IsNullOrEmpty(editorPath))
return false;
if (!IsCandidateToDiscovery(editorPath))
return false;
// On windows we use the executable directly, so we can query extra information
var fvi = editorPath;
// On Mac we use the .app folder, so we need to access to main assembly
if (VisualStudioEditor.IsOSX)
fvi = Path.Combine(editorPath, "Contents", "Resources", "lib", "monodevelop", "bin", "VisualStudio.exe");
if (!File.Exists(fvi))
return false;
// VS preview are not using the isPrerelease flag so far
// On Windows FileDescription contains "Preview", but not on Mac
var vi = FileVersionInfo.GetVersionInfo(fvi);
var version = new Version(vi.ProductVersion);
var isPrerelease = vi.IsPreRelease || string.Concat(editorPath, "/" + vi.FileDescription).ToLower().Contains("preview");
installation = new VisualStudioInstallation()
{
IsPrerelease = isPrerelease,
Name = $"{vi.FileDescription}{(isPrerelease && VisualStudioEditor.IsOSX ? " Preview" : string.Empty)} [{version.ToString(3)}]",
Path = editorPath,
Version = version
};
return true;
}
#region VsWhere Json Schema
#pragma warning disable CS0649
[Serializable]
internal class VsWhereResult
{
public VsWhereEntry[] entries;
public static VsWhereResult FromJson(string json)
{
return JsonUtility.FromJson<VsWhereResult>("{ \"" + nameof(VsWhereResult.entries) + "\": " + json + " }");
}
public IEnumerable<VisualStudioInstallation> ToVisualStudioInstallations()
{
foreach(var entry in entries)
{
yield return new VisualStudioInstallation()
{
Name = $"{entry.displayName} [{entry.catalog.productDisplayVersion}]",
Path = entry.productPath,
IsPrerelease = entry.isPrerelease,
Version = Version.Parse(entry.catalog.buildVersion)
};
}
}
}
[Serializable]
internal class VsWhereEntry
{
public string displayName;
public bool isPrerelease;
public string productPath;
public VsWhereCatalog catalog;
}
[Serializable]
internal class VsWhereCatalog
{
public string productDisplayVersion; // non parseable like "16.3.0 Preview 3.0"
public string buildVersion;
}
#pragma warning restore CS3021
#endregion
private static IEnumerable<VisualStudioInstallation> QueryVsWhere()
{
var progpath = FileUtility
.FindPackageAssetFullPath("VSWhere a:packages", "vswhere.exe")
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(progpath))
return Enumerable.Empty<VisualStudioInstallation>();
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = progpath,
Arguments = "-prerelease -format json",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
}
};
using (process)
{
var json = string.Empty;
process.OutputDataReceived += (o, e) => json += e.Data;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
var result = VsWhereResult.FromJson(json);
return result.ToVisualStudioInstallations();
}
}
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: abe003ac6fee32e4892100a78f555011
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: abe003ac6fee32e4892100a78f555011
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

92
Editor/FileUtility.cs Normal file
View File

@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Unity Technologies.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class FileUtility
{
public const char WinSeparator = '\\';
public const char UnixSeparator = '/';
// Safe for packages as we use packageInfo.resolvedPath, so this should work in library package cache as well
public static string[] FindPackageAssetFullPath(string assetfilter, string filefilter)
{
return AssetDatabase.FindAssets(assetfilter)
.Select(AssetDatabase.GUIDToAssetPath)
.Where(assetPath => assetPath.Contains(filefilter))
.Select(asset =>
{
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(asset);
return Normalize(packageInfo.resolvedPath + asset.Substring(packageInfo.assetPath.Length));
})
.ToArray();
}
public static string GetAssetFullPath(string asset)
{
var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
return Path.GetFullPath(Path.Combine(basePath, Normalize(asset)));
}
public static string Normalize(string path)
{
if (string.IsNullOrEmpty(path))
return path;
if (Path.DirectorySeparatorChar == WinSeparator)
path = path.Replace(UnixSeparator, WinSeparator);
return path.Replace(string.Concat(WinSeparator, WinSeparator), WinSeparator.ToString());
}
internal static bool IsFileInProjectDirectory(string fileName)
{
var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
fileName = Normalize(fileName);
if (!Path.IsPathRooted(fileName))
fileName = Path.Combine(basePath, fileName);
return string.Equals(Path.GetDirectoryName(fileName), basePath, StringComparison.OrdinalIgnoreCase);
}
public static string FileNameWithoutExtension(string path)
{
if (string.IsNullOrEmpty(path))
{
return "";
}
var indexOfDot = -1;
var indexOfSlash = 0;
for (var i = path.Length - 1; i >= 0; i--)
{
if (indexOfDot == -1 && path[i] == '.')
{
indexOfDot = i;
}
if (indexOfSlash == 0 && path[i] == '/' || path[i] == '\\')
{
indexOfSlash = i + 1;
break;
}
}
if (indexOfDot == -1)
{
indexOfDot = path.Length - 1;
}
return path.Substring(indexOfSlash, indexOfDot - indexOfSlash);
}
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 6f1dc05fb6e7d3e4f89ae9ca482735be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 6f1dc05fb6e7d3e4f89ae9ca482735be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 55aae143147983c4ca41af0f79695248
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 2f820130c86c28547a0f1a2f4c73155b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System.IO;
using System.Text;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class Deserializer
{
private readonly BinaryReader _reader;
// Max UDP packet size is 65507
private const int MaxStringLength = 65000;
public Deserializer(byte[] buffer)
{
_reader = new BinaryReader(new MemoryStream(buffer));
}
public int ReadInt32()
{
return _reader.ReadInt32();
}
public string ReadString()
{
var length = ReadInt32();
return length > 0 && length <= MaxStringLength
? Encoding.UTF8.GetString(_reader.ReadBytes(length))
: "";
}
public bool CanReadMore()
{
return _reader.BaseStream.Position < _reader.BaseStream.Length;
}
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: c70a64eb158b0104bb528b12e1d80c2a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 3eda7a83649158546826efb3ffe6c1e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class ExceptionEventArgs
{
public Exception Exception { get; }
public ExceptionEventArgs(Exception exception)
{
Exception = exception;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 917a51fff055ce547b4ad1215321f3da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System.Globalization;
using System.Net;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class Message
{
public MessageType Type { get; set; }
public string Value { get; set; }
public IPEndPoint Origin { get; set; }
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "<Message type:{0} value:{1}>", Type, Value);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de1c9ea7b82c9904d9e5fba2ee70a998
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class MessageEventArgs
{
public Message Message
{
get;
}
public MessageEventArgs(Message message)
{
Message = message;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 275143c81d816ef4286fdc67aabc20c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal enum MessageType
{
None = 0,
Ping,
Pong,
Play,
Stop,
Pause,
Unpause,
Build,
Refresh,
Info,
Error,
Warning,
Open,
Opened,
Version,
UpdatePackage,
ProjectPath,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f3edbdc86577af648a23263aa75161e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,176 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
using System.Net;
using System.Net.Sockets;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class Messager : IDisposable
{
public event EventHandler<MessageEventArgs> ReceiveMessage;
public event EventHandler<ExceptionEventArgs> MessagerException;
private readonly UdpSocket _socket;
private readonly object _disposeLock = new object();
private bool _disposed;
protected Messager(int port)
{
_socket = new UdpSocket();
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false);
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_socket.Bind(IPAddress.Any, port);
BeginReceiveMessage();
}
private void BeginReceiveMessage()
{
var buffer = new byte[UdpSocket.BufferSize];
var any = UdpSocket.Any();
try
{
lock (_disposeLock)
{
if (_disposed)
return;
_socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref any, ReceiveMessageCallback, buffer);
}
}
catch (SocketException se)
{
MessagerException?.Invoke(this, new ExceptionEventArgs(se));
BeginReceiveMessage();
}
catch (ObjectDisposedException)
{
}
}
private void ReceiveMessageCallback(IAsyncResult result)
{
try
{
var endPoint = UdpSocket.Any();
lock (_disposeLock)
{
if (_disposed)
return;
_socket.EndReceiveFrom(result, ref endPoint);
}
var message = DeserializeMessage(UdpSocket.BufferFor(result));
if (message != null)
{
message.Origin = (IPEndPoint)endPoint;
ReceiveMessage?.Invoke(this, new MessageEventArgs(message));
}
}
catch (ObjectDisposedException)
{
return;
}
catch (Exception e)
{
RaiseMessagerException(e);
}
BeginReceiveMessage();
}
private void RaiseMessagerException(Exception e)
{
MessagerException?.Invoke(this, new ExceptionEventArgs(e));
}
private static Message MessageFor(MessageType type, string value)
{
return new Message { Type = type, Value = value };
}
public void SendMessage(IPEndPoint target, MessageType type, string value = "")
{
var message = MessageFor(type, value);
var buffer = SerializeMessage(message);
try
{
lock (_disposeLock)
{
if (_disposed)
return;
_socket.BeginSendTo(buffer, 0, Math.Min(buffer.Length, UdpSocket.BufferSize), SocketFlags.None, target, SendMessageCallback, null);
}
}
catch (SocketException se)
{
MessagerException?.Invoke(this, new ExceptionEventArgs(se));
}
}
private void SendMessageCallback(IAsyncResult result)
{
try
{
lock (_disposeLock)
{
if (_disposed)
return;
_socket.EndSendTo(result);
}
}
catch (SocketException se)
{
MessagerException?.Invoke(this, new ExceptionEventArgs(se));
}
catch (ObjectDisposedException)
{
}
}
private static byte[] SerializeMessage(Message message)
{
var serializer = new Serializer();
serializer.WriteInt32((int)message.Type);
serializer.WriteString(message.Value);
return serializer.Buffer();
}
private static Message DeserializeMessage(byte[] buffer)
{
if (buffer.Length < 4)
return null;
var deserializer = new Deserializer(buffer);
var type = (MessageType)deserializer.ReadInt32();
var value = deserializer.ReadString();
return new Message { Type = type, Value = value };
}
public static Messager BindTo(int port)
{
return new Messager(port);
}
public void Dispose()
{
lock (_disposeLock)
{
_disposed = true;
_socket.Close();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e249ae353801f043a6e4173410c6152
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System.IO;
using System.Text;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class Serializer
{
private readonly MemoryStream _stream;
private readonly BinaryWriter _writer;
public Serializer()
{
_stream = new MemoryStream();
_writer = new BinaryWriter(_stream);
}
public void WriteInt32(int i)
{
_writer.Write(i);
}
public void WriteString(string s)
{
var bytes = Encoding.UTF8.GetBytes(s ?? "");
if (bytes.Length > 0)
{
_writer.Write(bytes.Length);
_writer.Write(bytes);
}
else
_writer.Write(0);
}
public byte[] Buffer()
{
return _stream.ToArray();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 369c09afe05d2c346af49faef943c773
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
using System.Net;
using System.Net.Sockets;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class UdpSocket : Socket
{
public const int BufferSize = 1024 * 8;
internal UdpSocket()
: base(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
{
SetIOControl();
}
public void Bind(IPAddress address, int port = 0)
{
Bind(new IPEndPoint(address ?? IPAddress.Any, port));
}
private void SetIOControl()
{
if (!VisualStudioEditor.IsWindows)
return;
try
{
const int SIO_UDP_CONNRESET = -1744830452;
IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, new byte[0]);
}
catch
{
// fallback
}
}
public static byte[] BufferFor(IAsyncResult result)
{
return (byte[])result.AsyncState;
}
public static EndPoint Any()
{
return new IPEndPoint(IPAddress.Any, 0);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38cb3a4a17d2cfd41926da95ce675934
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 1e5abb64fdd0542b38f4dc1b60343e8a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 1e5abb64fdd0542b38f4dc1b60343e8a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,28 +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:
fileFormatVersion: 2
guid: a20df6e3467b24ed5b49c857ce39e096
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:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 179209ff257e808409c755d32ecf1086
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 058b02c03ea09473aab4dc5fc50af1ef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,44 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>18E226</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>AppleEventIntegrationPlugin</string>
<key>CFBundleIdentifier</key>
<string>com.unity.AppleEventIntegrationPlugin</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>AppleEventIntegrationPlugin</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>10E1001</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>18E219</string>
<key>DTSDKName</key>
<string>macosx10.14</string>
<key>DTXcode</key>
<string>1020</string>
<key>DTXcodeBuild</key>
<string>10E1001</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Unity. All rights reserved.</string>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>19B88</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>AppleEventIntegration</string>
<key>CFBundleIdentifier</key>
<string>com.unity.visualstudio.AppleEventIntegration</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>AppleEventIntegration</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>10E125</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>18E219</string>
<key>DTSDKName</key>
<string>macosx10.14</string>
<key>DTXcode</key>
<string>1020</string>
<key>DTXcodeBuild</key>
<string>10E125</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Unity. All rights reserved.</string>
</dict>
</plist>

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: d2694cce95579ec47a939a1a3efb8f33
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 680cf1008b4284eddbb82ec4d76644a1
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: a0f0c231a0e381e4ea37a97a8e7837a0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: ba4355216f6c44abbb17503872c42c97
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: b94b55fbf62c6bd42a2f3edf2fd52f83
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 4e53c7f7b5c7e4a3d890cb596ed56c22
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,115 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict/>
<key>files2</key>
<dict/>
<key>rules</key>
<dict>
<key>^Resources/</key>
<true/>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^.*</key>
<true/>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 8529f5a60acb8cd4395bd1cec74495ae
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: c55bbb5ab4b342946980281be9e63c1a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: c55bbb5ab4b342946980281be9e63c1a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

12
Editor/Solution.cs Normal file
View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
namespace Microsoft.Unity.VisualStudio.Editor
{
internal class Solution
{
public SolutionProjectEntry[] Projects { get; set; }
public SolutionProperties[] Properties { get; set; }
}
}

11
Editor/Solution.cs.meta Normal file
View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: af4c2c762e1d8e949a6bc458973df6e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

80
Editor/SolutionParser.cs Normal file
View File

@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class SolutionParser
{
// Compared to the bridge implementation, we are not returning "{" "}" from Guids
private static readonly Regex ProjectDeclaration = new Regex(@"Project\(\""{(?<projectFactoryGuid>.*?)}\""\)\s+=\s+\""(?<name>.*?)\"",\s+\""(?<fileName>.*?)\"",\s+\""{(?<projectGuid>.*?)}\""(?<content>.*?)\bEndProject\b", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
private static readonly Regex PropertiesDeclaration = new Regex(@"GlobalSection\((?<name>[\w]+Properties)\)\s+=\s+(?<type>(?:post|pre)Solution)(?<entries>.*?)EndGlobalSection", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
private static readonly Regex PropertiesEntryDeclaration = new Regex(@"^\s*(?<key>.*?)=(?<value>.*?)$", RegexOptions.Multiline | RegexOptions.ExplicitCapture);
public static Solution ParseSolutionFile(string filename)
{
return ParseSolutionContent(File.ReadAllText(filename));
}
public static Solution ParseSolutionContent(string content)
{
return new Solution
{
Projects = ParseSolutionProjects(content),
Properties = ParseSolutionProperties(content)
};
}
private static SolutionProjectEntry[] ParseSolutionProjects(string content)
{
var projects = new List<SolutionProjectEntry>();
var mc = ProjectDeclaration.Matches(content);
foreach (Match match in mc)
{
projects.Add(new SolutionProjectEntry
{
ProjectFactoryGuid = match.Groups["projectFactoryGuid"].Value,
Name = match.Groups["name"].Value,
FileName = match.Groups["fileName"].Value,
ProjectGuid = match.Groups["projectGuid"].Value,
});
}
return projects.ToArray();
}
private static SolutionProperties[] ParseSolutionProperties(string content)
{
var properties = new List<SolutionProperties>();
var mc = PropertiesDeclaration.Matches(content);
foreach (Match match in mc)
{
var sp = new SolutionProperties
{
Entries = new List<KeyValuePair<string, string>>(),
Name = match.Groups["name"].Value,
Type = match.Groups["type"].Value
};
var entries = match.Groups["entries"].Value;
var mec = PropertiesEntryDeclaration.Matches(entries);
foreach (Match entry in mec)
{
var key = entry.Groups["key"].Value.Trim();
var value = entry.Groups["value"].Value.Trim();
sp.Entries.Add(new KeyValuePair<string, string>(key, value));
}
properties.Add(sp);
}
return properties.ToArray();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fbbb1ee655846b043baf6c3502b5ce49
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
namespace Microsoft.Unity.VisualStudio.Editor
{
internal class SolutionProjectEntry
{
public string ProjectFactoryGuid { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
public string ProjectGuid { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c1b8a755d2c97640bbb207c43f4cf61
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System.Collections.Generic;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal class SolutionProperties
{
public string Name { get; set; }
public IList<KeyValuePair<string, string>> Entries { get; set; }
public string Type { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 829d4d6bc39fd1044ba4c5fc2a9c911f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

30
Editor/Symbols.cs Normal file
View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
using System.IO;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class Symbols
{
public static bool IsPortableSymbolFile(string pdbFile)
{
try
{
using (var stream = File.OpenRead(pdbFile))
{
return stream.ReadByte() == 'B'
&& stream.ReadByte() == 'S'
&& stream.ReadByte() == 'J'
&& stream.ReadByte() == 'B';
}
}
catch (Exception)
{
return false;
}
}
}
}

11
Editor/Symbols.cs.meta Normal file
View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b9308b762484008498bb5cd1886aa491
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,35 +0,0 @@
namespace VisualStudioEditor
{
public static class Utility
{
public static string FileNameWithoutExtension(string path)
{
if (string.IsNullOrEmpty(path))
{
return "";
}
var indexOfDot = -1;
var indexOfSlash = 0;
for (var i = path.Length - 1; i >= 0; i--)
{
if (indexOfDot == -1 && path[i] == '.')
{
indexOfDot = i;
}
if (indexOfSlash == 0 && path[i] == '/' || path[i] == '\\')
{
indexOfSlash = i + 1;
break;
}
}
if (indexOfDot == -1)
{
indexOfDot = path.Length - 1;
}
return path.Substring(indexOfSlash, indexOfDot - indexOfSlash);
}
}
}

View File

@@ -1,335 +0,0 @@
using System;
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
{
public enum VisualStudioVersion
{
Invalid = 0,
VisualStudio2008 = 9,
VisualStudio2010 = 10,
VisualStudio2012 = 11,
VisualStudio2013 = 12,
VisualStudio2015 = 14,
VisualStudio2017 = 15,
VisualStudio2019 = 16,
}
[InitializeOnLoad]
public class VSEditor : IExternalCodeEditor
{
static readonly string k_ExpressNotSupportedMessage = L10n.Tr(
"Unfortunately Visual Studio Express does not allow itself to be controlled by external applications. " +
"You can still use it by manually opening the Visual Studio project file, but Unity cannot automatically open files for you when you doubleclick them. " +
"\n(This does work with Visual Studio Pro)"
);
static VSEditor()
{
try
{
InstalledVisualStudios = Discovery.GetInstalledVisualStudios();
}
catch (Exception ex)
{
UnityEngine.Debug.Log($@"Error detecting Visual Studio installations: {ex.Message}{Environment.NewLine}{ex.StackTrace}");
InstalledVisualStudios = new Dictionary<VisualStudioVersion, string[]>();
}
var editor = new VSEditor(new Discovery(), new ProjectGeneration());
CodeEditor.Register(editor);
var current = CodeEditor.CurrentEditorInstallation;
if (editor.TryGetInstallationForPath(current, out var installation))
{
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_Initializer = new VSInitializer();
public VSEditor(IDiscovery discovery, IGenerator projectGeneration)
{
m_Discoverability = discovery;
m_Generation = projectGeneration;
}
internal static Dictionary<VisualStudioVersion, string[]> InstalledVisualStudios { get; private set; }
internal static bool IsOSX => Application.platform == RuntimePlatform.OSXEditor;
internal static bool IsWindows => !IsOSX && Path.DirectorySeparatorChar == '\\' && Environment.NewLine == "\r\n";
public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback();
public void CreateIfDoesntExist()
{
if (!m_Generation.HasSolutionBeenGenerated())
{
m_Generation.Sync();
}
}
public bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation)
{
var lowerCasePath = editorPath.ToLower();
if (lowerCasePath.EndsWith("vcsexpress.exe", StringComparison.OrdinalIgnoreCase))
{
installation = new CodeEditor.Installation
{
Name = "VSExpress",
Path = editorPath
};
m_Installation = installation;
return true;
}
if (lowerCasePath.EndsWith("devenv.exe", StringComparison.OrdinalIgnoreCase)
|| lowerCasePath.Replace(" ", "").EndsWith("visualstudio.app", StringComparison.OrdinalIgnoreCase)
|| lowerCasePath.Replace(" ", "").EndsWith("visualstudio(preview).app", StringComparison.OrdinalIgnoreCase))
{
installation = new CodeEditor.Installation
{
Name = "VisualStudio",
Path = editorPath
};
m_Installation = installation;
return true;
}
if (lowerCasePath.Contains("monodevelop")
|| lowerCasePath.Replace(" ", "").Contains("xamarinstudio"))
{
installation = new CodeEditor.Installation
{
Name = "MonoDevelop",
Path = editorPath
};
m_Installation = installation;
return true;
}
installation = default;
m_Installation = installation;
return false;
}
public void OnGUI()
{
if (m_Installation.Name.Equals("VSExpress"))
{
GUILayout.BeginHorizontal(EditorStyles.helpBox);
GUILayout.Label("", "CN EntryWarn");
GUILayout.Label(k_ExpressNotSupportedMessage, "WordWrappedLabel");
GUILayout.EndHorizontal();
}
var prevGenerate = EditorPrefs.GetBool(unity_generate_all, false);
var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate);
if (generateAll != prevGenerate)
{
EditorPrefs.SetBool(unity_generate_all, generateAll);
}
m_Generation.GenerateAll(generateAll);
}
public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles)
{
m_Generation.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles), importedFiles);
}
public void SyncAll()
{
AssetDatabase.Refresh();
m_Generation.Sync();
}
public void Initialize(string editorInstallationPath)
{
m_Initializer.Initialize(editorInstallationPath, InstalledVisualStudios);
}
public bool OpenProject(string path, int line, int column)
{
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;
}
UnityEditor.PackageManager.PackageInfo packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(comAssetPath);
var progpath = packageInfo.resolvedPath + comAssetPath.Substring("Packages/com.unity.ide.visualstudio".Length);
string absolutePath = "";
if (!string.IsNullOrWhiteSpace(path))
{
absolutePath = Path.GetFullPath(path);
}
var solution = GetOrGenerateSolutionFile(path);
solution = solution == "" ? "" : $"\"{solution}\"";
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = progpath,
Arguments = $"\"{CodeEditor.CurrentEditorInstallation}\" \"{absolutePath}\" {solution} {line}",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
}
};
var result = process.Start();
while (!process.StandardOutput.EndOfStream)
{
var outputLine = process.StandardOutput.ReadLine();
if (outputLine == "displayProgressBar")
{
EditorUtility.DisplayProgressBar("Opening Visual Studio", "Starting up Visual Studio, this might take some time.", .5f);
}
if (outputLine == "clearprogressbar")
{
EditorUtility.ClearProgressBar();
}
}
var errorOutput = process.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(errorOutput))
{
Console.WriteLine("Error: \n" + errorOutput);
}
process.WaitForExit();
return result;
}
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())
{
var baseFolder = GetBaseUnityDeveloperFolder();
var lowerPath = path.ToLowerInvariant();
var isUnitySourceCode =
lowerPath.Contains((baseFolder + "/Runtime").ToLowerInvariant())
|| lowerPath.Contains((baseFolder + "/Editor").ToLowerInvariant());
if (isUnitySourceCode)
{
return Path.Combine(baseFolder, "Projects/CSharp/Unity.CSharpProjects.gen.sln");
}
}
var solutionFile = m_Generation.SolutionFile();
if (File.Exists(solutionFile))
{
return solutionFile;
}
return "";
}
static string GetBaseUnityDeveloperFolder()
{
return Directory.GetParent(EditorApplication.applicationPath).Parent.Parent.FullName;
}
}
}

View File

@@ -1,276 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Win32;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace VisualStudioEditor
{
internal class VSInitializer
{
public void Initialize(string editorPath, Dictionary<VisualStudioVersion, string[]> installedVisualStudios)
{
switch (Application.platform) {
case RuntimePlatform.OSXEditor:
InitializeVSForMac(editorPath);
break;
case RuntimePlatform.WindowsEditor:
InitializeVisualStudio(editorPath, installedVisualStudios);
break;
}
}
static void InitializeVSForMac(string externalEditor)
{
if (!IsVSForMac(externalEditor, out var vsfmVersion))
return;
var bridgeFile = GetVSForMacBridgeAssembly(externalEditor, vsfmVersion);
if (string.IsNullOrEmpty(bridgeFile) || !File.Exists(bridgeFile))
{
Debug.Log("Unable to find Tools for Unity bridge dll for Visual Studio for Mac " + externalEditor);
return;
}
AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(bridgeFile));
}
static bool IsVisualStudioForMac(string path)
{
var lowerCasePath = path.ToLower();
var filename = Path.GetFileName(lowerCasePath).Replace(" ", "");
return filename.StartsWith("visualstudio") && !filename.Contains("code") && filename.EndsWith(".app");
}
static bool IsVSForMac(string externalEditor, out Version vsfmVersion)
{
vsfmVersion = null;
if (!IsVisualStudioForMac(externalEditor))
return false;
// We need to extract the version used by VS for Mac
// to lookup its addin registry
try
{
return GetVSForMacVersion(externalEditor, out vsfmVersion);
}
catch (Exception e)
{
Debug.Log("Failed to read Visual Studio for Mac information");
Debug.LogException(e);
return false;
}
}
static bool GetVSForMacVersion(string externalEditor, out Version vsfmVersion)
{
vsfmVersion = null;
// Read the full VS for Mac version from the plist, it will look like this:
//
// <key>CFBundleShortVersionString</key>
// <string>X.X.X.X</string>
var plist = Path.Combine(externalEditor, "Contents/Info.plist");
if (!File.Exists(plist))
return false;
const string versionStringRegex = @"\<key\>CFBundleShortVersionString\</key\>\s+\<string\>(?<version>\d+\.\d+\.\d+\.\d+?)\</string\>";
var file = File.ReadAllText(plist);
var match = Regex.Match(file, versionStringRegex);
var versionGroup = match.Groups["version"];
if (!versionGroup.Success)
return false;
vsfmVersion = new Version(versionGroup.Value);
return true;
}
static void InitializeVisualStudio(string externalEditor, Dictionary<VisualStudioVersion, string[]> installedVisualStudios)
{
if (!externalEditor.Contains("2017"))
{
return;
}
FindVisualStudio(externalEditor, out var vsVersion, installedVisualStudios);
var bridgeFile = GetVstuBridgeAssembly(vsVersion);
if (bridgeFile == null)
{
Debug.Log("Unable to find bridge dll in registry for Microsoft Visual Studio Tools for Unity for " + externalEditor);
return;
}
if (!File.Exists(bridgeFile))
{
Debug.Log("Unable to find bridge dll on disk for Microsoft Visual Studio Tools for Unity for " + bridgeFile);
return;
}
AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(bridgeFile));
}
static string GetVstuBridgeAssembly(VisualStudioVersion version)
{
try
{
var vsVersion = string.Empty;
switch (version)
{
// Starting with VS 15, the registry key is using the VS version
// to avoid taking a dependency on the product name
case VisualStudioVersion.VisualStudio2017:
vsVersion = "15.0";
break;
case VisualStudioVersion.VisualStudio2019:
vsVersion = "16.0";
break;
// VS 2015 and under are still installed in the registry
// using their project names
case VisualStudioVersion.VisualStudio2015:
vsVersion = "2015";
break;
case VisualStudioVersion.VisualStudio2013:
vsVersion = "2013";
break;
case VisualStudioVersion.VisualStudio2012:
vsVersion = "2012";
break;
case VisualStudioVersion.VisualStudio2010:
vsVersion = "2010";
break;
}
// search first for the current user with a fallback to machine wide setting
return GetVstuBridgePathFromRegistry(vsVersion, true)
?? GetVstuBridgePathFromRegistry(vsVersion, false);
}
catch (Exception)
{
return null;
}
}
static string GetVstuBridgePathFromRegistry(string vsVersion, bool currentUser)
{
var registryKey = $@"{(currentUser ? "HKEY_CURRENT_USER" : "HKEY_LOCAL_MACHINE")}\Software\Microsoft\Microsoft Visual Studio {vsVersion} Tools for Unity";
return (string)Registry.GetValue(registryKey, "UnityExtensionPath", null);
}
static void FindVisualStudio(string externalEditor, out VisualStudioVersion vsVersion, Dictionary<VisualStudioVersion, string[]> installedVisualStudios)
{
if (string.IsNullOrEmpty(externalEditor))
{
vsVersion = VisualStudioVersion.Invalid;
return;
}
// If it's a VS found through envvars or the registry
var matches = installedVisualStudios.Where(kvp => kvp.Value.Any(v => Path.GetFullPath(v).Equals(Path.GetFullPath(externalEditor), StringComparison.OrdinalIgnoreCase))).ToArray();
if (matches.Length > 0)
{
vsVersion = matches[0].Key;
return;
}
// If it's a side-by-side VS selected manually
if (externalEditor.EndsWith("devenv.exe", StringComparison.OrdinalIgnoreCase))
{
if (TryGetVisualStudioVersion(externalEditor, out vsVersion)) return;
}
vsVersion = VisualStudioVersion.Invalid;
}
static bool TryGetVisualStudioVersion(string externalEditor, out VisualStudioVersion vsVersion)
{
switch (ProductVersion(externalEditor).Major)
{
case 9:
vsVersion = VisualStudioVersion.VisualStudio2008;
return true;
case 10:
vsVersion = VisualStudioVersion.VisualStudio2010;
return true;
case 11:
vsVersion = VisualStudioVersion.VisualStudio2012;
return true;
case 12:
vsVersion = VisualStudioVersion.VisualStudio2013;
return true;
case 14:
vsVersion = VisualStudioVersion.VisualStudio2015;
return true;
case 15:
vsVersion = VisualStudioVersion.VisualStudio2017;
return true;
case 16:
vsVersion = VisualStudioVersion.VisualStudio2019;
return true;
}
vsVersion = VisualStudioVersion.Invalid;
return false;
}
static Version ProductVersion(string externalEditor)
{
try
{
return new Version(FileVersionInfo.GetVersionInfo(externalEditor).ProductVersion);
}
catch (Exception)
{
return new Version(0, 0);
}
}
static string GetVSForMacBridgeAssembly(string externalEditor, Version vsfmVersion)
{
// Check first if we're overriden
// Useful when developing UnityVS for Mac
var bridge = Environment.GetEnvironmentVariable("VSTUM_BRIDGE");
if (!string.IsNullOrEmpty(bridge) && File.Exists(bridge))
return bridge;
// Look for installed addin
const string addinBridge = "Editor/SyntaxTree.VisualStudio.Unity.Bridge.dll";
const string addinName = "MonoDevelop.Unity";
// Check if we're installed in the user addins repository
// ~/Library/Application Support/VisualStudio/X.0/LocalInstall/Addins
var localAddins = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
"Library/Application Support/VisualStudio/" + vsfmVersion.Major + ".0" + "/LocalInstall/Addins");
// In the user addins repository, the addins are suffixed by their versions, like `MonoDevelop.Unity.1.0`
// When installing another local user addin, MD will remove files inside the folder
// So we browse all VSTUM addins, and return the one with a bridge, which is the one MD will load
if (Directory.Exists(localAddins))
{
foreach (var folder in Directory.GetDirectories(localAddins, addinName + "*", SearchOption.TopDirectoryOnly))
{
bridge = Path.Combine(folder, addinBridge);
if (File.Exists(bridge))
return bridge;
}
}
// Check in Visual Studio.app/
// In that case the name of the addin is used
bridge = Path.Combine(externalEditor, "Contents/Resources/lib/monodevelop/AddIns/" + addinName + "/" + addinBridge);
if (File.Exists(bridge))
return bridge;
return null;
}
}
}

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 5b17896803f77494da73d73448fb6cb4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 5b17896803f77494da73d73448fb6cb4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: 585c3fb85b32bd64e8814074e754163e
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 585c3fb85b32bd64e8814074e754163e
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,258 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Unity Technologies.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using Unity.CodeEditor;
using System.Runtime.InteropServices;
namespace Microsoft.Unity.VisualStudio.Editor
{
[InitializeOnLoad]
public class VisualStudioEditor : IExternalCodeEditor
{
private static readonly VisualStudioInstallation[] _installations;
internal static bool IsOSX => Application.platform == RuntimePlatform.OSXEditor;
internal static bool IsWindows => !IsOSX && Path.DirectorySeparatorChar == '\\' && Environment.NewLine == "\r\n";
CodeEditor.Installation[] IExternalCodeEditor.Installations => _installations
.Select(i => i.ToCodeEditorInstallation())
.ToArray();
private readonly IGenerator _generator = new ProjectGeneration();
static VisualStudioEditor()
{
try
{
_installations = Discovery
.GetVisualStudioInstallations()
.ToArray();
}
catch (Exception ex)
{
UnityEngine.Debug.Log($"Error detecting Visual Studio installations: {ex}");
_installations = Array.Empty<VisualStudioInstallation>();
}
CodeEditor.Register(new VisualStudioEditor());
}
public void CreateIfDoesntExist()
{
if (!_generator.HasSolutionBeenGenerated())
_generator.Sync();
}
public void Initialize(string editorInstallationPath)
{
}
internal bool TryGetVisualStudioInstallationForPath(string editorPath, out VisualStudioInstallation installation)
{
// lookup for well known installations
foreach (var candidate in _installations)
{
if (!string.Equals(Path.GetFullPath(editorPath), Path.GetFullPath(candidate.Path), StringComparison.OrdinalIgnoreCase))
continue;
installation = candidate;
return true;
}
return Discovery.TryDiscoverInstallation(editorPath, out installation);
}
public bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation)
{
var result = TryGetVisualStudioInstallationForPath(editorPath, out var vsi);
installation = vsi == null ? default : vsi.ToCodeEditorInstallation();
return result;
}
public void OnGUI()
{
const string unity_generate_all = "unity_generate_all_csproj";
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
var package = UnityEditor.PackageManager.PackageInfo.FindForAssembly(GetType().Assembly);
var style = new GUIStyle
{
richText = true,
margin = new RectOffset(0, 4, 0, 0)
};
GUILayout.Label($"<size=10><color=grey>{package.displayName} v{package.version} enabled</color></size>", style);
GUILayout.EndHorizontal();
var prevGenerate = EditorPrefs.GetBool(unity_generate_all, false);
var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate);
if (generateAll != prevGenerate)
{
EditorPrefs.SetBool(unity_generate_all, generateAll);
}
_generator.GenerateAll(generateAll);
}
public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles)
{
_generator.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles), importedFiles);
foreach (var file in importedFiles.Where(a => Path.GetExtension(a) == ".pdb"))
{
var pdbFile = FileUtility.GetAssetFullPath(file);
if (Symbols.IsPortableSymbolFile(pdbFile))
continue;
UnityEngine.Debug.LogWarning($"Unity is only able to load mdb or portable-pdb symbols. {file} is using a legacy pdb format.");
}
}
public void SyncAll()
{
AssetDatabase.Refresh();
_generator.Sync();
}
bool IsSupportedPath(string path)
{
// Path is empty with "Open C# Project", as we only want to open the solution without specific files
if (string.IsNullOrEmpty(path))
return true;
// cs, uxml, uss, shader, compute, cginc, hlsl, glslinc, template are part of Unity builtin extensions
// txt, xml, fnt, cd are -often- par of Unity user extensions
// asdmdef is mandatory included
if (_generator.IsSupportedFile(path))
return true;
return false;
}
public bool OpenProject(string path, int line, int column)
{
if (!IsSupportedPath(path))
return false;
if (IsOSX)
return OpenOSXApp(path, line, column);
if (IsWindows)
return OpenWindowsApp(path, line);
return false;
}
private bool OpenWindowsApp(string path, int line)
{
var progpath = FileUtility
.FindPackageAssetFullPath("COMIntegration a:packages", "COMIntegration.exe")
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(progpath))
return false;
string absolutePath = "";
if (!string.IsNullOrWhiteSpace(path))
{
absolutePath = Path.GetFullPath(path);
}
// We remove all invalid chars from the solution filename, but we cannot prevent the user from using a specific path for the Unity project
// So process the fullpath to make it compatible with VS
var solution = GetOrGenerateSolutionFile(path);
if (!string.IsNullOrWhiteSpace(solution))
{
solution = $"\"{solution}\"";
solution = solution.Replace("^", "^^");
}
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = progpath,
Arguments = $"\"{CodeEditor.CurrentEditorInstallation}\" \"{absolutePath}\" {solution} {line}",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
}
};
var result = process.Start();
while (!process.StandardOutput.EndOfStream)
{
var outputLine = process.StandardOutput.ReadLine();
if (outputLine == "displayProgressBar")
{
EditorUtility.DisplayProgressBar("Opening Visual Studio", "Starting up Visual Studio, this might take some time.", .5f);
}
if (outputLine == "clearprogressbar")
{
EditorUtility.ClearProgressBar();
}
}
var errorOutput = process.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(errorOutput))
{
Console.WriteLine("Error: \n" + errorOutput);
}
process.WaitForExit();
return result;
}
[DllImport("AppleEventIntegration")]
static extern bool OpenVisualStudio(string appPath, string solutionPath, string filePath, int line);
bool OpenOSXApp(string path, int line, int column)
{
string absolutePath = "";
if (!string.IsNullOrWhiteSpace(path))
{
absolutePath = Path.GetFullPath(path);
}
string solution = GetOrGenerateSolutionFile(path);
return OpenVisualStudio(CodeEditor.CurrentEditorInstallation, solution, absolutePath, line);
}
private string GetOrGenerateSolutionFile(string path)
{
var solution = GetSolutionFile(path);
if (solution == "")
{
_generator.Sync();
solution = GetSolutionFile(path);
}
return solution;
}
string GetSolutionFile(string path)
{
var solutionFile = _generator.SolutionFile();
if (File.Exists(solutionFile))
{
return solutionFile;
}
return "";
}
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 0173aff8c07e06b42af07ebdd7f08032
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 0173aff8c07e06b42af07ebdd7f08032
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,126 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
using System.IO;
using Microsoft.Win32;
using Unity.CodeEditor;
using IOPath = System.IO.Path;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal class VisualStudioInstallation
{
public string Name { get; set; }
public string Path { get; set; }
public Version Version { get; set; }
public bool IsPrerelease { get; set; }
public bool SupportsAnalyzers
{
get
{
if (VisualStudioEditor.IsWindows)
return Version >= new Version(16, 3);
if (VisualStudioEditor.IsOSX)
return Version >= new Version(8, 3);
return false;
}
}
private static string ReadRegistry(RegistryKey hive, string keyName, string valueName)
{
try
{
var unitykey = hive.OpenSubKey(keyName);
var result = (string)unitykey?.GetValue(valueName);
return result;
}
catch (Exception)
{
return null;
}
}
// We only use this to find analyzers, we do not need to load this assembly anymore
private string GetBridgeLocation()
{
if (VisualStudioEditor.IsWindows)
{
// Registry, using legacy bridge location
var keyName = $"Software\\Microsoft\\Microsoft Visual Studio {Version.Major}.0 Tools for Unity";
const string valueName = "UnityExtensionPath";
var bridge = ReadRegistry(Registry.CurrentUser, keyName, valueName);
if (string.IsNullOrEmpty(bridge))
bridge = ReadRegistry(Registry.LocalMachine, keyName, valueName);
return bridge;
}
if (VisualStudioEditor.IsOSX)
{
// Environment, useful when developing UnityVS for Mac
var bridge = Environment.GetEnvironmentVariable("VSTUM_BRIDGE");
if (!string.IsNullOrEmpty(bridge) && File.Exists(bridge))
return bridge;
const string addinBridge = "Editor/SyntaxTree.VisualStudio.Unity.Bridge.dll";
const string addinName = "MonoDevelop.Unity";
// user addins repository
var localAddins = IOPath.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
$"Library/Application Support/VisualStudio/${Version.Major}.0" + "/LocalInstall/Addins");
// In the user addins repository, the addins are suffixed by their versions, like `MonoDevelop.Unity.1.0`
// When installing another local user addin, MD will remove files inside the folder
// So we browse all VSTUM addins, and return the one with a bridge, which is the one MD will load
if (Directory.Exists(localAddins))
{
foreach (var folder in Directory.GetDirectories(localAddins, addinName + "*", SearchOption.TopDirectoryOnly))
{
bridge = IOPath.Combine(folder, addinBridge);
if (File.Exists(bridge))
return bridge;
}
}
// Check in Visual Studio.app/
// In that case the name of the addin is used
bridge = IOPath.Combine(Path, $"Contents/Resources/lib/monodevelop/AddIns/{addinName}/{addinBridge}");
if (File.Exists(bridge))
return bridge;
}
return null;
}
public string[] GetAnalyzers()
{
var bridge = GetBridgeLocation();
if (!string.IsNullOrEmpty(bridge))
{
var baseLocation = IOPath.Combine(IOPath.GetDirectoryName(bridge), "..");
var analyzerLocation = IOPath.GetFullPath(IOPath.Combine(baseLocation, "Analyzers"));
if (Directory.Exists(analyzerLocation))
return Directory.GetFiles(analyzerLocation, "*Analyzers.dll", SearchOption.AllDirectories);
}
// Local assets
// return FileUtility.FindPackageAssetFullPath("Analyzers a:packages", ".Analyzers.dll");
return Array.Empty<string>();
}
public CodeEditor.Installation ToCodeEditorInstallation()
{
return new CodeEditor.Installation() { Name = Name, Path = Path };
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bb86eea06f54fb24caa7046a8a764945
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,174 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Microsoft.Unity.VisualStudio.Editor.Messaging;
using UnityEditor;
using UnityEngine;
using MessageType = Microsoft.Unity.VisualStudio.Editor.Messaging.MessageType;
namespace Microsoft.Unity.VisualStudio.Editor
{
[InitializeOnLoad]
internal class VisualStudioIntegration
{
private static Messager _messager;
private static readonly Queue<Message> Incoming = new Queue<Message>();
private static readonly object IncomingLock = new object();
public static Application.LogCallback LogCallback = delegate { };
static VisualStudioIntegration()
{
RunOnceOnUpdate(() =>
{
_messager = Messager.BindTo(MessagingPort());
_messager.ReceiveMessage += ReceiveMessage;
RunOnShutdown(Shutdown);
});
EditorApplication.update += OnUpdate;
}
private static void RunOnceOnUpdate(Action action)
{
var callback = null as EditorApplication.CallbackFunction;
callback = () =>
{
EditorApplication.update -= callback;
action();
};
EditorApplication.update += callback;
}
private static void RunOnShutdown(Action action)
{
// Mono on OSX has all kinds of quirks on AppDomain shutdown
if (!VisualStudioEditor.IsWindows)
return;
AppDomain.CurrentDomain.DomainUnload += (_, __) => action();
}
private static int DebuggingPort()
{
return 56000 + (System.Diagnostics.Process.GetCurrentProcess().Id % 1000);
}
private static int MessagingPort()
{
return DebuggingPort() + 2;
}
private static void ReceiveMessage(object sender, MessageEventArgs args)
{
OnMessage(args.Message);
}
private static void OnUpdate()
{
lock (IncomingLock)
{
while (Incoming.Count > 0)
{
ProcessIncoming(Incoming.Dequeue());
}
}
}
private static void AddMessage(Message message)
{
lock (IncomingLock)
{
Incoming.Enqueue(message);
}
}
private static void ProcessIncoming(Message message)
{
switch (message.Type)
{
case MessageType.Ping:
Answer(message, MessageType.Pong);
break;
case MessageType.Play:
Shutdown();
EditorApplication.isPlaying = true;
break;
case MessageType.Stop:
EditorApplication.isPlaying = false;
break;
case MessageType.Pause:
EditorApplication.isPaused = true;
break;
case MessageType.Unpause:
EditorApplication.isPaused = false;
break;
case MessageType.Build:
// Not used anymore
break;
case MessageType.Refresh:
Refresh();
break;
case MessageType.Version:
Answer(message, MessageType.Version, PackageVersion());
break;
case MessageType.UpdatePackage:
// Not used anymore
break;
case MessageType.ProjectPath:
Answer(message, MessageType.ProjectPath, Path.GetFullPath(Path.Combine(Application.dataPath, "..")));
break;
}
}
private static string PackageVersion()
{
var package = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(VisualStudioIntegration).Assembly);
return package.version;
}
private static void Refresh()
{
RunOnceOnUpdate(AssetDatabase.Refresh);
}
private static void OnMessage(Message message)
{
AddMessage(message);
}
private static void Answer(Message message, MessageType answerType, string answerValue = "")
{
var targetEndPoint = message.Origin;
Answer(
targetEndPoint,
answerType,
answerValue);
}
private static void Answer(IPEndPoint targetEndPoint, MessageType answerType, string answerValue)
{
_messager?.SendMessage(targetEndPoint, answerType, answerValue);
}
private static void Shutdown()
{
if (_messager == null)
return;
_messager.ReceiveMessage -= ReceiveMessage;
_messager.Dispose();
_messager = null;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 48fcd6ebd5ce8fd4cbe931895233677d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,9 +1,9 @@
{
"name": "Unity.VisualStudio.Editor",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": []
}
{
"name": "Unity.VisualStudio.Editor",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": []
}

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: b93f844d45cfcc44fa2b0eed5c9ec6bb
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: b93f844d45cfcc44fa2b0eed5c9ec6bb
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: