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

@@ -1,89 +1,117 @@
# Code Editor Package for Visual Studio # Code Editor Package for Visual Studio
## [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.
## [2.0.5] - 2020-10-30 ## [2.0.5] - 2020-10-30
Integration: Integration:
Disable legacy pdb symbol checking for Unity packages - Disable legacy pdb symbol checking for Unity packages.
## [2.0.4] - 2020-10-15
Project generation:
- Added support for embedded Roslyn analyzer DLLs and ruleset files.
- Warn the user when the opened script is not part of the generation scope.
- Warn the user when the selected Visual Studio installation is not found.
- Generate a .vsconfig file to ensure Visual Studio installation is compatible.
Integration:
- Fix automation issues on MacOS, where a new Visual Studio instance is opened every time.
## [2.0.3] - 2020-09-09 ## [2.0.3] - 2020-09-09
Project generation: Project generation:
Added C#8 language support. - Added C#8 language support.
Added UnityProjectGeneratorVersion property. - Added UnityProjectGeneratorVersion property.
Local and Embedded packages are now selected by default for generation. - Local and Embedded packages are now selected by default for generation.
Added support for asmdef root namespace. - Added support for asmdef root namespace.
Integration: Integration:
When the user disabled auto-refresh in Unity, do not try to force refresh the Asset database. - When the user disabled auto-refresh in Unity, do not try to force refresh the Asset database.
Fix Visual Studio detection issues with languages using special characters. - Fix Visual Studio detection issues with languages using special characters.
## [2.0.2] - 2020-05-27 ## [2.0.2] - 2020-05-27
Added support for solution folders. - Added support for solution folders.
Only bind the messenger when the VS editor is selected. - Only bind the messenger when the VS editor is selected.
Warn when unable to create the messenger. - Warn when unable to create the messenger.
Fixed an initialization issue triggering legacy code generation. - Fixed an initialization issue triggering legacy code generation.
Allow package source in assembly to be generated when referenced from asmref. - Allow package source in assembly to be generated when referenced from asmref.
## [2.0.1] - 2020-03-19 ## [2.0.1] - 2020-03-19
When Visual Studio installation is compatible with C# 8.0, setup the language version to not prompt the user with unsupported constructs. (So far Unity only supports C# 7.3). - When Visual Studio installation is compatible with C# 8.0, setup the language version to not prompt the user with unsupported constructs. (So far Unity only supports C# 7.3).
Use Unity's TypeCache to improve project generation speed. - Use Unity's TypeCache to improve project generation speed.
Properly check for a managed assembly before displaying a warning regarding legacy PDB usage. - Properly check for a managed assembly before displaying a warning regarding legacy PDB usage.
Add support for selective project generation (embedded, local, registry, git, builtin, player). - Add support for selective project generation (embedded, local, registry, git, builtin, player).
## [2.0.0] - 2019-11-06 ## [2.0.0] - 2019-11-06
- Improved Visual Studio and Visual Studio for Mac automatic discovery - 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 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 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 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. - Added a warning when using legacy pdb symbol files.
- Fixed issues while Opening Visual Studio on Windows - Fixed issues while Opening Visual Studio on Windows.
- Fixed issues while Opening Visual Studio on Mac - Fixed issues while Opening Visual Studio on Mac.
## [1.1.1] - 2019-05-29 ## [1.1.1] - 2019-05-29
Fix Bridge assembly loading with non VS2017 editors - Fix Bridge assembly loading with non VS2017 editors.
## [1.1.0] - 2019-05-27 ## [1.1.0] - 2019-05-27
Move internal extension handling to package. - Move internal extension handling to package.
## [1.0.11] - 2019-05-21 ## [1.0.11] - 2019-05-21
Fix detection of visual studio for mac installation. - Fix detection of visual studio for mac installation.
## [1.0.10] - 2019-05-04 ## [1.0.10] - 2019-05-04
Fix ignored comintegration executable - Fix ignored comintegration executable.
## [1.0.9] - 2019-03-05 ## [1.0.9] - 2019-03-05
Updated MonoDevelop support, to pass correct arguments, and not import VSTU plugin - Updated MonoDevelop support, to pass correct arguments, and not import VSTU plugin.
Use release build of COMIntegration for Visual Studio - Use release build of COMIntegration for Visual Studio.
## [1.0.7] - 2019-04-30 ## [1.0.7] - 2019-04-30
Ensure asset database is refreshed when generating csproj and solution files. - Ensure asset database is refreshed when generating csproj and solution files.
## [1.0.6] - 2019-04-27 ## [1.0.6] - 2019-04-27
Add support for generating all csproj files. - Add support for generating all csproj files.
## [1.0.5] - 2019-04-18 ## [1.0.5] - 2019-04-18
Fix relative package paths. - Fix relative package paths.
Fix opening editor on mac. - Fix opening editor on mac.
## [1.0.4] - 2019-04-12 ## [1.0.4] - 2019-04-12
@@ -94,4 +122,4 @@ Fix opening editor on mac.
### This is the first release of *Unity Package visualstudio_editor*. ### This is the first release of *Unity Package visualstudio_editor*.
Using the newly created api to integrate Visual Studio with Unity. - Using the newly created api to integrate Visual Studio with Unity.

View File

@@ -57,7 +57,17 @@ namespace Microsoft.Unity.VisualStudio.Editor
return path.Replace(WinSeparator, UnixSeparator); 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, "..")); var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
fileName = Normalize(fileName); fileName = Normalize(fileName);
@@ -65,7 +75,13 @@ namespace Microsoft.Unity.VisualStudio.Editor
if (!Path.IsPathRooted(fileName)) if (!Path.IsPathRooted(fileName))
fileName = Path.Combine(basePath, 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; private readonly BinaryReader _reader;
// Max UDP packet size is 65507
private const int MaxStringLength = 65000;
public Deserializer(byte[] buffer) public Deserializer(byte[] buffer)
{ {
_reader = new BinaryReader(new MemoryStream(buffer)); _reader = new BinaryReader(new MemoryStream(buffer));
@@ -27,7 +24,7 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
public string ReadString() public string ReadString()
{ {
var length = ReadInt32(); var length = ReadInt32();
return length > 0 && length <= MaxStringLength return length > 0
? Encoding.UTF8.GetString(_reader.ReadBytes(length)) ? Encoding.UTF8.GetString(_reader.ReadBytes(length))
: ""; : "";
} }

View File

@@ -30,5 +30,19 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
UpdatePackage, UpdatePackage,
ProjectPath, 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) if (message != null)
{ {
message.Origin = (IPEndPoint)endPoint; 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) catch (ObjectDisposedException)
@@ -86,6 +102,22 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
BeginReceiveMessage(); 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) private void RaiseMessagerException(Exception e)
{ {
MessagerException?.Invoke(this, new ExceptionEventArgs(e)); MessagerException?.Invoke(this, new ExceptionEventArgs(e));
@@ -108,6 +140,18 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
if (_disposed) if (_disposed)
return; 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); _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 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; public const int BufferSize = 1024 * 8;
internal UdpSocket() internal UdpSocket()

View File

@@ -560,15 +560,13 @@ namespace Microsoft.Unity.VisualStudio.Editor
var targetFrameworkVersion = "v4.7.1"; var targetFrameworkVersion = "v4.7.1";
var targetLanguageVersion = "latest"; // danger: latest is not the same absolute value depending on the VS version. 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 // Use the minimal supported version between VS and Unity, so that compilation will work in both
// Unity 2020.2.0a12 added support for C# 8 targetLanguageVersion = (vsLanguageSupport <= unityLanguageSupport ? vsLanguageSupport : unityLanguageSupport).ToString(2); // (major, minor) only
// <=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
} }
var projectType = ProjectTypeOf(assembly.name); 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 // 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 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)); .Where(p => generatedProjects.All(gp => gp.FileName != p.FileName));
projects.AddRange(externalProjects); 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 UnityEngine;
using Unity.CodeEditor; using Unity.CodeEditor;
using System.Runtime.InteropServices; 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 namespace Microsoft.Unity.VisualStudio.Editor
{ {

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using Microsoft.Win32; using Microsoft.Win32;
using Unity.CodeEditor; using Unity.CodeEditor;
using IOPath = System.IO.Path; using IOPath = System.IO.Path;
@@ -14,7 +15,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
{ {
string Path { get; } string Path { get; }
bool SupportsAnalyzers { get; } bool SupportsAnalyzers { get; }
bool SupportsCSharp8 { get; } Version LatestLanguageVersionSupported { get; }
string[] GetAnalyzers(); string[] GetAnalyzers();
CodeEditor.Installation ToCodeEditorInstallation(); 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 get
{ {
VersionPair[] versions = null;
if (VisualStudioEditor.IsWindows) if (VisualStudioEditor.IsWindows)
return Version >= new Version(16, 0); versions = WindowsVersionTable;
if (VisualStudioEditor.IsOSX) 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using Microsoft.Unity.VisualStudio.Editor.Messaging; using Microsoft.Unity.VisualStudio.Editor.Messaging;
using Microsoft.Unity.VisualStudio.Editor.Testing;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using MessageType = Microsoft.Unity.VisualStudio.Editor.Messaging.MessageType; using MessageType = Microsoft.Unity.VisualStudio.Editor.Messaging.MessageType;
@@ -17,10 +19,18 @@ namespace Microsoft.Unity.VisualStudio.Editor
[InitializeOnLoad] [InitializeOnLoad]
internal class VisualStudioIntegration internal class VisualStudioIntegration
{ {
class Client
{
public IPEndPoint EndPoint { get; set; }
public DateTime LastMessage { get; set; }
}
private static Messager _messager; private static Messager _messager;
private static readonly Queue<Message> Incoming = new Queue<Message>(); private static readonly Queue<Message> _incoming = new Queue<Message>();
private static readonly object IncomingLock = new object(); 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() static VisualStudioIntegration()
{ {
@@ -90,25 +100,39 @@ namespace Microsoft.Unity.VisualStudio.Editor
private static void OnUpdate() 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) private static void AddMessage(Message message)
{ {
lock (IncomingLock) lock (_incomingLock)
{ {
Incoming.Enqueue(message); _incoming.Enqueue(message);
} }
} }
private static void ProcessIncoming(Message message) private static void ProcessIncoming(Message message)
{ {
lock (_clientsLock)
{
CheckClient(message);
}
switch (message.Type) switch (message.Type)
{ {
case MessageType.Ping: case MessageType.Ping:
@@ -142,6 +166,36 @@ namespace Microsoft.Unity.VisualStudio.Editor
case MessageType.ProjectPath: case MessageType.ProjectPath:
Answer(message, MessageType.ProjectPath, Path.GetFullPath(Path.Combine(Application.dataPath, ".."))); Answer(message, MessageType.ProjectPath, Path.GetFullPath(Path.Combine(Application.dataPath, "..")));
break; 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); 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 = "") private static void Answer(Message message, MessageType answerType, string answerValue = "")
{ {
var targetEndPoint = message.Origin; var targetEndPoint = message.Origin;
@@ -189,5 +248,16 @@ namespace Microsoft.Unity.VisualStudio.Editor
_messager.Dispose(); _messager.Dispose();
_messager = null; _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", "name": "Unity.VisualStudio.Editor",
"references": [], "references": [],
"optionalUnityReferences": [],
"includePlatforms": [ "includePlatforms": [
"Editor" "Editor"
], ],
"excludePlatforms": [] "excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"Newtonsoft.Json.dll"
],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
} }

View File

@@ -2,18 +2,21 @@
"name": "com.unity.ide.visualstudio", "name": "com.unity.ide.visualstudio",
"displayName": "Visual Studio Editor", "displayName": "Visual Studio Editor",
"description": "Code editor integration for supporting Visual Studio as code editor for unity. Adds support for generating csproj files for intellisense purposes, auto discovery of installations, etc.", "description": "Code editor integration for supporting Visual Studio as code editor for unity. Adds support for generating csproj files for intellisense purposes, auto discovery of installations, etc.",
"version": "2.0.5", "version": "2.0.7",
"unity": "2020.1", "unity": "2020.1",
"unityRelease": "0a12", "unityRelease": "0a12",
"dependencies": {
"com.unity.test-framework": "1.1.9"
},
"relatedPackages": { "relatedPackages": {
"com.unity.ide.visualstudio.tests": "2.0.5" "com.unity.ide.visualstudio.tests": "2.0.7"
}, },
"upmCi": { "upmCi": {
"footprint": "848c02b3f0fe476a599004cd972346a89e39d26f" "footprint": "b6515ac9d75224fe45e288270d26a9e031c550a8"
}, },
"repository": { "repository": {
"url": "https://github.cds.internal.unity3d.com/unity/com.unity.ide.visualstudio.git", "url": "https://github.cds.internal.unity3d.com/unity/com.unity.ide.visualstudio.git",
"type": "git", "type": "git",
"revision": "83ca94e82bb6da515dc57e0d860b6b2224f56991" "revision": "dec282022c7a95fada560c36f53da9dd155a142c"
} }
} }