Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
<MicrosoftVisualStudioThreadingPackagesVersion>17.5.10-alpha</MicrosoftVisualStudioThreadingPackagesVersion>
<MicrosoftBuildOverallPackagesVersion>17.4.0-preview-22469-04</MicrosoftBuildOverallPackagesVersion>
<!-- Roslyn packages -->
<MicrosoftCodeAnalysisFeaturesVersion>$(RoslynVersion)</MicrosoftCodeAnalysisFeaturesVersion>
<MicrosoftCodeAnalysisEditorFeaturesVersion>$(RoslynVersion)</MicrosoftCodeAnalysisEditorFeaturesVersion>
<MicrosoftCodeAnalysisEditorFeaturesTextVersion>$(RoslynVersion)</MicrosoftCodeAnalysisEditorFeaturesTextVersion>
<MicrosoftCodeAnalysisEditorFeaturesWpfVersion>$(RoslynVersion)</MicrosoftCodeAnalysisEditorFeaturesWpfVersion>
Expand Down Expand Up @@ -163,6 +164,7 @@
<MicrosoftVisualStudioExtensibilityTestingVersion>0.1.149-beta</MicrosoftVisualStudioExtensibilityTestingVersion>
<MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion>$(MicrosoftVisualStudioExtensibilityTestingVersion)</MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion>
<MicrosoftVisualStudioExtensibilityTestingXunitVersion>$(MicrosoftVisualStudioExtensibilityTestingVersion)</MicrosoftVisualStudioExtensibilityTestingXunitVersion>
<InputSimulatorPlusVersion>1.0.7</InputSimulatorPlusVersion>
<!-- Visual Studio Threading packags -->
<MicrosoftVisualStudioThreadingVersion>$(MicrosoftVisualStudioThreadingPackagesVersion)</MicrosoftVisualStudioThreadingVersion>
<!-- Visual Studio Project System packages-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="InputSimulatorPlus" Version="$(InputSimulatorPlusVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Editor" Version="$(MicrosoftVisualStudioEditorVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Testing.Xunit" Version="$(MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Testing.SourceGenerator" Version="$(MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.ExternalAccess.FSharp" Version="$(MicrosoftCodeAnalysisExternalAccessFSharpVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Language.Intellisense" Version="$(MicrosoftVisualStudioLanguageIntellisenseVersion)" />
<PackageReference Include="Microsoft.VisualStudio.LanguageServices" Version="$(MicrosoftVisualStudioLanguageServicesVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.Features" Version="$(MicrosoftCodeAnalysisFeaturesVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.EditorFeatures" Version="$(MicrosoftCodeAnalysisEditorFeaturesVersion)" />
<PackageReference Include="NuGet.SolutionRestoreManager.Interop" Version="$(NuGetSolutionRestoreManagerInteropVersion)" />
<PackageReference Include="Nullable" Version="1.3.0" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Threading;
using FSharp.Editor.IntegrationTests;
using Microsoft.VisualStudio.Text.Editor;

namespace Microsoft.VisualStudio.Extensibility.Testing;

Expand All @@ -28,4 +35,28 @@ public async Task SetTextAsync(string text, CancellationToken cancellationToken)
var replacementSpan = new SnapshotSpan(textSnapshot, 0, textSnapshot.Length);
view.TextBuffer.Replace(replacementSpan, text);
}

public async Task WaitForEditorOperationsAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

var shell = await GetRequiredGlobalServiceAsync<SVsShell, IVsShell>(cancellationToken);
if (shell.IsPackageLoaded(DefGuidList.guidEditorPkg, out var editorPackage) == VSConstants.S_OK)
{
var asyncPackage = (AsyncPackage)editorPackage;
var collection = asyncPackage.GetPropertyValue<JoinableTaskCollection>("JoinableTaskCollection");
await collection.JoinTillEmptyAsync(cancellationToken);
}
}

public async Task<string> GetSelectedTextAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

var view = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken);
var subjectBuffer = ITextViewExtensions.GetBufferContainingCaret(view);

var selectedSpan = view.Selection.SelectedSpans[0];
return subjectBuffer.CurrentSnapshot.GetText(selectedSpan);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using WindowsInput;
using WindowsInput.Native;

namespace Microsoft.VisualStudio.Extensibility.Testing;

[TestService]
internal partial class InputInProcess
{
internal async Task SendToNavigateToAsync(InputKey[] keys, CancellationToken cancellationToken)
{
// AbstractSendKeys runs synchronously, so switch to a background thread before the call
await TaskScheduler.Default;

// Take no direct action regarding activation, but assert the correct item already has focus
await TestServices.JoinableTaskFactory.RunAsync(async () =>
{
await TestServices.JoinableTaskFactory.SwitchToMainThreadAsync();
});

var inputSimulator = new InputSimulator();
foreach (var key in keys)
{
// If it is enter key, we need to wait for search item shows up in the search dialog.
if (key.VirtualKeyCode == VirtualKeyCode.RETURN)
{
await WaitNavigationItemShowsUpAsync(cancellationToken);
}

key.Apply(inputSimulator);
}

await TestServices.JoinableTaskFactory.RunAsync(async () =>
{
await WaitForApplicationIdleAsync(cancellationToken);
});
}

private async Task WaitNavigationItemShowsUpAsync(CancellationToken cancellationToken)
{
// Wait for the NavigateTo Features completes on Roslyn side.
await TestServices.Workspace.WaitForAllAsyncOperationsAsync(new[] { "NavigateTo" }, cancellationToken);
// Since the all-in-one search experience populates its results asychronously we need
// to give it time to update the UI. Note: This is not a perfect solution.
await Task.Delay(1000);
await WaitForApplicationIdleAsync(cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Immutable;
using WindowsInput;
using WindowsInput.Native;

namespace Microsoft.VisualStudio.Extensibility.Testing;

internal readonly struct InputKey
{
public readonly ImmutableArray<VirtualKeyCode> Modifiers;
public readonly VirtualKeyCode VirtualKeyCode;
public readonly char? Character;
public readonly string? Text;

public InputKey(VirtualKeyCode virtualKeyCode, ImmutableArray<VirtualKeyCode> modifiers)
{
Modifiers = modifiers;
VirtualKeyCode = virtualKeyCode;
Character = null;
Text = null;
}

public InputKey(char character)
{
Modifiers = ImmutableArray<VirtualKeyCode>.Empty;
VirtualKeyCode = 0;
Character = character;
Text = null;
}

public InputKey(string text)
{
Modifiers = ImmutableArray<VirtualKeyCode>.Empty;
VirtualKeyCode = 0;
Character = null;
Text = text;
}

public static implicit operator InputKey(char character)
=> new(character);

public static implicit operator InputKey(string text)
=> new(text);

public static implicit operator InputKey(VirtualKeyCode virtualKeyCode)
=> new(virtualKeyCode, ImmutableArray<VirtualKeyCode>.Empty);

public static implicit operator InputKey((VirtualKeyCode virtualKeyCode, VirtualKeyCode modifier) modifiedKey)
=> new(modifiedKey.virtualKeyCode, ImmutableArray.Create(modifiedKey.modifier));

public void Apply(IInputSimulator simulator)
{
if (Character is { } c)
{
if (c == '\n')
simulator.Keyboard.KeyPress(VirtualKeyCode.RETURN);
else if (c == '\t')
simulator.Keyboard.KeyPress(VirtualKeyCode.TAB);
else
simulator.Keyboard.TextEntry(c);

return;
}
else if (Text is not null)
{
var offset = 0;
while (offset < Text.Length)
{
if (Text[offset] == '\r' && offset < Text.Length - 1 && Text[offset + 1] == '\n')
{
// Treat \r\n as a single RETURN character
offset++;
continue;
}
else if (Text[offset] == '\n')
{
simulator.Keyboard.KeyPress(VirtualKeyCode.RETURN);
offset++;
continue;
}
else if (Text[offset] == '\t')
{
simulator.Keyboard.KeyPress(VirtualKeyCode.TAB);
offset++;
continue;
}
else
{
var nextSpecial = Text.IndexOfAny(new[] { '\r', '\n', '\t' }, offset);
var endOfCurrentSegment = nextSpecial < 0 ? Text.Length : nextSpecial;
simulator.Keyboard.TextEntry(Text.Substring(offset, endOfCurrentSegment - offset));
offset = endOfCurrentSegment;
}
}

return;
}

if (Modifiers.IsEmpty)
{
simulator.Keyboard.KeyPress(VirtualKeyCode);
}
else
{
simulator.Keyboard.ModifiedKeyStroke(Modifiers, VirtualKeyCode);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using FSharp.Editor.IntegrationTests;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
using Xunit;

namespace Microsoft.VisualStudio.Extensibility.Testing;

internal partial class ShellInProcess
{
public async Task ShowNavigateToDialogAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

await TestServices.Shell.ExecuteCommandAsync(VSConstants.VSStd12CmdID.NavigateTo, cancellationToken);

await WaitForNavigateToFocusAsync(cancellationToken);

async Task WaitForNavigateToFocusAsync(CancellationToken cancellationToken)
{
bool isSearchActive = false;

while (true)
{
cancellationToken.ThrowIfCancellationRequested();

// Take no direct action regarding activation, but assert the correct item already has focus
await TestServices.JoinableTaskFactory.RunAsync(async () =>
{
await TestServices.JoinableTaskFactory.SwitchToMainThreadAsync();
var searchBox = Assert.IsAssignableFrom<Control>(Keyboard.FocusedElement);
if ("PART_SearchBox" == searchBox.Name || "SearchBoxControl" == searchBox.Name)
{
isSearchActive = true;
}
});

if (isSearchActive)
{
return;
}

// If the dialog has not been displayed, then wait some time for it to show. The
// cancellation token passed in should be hang mitigating to avoid possible
// infinite loop.
await Task.Delay(100);
}
}
}

// This is based on WaitForQuiescenceAsync in the FileChangeService tests
public async Task WaitForFileChangeNotificationsAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

var fileChangeService = await GetRequiredGlobalServiceAsync<SVsFileChangeEx, IVsFileChangeEx>(cancellationToken);
Assumes.Present(fileChangeService);

var jobSynchronizer = fileChangeService.GetPropertyValue("JobSynchronizer");
Assumes.Present(jobSynchronizer);

var type = jobSynchronizer.GetType();
var methodInfo = type.GetMethod("GetActiveSpawnedTasks", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
Assumes.Present(methodInfo);

while (true)
{
var tasks = (Task[])methodInfo.Invoke(jobSynchronizer, null);
if (!tasks.Any())
return;

await Task.WhenAll(tasks);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,34 @@ public async Task AddProjectAsync(string projectName, string projectTemplate, Ca
ErrorHandler.ThrowOnFailure(solution.AddNewProjectFromTemplate(projectTemplatePath, null, null, projectPath, projectName, null, out _));
}

public async Task AddFileAsync(string projectName, string fileName, string? contents, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

var project = await GetProjectAsync(projectName, cancellationToken);
var projectDirectory = Path.GetDirectoryName(project.FullName);
var filePath = Path.Combine(projectDirectory, fileName);
var directoryPath = Path.GetDirectoryName(filePath);
Directory.CreateDirectory(directoryPath);

File.WriteAllText(filePath, contents);
_ = project.ProjectItems.AddFromFile(filePath);
}

public async Task OpenFileAsync(string projectName, string relativeFilePath, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

var filePath = await GetAbsolutePathForProjectRelativeFilePathAsync(projectName, relativeFilePath, cancellationToken);
VsShellUtilities.OpenDocument(ServiceProvider.GlobalProvider, filePath, VSConstants.LOGVIEWID.Code_guid, out _, out _, out _, out var view);

// Reliably set focus using NavigateToLineAndColumn
var textManager = await GetRequiredGlobalServiceAsync<SVsTextManager, IVsTextManager>(cancellationToken);
ErrorHandler.ThrowOnFailure(view.GetBuffer(out var textLines));
ErrorHandler.ThrowOnFailure(view.GetCaretPos(out var line, out var column));
ErrorHandler.ThrowOnFailure(textManager.NavigateToLineAndColumn(textLines, VSConstants.LOGVIEWID.Code_guid, line, column, line, column));
}

private async Task<string> GetProjectTemplatePathAsync(string projectTemplate, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
Expand All @@ -76,6 +104,19 @@ private async Task<string> GetProjectTemplatePathAsync(string projectTemplate, C
return solution.GetProjectTemplate(projectTemplate, "FSharp");
}

private async Task<string> GetAbsolutePathForProjectRelativeFilePathAsync(string projectName, string relativeFilePath, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
var solution = dte.Solution;
Assumes.Present(solution);

var project = solution.Projects.Cast<EnvDTE.Project>().First(x => x.Name == projectName);
var projectPath = Path.GetDirectoryName(project.FullName);
return Path.Combine(projectPath, relativeFilePath);
}

public async Task RestoreNuGetPackagesAsync(CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
Expand Down
Loading