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
## [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
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
Project generation:
Added C#8 language support.
Added UnityProjectGeneratorVersion property.
Local and Embedded packages are now selected by default for generation.
Added support for asmdef root namespace.
- Added C#8 language support.
- Added UnityProjectGeneratorVersion property.
- Local and Embedded packages are now selected by default for generation.
- Added support for asmdef root namespace.
Integration:
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.
- 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.
## [2.0.2] - 2020-05-27
Added support for solution folders.
Only bind the messenger when the VS editor is selected.
Warn when unable to create the messenger.
Fixed an initialization issue triggering legacy code generation.
Allow package source in assembly to be generated when referenced from asmref.
- Added support for solution folders.
- Only bind the messenger when the VS editor is selected.
- Warn when unable to create the messenger.
- Fixed an initialization issue triggering legacy code generation.
- Allow package source in assembly to be generated when referenced from asmref.
## [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).
Use Unity's TypeCache to improve project generation speed.
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).
- 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.
- 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).
## [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)
- 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
- 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
- Fix Bridge assembly loading with non VS2017 editors.
## [1.1.0] - 2019-05-27
Move internal extension handling to package.
- Move internal extension handling to package.
## [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
Fix ignored comintegration executable
- Fix ignored comintegration executable.
## [1.0.9] - 2019-03-05
Updated MonoDevelop support, to pass correct arguments, and not import VSTU plugin
Use release build of COMIntegration for Visual Studio
- Updated MonoDevelop support, to pass correct arguments, and not import VSTU plugin.
- Use release build of COMIntegration for Visual Studio.
## [1.0.7] - 2019-04-30
Ensure asset database is refreshed when generating csproj and solution files.
- Ensure asset database is refreshed when generating csproj and solution files.
## [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
Fix relative package paths.
Fix opening editor on mac.
- Fix relative package paths.
- Fix opening editor on mac.
## [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*.
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);
}
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,9 +71,25 @@ namespace Microsoft.Unity.VisualStudio.Editor.Messaging
if (message != null)
{
message.Origin = (IPEndPoint)endPoint;
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)
{
return;
@@ -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
}

View File

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