com.unity.ide.visualstudio@2.0.7

## [2.0.7] - 2021-02-02

Integration:

Remove com.unity.nuget.newtonsoft-json dependency in favor of the built-in JsonUtility for the VS Test Runner.

## [2.0.6] - 2021-01-20

Project generation:

- Improved language version detection.

Integration:

- Added support for the VS Test Runner.
- Added initial support for displaying asset usage.
- Fixed remaining issues with special characters in file/path.
This commit is contained in:
Unity Technologies
2021-02-02 00:00:00 +00:00
parent 08ab0fbd80
commit 8cbbe811d0
34 changed files with 999 additions and 70 deletions

View File

@@ -57,7 +57,17 @@ namespace Microsoft.Unity.VisualStudio.Editor
return path.Replace(WinSeparator, UnixSeparator);
}
internal static bool IsFileInProjectDirectory(string fileName)
internal static bool IsFileInProjectRootDirectory(string fileName)
{
var relative = MakeRelativeToProjectPath(fileName);
if (string.IsNullOrEmpty(relative))
return false;
return relative == Path.GetFileName(relative);
}
// returns null if outside of the project scope
internal static string MakeRelativeToProjectPath(string fileName)
{
var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
fileName = Normalize(fileName);
@@ -65,7 +75,13 @@ namespace Microsoft.Unity.VisualStudio.Editor
if (!Path.IsPathRooted(fileName))
fileName = Path.Combine(basePath, fileName);
return string.Equals(Path.GetDirectoryName(fileName), basePath, StringComparison.OrdinalIgnoreCase);
if (!fileName.StartsWith(basePath, StringComparison.OrdinalIgnoreCase))
return null;
return fileName
.Substring(basePath.Length)
.Trim(Path.DirectorySeparatorChar);
}
}
}

View File

@@ -11,9 +11,6 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
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));
@@ -27,7 +24,7 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
public string ReadString()
{
var length = ReadInt32();
return length > 0 && length <= MaxStringLength
return length > 0
? Encoding.UTF8.GetString(_reader.ReadBytes(length))
: "";
}

View File

@@ -30,5 +30,19 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
UpdatePackage,
ProjectPath,
// This message is a technical one for big messages, not intended to be used directly
Tcp,
RunStarted,
RunFinished,
TestStarted,
TestFinished,
TestListRetrieved,
RetrieveTestList,
ExecuteTests,
ShowUsage
}
}

View File

@@ -71,7 +71,23 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
if (message != null)
{
message.Origin = (IPEndPoint)endPoint;
ReceiveMessage?.Invoke(this, new MessageEventArgs(message));
int port;
int bufferSize;
if (IsValidTcpMessage(message, out port, out bufferSize))
{
// switch to TCP mode to handle big messages
TcpClient.Queue(message.Origin.Address, port, bufferSize, buffer =>
{
var originalMessage = DeserializeMessage(buffer);
originalMessage.Origin = message.Origin;
ReceiveMessage?.Invoke(this, new MessageEventArgs(originalMessage));
});
}
else
{
ReceiveMessage?.Invoke(this, new MessageEventArgs(message));
}
}
}
catch (ObjectDisposedException)
@@ -86,6 +102,22 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
BeginReceiveMessage();
}
private static bool IsValidTcpMessage(Message message, out int port, out int bufferSize)
{
port = 0;
bufferSize = 0;
if (message.Value == null)
return false;
if (message.Type != MessageType.Tcp)
return false;
var parts = message.Value.Split(':');
if (parts.Length != 2)
return false;
if (!int.TryParse(parts[0], out port))
return false;
return int.TryParse(parts[1], out bufferSize);
}
private void RaiseMessagerException(Exception e)
{
MessagerException?.Invoke(this, new ExceptionEventArgs(e));
@@ -108,6 +140,18 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
if (_disposed)
return;
if (buffer.Length >= UdpSocket.BufferSize)
{
// switch to TCP mode to handle big messages
var port = TcpListener.Queue(buffer);
if (port > 0)
{
// success, replace original message with "switch to tcp" marker + port information + buffer length
message = MessageFor(MessageType.Tcp, string.Concat(port, ':', buffer.Length));
buffer = SerializeMessage(message);
}
}
_socket.BeginSendTo(buffer, 0, Math.Min(buffer.Length, UdpSocket.BufferSize), SocketFlags.None, target, SendMessageCallback, null);
}
}

View File

@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* 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;
using System.Threading;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class TcpClient
{
private const int ConnectOrReadTimeoutMilliseconds = 5000;
private class State
{
public System.Net.Sockets.TcpClient TcpClient;
public NetworkStream NetworkStream;
public byte[] Buffer;
public Action<byte[]> OnBufferAvailable;
}
public static void Queue(IPAddress address, int port, int bufferSize, Action<byte[]> onBufferAvailable)
{
var client = new System.Net.Sockets.TcpClient();
var state = new State {OnBufferAvailable = onBufferAvailable, TcpClient = client, Buffer = new byte[bufferSize]};
try
{
ThreadPool.QueueUserWorkItem(_ =>
{
var handle = client.BeginConnect(address, port, OnClientConnected, state);
if (!handle.AsyncWaitHandle.WaitOne(ConnectOrReadTimeoutMilliseconds))
Cleanup(state);
});
}
catch (Exception)
{
Cleanup(state);
}
}
private static void OnClientConnected(IAsyncResult result)
{
var state = (State)result.AsyncState;
try
{
state.TcpClient.EndConnect(result);
state.NetworkStream = state.TcpClient.GetStream();
var handle = state.NetworkStream.BeginRead(state.Buffer, 0, state.Buffer.Length, OnEndRead, state);
if (!handle.AsyncWaitHandle.WaitOne(ConnectOrReadTimeoutMilliseconds))
Cleanup(state);
}
catch (Exception)
{
Cleanup(state);
}
}
private static void OnEndRead(IAsyncResult result)
{
var state = (State)result.AsyncState;
try
{
var count = state.NetworkStream.EndRead(result);
if (count == state.Buffer.Length)
state.OnBufferAvailable?.Invoke(state.Buffer);
}
catch (Exception)
{
// Ignore and cleanup
}
finally
{
Cleanup(state);
}
}
private static void Cleanup(State state)
{
state.NetworkStream?.Dispose();
state.TcpClient?.Close();
state.NetworkStream = null;
state.TcpClient = null;
state.Buffer = null;
state.OnBufferAvailable = null;
}
}
}

View File

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

View File

@@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* 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.Threading;
namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class TcpListener
{
private const int ListenTimeoutMilliseconds = 5000;
private class State
{
public System.Net.Sockets.TcpListener TcpListener;
public byte[] Buffer;
}
public static int Queue(byte[] buffer)
{
var tcpListener = new System.Net.Sockets.TcpListener(IPAddress.Any, 0);
var state = new State {Buffer = buffer, TcpListener = tcpListener};
try
{
tcpListener.Start();
int port = ((IPEndPoint)tcpListener.LocalEndpoint).Port;
ThreadPool.QueueUserWorkItem(_ =>
{
bool listening = true;
while (listening)
{
var handle = tcpListener.BeginAcceptTcpClient(OnIncomingConnection, state);
listening = handle.AsyncWaitHandle.WaitOne(ListenTimeoutMilliseconds);
}
Cleanup(state);
});
return port;
}
catch (Exception)
{
Cleanup(state);
return -1;
}
}
private static void OnIncomingConnection(IAsyncResult result)
{
var state = (State)result.AsyncState;
try
{
using (var client = state.TcpListener.EndAcceptTcpClient(result))
{
using (var stream = client.GetStream())
{
stream.Write(state.Buffer, 0, state.Buffer.Length);
}
}
}
catch (Exception)
{
// Ignore and cleanup
}
}
private static void Cleanup(State state)
{
state.TcpListener?.Stop();
state.TcpListener = null;
state.Buffer = null;
}
}
}

View File

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

View File

@@ -10,6 +10,8 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
{
internal class UdpSocket : Socket
{
// Maximum UDP payload is 65507 bytes.
// TCP mode will be used when the payload is bigger than this BufferSize
public const int BufferSize = 1024 * 8;
internal UdpSocket()

View File

@@ -560,15 +560,13 @@ namespace Microsoft.Unity.VisualStudio.Editor
var targetFrameworkVersion = "v4.7.1";
var targetLanguageVersion = "latest"; // danger: latest is not the same absolute value depending on the VS version.
if (m_CurrentInstallation != null && m_CurrentInstallation.SupportsCSharp8)
if (m_CurrentInstallation != null)
{
// Current VS installation is compatible with C# 8.
var vsLanguageSupport = m_CurrentInstallation.LatestLanguageVersionSupported;
var unityLanguageSupport = UnityInstallation.LatestLanguageVersionSupported(assembly);
#if !UNITY_2020_2_OR_NEWER
// Unity 2020.2.0a12 added support for C# 8
// <=2020.1 has no support for C# 8 constructs, so tell the compiler to accept only C# 7.3 or lower.
targetLanguageVersion = "7.3";
#endif
// Use the minimal supported version between VS and Unity, so that compilation will work in both
targetLanguageVersion = (vsLanguageSupport <= unityLanguageSupport ? vsLanguageSupport : unityLanguageSupport).ToString(2); // (major, minor) only
}
var projectType = ProjectTypeOf(assembly.name);
@@ -801,7 +799,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
{
// Add all projects that were previously in the solution and that are not generated by Unity, nor generated in the project root directory
var externalProjects = previousSolution.Projects
.Where(p => p.IsSolutionFolderProjectFactory() || !FileUtility.IsFileInProjectDirectory(p.FileName))
.Where(p => p.IsSolutionFolderProjectFactory() || !FileUtility.IsFileInProjectRootDirectory(p.FileName))
.Where(p => generatedProjects.All(gp => gp.FileName != p.FileName));
projects.AddRange(externalProjects);

8
Editor/Testing.meta Normal file
View File

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

View File

@@ -0,0 +1,39 @@
using System;
using UnityEditor.TestTools.TestRunner.Api;
namespace Microsoft.Unity.VisualStudio.Editor.Testing
{
[Serializable]
internal class TestAdaptorContainer
{
public TestAdaptor[] TestAdaptors;
}
[Serializable]
internal class TestAdaptor
{
public string Id;
public string Name;
public string FullName;
public string Type;
public string Method;
public string Assembly;
public int Parent;
public TestAdaptor(ITestAdaptor testAdaptor, int parent)
{
Id = testAdaptor.Id;
Name = testAdaptor.Name;
FullName = testAdaptor.FullName;
Type = testAdaptor.TypeInfo?.FullName;
Method = testAdaptor?.Method?.Name;
Assembly = testAdaptor.TypeInfo?.Assembly?.Location;
Parent = parent;
}
}
}

View File

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

View File

@@ -0,0 +1,60 @@
using System;
using UnityEditor.TestTools.TestRunner.Api;
namespace Microsoft.Unity.VisualStudio.Editor.Testing
{
[Serializable]
internal class TestResultAdaptorContainer
{
public TestResultAdaptor[] TestResultAdaptors;
}
[Serializable]
internal class TestResultAdaptor
{
public string Name;
public string FullName;
public int PassCount;
public int FailCount;
public int InconclusiveCount;
public int SkipCount;
public string ResultState;
public string StackTrace;
public TestStatusAdaptor TestStatus;
public int Parent;
public TestResultAdaptor(ITestResultAdaptor testResultAdaptor, int parent)
{
Name = testResultAdaptor.Name;
FullName = testResultAdaptor.FullName;
PassCount = testResultAdaptor.PassCount;
FailCount = testResultAdaptor.FailCount;
InconclusiveCount = testResultAdaptor.InconclusiveCount;
SkipCount = testResultAdaptor.SkipCount;
switch (testResultAdaptor.TestStatus)
{
case UnityEditor.TestTools.TestRunner.Api.TestStatus.Passed:
TestStatus = TestStatusAdaptor.Passed;
break;
case UnityEditor.TestTools.TestRunner.Api.TestStatus.Skipped:
TestStatus = TestStatusAdaptor.Skipped;
break;
case UnityEditor.TestTools.TestRunner.Api.TestStatus.Inconclusive:
TestStatus = TestStatusAdaptor.Inconclusive;
break;
case UnityEditor.TestTools.TestRunner.Api.TestStatus.Failed:
TestStatus = TestStatusAdaptor.Failed;
break;
}
Parent = parent;
}
}
}

View File

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

View File

@@ -0,0 +1,52 @@
using System;
using UnityEditor;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;
namespace Microsoft.Unity.VisualStudio.Editor.Testing
{
[InitializeOnLoad]
internal class TestRunnerApiListener
{
private static TestRunnerApi _testRunnerApi;
private static TestRunnerCallbacks _testRunnerCallbacks;
static TestRunnerApiListener()
{
_testRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
_testRunnerCallbacks = new TestRunnerCallbacks();
_testRunnerApi.RegisterCallbacks(_testRunnerCallbacks);
}
public static void RetrieveTestList(string mode)
{
RetrieveTestList((TestMode) Enum.Parse(typeof(TestMode), mode));
}
private static void RetrieveTestList(TestMode mode)
{
_testRunnerApi.RetrieveTestList(mode, (ta) => _testRunnerCallbacks.TestListRetrieved(mode, ta));
}
public static void ExecuteTests(string command)
{
// ExecuteTests format:
// TestMode:FullName
var index = command.IndexOf(':');
if (index < 0)
return;
var testMode = (TestMode)Enum.Parse(typeof(TestMode), command.Substring(0, index));
var filter = command.Substring(index + 1);
ExecuteTests(new Filter() { testMode = testMode, testNames = new string[] { filter } });
}
private static void ExecuteTests(Filter filter)
{
_testRunnerApi.Execute(new ExecutionSettings(filter));
}
}
}

View File

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

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;
namespace Microsoft.Unity.VisualStudio.Editor.Testing
{
internal class TestRunnerCallbacks : ICallbacks
{
private string Serialize<TContainer, TSource, TAdaptor>(
TSource source,
Func<TSource, int, TAdaptor> createAdaptor,
Func<TSource, IEnumerable<TSource>> children,
Func<TAdaptor[], TContainer> container)
{
var adaptors = new List<TAdaptor>();
void AddAdaptor(TSource item, int parentIndex)
{
var index = adaptors.Count;
adaptors.Add(createAdaptor(item, parentIndex));
foreach (var child in children(item))
AddAdaptor(child, index);
}
AddAdaptor(source, -1);
return JsonUtility.ToJson(container(adaptors.ToArray()));
}
private string Serialize(ITestAdaptor testAdaptor)
{
return Serialize(
testAdaptor,
(a, parentIndex) => new TestAdaptor(a, parentIndex),
(a) => a.Children,
(r) => new TestAdaptorContainer { TestAdaptors = r });
}
private string Serialize(ITestResultAdaptor testResultAdaptor)
{
return Serialize(
testResultAdaptor,
(a, parentIndex) => new TestResultAdaptor(a, parentIndex),
(a) => a.Children,
(r) => new TestResultAdaptorContainer { TestResultAdaptors = r });
}
public void RunFinished(ITestResultAdaptor testResultAdaptor)
{
VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.RunFinished, Serialize(testResultAdaptor));
}
public void RunStarted(ITestAdaptor testAdaptor)
{
VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.RunStarted, Serialize(testAdaptor));
}
public void TestFinished(ITestResultAdaptor testResultAdaptor)
{
VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.TestFinished, Serialize(testResultAdaptor));
}
public void TestStarted(ITestAdaptor testAdaptor)
{
VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.TestStarted, Serialize(testAdaptor));
}
private static string TestModeName(TestMode testMode)
{
switch (testMode)
{
case TestMode.EditMode: return "EditMode";
case TestMode.PlayMode: return "PlayMode";
}
throw new ArgumentOutOfRangeException();
}
internal void TestListRetrieved(TestMode testMode, ITestAdaptor testAdaptor)
{
// TestListRetrieved format:
// TestMode:Json
var value = TestModeName(testMode) + ":" + Serialize(testAdaptor);
VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.TestListRetrieved, value);
}
}
}

View File

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

View File

@@ -0,0 +1,13 @@
using System;
namespace Microsoft.Unity.VisualStudio.Editor.Testing
{
[Serializable]
internal enum TestStatusAdaptor
{
Passed,
Skipped,
Inconclusive,
Failed,
}
}

View File

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

View File

@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* 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 UnityEditor.Compilation;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal static class UnityInstallation
{
public static Version LatestLanguageVersionSupported(Assembly assembly)
{
#if UNITY_2020_2_OR_NEWER
if (assembly?.compilerOptions != null && Version.TryParse(assembly.compilerOptions.LanguageVersion, out var result))
return result;
// if parsing fails, we know at least we have support for 8.0
return new Version(8, 0);
#else
return new Version(7, 3);
#endif
}
}
}

View File

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

118
Editor/UsageUtility.cs Normal file
View File

@@ -0,0 +1,118 @@
/*---------------------------------------------------------------------------------------------
* 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 UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
namespace Microsoft.Unity.VisualStudio.Editor
{
[Serializable]
internal class FileUsage
{
public string Path;
public string[] GameObjectPath;
}
internal static class UsageUtility
{
internal static void ShowUsage(string json)
{
try
{
var usage = JsonUtility.FromJson<FileUsage>(json);
ShowUsage(usage.Path, usage.GameObjectPath);
}
catch (Exception)
{
// ignore malformed request
}
}
internal static void ShowUsage(string path, string[] gameObjectPath)
{
path = FileUtility.MakeRelativeToProjectPath(path);
if (path == null)
return;
path = FileUtility.NormalizeWindowsToUnix(path);
var extension = Path.GetExtension(path).ToLower();
EditorUtility.FocusProjectWindow();
switch (extension)
{
case ".unity":
ShowSceneUsage(path, gameObjectPath);
break;
default:
var asset = AssetDatabase.LoadMainAssetAtPath(path);
Selection.activeObject = asset;
EditorGUIUtility.PingObject(asset);
break;
}
}
private static void ShowSceneUsage(string scenePath, string[] gameObjectPath)
{
var scene = SceneManager.GetSceneByPath(scenePath.Replace(Path.DirectorySeparatorChar, '/'));
if (!scene.isLoaded)
{
var result = UnityEditor.EditorUtility.DisplayDialogComplex("Show Usage",
$"Do you want to open \"{Path.GetFileName(scenePath)}\"?",
"Open Scene",
"Cancel",
"Open Scene (additive)");
switch (result)
{
case 0:
EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
break;
case 1:
return;
case 2:
scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
break;
}
}
ShowSceneUsage(scene, gameObjectPath);
}
private static void ShowSceneUsage(Scene scene, string[] gameObjectPath)
{
if (gameObjectPath == null || gameObjectPath.Length == 0)
return;
var go = scene.GetRootGameObjects().FirstOrDefault(g => g.name == gameObjectPath[0]);
if (go == null)
return;
for (var ni = 1; ni < gameObjectPath.Length; ni++)
{
var transform = go.transform;
for (var i = 0; i < transform.childCount; i++)
{
var child = transform.GetChild(i);
var childgo = child.gameObject;
if (childgo.name == gameObjectPath[ni])
{
go = childgo;
break;
}
}
}
Selection.activeObject = go;
EditorGUIUtility.PingObject(go);
}
}
}

View File

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

16
Editor/VersionPair.cs Normal file
View File

@@ -0,0 +1,16 @@
using System;
namespace Microsoft.Unity.VisualStudio.Editor
{
internal struct VersionPair
{
public Version IdeVersion;
public Version LanguageVersion;
public VersionPair(int idemajor, int ideminor, int languageMajor, int languageMinor)
{
IdeVersion = new Version(idemajor, ideminor);
LanguageVersion = new Version(languageMajor, languageMinor);
}
}
}

View File

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

View File

@@ -11,6 +11,11 @@ using UnityEditor;
using UnityEngine;
using Unity.CodeEditor;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.VisualStudio.EditorTests")]
[assembly: InternalsVisibleTo("Unity.VisualStudio.Standalone.EditorTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
namespace Microsoft.Unity.VisualStudio.Editor
{

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Linq;
using Microsoft.Win32;
using Unity.CodeEditor;
using IOPath = System.IO.Path;
@@ -14,7 +15,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
{
string Path { get; }
bool SupportsAnalyzers { get; }
bool SupportsCSharp8 { get; }
Version LatestLanguageVersionSupported { get; }
string[] GetAnalyzers();
CodeEditor.Installation ToCodeEditorInstallation();
}
@@ -40,17 +41,52 @@ namespace Microsoft.Unity.VisualStudio.Editor
}
}
public bool SupportsCSharp8
// C# language version support for Visual Studio
private static VersionPair[] WindowsVersionTable =
{
// VisualStudio 2019
new VersionPair(16,8, /* => */ 9,0),
new VersionPair(16,0, /* => */ 8,0),
// VisualStudio 2017
new VersionPair(15,7, /* => */ 7,3),
new VersionPair(15,5, /* => */ 7,2),
new VersionPair(15,3, /* => */ 7,1),
new VersionPair(15,0, /* => */ 7,0),
};
// C# language version support for Visual Studio for Mac
private static VersionPair[] OSXVersionTable =
{
// VisualStudio for Mac 8.x
new VersionPair(8,8, /* => */ 9,0),
new VersionPair(8,3, /* => */ 8,0),
new VersionPair(8,0, /* => */ 7,3),
};
public Version LatestLanguageVersionSupported
{
get
{
VersionPair[] versions = null;
if (VisualStudioEditor.IsWindows)
return Version >= new Version(16, 0);
versions = WindowsVersionTable;
if (VisualStudioEditor.IsOSX)
return Version >= new Version(8, 2);
versions = OSXVersionTable;
return false;
if (versions != null)
{
foreach(var entry in versions)
{
if (Version >= entry.IdeVersion)
return entry.LanguageVersion;
}
}
// default to 7.0 given we support at least VS 2017
return new Version(7,0);
}
}

View File

@@ -5,9 +5,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using Microsoft.Unity.VisualStudio.Editor.Messaging;
using Microsoft.Unity.VisualStudio.Editor.Testing;
using UnityEditor;
using UnityEngine;
using MessageType = Microsoft.Unity.VisualStudio.Editor.Messaging.MessageType;
@@ -17,10 +19,18 @@ namespace Microsoft.Unity.VisualStudio.Editor
[InitializeOnLoad]
internal class VisualStudioIntegration
{
class Client
{
public IPEndPoint EndPoint { get; set; }
public DateTime LastMessage { get; set; }
}
private static Messager _messager;
private static readonly Queue<Message> Incoming = new Queue<Message>();
private static readonly object IncomingLock = new object();
private static readonly Queue<Message> _incoming = new Queue<Message>();
private static readonly Dictionary<IPEndPoint, Client> _clients = new Dictionary<IPEndPoint, Client>();
private static readonly object _incomingLock = new object();
private static readonly object _clientsLock = new object();
static VisualStudioIntegration()
{
@@ -90,25 +100,39 @@ namespace Microsoft.Unity.VisualStudio.Editor
private static void OnUpdate()
{
lock (IncomingLock)
lock (_incomingLock)
{
while (Incoming.Count > 0)
while (_incoming.Count > 0)
{
ProcessIncoming(Incoming.Dequeue());
ProcessIncoming(_incoming.Dequeue());
}
}
lock (_clientsLock)
{
foreach (var client in _clients.Values.ToArray())
{
if (DateTime.Now.Subtract(client.LastMessage) > TimeSpan.FromMilliseconds(4000))
_clients.Remove(client.EndPoint);
}
}
}
private static void AddMessage(Message message)
{
lock (IncomingLock)
lock (_incomingLock)
{
Incoming.Enqueue(message);
_incoming.Enqueue(message);
}
}
private static void ProcessIncoming(Message message)
{
lock (_clientsLock)
{
CheckClient(message);
}
switch (message.Type)
{
case MessageType.Ping:
@@ -142,6 +166,36 @@ namespace Microsoft.Unity.VisualStudio.Editor
case MessageType.ProjectPath:
Answer(message, MessageType.ProjectPath, Path.GetFullPath(Path.Combine(Application.dataPath, "..")));
break;
case MessageType.ExecuteTests:
TestRunnerApiListener.ExecuteTests(message.Value);
break;
case MessageType.RetrieveTestList:
TestRunnerApiListener.RetrieveTestList(message.Value);
break;
case MessageType.ShowUsage:
UsageUtility.ShowUsage(message.Value);
break;
}
}
private static void CheckClient(Message message)
{
Client client;
var endPoint = message.Origin;
if (!_clients.TryGetValue(endPoint, out client))
{
client = new Client
{
EndPoint = endPoint,
LastMessage = DateTime.Now
};
_clients.Add(endPoint, client);
}
else
{
client.LastMessage = DateTime.Now;
}
}
@@ -165,6 +219,11 @@ namespace Microsoft.Unity.VisualStudio.Editor
AddMessage(message);
}
private static void Answer(Client client, MessageType answerType, string answerValue)
{
Answer(client.EndPoint, answerType, answerValue);
}
private static void Answer(Message message, MessageType answerType, string answerValue = "")
{
var targetEndPoint = message.Origin;
@@ -189,5 +248,16 @@ namespace Microsoft.Unity.VisualStudio.Editor
_messager.Dispose();
_messager = null;
}
internal static void BroadcastMessage(MessageType type, string value)
{
lock (_clientsLock)
{
foreach (var client in _clients.Values.ToArray())
{
Answer(client, type, value);
}
}
}
}
}

View File

@@ -1,9 +1,17 @@
{
"name": "Unity.VisualStudio.Editor",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": []
}
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"Newtonsoft.Json.dll"
],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}