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
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace VSProjectQueryAPISample
{
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.Commands;
using Microsoft.VisualStudio.Extensibility.Shell;
using Microsoft.VisualStudio.ProjectSystem.Query;

/// <summary>
/// MoveFile handler.
/// </summary>
[VisualStudioContribution]
internal class MoveFileCommand : Command
{
private readonly TraceSource logger;

/// <summary>
/// Initializes a new instance of the <see cref="MoveFileCommand"/> class.
/// </summary>
/// <param name="traceSource">Trace source instance to utilize.</param>
public MoveFileCommand(TraceSource traceSource)
{
// This optional TraceSource can be used for logging in the command. You can use dependency injection to access
// other services here as well.
this.logger = Requires.NotNull(traceSource, nameof(traceSource));
}

/// <inheritdoc />
public override CommandConfiguration CommandConfiguration => new(displayName: "%VSProjectQueryAPISample.MoveFileCommand.DisplayName%")
{
// Use this object initializer to set optional parameters for the command. The required parameter,
// displayName, is set above. To localize the displayName, add an entry in .vsextension\string-resources.json
// and reference it here by passing "%VSProjectQueryAPISample.MoveFile.DisplayName%" as a constructor parameter.
Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu],
Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText),
};

/// <inheritdoc />
public override Task InitializeAsync(CancellationToken cancellationToken)
{
// Use InitializeAsync for any one-time setup or initialization.
return base.InitializeAsync(cancellationToken);
}

/// <inheritdoc />
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
{
// Move File Workaround
WorkspacesExtensibility querySpace = this.Extensibility.Workspaces();
StringBuilder sb = new StringBuilder("Move file from ");

// Query the source project to retrieve the path of the file to be moved.
IQueryResults<IProjectSnapshot> consoleApp1QueryResults = await querySpace.QueryProjectsAsync(
project => project.Where(p => p.Name == "ConsoleApp1")
.With(project => project.Path)
.With(project => project.Files
.With(f => f.FileName)
.With(f => f.Path)),
cancellationToken);

IFileSnapshot sourceFile = consoleApp1QueryResults.First().Files.First();

var sourceFilePath = sourceFile.Path;
sb.Append(sourceFilePath + " to ");

// Query the destination project to retrieve its path.
IQueryResults<IProjectSnapshot> destinationProjectQueryResults = await querySpace.QueryProjectsAsync(
project => project.Where(p => p.Name == "ConsoleApp2")
.With(project => project.Path),
cancellationToken);

var destinationProject = Directory.GetParent(destinationProjectQueryResults.First().Path!)!.ToString();

sb.Append(destinationProject);

// Add the source file to the destination project.
await querySpace.UpdateProjectsAsync(
project => project.Where(project => project.Name == "ConsoleApp2"),
project => project.AddFileFromCopy(sourceFilePath, destinationProject),
cancellationToken);

// Action query to delete the source file from the source project.
await sourceFile.AsUpdatable().Delete().ExecuteAsync();

await this.Extensibility.Shell().ShowPromptAsync(sb.ToString(), PromptOptions.OK, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ While building on the project level, determine the selected project you want to
await result.First().BuildAsync(cancellationToken);
```

### Rename Project
### Renaming a Project

In the example below, we specify the name of the project we would like to update. We then call `Rename` while passing in the new name of the project.

Expand All @@ -224,6 +224,17 @@ var result = await querySpace.Projects
.ExecuteAsync(cancellationToken);
```


### Renaming a file
`RenameFile` takes the file path of the file you want to rename and the new name of the file. In the example below, we rename a file in a project called `ConsoleApp1` to `newName.cs`.

```csharp
var result = await querySpace.UpdateProjectsAsync(
project => project.Where(project => project.Name == "ConsoleApp1"),
project => project.RenameFile(filePath, "newName.cs"),
cancellationToken);
```

### Skip 1 Project

In the code sample, we will query the projects in a solution and skip the first one. Let's say there are 3 projects in the solution. The first result will be skipped and will return the two remaining projects. Note: the order is not guaranteed.
Expand All @@ -244,4 +255,21 @@ var unsubscriber = await singleProject
.Files
.With(f => f.FileName)
.TrackUpdatesAsync(new TrackerObserver(), CancellationToken.None);
```

### Moving a file
This example offers a temporary workaround to move a file by copying it to the new location and then deleting the original file. Currently, a `MoveFile` API is not available in the Project Query API. In this specific case, we are transferring a file from the `ConsoleApp1` project to the `ConsoleApp2` project.

The first step is to copy the original file to the new destination.
```csharp
var result = await querySpace.UpdateProjectsAsync(
project => project.Where(project => project.Name == "ConsoleApp2"),
project => project.AddFileFromCopy(sourceFilePath, destinationProject),
cancellationToken);
```

Next, delete the original file by obtaining an `IFileSnapshot` instance of the file and proceeding with its deletion. The `AsUpdatable()` method indicates that an action will be performed on the project system. This is followed by the `Delete()` action and its execution using `ExecuteAsync()`.

```csharp
await sourceFile.AsUpdatable().Delete().ExecuteAsync();
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace VSProjectQueryAPISample
{
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.Commands;
using Microsoft.VisualStudio.Extensibility.Shell;
using Microsoft.VisualStudio.ProjectSystem.Query;

/// <summary>
/// RenameFile handler.
/// </summary>
[VisualStudioContribution]
internal class RenameFileCommand : Command
{
private readonly TraceSource logger;

/// <summary>
/// Initializes a new instance of the <see cref="RenameFileCommand"/> class.
/// </summary>
/// <param name="traceSource">Trace source instance to utilize.</param>
public RenameFileCommand(TraceSource traceSource)
{
// This optional TraceSource can be used for logging in the command. You can use dependency injection to access
// other services here as well.
this.logger = Requires.NotNull(traceSource, nameof(traceSource));
}

/// <inheritdoc />
public override CommandConfiguration CommandConfiguration => new(displayName: "%VSProjectQueryAPISample.RenameFileCommand.DisplayName%")
{
// Use this object initializer to set optional parameters for the command. The required parameter,
// displayName, is set above. To localize the displayName, add an entry in .vsextension\string-resources.json
// and reference it here by passing "%VSProjectQueryAPISample.RenameFile.DisplayName%" as a constructor parameter.
Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu],
Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText),
};

/// <inheritdoc />
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
{
WorkspacesExtensibility querySpace = this.Extensibility.Workspaces();
StringBuilder sb = new StringBuilder("Renamed file at ");

IQueryResults<IProjectSnapshot> consoleApp1QueryResults = await querySpace.QueryProjectsAsync(
project => project.Where(p => p.Name == "ConsoleApp1")
.With(project => project.Files
.With(f => f.FileName)
.With(f => f.Path)),
cancellationToken);

var filePath = consoleApp1QueryResults.First().Files.First().Path;
sb.Append(filePath);

await querySpace.UpdateProjectsAsync(
project => project.Where(project => project.Name == "ConsoleApp1"),
project => project.RenameFile(filePath, "newName.cs"),
cancellationToken);

sb.Append(" to newName.cs");

await this.Extensibility.Shell().ShowPromptAsync(sb.ToString(), PromptOptions.OK, cancellationToken);
}
}
}