提交 f63c57ea 编写于 作者: D Dustin Campbell

Further clean up and make SendKeys less flaky

This change makes SendKeys less flaky and removes the delay between characters, which was hiding bugs. Now, we just wait for application idle before blasting keys to the VS process. Additionally, I've started more cleanly separate code that runs in process vs. out of process.
上级 14ce33b7
......@@ -13,7 +13,7 @@ public abstract class EditorTestFixture : IDisposable
private readonly Workspace _workspace;
private readonly Solution _solution;
private readonly Project _project;
protected readonly EditorWindow EditorWindow;
private readonly EditorWindow _editorWindow;
protected EditorTestFixture(VisualStudioInstanceFactory instanceFactory, string solutionName)
{
......@@ -25,7 +25,7 @@ protected EditorTestFixture(VisualStudioInstanceFactory instanceFactory, string
_workspace = _visualStudio.Instance.Workspace;
_workspace.UseSuggestionMode = false;
EditorWindow = _visualStudio.Instance.EditorWindow;
_editorWindow = _visualStudio.Instance.EditorWindow;
}
public void Dispose()
......@@ -49,11 +49,16 @@ protected void SetUpEditor(string markupCode)
int caretPosition;
MarkupTestFile.GetPosition(markupCode, out code, out caretPosition);
EditorWindow.SetText(code);
EditorWindow.MoveCaret(caretPosition);
_editorWindow.SetText(code);
_editorWindow.MoveCaret(caretPosition);
}
protected void VerifyCurrentLine(string expectedText, bool trimWhitespace = false)
protected void SendKeys(params object[] textOrVirtualKeys)
{
_editorWindow.SendKeys(textOrVirtualKeys);
}
protected void VerifyCurrentLineText(string expectedText, bool trimWhitespace = false)
{
var caretStartIndex = expectedText.IndexOf("$$");
......@@ -69,7 +74,7 @@ protected void VerifyCurrentLine(string expectedText, bool trimWhitespace = fals
? expectedText.Substring(caretEndIndex)
: string.Empty;
var lineText = EditorWindow.GetCurrentLineText();
var lineText = _editorWindow.GetCurrentLineText();
if (trimWhitespace)
{
......@@ -90,7 +95,7 @@ protected void VerifyCurrentLine(string expectedText, bool trimWhitespace = fals
}
else
{
var lineText = EditorWindow.GetCurrentLineText();
var lineText = _editorWindow.GetCurrentLineText();
Assert.Equal(expectedText, lineText);
}
}
......@@ -113,17 +118,17 @@ protected void VerifyTextContains(string expectedText)
var expectedTextWithoutCaret = expectedTextBeforeCaret + expectedTextAfterCaret;
var editorText = EditorWindow.GetText();
var editorText = _editorWindow.GetText();
Assert.Contains(expectedTextWithoutCaret, editorText);
var index = editorText.IndexOf(expectedTextWithoutCaret);
var caretPosition = EditorWindow.GetCaretPosition();
var caretPosition = _editorWindow.GetCaretPosition();
Assert.Equal(caretStartIndex + index, caretPosition);
}
else
{
var editorText = EditorWindow.GetText();
var editorText = _editorWindow.GetText();
Assert.Contains(expectedText, editorText);
}
}
......
using System;
using System.Windows;
using System.Windows.Threading;
using EnvDTE;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
namespace Roslyn.VisualStudio.Test.Utilities.InProcess
{
/// <summary>
/// Base class for all components that run inside of the Visual Studio process.
/// </summary>
public abstract class BaseInProcessComponent : MarshalByRefObject
{
protected BaseInProcessComponent() { }
private static Dispatcher CurrentApplicationDispatcher => Application.Current.Dispatcher;
protected static void InvokeOnUIThread(Action action)
{
CurrentApplicationDispatcher.Invoke(action);
}
protected static T InvokeOnUIThread<T>(Func<T> action)
{
return CurrentApplicationDispatcher.Invoke(action);
}
protected static TInterface GetGlobalService<TService, TInterface>()
{
return InvokeOnUIThread(() =>
{
return (TInterface)ServiceProvider.GlobalProvider.GetService(typeof(TService));
});
}
protected static DTE GetDTE()
{
return GetGlobalService<SDTE, DTE>();
}
/// <summary>
/// Waiting for the application to 'idle' means that it is done pumping messages (including WM_PAINT).
/// </summary>
protected static void WaitForApplicationIdle()
{
CurrentApplicationDispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
}
}
}
// 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.Runtime.InteropServices;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
namespace Roslyn.VisualStudio.Test.Utilities.Remoting
namespace Roslyn.VisualStudio.Test.Utilities.InProcess
{
internal class EditorWindowWrapper : MarshalByRefObject
internal class InProcessEditor : BaseInProcessComponent
{
private EditorWindowWrapper() { }
private static readonly Guid IWpfTextViewId = new Guid("8C40265E-9FDB-4F54-A0FD-EBB72B7D0476");
public static EditorWindowWrapper Create()
private InProcessEditor() { }
public static InProcessEditor Create()
{
return new InProcessEditor();
}
private static ITextView GetActiveTextView()
{
return GetActiveTextViewHost().TextView;
}
private static IVsTextView GetActiveVsTextView()
{
IVsTextView vsTextView = null;
var vsTextManager = GetGlobalService<SVsTextManager, IVsTextManager>();
var hresult = vsTextManager.GetActiveView(fMustHaveFocus: 1, pBuffer: null, ppView: out vsTextView);
Marshal.ThrowExceptionForHR(hresult);
return vsTextView;
}
private static IWpfTextViewHost GetActiveTextViewHost()
{
// The active text view might not have finished composing yet, waiting for the application to 'idle'
// means that it is done pumping messages (including WM_PAINT) and the window should return the correct text view
WaitForApplicationIdle();
var activeVsTextView = (IVsUserData)GetActiveVsTextView();
object wpfTextViewHost;
var hresult = activeVsTextView.GetData(IWpfTextViewId, out wpfTextViewHost);
Marshal.ThrowExceptionForHR(hresult);
return (IWpfTextViewHost)wpfTextViewHost;
}
private static void ExecuteOnActiveView(Action<ITextView> action)
{
InvokeOnUIThread(() =>
{
var view = GetActiveTextView();
action(view);
});
}
private static T ExecuteOnActiveView<T>(Func<ITextView, T> action)
{
return InvokeOnUIThread(() =>
{
var view = GetActiveTextView();
return action(view);
});
}
public void Activate()
{
return new EditorWindowWrapper();
GetDTE().ActiveDocument.Activate();
}
public string GetText()
{
return RemotingHelper.ExecuteOnActiveView(view =>
return ExecuteOnActiveView(view =>
{
return view.TextSnapshot.GetText();
});
......@@ -25,7 +85,7 @@ public string GetText()
public void SetText(string text)
{
RemotingHelper.ExecuteOnActiveView(view =>
ExecuteOnActiveView(view =>
{
var textSnapshot = view.TextSnapshot;
var replacementSpan = new SnapshotSpan(textSnapshot, 0, textSnapshot.Length);
......@@ -35,7 +95,7 @@ public void SetText(string text)
public string GetCurrentLineText()
{
return RemotingHelper.ExecuteOnActiveView(view =>
return ExecuteOnActiveView(view =>
{
var subjectBuffer = view.GetBufferContainingCaret();
var bufferPosition = view.Caret.Position.BufferPosition;
......@@ -47,7 +107,7 @@ public string GetCurrentLineText()
public int GetCaretPosition()
{
return RemotingHelper.ExecuteOnActiveView(view =>
return ExecuteOnActiveView(view =>
{
var subjectBuffer = view.GetBufferContainingCaret();
var bufferPosition = view.Caret.Position.BufferPosition;
......@@ -58,7 +118,7 @@ public int GetCaretPosition()
public string GetLineTextBeforeCaret()
{
return RemotingHelper.ExecuteOnActiveView(view =>
return ExecuteOnActiveView(view =>
{
var subjectBuffer = view.GetBufferContainingCaret();
var bufferPosition = view.Caret.Position.BufferPosition;
......@@ -71,7 +131,7 @@ public string GetLineTextBeforeCaret()
public string GetLineTextAfterCaret()
{
return RemotingHelper.ExecuteOnActiveView(view =>
return ExecuteOnActiveView(view =>
{
var subjectBuffer = view.GetBufferContainingCaret();
var bufferPosition = view.Caret.Position.BufferPosition;
......@@ -84,7 +144,7 @@ public string GetLineTextAfterCaret()
public void MoveCaret(int position)
{
RemotingHelper.ExecuteOnActiveView(view =>
ExecuteOnActiveView(view =>
{
var subjectBuffer = view.GetBufferContainingCaret();
var point = new SnapshotPoint(subjectBuffer.CurrentSnapshot, position);
......
// 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.Threading.Tasks;
using Roslyn.VisualStudio.Test.Utilities.Interop;
using Roslyn.VisualStudio.Test.Utilities.Remoting;
namespace Roslyn.VisualStudio.Test.Utilities
namespace Roslyn.VisualStudio.Test.Utilities.Input
{
public partial class EditorWindow
public class SendKeys
{
public enum VirtualKey : byte
{
Enter = 0x0D,
Tab = 0x09,
Escape = 0x1B,
PageUp = 0x21,
PageDown = 0x22,
End = 0x23,
Home = 0x24,
Left = 0x25,
Up = 0x26,
Right = 0x27,
Down = 0x28,
Shift = 0x10,
Control = 0x11,
Alt = 0x12,
CapsLock = 0x14,
NumLock = 0x90,
ScrollLock = 0x91,
PrintScreen = 0x2C,
Break = 0x03,
Help = 0x2F,
Backspace = 0x08,
Clear = 0x0C,
Insert = 0x2D,
Delete = 0x2E,
F1 = 0x70,
F2 = 0x71,
F3 = 0x72,
F4 = 0x73,
F5 = 0x74,
F6 = 0x75,
F7 = 0x76,
F8 = 0x77,
F9 = 0x78,
F10 = 0x79,
F11 = 0x7A,
F12 = 0x7B,
F13 = 0x7C,
F14 = 0x7D,
F15 = 0x7E,
F16 = 0x7F
}
[Flags]
public enum ShiftState : byte
{
Shift = 1,
Ctrl = 1 << 1,
Alt = 1 << 2,
Hankaku = 1 << 3,
Reserved1 = 1 << 4,
Reserved2 = 1 << 5
}
private readonly VisualStudioInstance _visualStudioInstance;
public async Task TypeTextAsync(string text, int wordsPerMinute = 120)
public SendKeys(VisualStudioInstance visualStudioInstance)
{
Activate();
var normalizedText = text
.Replace("\r\n", "\r")
.Replace("\n", "\r");
var charactersPerSecond = (wordsPerMinute * 4.5) / 60;
var delayBetweenCharacters = (int)((1 / charactersPerSecond) * 1000);
foreach (var character in normalizedText)
{
SendKey(character);
await Task.Delay(delayBetweenCharacters);
}
_visualStudioInstance = visualStudioInstance;
}
public void SendKey(VirtualKey virtualKey, ShiftState shiftState = 0)
public void Send(VirtualKey virtualKey, ShiftState shiftState = 0)
{
var foregroundWindow = IntPtr.Zero;
var inputBlocked = false;
......@@ -148,7 +75,7 @@ public void SendKey(VirtualKey virtualKey, ShiftState shiftState = 0)
}
}
private void SendKey(char character)
public void Send(char character)
{
var result = User32.VkKeyScan(character);
if (result == -1)
......@@ -160,14 +87,14 @@ private void SendKey(char character)
var virtualKeyCode = (VirtualKey)(result & 0xff);
var shiftState = (ShiftState)(((ushort)result >> 8) & 0xff);
SendKey(virtualKeyCode, shiftState);
Send(virtualKeyCode, shiftState);
}
private static bool IsExtendedKey(VirtualKey virtualKey)
{
return ((virtualKey >= VirtualKey.PageUp) && (virtualKey <= VirtualKey.Down))
|| (virtualKey == VirtualKey.Insert)
|| (virtualKey == VirtualKey.Delete);
return (virtualKey >= VirtualKey.PageUp && virtualKey <= VirtualKey.Down)
|| virtualKey == VirtualKey.Insert
|| virtualKey == VirtualKey.Delete;
}
private void SendKeyPressAndRelease(VirtualKey virtualKey)
......
// 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.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Roslyn.VisualStudio.Test.Utilities.Input
{
[Flags]
public enum ShiftState : byte
{
Shift = 1,
Ctrl = 1 << 1,
Alt = 1 << 2,
Hankaku = 1 << 3,
Reserved1 = 1 << 4,
Reserved2 = 1 << 5
}
}
// 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.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Roslyn.VisualStudio.Test.Utilities.Input
{
public enum VirtualKey : byte
{
Enter = 0x0D,
Tab = 0x09,
Escape = 0x1B,
PageUp = 0x21,
PageDown = 0x22,
End = 0x23,
Home = 0x24,
Left = 0x25,
Up = 0x26,
Right = 0x27,
Down = 0x28,
Shift = 0x10,
Control = 0x11,
Alt = 0x12,
CapsLock = 0x14,
NumLock = 0x90,
ScrollLock = 0x91,
PrintScreen = 0x2C,
Break = 0x03,
Help = 0x2F,
Backspace = 0x08,
Clear = 0x0C,
Insert = 0x2D,
Delete = 0x2E,
F1 = 0x70,
F2 = 0x71,
F3 = 0x72,
F4 = 0x73,
F5 = 0x74,
F6 = 0x75,
F7 = 0x76,
F8 = 0x77,
F9 = 0x78,
F10 = 0x79,
F11 = 0x7A,
F12 = 0x7B,
F13 = 0x7C,
F14 = 0x7D,
F15 = 0x7E,
F16 = 0x7F
}
}
......@@ -15,6 +15,7 @@
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.Win32;
using Roslyn.Utilities;
using Roslyn.VisualStudio.Test.Utilities.Input;
using Roslyn.VisualStudio.Test.Utilities.Interop;
using Process = System.Diagnostics.Process;
......@@ -432,10 +433,10 @@ private static string GetPrintableCharText(char ch)
private static void AppendVirtualKey(byte virtualKey, StringBuilder builder)
{
if (Enum.IsDefined(typeof(EditorWindow.VirtualKey), virtualKey))
if (Enum.IsDefined(typeof(VirtualKey), virtualKey))
{
builder.Append('(');
builder.Append(Enum.GetName(typeof(EditorWindow.VirtualKey), virtualKey));
builder.Append(Enum.GetName(typeof(VirtualKey), virtualKey));
builder.Append(") ");
}
}
......
......@@ -27,7 +27,6 @@ internal class IntegrationService : MarshalByRefObject
public IntegrationService()
{
// Make the channel name well-known by using a static base and appending the process ID of the host
this.PortName = GetPortName(Process.GetCurrentProcess().Id);
this.BaseUri = "ipc://" + this.PortName;
}
......
......@@ -5,8 +5,12 @@
namespace Roslyn.VisualStudio.Test.Utilities.Remoting
{
/// <summary>Provides a means of accessing the <see cref="IInteractiveWindow"/> service in the Visual Studio host.</summary>
/// <remarks>This object exists in the Visual Studio host and is marhsalled across the process boundary.</remarks>
/// <summary>
/// Provides a means of accessing the <see cref="IInteractiveWindow"/> service in the Visual Studio host.
/// </summary>
/// <remarks>
/// This object exists in the Visual Studio host and is marhsalled across the process boundary.
/// </remarks>
internal class InteractiveWindowWrapper : MarshalByRefObject
{
private readonly IInteractiveWindow _interactiveWindow;
......@@ -18,13 +22,10 @@ private InteractiveWindowWrapper(IInteractiveWindow interactiveWindow)
_interactiveWindow = interactiveWindow;
}
public string CurrentSnapshotText
=> _interactiveWindow.TextView.TextBuffer.CurrentSnapshot.GetText();
public string CurrentSnapshotText => _interactiveWindow.TextView.TextBuffer.CurrentSnapshot.GetText();
public bool IsInitializing
=> _interactiveWindow.IsInitializing;
public bool IsInitializing => _interactiveWindow.IsInitializing;
public void Submit(string text)
=> _interactiveWindow.SubmitAsync(new[] { text }).GetAwaiter().GetResult();
public void Submit(string text) => _interactiveWindow.SubmitAsync(new[] { text }).GetAwaiter().GetResult();
}
}
// 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.ObjectModel;
using System.ComponentModel.Composition.Hosting;
using System.Diagnostics;
using System.Linq;
......@@ -10,17 +9,14 @@
using System.Windows.Threading;
using EnvDTE;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.InteractiveWindow;
using Microsoft.VisualStudio.InteractiveWindow.Shell;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.LanguageServices.CSharp.Interactive;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Roslyn.Hosting.Diagnostics.Waiters;
......@@ -34,69 +30,16 @@ namespace Roslyn.VisualStudio.Test.Utilities.Remoting
/// </remarks>
internal static class RemotingHelper
{
private static readonly Guid IWpfTextViewId = new Guid("8C40265E-9FDB-4F54-A0FD-EBB72B7D0476");
private static readonly Guid RoslynPackageId = new Guid("6cf2e545-6109-4730-8883-cf43d7aec3e1");
private static readonly string[] SupportedLanguages = new string[] { LanguageNames.CSharp, LanguageNames.VisualBasic };
public static void ExecuteOnActiveView(Action<ITextView> action)
{
InvokeOnUIThread(() =>
{
var view = GetActiveTextView();
action(view);
});
}
public static T ExecuteOnActiveView<T>(Func<ITextView, T> action)
{
return InvokeOnUIThread(() =>
{
var view = GetActiveTextView();
return action(view);
});
}
public static IWpfTextViewMargin GetTextViewMargin(string marginName)
{
return InvokeOnUIThread(() =>
{
return GetActiveTextViewHost().GetTextViewMargin(marginName);
});
}
public static ReadOnlyCollection<ICompletionSession> ActiveTextViewCompletionSessions => CompletionBroker.GetSessions(GetActiveTextView());
public static IComponentModel ComponentModel => GetGlobalService<IComponentModel>(typeof(SComponentModel));
public static IInteractiveWindow CSharpInteractiveWindow => CSharpVsInteractiveWindow.InteractiveWindow;
public static VisualStudioWorkspace VisualStudioWorkspace => ComponentModel.GetService<VisualStudioWorkspace>();
private static ITextView GetActiveTextView()
{
return GetActiveTextViewHost().TextView;
}
private static IWpfTextViewHost GetActiveTextViewHost()
{
// The active text view might not have finished composing yet, waiting for the application to 'idle'
// means that it is done pumping messages (including WM_PAINT) and the window should return the correct text view
WaitForApplicationIdle();
var activeVsTextView = (IVsUserData)(VsTextManagerActiveView);
var wpfTextViewId = IWpfTextViewId;
object wpfTextViewHost = null;
var hresult = activeVsTextView.GetData(ref wpfTextViewId, out wpfTextViewHost);
Marshal.ThrowExceptionForHR(hresult);
return (IWpfTextViewHost)(wpfTextViewHost);
}
private static ICompletionBroker CompletionBroker => ComponentModel.GetService<ICompletionBroker>();
private static IVsInteractiveWindow CSharpVsInteractiveWindow => InvokeOnUIThread(() => CSharpVsInteractiveWindowProvider.Open(0, true));
private static CSharpVsInteractiveWindowProvider CSharpVsInteractiveWindowProvider => ComponentModel.GetService<CSharpVsInteractiveWindowProvider>();
......@@ -111,25 +54,10 @@ private static IWpfTextViewHost GetActiveTextViewHost()
private static ServiceProvider GlobalServiceProvider => ServiceProvider.GlobalProvider;
private static HostWorkspaceServices VisualStudioWorkspaceServices => VisualStudioWorkspace.Services;
private static IVsShell VsShell => GetGlobalService<IVsShell>(typeof(SVsShell));
private static IVsTextManager VsTextManager => GetGlobalService<IVsTextManager>(typeof(SVsTextManager));
private static IVsTextView VsTextManagerActiveView
{
get
{
IVsTextView vsTextView = null;
var hresult = VsTextManager.GetActiveView(fMustHaveFocus: 1, pBuffer: null, ppView: out vsTextView);
Marshal.ThrowExceptionForHR(hresult);
return vsTextView;
}
}
public static void ActivateMainWindow()
{
InvokeOnUIThread(() =>
......@@ -182,6 +110,9 @@ public static void WaitForSystemIdle()
CurrentApplicationDispatcher.Invoke(() => { }, DispatcherPriority.SystemIdle);
}
/// <summary>
/// Waiting for the application to 'idle' means that it is done pumping messages (including WM_PAINT).
/// </summary>
public static void WaitForApplicationIdle()
{
CurrentApplicationDispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
......
......@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using System.Windows.Automation;
using EnvDTE;
using Roslyn.VisualStudio.Test.Utilities.Input;
using Roslyn.VisualStudio.Test.Utilities.Remoting;
using Process = System.Diagnostics.Process;
......@@ -17,6 +18,7 @@ namespace Roslyn.VisualStudio.Test.Utilities
public class VisualStudioInstance
{
public DTE DTE { get; }
public SendKeys SendKeys { get; }
private readonly Process _hostProcess;
private readonly IntegrationService _integrationService;
......@@ -52,6 +54,8 @@ public VisualStudioInstance(Process hostProcess, DTE dte)
_solutionExplorer = new Lazy<SolutionExplorer>(() => new SolutionExplorer(this));
_workspace = new Lazy<Workspace>(() => new Workspace(this));
this.SendKeys = new SendKeys(this);
// Ensure we are in a known 'good' state by cleaning up anything changed by the previous instance
Cleanup();
}
......@@ -78,6 +82,13 @@ public T ExecuteInHostProcess<T>(Type type, string methodName)
return (T)Activator.GetObject(typeof(T), $"{_integrationService.BaseUri}/{objectUri}");
}
public void WaitForApplicationIdle()
{
ExecuteInHostProcess(
type: typeof(RemotingHelper),
methodName: nameof(RemotingHelper.WaitForApplicationIdle));
}
public bool IsRunning => !_hostProcess.HasExited;
public CSharpInteractiveWindow CSharpInteractiveWindow => _csharpInteractiveWindow.Value;
......@@ -222,7 +233,7 @@ private void CloseRemotingService()
{
try
{
if ((IntegrationHelper.RetryRpcCall(() => this.DTE?.Commands.Item(VisualStudioCommandNames.VsStopServiceCommand).IsAvailable).GetValueOrDefault()))
if (IntegrationHelper.RetryRpcCall(() => this.DTE?.Commands.Item(VisualStudioCommandNames.VsStopServiceCommand).IsAvailable).GetValueOrDefault())
{
this.DTE.ExecuteCommandAsync(VisualStudioCommandNames.VsStopServiceCommand).GetAwaiter().GetResult();
}
......
......@@ -19,9 +19,13 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Extensions\DteExtensions.cs" />
<Compile Include="InProcess\BaseInProcessComponent.cs" />
<Compile Include="Input\SendKeys.cs" />
<Compile Include="Input\ShiftState.cs" />
<Compile Include="Input\VirtualKey.cs" />
<Compile Include="IntegrationHelper.cs" />
<Compile Include="Interop\Kernel32.cs" />
<Compile Include="Remoting\EditorWindowWrapper.cs" />
<Compile Include="InProcess\InProcessEditor.cs" />
<Compile Include="Remoting\RemotingHelper.cs" />
<Compile Include="Remoting\WorkspaceWrapper.cs" />
<Compile Include="VisualStudioCommandNames.cs" />
......@@ -39,7 +43,6 @@
<Compile Include="VisualStudioInstanceFactory.cs" />
<Compile Include="Window\CSharpInteractiveWindow.cs" />
<Compile Include="Window\EditorWindow.cs" />
<Compile Include="Window\EditorWindow_SendKeys.cs" />
<Compile Include="Window\InteractiveWindow.cs" />
<Compile Include="Workspace\Project.cs" />
<Compile Include="Workspace\ProjectLanguage.cs" />
......
// 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 Roslyn.VisualStudio.Test.Utilities.Remoting;
using System;
using System.Threading.Tasks;
using Roslyn.VisualStudio.Test.Utilities.InProcess;
using Roslyn.VisualStudio.Test.Utilities.Input;
namespace Roslyn.VisualStudio.Test.Utilities
{
......@@ -10,34 +13,85 @@ namespace Roslyn.VisualStudio.Test.Utilities
public partial class EditorWindow
{
private readonly VisualStudioInstance _visualStudioInstance;
private readonly EditorWindowWrapper _editorWindowWrapper;
private readonly InProcessEditor _inProcessEditor;
internal EditorWindow(VisualStudioInstance visualStudioInstance)
{
_visualStudioInstance = visualStudioInstance;
// Create MarshalByRefObject that can be used to execute code in the VS process.
_editorWindowWrapper = _visualStudioInstance.ExecuteInHostProcess<EditorWindowWrapper>(
type: typeof(EditorWindowWrapper),
methodName: nameof(EditorWindowWrapper.Create));
_inProcessEditor = _visualStudioInstance.ExecuteInHostProcess<InProcessEditor>(
type: typeof(InProcessEditor),
methodName: nameof(InProcessEditor.Create));
}
public string GetText() => _editorWindowWrapper.GetText();
public void SetText(string value) => _editorWindowWrapper.SetText(value);
public void Activate() => _inProcessEditor.Activate();
public string GetCurrentLineText() => _editorWindowWrapper.GetCurrentLineText();
public int GetCaretPosition() => _editorWindowWrapper.GetCaretPosition();
public string GetLineTextBeforeCaret() => _editorWindowWrapper.GetLineTextBeforeCaret();
public string GetLineTextAfterCaret() => _editorWindowWrapper.GetLineTextAfterCaret();
public string GetText() => _inProcessEditor.GetText();
public void SetText(string value) => _inProcessEditor.SetText(value);
public void MoveCaret(int position) => _editorWindowWrapper.MoveCaret(position);
public string GetCurrentLineText() => _inProcessEditor.GetCurrentLineText();
public int GetCaretPosition() => _inProcessEditor.GetCaretPosition();
public string GetLineTextBeforeCaret() => _inProcessEditor.GetLineTextBeforeCaret();
public string GetLineTextAfterCaret() => _inProcessEditor.GetLineTextAfterCaret();
public void Activate()
public void MoveCaret(int position) => _inProcessEditor.MoveCaret(position);
public async Task TypeTextAsync(string text, int wordsPerMinute = 120)
{
Activate();
var normalizedText = text
.Replace("\r\n", "\r")
.Replace("\n", "\r");
var charactersPerSecond = (wordsPerMinute * 4.5) / 60;
var delayBetweenCharacters = (int)((1 / charactersPerSecond) * 1000);
foreach (var character in normalizedText)
{
_visualStudioInstance.SendKeys.Send(character);
await Task.Delay(0);
}
}
public void SendKeys(params object[] textOrVirtualKeys)
{
IntegrationHelper.RetryRpcCall(() =>
Activate();
foreach (var textOrVirtualKey in textOrVirtualKeys)
{
_visualStudioInstance.DTE.ActiveDocument.Activate();
});
if (textOrVirtualKey is string)
{
var text = ((string)textOrVirtualKey)
.Replace("\r\n", "\r")
.Replace("\n", "\r");
foreach (var ch in text)
{
_visualStudioInstance.SendKeys.Send(ch);
}
}
else if (textOrVirtualKey is char)
{
_visualStudioInstance.SendKeys.Send((char)textOrVirtualKey);
}
else if (textOrVirtualKey is VirtualKey)
{
_visualStudioInstance.SendKeys.Send((VirtualKey)textOrVirtualKey);
}
else if (textOrVirtualKey == null)
{
throw new ArgumentNullException(nameof(textOrVirtualKeys));
}
else
{
throw new ArgumentException($"Unexpected type encountered: {textOrVirtualKey.GetType()}", nameof(textOrVirtualKeys));
}
}
_visualStudioInstance.WaitForApplicationIdle();
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册