提交 fdfe5f43 编写于 作者: A Artur Spychaj

Improve send to REPL under cursor selection

Changes behavior of send to REPL when no selection is present.
In this case selects the line under caret.
When this line is invalid syntactically expands the selection to a valid
parent syntax node.
上级 aeaf982a
......@@ -84,6 +84,7 @@
<InternalsVisibleToTest Include="Roslyn.Services.Editor.CSharp.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests2" />
<InternalsVisibleToTest Include="Roslyn.VisualStudio.CSharp.UnitTests" />
</ItemGroup>
<ItemGroup>
<Compile Include="Completion\CompletionProviders\CSharpReplCommandCompletionProvider.cs" />
......@@ -97,6 +98,7 @@
</Compile>
<Compile Include="Interactive\CSharpInteractiveEvaluator.cs" />
<Compile Include="Interactive\CSharpReplServiceProvider.cs" />
<Compile Include="Interactive\CSharpSendToInteractiveSubmissionProvider.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.CoreUtility, Version=$(VisualStudioReferenceAssemblyVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
......
// 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 Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editor.Interactive;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
namespace Microsoft.CodeAnalysis.Editor.CSharp.Interactive
{
[Export(typeof(ISendToInteractiveSubmissionProvider))]
internal sealed class CSharpSendToInteractiveSubmissionProvider
: SendToInteractiveSubmissionProvider
{
protected override bool CanParseSubmission(string code)
{
SourceText sourceCode = SourceText.From(code);
ParseOptions options = CSharpParseOptions.Default.WithKind(SourceCodeKind.Script);
SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(sourceCode, options);
if (tree == null)
{
return false;
}
return tree.HasCompilationUnitRoot && !tree.GetDiagnostics().Any();
}
protected override IEnumerable<TextSpan> GetExecutableSyntaxTreeNodeSelection(TextSpan selectionSpan, SourceText source, SyntaxNode root, SemanticModel model)
{
SyntaxNode expandedNode = GetExecutableSyntaxTreeNode(selectionSpan, source, root, model);
return expandedNode != null
? new TextSpan[] { expandedNode.Span }
: Array.Empty<TextSpan>();
}
private SyntaxNode GetExecutableSyntaxTreeNode(TextSpan selectionSpan, SourceText source, SyntaxNode root, SemanticModel model)
{
Tuple<SyntaxToken, SyntaxToken> tokens = GetSelectedTokens(selectionSpan, root);
var startToken = tokens.Item1;
var endToken = tokens.Item2;
if (startToken != endToken && startToken.Span.End > endToken.SpanStart)
{
return null;
}
// If a selection falls within a single executable statement then execute that statement.
var startNode = GetGlobalExecutableStatement(startToken);
var endNode = GetGlobalExecutableStatement(endToken);
if (startNode == null || endNode == null)
{
return null;
}
// If one of the nodes is an ancestor of another node return that node.
if (startNode.Span.Contains(endNode.Span))
{
return startNode;
}
else if (endNode.Span.Contains(startNode.Span))
{
return endNode;
}
// Selection spans multiple statements.
// In this case find common parent and find a span of statements within that parent.
var commonNode = root.FindNode(TextSpan.FromBounds(startNode.Span.Start, endNode.Span.End));
return commonNode;
}
private static SyntaxNode GetGlobalExecutableStatement(SyntaxToken token)
{
return GetGlobalExecutableStatement(token.Parent);
}
private static SyntaxNode GetGlobalExecutableStatement(SyntaxNode node)
{
SyntaxNode candidate = node.GetAncestorOrThis<StatementSyntax>();
if (candidate != null)
{
return candidate;
}
candidate = node.GetAncestorsOrThis<SyntaxNode>()
.Where(n => IsGlobalExecutableStatement(n)).FirstOrDefault();
if (candidate != null)
{
return candidate;
}
return null;
}
private static bool IsGlobalExecutableStatement(SyntaxNode node)
{
var kind = node.Kind();
return SyntaxFacts.IsTypeDeclaration(kind)
|| SyntaxFacts.IsGlobalMemberDeclaration(kind)
|| node.IsKind(SyntaxKind.UsingDirective);
}
private Tuple<SyntaxToken, SyntaxToken> GetSelectedTokens(TextSpan selectionSpan, SyntaxNode root)
{
if (selectionSpan.Length == 0)
{
var selectedToken = root.FindTokenOnLeftOfPosition(selectionSpan.End);
return Tuple.Create(
selectedToken,
selectedToken);
}
else
{
// For a selection find the first and the last token of the selection.
// Ensure that the first token comes before the last token.
return Tuple.Create(
root.FindTokenOnRightOfPosition(selectionSpan.Start),
root.FindTokenOnLeftOfPosition(selectionSpan.End));
}
}
}
}
// 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 Microsoft.VisualStudio.Text.Editor;
using System.Threading;
namespace Microsoft.CodeAnalysis.Editor.Interactive
{
internal interface ISendToInteractiveSubmissionProvider
{
string GetSelectedText(IEditorOptions editorOptions, CommandArgs args, CancellationToken cancellationToken);
}
}
// 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Editor.Commands;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.VisualStudio.InteractiveWindow;
......@@ -13,6 +11,8 @@
using Microsoft.VisualStudio.Text.Formatting;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.Utilities;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.Host;
namespace Microsoft.CodeAnalysis.Editor.Interactive
{
......@@ -23,49 +23,52 @@ internal abstract class InteractiveCommandHandler :
private readonly IContentTypeRegistryService _contentTypeRegistryService;
private readonly IEditorOptionsFactoryService _editorOptionsFactoryService;
private readonly IEditorOperationsFactoryService _editorOperationsFactoryService;
private readonly IWaitIndicator _waitIndicator;
protected InteractiveCommandHandler(
IContentTypeRegistryService contentTypeRegistryService,
IEditorOptionsFactoryService editorOptionsFactoryService,
IEditorOperationsFactoryService editorOperationsFactoryService)
IEditorOperationsFactoryService editorOperationsFactoryService,
IWaitIndicator waitIndicator)
{
_contentTypeRegistryService = contentTypeRegistryService;
_editorOptionsFactoryService = editorOptionsFactoryService;
_editorOperationsFactoryService = editorOperationsFactoryService;
_waitIndicator = waitIndicator;
}
protected IContentTypeRegistryService ContentTypeRegistryService { get { return _contentTypeRegistryService; } }
protected abstract IInteractiveWindow OpenInteractiveWindow(bool focus);
private static IEnumerable<SnapshotSpan> GetSelectedSpans(CommandArgs args)
{
return args.TextView.Selection.GetSnapshotSpansOnBuffer(args.SubjectBuffer).Where(ss => ss.Length > 0);
}
protected abstract ISendToInteractiveSubmissionProvider SendToInteractiveSubmissionProvider { get; }
private string GetSelectedText(CommandArgs args)
private string GetSelectedText(CommandArgs args, CancellationToken cancellationToken)
{
var editorOptions = _editorOptionsFactoryService.GetOptions(args.SubjectBuffer);
// If we have multiple selections, that's probably a box-selection scenario.
// So let's join the text together with newlines
return string.Join(editorOptions.GetNewLineCharacter(), GetSelectedSpans(args).Select(ss => ss.GetText()));
return SendToInteractiveSubmissionProvider.GetSelectedText(editorOptions, args, cancellationToken);
}
CommandState ICommandHandler<ExecuteInInteractiveCommandArgs>.GetCommandState(ExecuteInInteractiveCommandArgs args, Func<CommandState> nextHandler)
{
return GetSelectedSpans(args).Any() ? CommandState.Available : CommandState.Unavailable;
return CommandState.Available;
}
void ICommandHandler<ExecuteInInteractiveCommandArgs>.ExecuteCommand(ExecuteInInteractiveCommandArgs args, Action nextHandler)
{
var window = OpenInteractiveWindow(focus: false);
window.SubmitAsync(new[] { GetSelectedText(args) });
_waitIndicator.Wait(
InteractiveEditorFeaturesResources.ExecuteInInteractiveDescription,
allowCancel: true,
action: context =>
{
window.SubmitAsync(new[] { GetSelectedText(args, context.CancellationToken) });
});
}
CommandState ICommandHandler<CopyToInteractiveCommandArgs>.GetCommandState(CopyToInteractiveCommandArgs args, Func<CommandState> nextHandler)
{
return GetSelectedSpans(args).Any() ? CommandState.Available : CommandState.Unavailable;
return CommandState.Available;
}
void ICommandHandler<CopyToInteractiveCommandArgs>.ExecuteCommand(CopyToInteractiveCommandArgs args, Action nextHandler)
......@@ -97,21 +100,27 @@ private void CopyToWindow(IInteractiveWindow window, CopyToInteractiveCommandArg
using (var edit = buffer.CreateEdit())
{
var text = GetSelectedText(args);
// If the last line isn't empty in the existing submission buffer, we will prepend a
// newline
var lastLine = buffer.CurrentSnapshot.GetLineFromLineNumber(buffer.CurrentSnapshot.LineCount - 1);
if (lastLine.Extent.Length > 0)
{
var editorOptions = _editorOptionsFactoryService.GetOptions(args.SubjectBuffer);
text = editorOptions.GetNewLineCharacter() + text;
_waitIndicator.Wait(
InteractiveEditorFeaturesResources.CopyToInteractiveDescription,
allowCancel: true,
action: context =>
{
var text = GetSelectedText(args, context.CancellationToken);
// If the last line isn't empty in the existing submission buffer, we will prepend a
// newline
var lastLine = buffer.CurrentSnapshot.GetLineFromLineNumber(buffer.CurrentSnapshot.LineCount - 1);
if (lastLine.Extent.Length > 0)
{
var editorOptions = _editorOptionsFactoryService.GetOptions(args.SubjectBuffer);
text = editorOptions.GetNewLineCharacter() + text;
}
edit.Insert(buffer.CurrentSnapshot.Length, text);
edit.Apply();
});
}
edit.Insert(buffer.CurrentSnapshot.Length, text);
edit.Apply();
}
// Move the caret to the end
var editorOperations = _editorOperationsFactoryService.GetEditorOperations(window.TextView);
var endPoint = new VirtualSnapshotPoint(window.TextView.TextBuffer.CurrentSnapshot, window.TextView.TextBuffer.CurrentSnapshot.Length);
......
// 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 Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
namespace Microsoft.CodeAnalysis.Editor.Interactive
{
/// <summary>
/// Implementers of this interface are responsible for retrieving source code that
/// should be sent to the REPL given the user's selection.
///
/// If the user does not make a selection then a line should be selected.
/// If the user selects code that fails to be parsed then the selection gets expanded
/// to a syntax node.
/// </summary>
internal abstract class SendToInteractiveSubmissionProvider : ISendToInteractiveSubmissionProvider
{
/// <summary>Expands the selection span of an invalid selection to a span that should be sent to REPL.</summary>
protected abstract IEnumerable<TextSpan> GetExecutableSyntaxTreeNodeSelection(TextSpan selectedSpan, SourceText source, SyntaxNode node, SemanticModel model);
/// <summary>Returns whether the submission can be parsed in interactive.</summary>
protected abstract bool CanParseSubmission(string code);
public string GetSelectedText(IEditorOptions editorOptions, CommandArgs args, CancellationToken cancellationToken)
{
IEnumerable<SnapshotSpan> selectedSpans = args.TextView.Selection.GetSnapshotSpansOnBuffer(args.SubjectBuffer).Where(ss => ss.Length > 0);
// If there is no selection select the current line.
if (!selectedSpans.Any())
{
selectedSpans = GetSelectedLine(args);
}
// Send the selection as is if it does not contain any parsing errors.
var candidateSubmission = GetSubmissionFromSelectedSpans(editorOptions, selectedSpans);
if (CanParseSubmission(candidateSubmission))
{
return candidateSubmission;
}
// Otherwise heuristically try to expand it.
return GetSubmissionFromSelectedSpans(editorOptions, ExpandSelection(selectedSpans, args, cancellationToken));
}
/// <summary>Returns the span for the currently selected line.</summary>
private static IEnumerable<SnapshotSpan> GetSelectedLine(CommandArgs args)
{
SnapshotPoint? caret = args.TextView.GetCaretPoint(args.SubjectBuffer);
int caretPosition = args.TextView.Caret.Position.BufferPosition.Position;
ITextSnapshotLine containingLine = caret.Value.GetContainingLine();
return new SnapshotSpan[] {
new SnapshotSpan(containingLine.Start, containingLine.End)
};
}
private async Task<IEnumerable<SnapshotSpan>> GetExecutableSyntaxTreeNodeSelection(
TextSpan selectionSpan,
CommandArgs args,
ITextSnapshot snapshot,
CancellationToken cancellationToken)
{
Document doc = args.SubjectBuffer.GetRelatedDocuments().FirstOrDefault();
var semanticDocument = await SemanticDocument.CreateAsync(doc, cancellationToken).ConfigureAwait(false);
var text = semanticDocument.Text;
var root = semanticDocument.Root;
var model = semanticDocument.SemanticModel;
return GetExecutableSyntaxTreeNodeSelection(selectionSpan, text, root, model)
.Select(span => new SnapshotSpan(snapshot, span.Start, span.Length));
}
private IEnumerable<SnapshotSpan> ExpandSelection(IEnumerable<SnapshotSpan> selectedSpans, CommandArgs args, CancellationToken cancellationToken)
{
var selectedSpansStart = selectedSpans.Min(span => span.Start);
var selectedSpansEnd = selectedSpans.Max(span => span.End);
ITextSnapshot snapshot = args.TextView.TextSnapshot;
IEnumerable<SnapshotSpan> newSpans = GetExecutableSyntaxTreeNodeSelection(
TextSpan.FromBounds(selectedSpansStart, selectedSpansEnd),
args,
snapshot,
cancellationToken).WaitAndGetResult(cancellationToken);
return newSpans.Any()
? newSpans.Select(n => new SnapshotSpan(snapshot, n.Span.Start, n.Span.Length))
: selectedSpans;
}
private static string GetSubmissionFromSelectedSpans(IEditorOptions editorOptions, IEnumerable<SnapshotSpan> selectedSpans)
{
return string.Join(editorOptions.GetNewLineCharacter(), selectedSpans.Select(ss => ss.GetText()));
}
}
}
......@@ -134,6 +134,8 @@
<Compile Include="Extensibility\Interactive\InteractiveCommandHandler.cs" />
<Compile Include="Extensibility\Interactive\InteractiveEvaluator.cs" />
<Compile Include="Extensibility\Interactive\CSharpVBInteractiveCommandContentTypes.cs" />
<Compile Include="Extensibility\Interactive\ISendToInteractiveSubmissionProvider.cs" />
<Compile Include="Extensibility\Interactive\SendToInteractiveSubmissionProvider.cs" />
<Compile Include="Extensibility\Interactive\ResetInteractive.cs" />
<Compile Include="Implementation\Completion\InteractiveCommandCompletionService.cs" />
<Compile Include="Implementation\Completion\Presentation\CompletionPresenter.cs" />
......@@ -166,4 +168,4 @@
<ImportGroup Label="Targets">
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
......@@ -69,6 +69,24 @@ internal class InteractiveEditorFeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Copying selection to interactive window..
/// </summary>
internal static string CopyToInteractiveDescription {
get {
return ResourceManager.GetString("CopyToInteractiveDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Executing selection in interactive window..
/// </summary>
internal static string ExecuteInInteractiveDescription {
get {
return ResourceManager.GetString("ExecuteInInteractiveDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Print a list of referenced assemblies..
/// </summary>
......
......@@ -120,6 +120,12 @@
<data name="BuildingProject" xml:space="preserve">
<value>Building Project</value>
</data>
<data name="CopyToInteractiveDescription" xml:space="preserve">
<value>Copying selection to interactive window.</value>
</data>
<data name="ExecuteInInteractiveDescription" xml:space="preserve">
<value>Executing selection in interactive window.</value>
</data>
<data name="ReferencesCommandDescription" xml:space="preserve">
<value>Print a list of referenced assemblies.</value>
</data>
......
......@@ -86,6 +86,7 @@
<Compile Include="Interactive\CompletionProviders\VisualBasicReplCommandCompletionProvider.vb" />
<Compile Include="Interactive\VisualBasicInteractiveEvaluator.vb" />
<Compile Include="Interactive\VisualBasicReplServiceProvider.vb" />
<Compile Include="Interactive\VisualBasicSendToInteractiveSubmissionProvider.vb" />
<Compile Include="VBInteractiveEditorResources.Designer.vb">
<DependentUpon>VBInteractiveEditorResources.resx</DependentUpon>
<AutoGen>True</AutoGen>
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.ComponentModel.Composition
Imports Microsoft.CodeAnalysis.Editor.Interactive
Imports Microsoft.CodeAnalysis.Text
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Interactive
<Export(GetType(ISendToInteractiveSubmissionProvider))>
Friend NotInheritable Class VisualBasicSendToInteractiveSubmissionProvider
Inherits SendToInteractiveSubmissionProvider
Protected Overrides Function CanParseSubmission(code As String) As Boolean
' Return True to send the direct selection.
Return True
End Function
Protected Overrides Function GetExecutableSyntaxTreeNodeSelection(position As TextSpan, source As SourceText, node As SyntaxNode, model As SemanticModel) As IEnumerable(Of TextSpan)
Return Nothing
End Function
End Class
End Namespace
......@@ -7,6 +7,8 @@
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.Utilities;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.CSharp.Interactive;
namespace Microsoft.VisualStudio.LanguageServices.CSharp.Interactive
{
......@@ -15,17 +17,23 @@ internal sealed class CSharpInteractiveCommandHandler : InteractiveCommandHandle
{
private readonly CSharpVsInteractiveWindowProvider _interactiveWindowProvider;
private readonly ISendToInteractiveSubmissionProvider _sendToInteractiveSubmissionProvider;
[ImportingConstructor]
public CSharpInteractiveCommandHandler(
CSharpVsInteractiveWindowProvider interactiveWindowProvider,
IContentTypeRegistryService contentTypeRegistryService,
IEditorOptionsFactoryService editorOptionsFactoryService,
IEditorOperationsFactoryService editorOperationsFactoryService)
: base(contentTypeRegistryService, editorOptionsFactoryService, editorOperationsFactoryService)
IEditorOperationsFactoryService editorOperationsFactoryService,
IWaitIndicator waitIndicator)
: base(contentTypeRegistryService, editorOptionsFactoryService, editorOperationsFactoryService, waitIndicator)
{
_interactiveWindowProvider = interactiveWindowProvider;
_sendToInteractiveSubmissionProvider = new CSharpSendToInteractiveSubmissionProvider();
}
protected override ISendToInteractiveSubmissionProvider SendToInteractiveSubmissionProvider => _sendToInteractiveSubmissionProvider;
protected override IInteractiveWindow OpenInteractiveWindow(bool focus)
{
return _interactiveWindowProvider.Open(instanceId: 0, focus: focus).InteractiveWindow;
......
......@@ -30,6 +30,10 @@
<Project>{92412d1a-0f23-45b5-b196-58839c524917}</Project>
<Name>InteractiveEditorFeatures</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\Interactive\EditorFeatures\CSharp\CSharpInteractiveEditorFeatures.csproj">
<Project>{fe2cbea6-d121-4faa-aa8b-fc9900bf8c83}</Project>
<Name>CSharpInteractiveEditorFeatures</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\Test\Utilities\Portable.FX45\TestUtilities.FX45.csproj">
<Project>{f7712928-1175-47b3-8819-ee086753dee2}</Project>
<Name>TestUtilities.FX45</Name>
......
......@@ -9,30 +9,51 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Interactive.Commands
{
internal class InteractiveCommandHandlerTests
{
private const string ExampleCode1 =
private const string Caret = "$$";
private const string ExampleCode1 = @"var x = 1;";
private const string ExampleCode2 =
@"var x = 1;
Task.Run(() => { return 1; });";
private const string ExampleCode2Line2 =
@"Task.Run(() => { return 1; });";
private const string ExampleCode3 =
@"Console.WriteLine(
""InteractiveCommandHandlerExample"");";
private const string ExampleMultiline =
@"namespace N {
void foo() {
Console.WriteLine(
$$""LLL"");
}
}";
private const string ExpectedMultilineSelection =
@"Console.WriteLine(
""LLL"");";
[WpfFact]
[Trait(Traits.Feature, Traits.Features.Interactive)]
public void TestExecuteInInteractiveWithoutSelection()
{
AssertUnavailableExecuteInInteractive("$$");
AssertUnavailableExecuteInInteractive($"{ExampleCode1}$$");
AssertExecuteInInteractive(Caret, new string[0]);
AssertExecuteInInteractive(ExampleCode1 + Caret, ExampleCode1);
AssertExecuteInInteractive(ExampleCode1.Insert(3, Caret), ExampleCode1);
AssertExecuteInInteractive(ExampleCode2 + Caret, ExampleCode2Line2);
AssertExecuteInInteractive(ExampleMultiline, ExpectedMultilineSelection);
}
[WpfFact]
[Trait(Traits.Feature, Traits.Features.Interactive)]
public void TestExecuteInInteractiveWithEmptyBuffer()
{
AssertExecuteInInteractive(@"{|Selection:var x = 1;$$|}", "var x = 1;");
AssertExecuteInInteractive($@"{{|Selection:{ExampleCode1}$$|}}", ExampleCode1);
AssertExecuteInInteractive($@"{{|Selection:{ExampleCode2}$$|}}", ExampleCode2);
AssertExecuteInInteractive(
$@"var o = new object[] {{ 1, 2, 3 }};
Console.WriteLine(o);
{{|Selection:{ExampleCode1}$$|}}
{{|Selection:{ExampleCode2}$$|}}
Console.WriteLine(x);", ExampleCode1);
Console.WriteLine(x);", ExampleCode2);
}
[WpfFact]
......@@ -71,17 +92,23 @@ public void TestExecuteInInteractiveWithNonEmptyBuffer()
[Trait(Traits.Feature, Traits.Features.Interactive)]
public void TestCopyToInteractiveWithoutSelection()
{
AssertUnavailableCopyToInteractive("$$");
AssertUnavailableCopyToInteractive($"{ExampleCode1}$$");
AssertUnavailableCopyToInteractive($"{ExampleCode1}$$", submissionBuffer: "var x = 1;");
AssertUnavailableCopyToInteractive($"{ExampleCode1}$$", submissionBuffer: "x = 2;");
AssertCopyToInteractive(Caret, "");
AssertCopyToInteractive($"{ExampleCode2}$$", ExampleCode2Line2);
AssertCopyToInteractive(
code: ExampleCode2 + Caret,
submissionBuffer: ExampleCode1,
expectedBufferText: ExampleCode1 + "\r\n" + ExampleCode2Line2);
AssertCopyToInteractive(
code: ExampleCode2 + Caret,
submissionBuffer: "x = 2;",
expectedBufferText: "x = 2;\r\n" + ExampleCode2Line2);
}
[WpfFact]
[Trait(Traits.Feature, Traits.Features.Interactive)]
public void TestCopyToInteractive()
{
AssertCopyToInteractive($"{{|Selection:{ExampleCode1}$$|}}", ExampleCode1);
AssertCopyToInteractive($"{{|Selection:{ExampleCode2}$$|}}", ExampleCode2);
}
[WpfFact]
......@@ -91,20 +118,11 @@ public void TestCopyToInteractiveWithNonEmptyBuffer()
// Copy to interactive does not clear the existing buffer.
// Therefore `var x = 1;` will still be present in the final buffer.
AssertCopyToInteractive(
$"{{|Selection:{ExampleCode1}$$|}}",
$"var x = 1;\r\n{ExampleCode1}",
$"{{|Selection:{ExampleCode2}$$|}}",
$"var x = 1;\r\n{ExampleCode2}",
submissionBuffer: "var x = 1;");
}
private static void AssertUnavailableCopyToInteractive(string code, string submissionBuffer = null)
{
using (var workspace = InteractiveWindowCommandHandlerTestState.CreateTestState(code))
{
PrepareSubmissionBuffer(submissionBuffer, workspace);
Assert.Equal(CommandState.Unavailable, workspace.GetStateForCopyToInteractive());
}
}
private static void AssertCopyToInteractive(string code, string expectedBufferText, string submissionBuffer = null)
{
using (var workspace = InteractiveWindowCommandHandlerTestState.CreateTestState(code))
......@@ -115,15 +133,12 @@ private static void AssertCopyToInteractive(string code, string expectedBufferTe
}
}
private static void AssertUnavailableExecuteInInteractive(string code)
private static void AssertExecuteInInteractive(string code, string expectedSubmission, string submissionBuffer = null)
{
using (var workspace = InteractiveWindowCommandHandlerTestState.CreateTestState(code))
{
Assert.Equal(CommandState.Unavailable, workspace.GetStateForExecuteInInteractive());
}
AssertExecuteInInteractive(code, new string[] { expectedSubmission }, submissionBuffer);
}
private static void AssertExecuteInInteractive(string code, string expectedSubmission, string submissionBuffer = null)
private static void AssertExecuteInInteractive(string code, string[] expectedSubmissions, string submissionBuffer = null)
{
List<string> submissions = new List<string>();
EventHandler<string> appendSubmission = (_, item) => { submissions.Add(item.TrimEnd()); };
......@@ -135,7 +150,7 @@ private static void AssertExecuteInInteractive(string code, string expectedSubmi
workspace.Evaluator.OnExecute += appendSubmission;
workspace.ExecuteInInteractive();
AssertEx.Equal(new string[] { expectedSubmission }, submissions);
AssertEx.Equal(expectedSubmissions, submissions);
}
}
......
......@@ -8,6 +8,7 @@
using Microsoft.VisualStudio.Utilities;
using System.Xml.Linq;
using Microsoft.VisualStudio.Text;
using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Interactive.Commands
{
......@@ -42,7 +43,8 @@ public InteractiveWindowCommandHandlerTestState(XElement workspaceElement)
TestHost.Window,
GetExportedValue<IContentTypeRegistryService>(),
GetExportedValue<IEditorOptionsFactoryService>(),
GetExportedValue<IEditorOperationsFactoryService>());
GetExportedValue<IEditorOperationsFactoryService>(),
TestWaitIndicator.Default);
}
public static InteractiveWindowCommandHandlerTestState CreateTestState(string markup)
......
// 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 Microsoft.CodeAnalysis.Editor.CSharp.Interactive;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Interactive;
using Microsoft.VisualStudio.InteractiveWindow;
using Microsoft.VisualStudio.Text.Editor;
......@@ -12,16 +14,22 @@ internal class TestInteractiveCommandHandler : InteractiveCommandHandler
{
private IInteractiveWindow _interactiveWindow;
private ISendToInteractiveSubmissionProvider _sendToInteractiveSubmissionProvider;
public TestInteractiveCommandHandler(
IInteractiveWindow interactiveWindow,
IContentTypeRegistryService contentTypeRegistryService,
IEditorOptionsFactoryService editorOptionsFactoryService,
IEditorOperationsFactoryService editorOperationsFactoryService)
: base(contentTypeRegistryService, editorOptionsFactoryService, editorOperationsFactoryService)
IEditorOperationsFactoryService editorOperationsFactoryService,
IWaitIndicator waitIndicator)
: base(contentTypeRegistryService, editorOptionsFactoryService, editorOperationsFactoryService, waitIndicator)
{
_interactiveWindow = interactiveWindow;
_sendToInteractiveSubmissionProvider = new CSharpSendToInteractiveSubmissionProvider();
}
protected override ISendToInteractiveSubmissionProvider SendToInteractiveSubmissionProvider => _sendToInteractiveSubmissionProvider;
protected override IInteractiveWindow OpenInteractiveWindow(bool focus)
{
return _interactiveWindow;
......
......@@ -23,6 +23,10 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\..\..\Compilers\Core\Portable\CodeAnalysis.csproj">
<Project>{1ee8cad3-55f9-4d91-96b2-084641da9a6c}</Project>
<Name>CodeAnalysis</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\InteractiveWindow\Editor\InteractiveWindow.csproj">
<Project>{01e9bd68-0339-4a13-b42f-a3ca84d164f3}</Project>
<Name>InteractiveWindow</Name>
......@@ -167,4 +171,4 @@
<ImportGroup Label="Targets">
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
......@@ -2,7 +2,9 @@
Imports System.ComponentModel.Composition
Imports Microsoft.CodeAnalysis.Editor
Imports Microsoft.CodeAnalysis.Editor.Host
Imports Microsoft.CodeAnalysis.Editor.Interactive
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.Interactive
Imports Microsoft.VisualStudio.InteractiveWindow
Imports Microsoft.VisualStudio.Text.Editor
Imports Microsoft.VisualStudio.Text.Operations
......@@ -16,17 +18,27 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Interactive
Private ReadOnly _interactiveWindowProvider As VisualBasicVsInteractiveWindowProvider
Private ReadOnly _sendToInteractiveSubmissionProvider As ISendToInteractiveSubmissionProvider
<ImportingConstructor>
Public Sub New(
interactiveWindowProvider As VisualBasicVsInteractiveWindowProvider,
contentTypeRegistryService As IContentTypeRegistryService,
editorOptionsFactoryService As IEditorOptionsFactoryService,
editorOperationsFactoryService As IEditorOperationsFactoryService)
editorOperationsFactoryService As IEditorOperationsFactoryService,
waitIndicator As IWaitIndicator)
MyBase.New(contentTypeRegistryService, editorOptionsFactoryService, editorOperationsFactoryService)
MyBase.New(contentTypeRegistryService, editorOptionsFactoryService, editorOperationsFactoryService, waitIndicator)
_interactiveWindowProvider = interactiveWindowProvider
_sendToInteractiveSubmissionProvider = New VisualBasicSendToInteractiveSubmissionProvider()
End Sub
Protected Overrides ReadOnly Property SendToInteractiveSubmissionProvider As ISendToInteractiveSubmissionProvider
Get
Return _sendToInteractiveSubmissionProvider
End Get
End Property
Protected Overrides Function OpenInteractiveWindow(focus As Boolean) As IInteractiveWindow
Return _interactiveWindowProvider.Open(instanceId:=0, focus:=focus).InteractiveWindow
End Function
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册