提交 61245c99 编写于 作者: A Artur Spychaj

Extract ResetInteractive base class

ResetInteractive base class is not coupled to VS and therefore is much easier to test.
上级 10bc31bb
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Threading.Tasks;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Interactive;
using Microsoft.VisualStudio.InteractiveWindow;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Editor;
namespace Microsoft.VisualStudio.LanguageServices.Interactive
{
/// <summary>
/// ResetInteractive class that implements base functionality for reset interactive command.
/// Does not depend on VS classes to make it easier to stub out and test.
/// </summary>
internal abstract class ResetInteractive
{
private readonly Func<string, string> _createReference;
private readonly Func<string, string> _createImport;
internal event EventHandler ExecutionCompleted;
internal ResetInteractive(Func<string, string> createReference, Func<string, string> createImport)
{
_createReference = createReference;
_createImport = createImport;
}
internal Task Execute(IInteractiveWindow interactiveWindow, string title)
{
ImmutableArray<string> references, referenceSearchPaths, sourceSearchPaths, namespacesToImport;
string projectDirectory;
if (GetProjectProperties(out references, out referenceSearchPaths, out sourceSearchPaths, out namespacesToImport, out projectDirectory))
{
// Now, we're going to do a bunch of async operations. So create a wait
// indicator so the user knows something is happening, and also so they cancel.
var waitIndicator = GetWaitIndicator();
var waitContext = waitIndicator.StartWait(title, InteractiveEditorFeaturesResources.BuildingProject, allowCancel: true);
var resetInteractiveTask = ResetInteractiveAsync(
interactiveWindow,
references,
referenceSearchPaths,
sourceSearchPaths,
namespacesToImport,
projectDirectory,
waitContext);
// Once we're done resetting, dismiss the wait indicator and focus the REPL window.
return resetInteractiveTask.SafeContinueWith(
_ =>
{
waitContext.Dispose();
ExecutionCompleted?.Invoke(this, new EventArgs());
},
TaskScheduler.FromCurrentSynchronizationContext());
}
return Task.CompletedTask;
}
private async Task ResetInteractiveAsync(
IInteractiveWindow interactiveWindow,
ImmutableArray<string> referencePaths,
ImmutableArray<string> referenceSearchPaths,
ImmutableArray<string> sourceSearchPaths,
ImmutableArray<string> namespacesToImport,
string projectDirectory,
IWaitContext waitContext)
{
// First, open the repl window.
IInteractiveEvaluator evaluator = interactiveWindow.Evaluator;
// If the user hits the cancel button on the wait indicator, then we want to stop the
// build.
waitContext.CancellationToken.Register(() =>
CancelBuildProject(), useSynchronizationContext: true);
// First, start a build.
// If the build fails do not reset the REPL.
var builtSuccessfully = await BuildProject().ConfigureAwait(true);
if (!builtSuccessfully)
{
return;
}
// Then reset the REPL
waitContext.Message = InteractiveEditorFeaturesResources.ResettingInteractive;
await interactiveWindow.Operations.ResetAsync(initialize: true).ConfigureAwait(true);
// TODO: load context from an rsp file.
// Now send the reference paths we've collected to the repl.
// The SetPathsAsync method is not available through an Interface.
// Execute the method only if the cast to a concrete InteractiveEvaluator succeeds.
InteractiveEvaluator interactiveEvaluator = evaluator as InteractiveEvaluator;
if (interactiveEvaluator != null)
{
await interactiveEvaluator.SetPathsAsync(referenceSearchPaths, sourceSearchPaths, projectDirectory).ConfigureAwait(true);
}
await interactiveWindow.SubmitAsync(new[]
{
referencePaths.Select(_createReference).Join("\r\n"),
namespacesToImport.Select(_createImport).Join("\r\n")
}).ConfigureAwait(true);
}
/// <summary>
/// Gets the properties of the currently selected projects necessary for reset.
/// </summary>
protected abstract bool GetProjectProperties(
out ImmutableArray<string> references,
out ImmutableArray<string> referenceSearchPaths,
out ImmutableArray<string> sourceSearchPaths,
out ImmutableArray<string> namespacesToImport,
out string projectDirectory);
/// <summary>
/// A method that should trigger an async project build.
/// </summary>
/// <returns>Whether or not the build was successful.</returns>
protected abstract Task<bool> BuildProject();
/// <summary>
/// A method that should trigger a project cancellation.
/// </summary>
protected abstract void CancelBuildProject();
protected abstract IWaitIndicator GetWaitIndicator();
}
}
......@@ -107,7 +107,7 @@
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.VisualBasic.InteractiveEditorFeatures" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.CSharp.Repl" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.VisualBasic.Repl" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.InteractiveServices" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.InteractiveServices" />
<!-- The rest are for test purposes only. -->
<InternalsVisibleToTest Include="Roslyn.Hosting.Diagnostics" />
<InternalsVisibleToTest Include="Roslyn.InteractiveHost.UnitTests" />
......@@ -134,6 +134,7 @@
<Compile Include="Extensibility\Interactive\InteractiveCommandHandler.cs" />
<Compile Include="Extensibility\Interactive\InteractiveEvaluator.cs" />
<Compile Include="Extensibility\Interactive\CSharpVBInteractiveCommandContentTypes.cs" />
<Compile Include="Extensibility\Interactive\ResetInteractive.cs" />
<Compile Include="Implementation\Completion\InteractiveCommandCompletionService.cs" />
<Compile Include="Implementation\Completion\Presentation\CompletionPresenter.cs" />
<Compile Include="Implementation\Interactive\InertClassifierProvider.cs" />
......
......@@ -60,6 +60,15 @@ internal class InteractiveEditorFeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Building Project.
/// </summary>
internal static string BuildingProject {
get {
return ResourceManager.GetString("BuildingProject", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Print a list of referenced assemblies..
/// </summary>
......@@ -105,6 +114,15 @@ internal class InteractiveEditorFeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Resetting Interactive.
/// </summary>
internal static string ResettingInteractive {
get {
return ResourceManager.GetString("ResettingInteractive", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The CurrentWindow property may only be assigned once..
/// </summary>
......
......@@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="BuildingProject" xml:space="preserve">
<value>Building Project</value>
</data>
<data name="ReferencesCommandDescription" xml:space="preserve">
<value>Print a list of referenced assemblies.</value>
</data>
......@@ -132,6 +135,9 @@
<data name="ResettingExecutionEngine" xml:space="preserve">
<value>Resetting execution engine.</value>
</data>
<data name="ResettingInteractive" xml:space="preserve">
<value>Resetting Interactive</value>
</data>
<data name="WindowSetAgainException" xml:space="preserve">
<value>The CurrentWindow property may only be assigned once.</value>
</data>
......
......@@ -105,15 +105,6 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Building Project.
/// </summary>
internal static string BuildingProject {
get {
return ResourceManager.GetString("BuildingProject", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to C#/VB Build Table Data Source.
/// </summary>
......@@ -1002,15 +993,6 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Resetting Interactive.
/// </summary>
internal static string ResettingInteractive {
get {
return ResourceManager.GetString("ResettingInteractive", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Resolving breakpoint location....
/// </summary>
......
......@@ -135,12 +135,6 @@
<data name="FileNameMustHaveTheExtension" xml:space="preserve">
<value>File name must have the "{0}" extension.</value>
</data>
<data name="BuildingProject" xml:space="preserve">
<value>Building Project</value>
</data>
<data name="ResettingInteractive" xml:space="preserve">
<value>Resetting Interactive</value>
</data>
<data name="Debugger" xml:space="preserve">
<value>Debugger</value>
</data>
......
......@@ -85,7 +85,7 @@ private void InitializeResetInteractiveFromProjectCommand(OleMenuCommandService
var resetInteractiveFromProjectCommand = new OleMenuCommand(
(sender, args) =>
{
var resetInteractive = new ResetInteractive(
var resetInteractive = new VsResetInteractive(
(DTE)this.GetService(typeof(SDTE)),
_componentModel,
(IVsMonitorSelection)this.GetService(typeof(SVsShellMonitorSelection)),
......@@ -95,7 +95,16 @@ private void InitializeResetInteractiveFromProjectCommand(OleMenuCommandService
var vsInteractiveWindow = _interactiveWindowProvider.Open(instanceId: 0, focus: true);
resetInteractive.Execute(vsInteractiveWindow, LanguageName + " Interactive");
EventHandler focusWindow = null;
focusWindow = (s, e) =>
{
// We have to set focus to the Interactive Window *after* the wait indicator is dismissed.
vsInteractiveWindow.Show(focus: true);
resetInteractive.ExecutionCompleted -= focusWindow;
};
resetInteractive.Execute(vsInteractiveWindow.InteractiveWindow, LanguageName + " Interactive");
resetInteractive.ExecutionCompleted += focusWindow;
},
GetResetInteractiveFromProjectCommandID());
......
......@@ -7,49 +7,51 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using core::Roslyn.Utilities;
using EnvDTE;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Interactive;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.InteractiveWindow;
using Microsoft.VisualStudio.InteractiveWindow.Shell;
using VSLangProj;
using Project = EnvDTE.Project;
using System.Collections.Immutable;
namespace Microsoft.VisualStudio.LanguageServices.Interactive
{
internal sealed class ResetInteractive
internal sealed class VsResetInteractive : ResetInteractive
{
private readonly DTE _dte;
private readonly IComponentModel _componentModel;
private readonly IVsMonitorSelection _monitorSelection;
private readonly IVsSolutionBuildManager _buildManager;
private readonly Func<string, string> _createReference;
private readonly Func<string, string> _createImport;
internal ResetInteractive(DTE dte, IComponentModel componentModel, IVsMonitorSelection monitorSelection, IVsSolutionBuildManager buildManager, Func<string, string> createReference, Func<string, string> createImport)
internal VsResetInteractive(DTE dte, IComponentModel componentModel, IVsMonitorSelection monitorSelection, IVsSolutionBuildManager buildManager, Func<string, string> createReference, Func<string, string> createImport)
: base(createReference, createImport)
{
_dte = dte;
_componentModel = componentModel;
_monitorSelection = monitorSelection;
_buildManager = buildManager;
_createReference = createReference;
_createImport = createImport;
}
internal void Execute(IVsInteractiveWindow vsInteractiveWindow, string title)
/// <summary>
/// Gets the properties of the currently selected projects necessary for reset.
/// </summary>
protected override bool GetProjectProperties(
out ImmutableArray<string> references,
out ImmutableArray<string> referenceSearchPaths,
out ImmutableArray<string> sourceSearchPaths,
out ImmutableArray<string> namespacesToImport,
out string projectDirectory)
{
var hierarchyPointer = default(IntPtr);
var selectionContainerPointer = default(IntPtr);
references = ImmutableArray<string>.Empty;
referenceSearchPaths = ImmutableArray<string>.Empty;
sourceSearchPaths = ImmutableArray<string>.Empty;
namespacesToImport = ImmutableArray<string>.Empty;
projectDirectory = null;
try
{
......@@ -60,34 +62,8 @@ internal void Execute(IVsInteractiveWindow vsInteractiveWindow, string title)
if (hierarchyPointer != IntPtr.Zero)
{
List<string> references, referenceSearchPaths, sourceSearchPaths, namespacesToImport;
string projectDirectory;
GetProjectProperties(hierarchyPointer, out references, out referenceSearchPaths, out sourceSearchPaths, out namespacesToImport, out projectDirectory);
// Now, we're going to do a bunch of async operations. So create a wait
// indicator so the user knows something is happening, and also so they cancel.
var waitIndicator = _componentModel.GetService<IWaitIndicator>();
var waitContext = waitIndicator.StartWait(title, ServicesVSResources.BuildingProject, allowCancel: true);
var resetInteractiveTask = ResetInteractiveAsync(
vsInteractiveWindow,
references.ToImmutableArray(),
referenceSearchPaths.ToImmutableArray(),
sourceSearchPaths.ToImmutableArray(),
namespacesToImport.ToImmutableArray(),
projectDirectory,
waitContext);
// Once we're done resetting, dismiss the wait indicator and focus the REPL window.
resetInteractiveTask.SafeContinueWith(
_ =>
{
waitContext.Dispose();
// We have to set focus to the Interactive Window *after* the wait indicator is dismissed.
vsInteractiveWindow.Show(focus: true);
},
TaskScheduler.FromCurrentSynchronizationContext());
return true;
}
}
finally
......@@ -95,48 +71,16 @@ internal void Execute(IVsInteractiveWindow vsInteractiveWindow, string title)
SafeRelease(hierarchyPointer);
SafeRelease(selectionContainerPointer);
}
}
private async Task ResetInteractiveAsync(
IVsInteractiveWindow vsInteractiveWindow,
ImmutableArray<string> referencePaths,
ImmutableArray<string> referenceSearchPaths,
ImmutableArray<string> sourceSearchPaths,
ImmutableArray<string> namespacesToImport,
string projectDirectory,
IWaitContext waitContext)
{
// First, open the repl window.
var engine = (InteractiveEvaluator)vsInteractiveWindow.InteractiveWindow.Evaluator;
// If the user hits the cancel button on the wait indicator, then we want to stop the
// build.
waitContext.CancellationToken.Register(() =>
_dte.ExecuteCommand("Build.Cancel"), useSynchronizationContext: true);
// First, start a build
await BuildProject().ConfigureAwait(true);
// Then reset the REPL
waitContext.Message = ServicesVSResources.ResettingInteractive;
await vsInteractiveWindow.InteractiveWindow.Operations.ResetAsync(initialize: false).ConfigureAwait(true);
// Now send the reference paths we've collected to the repl.
await engine.SetPathsAsync(referenceSearchPaths, sourceSearchPaths, projectDirectory).ConfigureAwait(true);
await vsInteractiveWindow.InteractiveWindow.SubmitAsync(new[]
{
referencePaths.Select(_createReference).Join("\r\n"),
namespacesToImport.Select(_createImport).Join("\r\n")
}).ConfigureAwait(true);
return false;
}
private static void GetProjectProperties(
IntPtr hierarchyPointer,
out List<string> references,
out List<string> referenceSearchPaths,
out List<string> sourceSearchPaths,
out List<string> namespacesToImport,
out ImmutableArray<string> references,
out ImmutableArray<string> referenceSearchPaths,
out ImmutableArray<string> sourceSearchPaths,
out ImmutableArray<string> namespacesToImport,
out string projectDirectory)
{
var hierarchy = (IVsHierarchy)Marshal.GetObjectForIUnknown(hierarchyPointer);
......@@ -148,10 +92,10 @@ internal void Execute(IVsInteractiveWindow vsInteractiveWindow, string title)
var project = (Project)extensibilityObject;
var vsProject = (VSProject)project.Object;
references = new List<string>();
referenceSearchPaths = new List<string>();
sourceSearchPaths = new List<string>();
namespacesToImport = new List<string>();
var referencesBuilder = ImmutableArray.CreateBuilder<string>();
var referenceSearchPathsBuilder = ImmutableArray.CreateBuilder<string>();
var sourceSearchPathsBuilder = ImmutableArray.CreateBuilder<string>();
var namespacesToImportBuilder = ImmutableArray.CreateBuilder<string>();
var projectDir = (string)project.Properties.Item("FullPath").Value;
var outputFileName = (string)project.Properties.Item("OutputFileName").Value;
......@@ -167,27 +111,32 @@ internal void Execute(IVsInteractiveWindow vsInteractiveWindow, string title)
projectDirectory = projectDir;
referenceSearchPaths.Add(outputDir);
referenceSearchPaths.Add(RuntimeEnvironment.GetRuntimeDirectory());
referenceSearchPathsBuilder.Add(outputDir);
referenceSearchPathsBuilder.Add(RuntimeEnvironment.GetRuntimeDirectory());
foreach (Reference reference in vsProject.References)
{
var str = GetReferenceString(reference);
if (str != null)
{
references.Add(str);
referencesBuilder.Add(str);
}
}
references.Add(outputFileName);
referencesBuilder.Add(outputFileName);
// TODO (tomat): project Scripts dir
sourceSearchPaths.Add(Directory.Exists(scriptsDir) ? scriptsDir : projectDir);
sourceSearchPathsBuilder.Add(Directory.Exists(scriptsDir) ? scriptsDir : projectDir);
if (!string.IsNullOrEmpty(defaultNamespace))
{
namespacesToImport.Add(defaultNamespace);
namespacesToImportBuilder.Add(defaultNamespace);
}
references = referencesBuilder.ToImmutableArray();
referenceSearchPaths = referenceSearchPathsBuilder.ToImmutableArray();
sourceSearchPaths = sourceSearchPathsBuilder.ToImmutableArray();
namespacesToImport = namespacesToImportBuilder.ToImmutableArray();
}
private static string GetReferenceString(Reference reference)
......@@ -262,7 +211,7 @@ private static string GetReferenceString(Reference reference)
if (foundNonEquivalent)
{
// We found some equivalent assemblies but also some non-equivalent.
// We found some equivalent assemblies but also some non-equivalent.
// So simple name doesn't identify the reference uniquely.
return fullName;
}
......@@ -281,7 +230,7 @@ private static void SafeRelease(IntPtr pointer)
}
}
private Task<bool> BuildProject()
protected override Task<bool> BuildProject()
{
var taskSource = new TaskCompletionSource<bool>();
......@@ -293,5 +242,15 @@ private Task<bool> BuildProject()
return taskSource.Task;
}
protected override void CancelBuildProject()
{
_dte.ExecuteCommand("Build.Cancel");
}
protected override IWaitIndicator GetWaitIndicator()
{
return _componentModel.GetService<IWaitIndicator>();
}
}
}
......@@ -200,7 +200,7 @@
<Compile Include="Interactive\VsInteractiveWindowPackage.cs" />
<Compile Include="Interactive\VsInteractiveWindowProvider.cs" />
<Compile Include="Interactive\CommonVsUtils.cs" />
<Compile Include="Interactive\ResetInteractive.cs" />
<Compile Include="Interactive\VsResetInteractive.cs" />
<Compile Include="Interactive\ScriptingOleCommandTarget.cs" />
<Compile Include="Interactive\VsUpdateSolutionEvents.cs" />
</ItemGroup>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册