Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
## [2.0.17] - 2022-12-06

Integration:

- Fix rare deadlocks while discovering or launching Visual Studio on Windows.
- Improve launching Visual Studio on macOs.

Project generation:

- Include analyzers from response files.
- Update supported C# versions.
- Performance improvements.
  • Loading branch information
Unity Technologies committed Dec 6, 2022
1 parent 7c58d41 commit 4893e3b
Show file tree
Hide file tree
Showing 16 changed files with 410 additions and 216 deletions.
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
# Code Editor Package for Visual Studio

## [2.0.17] - 2022-12-06

Integration:

- Fix rare deadlocks while discovering or launching Visual Studio on Windows.
- Improve launching Visual Studio on macOs.

Project generation:

- Include analyzers from response files.
- Update supported C# versions.
- Performance improvements.


## [2.0.16] - 2022-06-08

Integration:

- Prevent ADB Refresh while being in safe-mode with a URP project
- Fixed an issue keeping the progress bar visible even after opening a script with Visual Studio.


## [2.0.15] - 2022-03-21

Integration:
Expand Down
21 changes: 8 additions & 13 deletions Editor/AppleEventIntegration~/AppleEventIntegration/main.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>

// 'FSnd' FourCC
#define keyFileSender 1179872868

// 16 bit aligned legacy struct - this should total 20 bytes
struct SelectionRange
typedef struct _SelectionRange
{
int16_t unused1; // 0 (not used)
int16_t lineNum; // line to select (<0 to specify range)
int32_t startRange; // start of selection range (if line < 0)
int32_t endRange; // end of selection range (if line < 0)
int32_t unused2; // 0 (not used)
int32_t theDate; // modification date/time
} __attribute__((packed));
} __attribute__((packed)) SelectionRange;

static NSString* MakeNSString(const char* str)
{
Expand Down Expand Up @@ -200,18 +200,13 @@ static BOOL TryQueryCurrentSolutionPath(NSRunningApplication* runningApp, NSStri

static NSRunningApplication* LaunchApplicationOnSolution(NSString* appPath, NSString* solutionPath)
{
NSURL* appUrl = [NSURL fileURLWithPath: appPath];
NSMutableDictionary* config = [[NSMutableDictionary alloc] init];

NSRunningApplication* runningApp = [[NSWorkspace sharedWorkspace]
launchApplicationAtURL: appUrl
return [[NSWorkspace sharedWorkspace]
launchApplicationAtURL: [NSURL fileURLWithPath: appPath]
options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance
configuration: config
configuration: @{
NSWorkspaceLaunchConfigurationArguments: @[ solutionPath ],
}
error: nil];

OpenFileAtLineWithAppleEvent(runningApp, solutionPath, -1);

return runningApp;
}

static NSRunningApplication* QueryOrLaunchApplication(NSString* appPath, NSString* solutionPath)
Expand Down
16 changes: 13 additions & 3 deletions Editor/AsyncOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,24 @@ namespace Microsoft.Unity.VisualStudio.Editor
internal class AsyncOperation<T>
{
private readonly Func<T> _producer;
private readonly Func<Exception, T> _exceptionHandler;
private readonly Action _finalHandler;
private readonly ManualResetEventSlim _resetEvent;

private T _result;
private Exception _exception;

private AsyncOperation(Func<T> producer)
private AsyncOperation(Func<T> producer, Func<Exception, T> exceptionHandler, Action finalHandler)
{
_producer = producer;
_exceptionHandler = exceptionHandler;
_finalHandler = finalHandler;
_resetEvent = new ManualResetEventSlim(initialState: false);
}

public static AsyncOperation<T> Run(Func<T> producer)
public static AsyncOperation<T> Run(Func<T> producer, Func<Exception, T> exceptionHandler = null, Action finalHandler = null)
{
var task = new AsyncOperation<T>(producer);
var task = new AsyncOperation<T>(producer, exceptionHandler, finalHandler);
task.Run();
return task;
}
Expand All @@ -40,9 +44,15 @@ private void Run()
catch (Exception e)
{
_exception = e;

if (_exceptionHandler != null)
{
_result = _exceptionHandler(e);
}
}
finally
{
_finalHandler?.Invoke();
_resetEvent.Set();
}
});
Expand Down
56 changes: 31 additions & 25 deletions Editor/COMIntegration/COMIntegration~/COMIntegration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,32 @@ static bool StartVisualStudioProcess(
return true;
}

static bool
MonikerIsVisualStudioProcess(const win::ComPtr<IMoniker> &moniker, const win::ComPtr<IBindCtx> &bindCtx, const DWORD dwProcessId = 0) {
LPOLESTR oleMonikerName;
if (FAILED(moniker->GetDisplayName(bindCtx, nullptr, &oleMonikerName)))
return false;

std::wstring monikerName(oleMonikerName);

// VisualStudio Moniker is "!VisualStudio.DTE.$Version:$PID"
// Example "!VisualStudio.DTE.14.0:1234"

if (monikerName.find(L"!VisualStudio.DTE") != 0)
return false;

if (dwProcessId == 0)
return true;

std::wstringstream suffixStream;
suffixStream << ":";
suffixStream << dwProcessId;

std::wstring suffix(suffixStream.str());

return monikerName.length() - suffix.length() == monikerName.find(suffix);
}

static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithSolution(
const std::filesystem::path &visualStudioExecutablePath,
const std::filesystem::path &solutionPath)
Expand Down Expand Up @@ -232,6 +258,9 @@ static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithSolution(
win::ComPtr<IMoniker> moniker;
ULONG monikersFetched = 0;
while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) {
if (!MonikerIsVisualStudioProcess(moniker, bindCtx))
continue;

if (FAILED(ROT->GetObject(moniker, &punk)))
continue;

Expand Down Expand Up @@ -285,29 +314,6 @@ static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithSolution(
return nullptr;
}

static bool
MonikerIsVisualStudioProcess(const win::ComPtr<IMoniker> &moniker, const win::ComPtr<IBindCtx> &bindCtx, const DWORD dwProcessId) {
LPOLESTR oleMonikerName;
if (FAILED(moniker->GetDisplayName(bindCtx, nullptr, &oleMonikerName)))
return false;

std::wstring monikerName(oleMonikerName);

// VisualStudio Moniker is "!VisualStudio.DTE.$Version:$PID"
// Example "!VisualStudio.DTE.14.0:1234"

if (monikerName.find(L"!VisualStudio.DTE") != 0)
return false;

std::wstringstream suffixStream;
suffixStream << ":";
suffixStream << dwProcessId;

std::wstring suffix(suffixStream.str());

return monikerName.length() - suffix.length() == monikerName.find(suffix);
}

static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithPID(const DWORD dwProcessId) {
win::ComPtr<IUnknown> punk = nullptr;
win::ComPtr<EnvDTE::_DTE> dte = nullptr;
Expand All @@ -329,10 +335,10 @@ static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithPID(const DWORD dwPr
win::ComPtr<IMoniker> moniker;
ULONG monikersFetched = 0;
while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) {
if (FAILED(ROT->GetObject(moniker, &punk)))
if (!MonikerIsVisualStudioProcess(moniker, bindCtx, dwProcessId))
continue;

if (!MonikerIsVisualStudioProcess(moniker, bindCtx, dwProcessId))
if (FAILED(ROT->GetObject(moniker, &punk)))
continue;

punk.As(&dte);
Expand Down
3 changes: 3 additions & 0 deletions Editor/COMIntegration/COMIntegration~/howtobuild.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Direct style:
cl /EHsc /std:c++17 COMIntegration.cpp /link Shlwapi.lib /out:"..\Release\COMIntegration.exe"

For a debug build with PDB:
cl /EHsc /std:c++17 /Z7 /DEBUG:FULL COMIntegration.cpp /link Shlwapi.lib /out:"..\Release\COMIntegration.exe"

CMake style:
cmake ../COMIntegration~ -B ./build
cmake --build ./build --config=release -- /p:OutDir=..
Binary file modified Editor/COMIntegration/Release/COMIntegration.exe
Binary file not shown.
30 changes: 7 additions & 23 deletions Editor/Discovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,31 +150,15 @@ private static IEnumerable<VisualStudioInstallation> QueryVsWhere()
if (string.IsNullOrWhiteSpace(progpath))
return Enumerable.Empty<VisualStudioInstallation>();

var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = progpath,
Arguments = "-prerelease -format json -utf8",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
}
};
var result = ProcessRunner.StartAndWaitForExit(progpath, "-prerelease -format json -utf8");

using (process)
{
var json = string.Empty;

process.OutputDataReceived += (o, e) => json += e.Data;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
if (!result.Success)
throw new Exception($"Failure while running vswhere: {result.Error}");

var result = VsWhereResult.FromJson(json);
return result.ToVisualStudioInstallations();
}
// Do not catch any JsonException here, this will be handled by the caller
return VsWhereResult
.FromJson(result.Output)
.ToVisualStudioInstallations();
}
}
}
Binary file not shown.
96 changes: 96 additions & 0 deletions Editor/ProcessRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.Unity.VisualStudio.Editor
{
internal class ProcessRunnerResult
{
public bool Success { get; set; }
public string Output { get; set; }
public string Error { get; set; }
}

internal static class ProcessRunner
{
public const int DefaultTimeoutInMilliseconds = 300000;

public static ProcessStartInfo ProcessStartInfoFor(string filename, string arguments)
{
return new ProcessStartInfo
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
FileName = filename,
Arguments = arguments
};
}

public static ProcessRunnerResult StartAndWaitForExit(string filename, string arguments, int timeoutms = DefaultTimeoutInMilliseconds, Action<string> onOutputReceived = null)
{
return StartAndWaitForExit(ProcessStartInfoFor(filename, arguments), timeoutms, onOutputReceived);
}

public static ProcessRunnerResult StartAndWaitForExit(ProcessStartInfo processStartInfo, int timeoutms = DefaultTimeoutInMilliseconds, Action<string> onOutputReceived = null)
{
var process = new Process { StartInfo = processStartInfo };

using (process)
{
var sbOutput = new StringBuilder();
var sbError = new StringBuilder();

var outputSource = new TaskCompletionSource<bool>();
var errorSource = new TaskCompletionSource<bool>();

process.OutputDataReceived += (_, e) =>
{
Append(sbOutput, e.Data, outputSource);
if (onOutputReceived != null && e.Data != null)
onOutputReceived(e.Data);
};
process.ErrorDataReceived += (_, e) => Append(sbError, e.Data, errorSource);

process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();

var run = Task.Run(() => process.WaitForExit(timeoutms));
var processTask = Task.WhenAll(run, outputSource.Task, errorSource.Task);

if (Task.WhenAny(Task.Delay(timeoutms), processTask).Result == processTask && run.Result)
return new ProcessRunnerResult {Success = true, Error = sbError.ToString(), Output = sbOutput.ToString()};

try
{
process.Kill();
}
catch
{
/* ignore */
}

return new ProcessRunnerResult {Success = false, Error = sbError.ToString(), Output = sbOutput.ToString()};
}
}

private static void Append(StringBuilder sb, string data, TaskCompletionSource<bool> taskSource)
{
if (data == null)
{
taskSource.SetResult(true);
return;
}

sb?.Append(data);
}
}
}
11 changes: 11 additions & 0 deletions Editor/ProcessRunner.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 4893e3b

Please sign in to comment.