Skip to content
Merged
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
8 changes: 7 additions & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,15 @@ stages:
displayName: Setup VS Hive
condition: or(eq(variables['_testKind'], 'testVs'), eq(variables['_testKind'], 'testIntegration'))

# yes, this is miserable, but - https://github.com/dotnet/arcade/issues/13239
- script: eng\CIBuild.cmd -compressallmetadata -configuration $(_configuration) -$(_testKind)
displayName: Build / Test
continueOnError: ${{ eq(variables['_testKind'], 'testIntegration') }}
condition: ne(variables['_testKind'], 'testIntegration')
- script: eng\CIBuild.cmd -compressallmetadata -configuration $(_configuration) -$(_testKind)
displayName: Build / Integration Test
continueOnError: true
condition: eq(variables['_testKind'], 'testIntegration')

- task: PublishTestResults@2
displayName: Publish Test Results
inputs:
Expand Down
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
<MicrosoftVisualStudioTextInternalVersion>$(VisualStudioEditorPackagesVersion)</MicrosoftVisualStudioTextInternalVersion>
<MicrosoftVisualStudioComponentModelHostVersion>$(VisualStudioEditorPackagesVersion)</MicrosoftVisualStudioComponentModelHostVersion>
<NuGetSolutionRestoreManagerInteropVersion>5.6.0</NuGetSolutionRestoreManagerInteropVersion>
<MicrosoftVisualStudioExtensibilityTestingVersion>0.1.149-beta</MicrosoftVisualStudioExtensibilityTestingVersion>
<MicrosoftVisualStudioExtensibilityTestingVersion>0.1.169-beta</MicrosoftVisualStudioExtensibilityTestingVersion>
<MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion>$(MicrosoftVisualStudioExtensibilityTestingVersion)</MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion>
<MicrosoftVisualStudioExtensibilityTestingXunitVersion>$(MicrosoftVisualStudioExtensibilityTestingVersion)</MicrosoftVisualStudioExtensibilityTestingXunitVersion>
<!-- Visual Studio Threading packags -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,60 @@ module Test

Assert.Contains(expectedText, actualText);
}

[IdeFact]
public async Task FsiAndFsFilesGoToCorrespondentDefinitions()
{
var template = WellKnownProjectTemplates.FSharpNetCoreClassLibrary;

var fsi = """
module Module

type SomeType =
| Number of int
| Letter of char

val id: t: SomeType -> SomeType
""";
var fs = """
module Module

type SomeType =
| Number of int
| Letter of char

let id (t: SomeType) = t
""";

await SolutionExplorer.CreateSingleProjectSolutionAsync("Library", template, TestToken);
await SolutionExplorer.RestoreNuGetPackagesAsync(TestToken);

// hack: when asked to add a file, VS API seems to insert it in the alphabetical order
// so adding Module.fsi and Module.fs we'll end up having signature file below the code file
// and this won't work. But it's possible to achieve having the right file order via their renaming
await SolutionExplorer.AddFileAsync("Library", "AModule.fsi", fsi, TestToken);
await SolutionExplorer.AddFileAsync("Library", "Module.fs", fs, TestToken);
await SolutionExplorer.RenameFileAsync("Library", "AModule.fsi", "Module.fsi", TestToken);
await SolutionExplorer.BuildSolutionAsync(TestToken);

await SolutionExplorer.OpenFileAsync("Library", "Module.fsi", TestToken);
await Editor.PlaceCaretAsync("SomeType ->", TestToken);
await Shell.ExecuteCommandAsync(VSStd97CmdID.GotoDefn, TestToken);
var expectedText = "type SomeType =";
var expectedWindow = "Module.fsi";
var actualText = await Editor.GetCurrentLineTextAsync(TestToken);
var actualWindow = await Shell.GetActiveWindowCaptionAsync(TestToken);
Assert.Equal(expectedText, actualText);
Assert.Equal(expectedWindow, actualWindow);

await SolutionExplorer.OpenFileAsync("Library", "Module.fs", TestToken);
await Editor.PlaceCaretAsync("SomeType)", TestToken);
await Shell.ExecuteCommandAsync(VSStd97CmdID.GotoDefn, TestToken);
expectedText = "type SomeType =";
expectedWindow = "Module.fs";
actualText = await Editor.GetCurrentLineTextAsync(TestToken);
actualWindow = await Shell.GetActiveWindowCaptionAsync(TestToken);
Assert.Equal(expectedText, actualText);
Assert.Equal(expectedWindow, actualWindow);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,6 @@ public async Task CreateSolutionAsync(string solutionName, CancellationToken can
var solutionPath = CreateTemporaryPath();
await CreateSolutionAsync(solutionPath, solutionName, cancellationToken);
}

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

var filePath = await GetAbsolutePathForProjectRelativeFilePathAsync(projectName, relativeFilePath, cancellationToken);
if (!File.Exists(filePath))
{
throw new FileNotFoundException(filePath);
}

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 CreateSolutionAsync(string solutionPath, string solutionName, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -207,19 +188,6 @@ private string CreateTemporaryPath()
return Path.Combine(Path.GetTempPath(), "fsharp-test", Path.GetRandomFileName());
}

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);
}

private async Task<EnvDTE.Project> GetProjectAsync(string nameOrFileName, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// 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;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Threading;
using Task = System.Threading.Tasks.Task;

namespace Microsoft.VisualStudio.Extensibility.Testing;

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

var filePath = await GetAbsolutePathForProjectRelativeFilePathAsync(projectName, relativeFilePath, cancellationToken);
if (!File.Exists(filePath))
{
throw new FileNotFoundException(filePath);
}

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));
}

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 RenameFileAsync(string projectName, string oldFileName, string newFileName, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var projectItem = await GetProjectItemAsync(projectName, oldFileName, cancellationToken);

projectItem.Name = newFileName;
}

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;

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

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

var solution = (await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken)).Solution;
var projects = solution.Projects.Cast<EnvDTE.Project>();
var project = projects.FirstOrDefault(x => x.Name == projectName);
var projectPath = Path.GetDirectoryName(project.FullName);
var fullFilePath = Path.Combine(projectPath, relativeFilePath);
var projectItems = project.ProjectItems.Cast<EnvDTE.ProjectItem>();
var document = projectItems.FirstOrDefault(d => d.get_FileNames(1).Equals(fullFilePath));

return document;
}
}