提交 9d70f8d5 编写于 作者: G Gen Lu

Merge pull request #6995 from genlu/fixTypeChar

Fix TypeChar handler in Interactive Window
......@@ -475,9 +475,26 @@ private void AppendLineNoPromptInjection(ITextBuffer buffer)
}
/// <summary>Implements <see cref="IInteractiveWindowOperations2.TypeChar"/>.</summary>
internal void TypeChar(char typedChar)
public void TypeChar(char typedChar)
{
InsertText(typedChar.ToString());
using (var transaction = UndoHistory?.CreateTransaction(InteractiveWindowResources.TypeChar))
{
if (transaction != null)
{
var mergeDirections = TextTransactionMergeDirections.Forward | TextTransactionMergeDirections.Backward;
// replacing selected text should be an atomic undo operation).
if ((!TextView.Selection.IsEmpty && !IsEmptyBoxSelection()))
{
mergeDirections = TextTransactionMergeDirections.Forward;
}
transaction.MergePolicy = new TextTransactionMergePolicy(mergeDirections);
}
if (InsertText(typedChar.ToString()))
{
transaction?.Complete();
}
}
}
/// <summary>Implements <see cref="IInteractiveWindow.InsertCode"/>.</summary>
......@@ -498,9 +515,7 @@ public void InsertCode(string text)
}
}
private void InsertText(string text)
{
using (var transaction = UndoHistory?.CreateTransaction(InteractiveWindowResources.TypeChar))
private bool InsertText(string text)
{
var selection = TextView.Selection;
var caretPosition = TextView.Caret.Position.BufferPosition;
......@@ -508,7 +523,7 @@ private void InsertText(string text)
{
if (!IsSelectionInsideCurrentSubmission())
{
return;
return false;
}
DeleteSelection();
......@@ -529,13 +544,10 @@ private void InsertText(string text)
}
else if (MapToEditableBuffer(caretPosition) == null)
{
return;
return false;
}
EditorOperations.InsertText(text);
transaction?.Complete();
}
return EditorOperations.InsertText(text);
}
/// <summary>Implements the core of <see cref="IInteractiveWindow.SubmitAsync"/>.</summary>
......
......@@ -20,7 +20,7 @@
</PropertyGroup>
<ItemGroup>
<InternalsVisibleToTest Include="Roslyn.InteractiveWindow.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests2"/>
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests2" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.InteractiveServices" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.VsInteractiveWindow" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.EditorFeatures" />
......@@ -124,6 +124,7 @@
<Compile Include="SmartIndent\InteractiveSmartIndenterProvider.cs" />
<Compile Include="SmartUpDownOption.cs" />
<Compile Include="SubmissionBufferAddedEventArgs.cs" />
<Compile Include="TextTransactionMergePolicy.cs" />
<Compile Include="Utils\Contract.cs" />
<Compile Include="Utils\EditorExtensions.cs" />
<Compile Include="..\..\Compilers\Core\Portable\InternalUtilities\ExceptionUtilities.cs">
......@@ -137,6 +138,7 @@
<EmbeddedResource Include="InteractiveWindowResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>InteractiveWindowResources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
......
......@@ -10,7 +10,6 @@
namespace Microsoft.VisualStudio.InteractiveWindow {
using System;
using System.Reflection;
/// <summary>
......@@ -40,7 +39,7 @@ internal class InteractiveWindowResources {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.InteractiveWindow.InteractiveWindowResources", typeof(InteractiveWindowResources).GetTypeInfo().Assembly);
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.InteractiveWindow.InteractiveWindowResources", typeof(InteractiveWindowResources).Assembly);
resourceMan = temp;
}
return resourceMan;
......
// 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 Microsoft.VisualStudio.Text.Operations;
namespace Microsoft.VisualStudio.InteractiveWindow
{
// The code here is copied from `Microsoft.VisualStudio.Text.Operations.Implementation`
// with minor modification to provide ability for merging undo transactions in InteractiveWindow .
[Flags]
internal enum TextTransactionMergeDirections
{
Forward = 0x0001,
Backward = 0x0002
}
/// <summary>
/// This is the merge policy used for determining whether text's undo transactions can be merged.
/// </summary>
internal class TextTransactionMergePolicy : IMergeTextUndoTransactionPolicy
{
TextTransactionMergeDirections _allowableMergeDirections;
public TextTransactionMergePolicy() : this(TextTransactionMergeDirections.Forward | TextTransactionMergeDirections.Backward)
{
}
public TextTransactionMergePolicy(TextTransactionMergeDirections allowableMergeDirections)
{
_allowableMergeDirections = allowableMergeDirections;
}
public bool CanMerge(ITextUndoTransaction newTransaction, ITextUndoTransaction oldTransaction)
{
// Validate
if (newTransaction == null)
{
throw new ArgumentNullException("newTransaction");
}
if (oldTransaction == null)
{
throw new ArgumentNullException("oldTransaction");
}
TextTransactionMergePolicy oldPolicy = oldTransaction.MergePolicy as TextTransactionMergePolicy;
TextTransactionMergePolicy newPolicy = newTransaction.MergePolicy as TextTransactionMergePolicy;
if (oldPolicy == null || newPolicy == null)
{
throw new InvalidOperationException("The MergePolicy for both transactions should be a TextTransactionMergePolicy.");
}
// Make sure the merge policy directions permit merging these two transactions.
if ((oldPolicy._allowableMergeDirections & TextTransactionMergeDirections.Forward) == 0 ||
(newPolicy._allowableMergeDirections & TextTransactionMergeDirections.Backward) == 0)
{
return false;
}
// Only merge text transactions that have the same description
if (newTransaction.Description != oldTransaction.Description)
{
return false;
}
return true;
}
public void PerformTransactionMerge(ITextUndoTransaction existingTransaction, ITextUndoTransaction newTransaction)
{
if (existingTransaction == null)
throw new ArgumentNullException("existingTransaction");
if (newTransaction == null)
throw new ArgumentNullException("newTransaction");
// Remove trailing AfterTextBufferChangeUndoPrimitive from previous transaction and skip copying
// initial BeforeTextBufferChangeUndoPrimitive from newTransaction, as they are unnecessary.
int copyStartIndex = 0;
// Copy items from newTransaction into existingTransaction.
for (int i = copyStartIndex; i < newTransaction.UndoPrimitives.Count; i++)
{
existingTransaction.UndoPrimitives.Add(newTransaction.UndoPrimitives[i]);
}
}
public bool TestCompatiblePolicy(IMergeTextUndoTransactionPolicy other)
{
if (other == null)
{
throw new ArgumentNullException("other");
}
// Only merge transaction if they are both a text transaction
return this.GetType() == other.GetType();
}
}
}
......@@ -1048,6 +1048,46 @@ private async Task SubmitAsync(params string[] submissions)
AssertEx.Equal(submissions, actualSubmissions);
}
[WorkItem(6397, "https://github.com/dotnet/roslyn/issues/6397")]
[WpfFact]
public void TypeCharWithUndoRedo()
{
Window.Operations.TypeChar('a');
Window.Operations.TypeChar('b');
Window.Operations.TypeChar('c');
Assert.Equal("> abc", GetTextFromCurrentSnapshot());
// undo/redo for consecutive TypeChar's shold be a single action
((InteractiveWindow)Window).Undo_TestOnly(1);
Assert.Equal("> ", GetTextFromCurrentSnapshot());
((InteractiveWindow)Window).Redo_TestOnly(1);
Assert.Equal("> abc", GetTextFromCurrentSnapshot());
// make a stream selection as follows:
// > |aaa|
Window.Operations.SelectAll();
Window.Operations.TypeChar('1');
Window.Operations.TypeChar('2');
Window.Operations.TypeChar('3');
Assert.Equal("> 123", GetTextFromCurrentSnapshot());
((InteractiveWindow)Window).Undo_TestOnly(1);
Assert.Equal("> abc", GetTextFromCurrentSnapshot());
((InteractiveWindow)Window).Undo_TestOnly(1);
Assert.Equal("> ", GetTextFromCurrentSnapshot());
// type in active prompt
MoveCaretToPreviousPosition(2);
Window.Operations.TypeChar('x');
Window.Operations.TypeChar('y');
Window.Operations.TypeChar('z');
Assert.Equal("> xyz", GetTextFromCurrentSnapshot());
}
private string GetTextFromCurrentSnapshot()
{
return Window.TextView.TextBuffer.CurrentSnapshot.GetText();
......@@ -1110,5 +1150,10 @@ internal static void CutLine(this IInteractiveWindowOperations operations)
{
((IInteractiveWindowOperations2)operations).CutLine();
}
internal static void TypeChar(this IInteractiveWindowOperations operations, char typedChar)
{
((IInteractiveWindowOperations2)operations).TypeChar(typedChar);
}
}
}
......@@ -215,6 +215,22 @@ private int PreEditorCommandFilterExec(ref Guid pguidCmdGroup, uint nCmdID, uint
{
switch ((VSConstants.VSStd2KCmdID)nCmdID)
{
case VSConstants.VSStd2KCmdID.TYPECHAR:
{
var operations = _window.Operations as IInteractiveWindowOperations2;
if (operations != null)
{
char typedChar = (char)(ushort)System.Runtime.InteropServices.Marshal.GetObjectForNativeVariant(pvaIn);
operations.TypeChar(typedChar);
return VSConstants.S_OK;
}
else
{
_window.Operations.Delete();
}
break;
}
case VSConstants.VSStd2KCmdID.RETURN:
if (_window.Operations.Return())
{
......@@ -416,19 +432,6 @@ private int PreLanguageCommandFilterExec(ref Guid pguidCmdGroup, uint nCmdID, ui
{
switch ((VSConstants.VSStd2KCmdID)nCmdID)
{
case VSConstants.VSStd2KCmdID.TYPECHAR:
{
var operations = _window.Operations as IInteractiveWindowOperations2;
if (operations != null)
{
char typedChar = (char)(ushort)System.Runtime.InteropServices.Marshal.GetObjectForNativeVariant(pvaIn);
operations.TypeChar(typedChar);
return VSConstants.S_OK;
}
_window.Operations.Delete();
break;
}
case VSConstants.VSStd2KCmdID.RETURN:
if (_window.Operations.TrySubmitStandardInput())
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册