Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Expand Up @@ -10,6 +10,7 @@
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources;
using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens;
using Microsoft.CodeAnalysis.Options;
Expand Down Expand Up @@ -40,11 +41,13 @@ internal sealed class AlwaysActivateInProcLanguageClient(
IThreadingContext threadingContext,
ExportProvider exportProvider,
IDiagnosticSourceManager diagnosticSourceManager,
[ImportMany] IEnumerable<Lazy<ILspBuildOnlyDiagnostics, ILspBuildOnlyDiagnosticsMetadata>> buildOnlyDiagnostics) : AbstractInProcLanguageClient(lspServiceProvider, globalOptions, lspLoggerFactory, threadingContext, exportProvider)
[ImportMany] IEnumerable<Lazy<ILspBuildOnlyDiagnostics, ILspBuildOnlyDiagnosticsMetadata>> buildOnlyDiagnostics,
[ImportMany] IEnumerable<Lazy<ILspWillRenameListener, ILspWillRenameListenerMetadata>> renameListeners) : AbstractInProcLanguageClient(lspServiceProvider, globalOptions, lspLoggerFactory, threadingContext, exportProvider)
{
private readonly ExperimentalCapabilitiesProvider _experimentalCapabilitiesProvider = defaultCapabilitiesProvider;
private readonly IDiagnosticSourceManager _diagnosticSourceManager = diagnosticSourceManager;
private readonly IEnumerable<Lazy<ILspBuildOnlyDiagnostics, ILspBuildOnlyDiagnosticsMetadata>> _buildOnlyDiagnostics = buildOnlyDiagnostics;
private readonly IEnumerable<Lazy<ILspWillRenameListener, ILspWillRenameListenerMetadata>> _renameListeners = renameListeners;

protected override ImmutableArray<string> SupportedLanguages => ProtocolConstants.RoslynLspLanguages;

Expand Down Expand Up @@ -127,6 +130,33 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa
};
serverCapabilities.ImplementationProvider = true;

if (clientCapabilities.Workspace?.FileOperations?.WillRename ?? false)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just discovered that myself. Was very easy to move over though :)
VSCodeRazorFile

{
// Register for file rename notifications based on the registered rename listeners.
using var _ = PooledObjects.ArrayBuilder<FileOperationFilter>.GetInstance(out var filters);
foreach (var listener in _renameListeners)
{
filters.Add(new FileOperationFilter
{
Pattern = new FileOperationPattern { Glob = listener.Metadata.Glob }
});
}

if (filters.Count > 0)
{
serverCapabilities.Workspace = new WorkspaceServerCapabilities
{
FileOperations = new WorkspaceFileOperationsServerCapabilities()
{
WillRename = new FileOperationRegistrationOptions()
{
Filters = filters.ToArray()
}
}
};
}
}

return serverCapabilities;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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.Composition;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler;

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
internal class ExportLspWillRenameListenerAttribute(string glob) : ExportAttribute(typeof(ILspWillRenameListener)), ILspWillRenameListenerMetadata
{
public string Glob { get; } = glob;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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 Roslyn.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler;

/// <summary>
/// Allows listening for workspace/didRenameFiles notifications.
/// </summary>
/// <remarks>
/// Although the registration for didRename allows specifying a document selector, and that registration is passed
Comment thread
davidwengier marked this conversation as resolved.
Outdated
/// along to the client, the LSP server itself does not filter notifications based on that selector. It is up to the
/// the listener to determine if it cares about the rename notification or not. If any listener returns an edit, no
/// further listeners are called.
/// </remarks>
internal interface ILspWillRenameListener
{
Task<WorkspaceEdit?> HandleWillRenameAsync(RenameFilesParams renameParams, RequestContext context, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// 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.

namespace Microsoft.CodeAnalysis.LanguageServer.Handler;

internal interface ILspWillRenameListenerMetadata
{
string Glob { get; }
}
101 changes: 101 additions & 0 deletions src/LanguageServer/Protocol/Handler/Rename/WillRenameHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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.Collections.Generic;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.LanguageServer.Protocol;
using LSP = Roslyn.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler;

[ExportCSharpVisualBasicStatelessLspService(typeof(WillRenameHandler)), Shared]
[Method(LSP.Methods.WorkspaceWillRenameFilesName)]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class WillRenameHandler(
Comment thread
davidwengier marked this conversation as resolved.
[ImportMany] IEnumerable<Lazy<ILspWillRenameListener, ILspWillRenameListenerMetadata>> renameListeners) : ILspServiceRequestHandler<LSP.RenameFilesParams, WorkspaceEdit?>
{
public bool MutatesSolutionState => true;
public bool RequiresLSPSolution => true;

public TextDocumentIdentifier GetTextDocumentIdentifier(RenameParams request) => request.TextDocument;

public async Task<WorkspaceEdit?> HandleRequestAsync(RenameFilesParams request, RequestContext requestContext, CancellationToken cancellationToken)
{
using var _1 = PooledDictionary<string, ArrayBuilder<TextEdit>>.GetInstance(out var changesBuilder);
using var _2 = ArrayBuilder<SumType<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>>.GetInstance(out var documentChangesBuilder);

foreach (var listener in renameListeners)
{
var edit = await listener.Value.HandleWillRenameAsync(request, requestContext, cancellationToken).ConfigureAwait(false);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than have Roslyn bother to handle globs, and deal with the complexity of one willRenameFiles request possibly containing multiple files that multiple handlers would want to know about, it seemed easier to just have each handler check the actual files being renamed and see if they care. They'd want to do it anyway.


if (edit is null)
{
continue;
}

if (edit.Changes is { } changes)
{
foreach (var (path, edits) in changes)
{
if (!changesBuilder.TryGetValue(path, out var existingEdits))
{
existingEdits = ArrayBuilder<TextEdit>.GetInstance();
changesBuilder.Add(path, existingEdits);
}

existingEdits.AddRange(edits);
}
}
else if (edit.DocumentChanges is { } documentChanges)
{
if (documentChanges.TryGetFirst(out var textDocumentEdits))
{
foreach (var textDocumentEdit in textDocumentEdits)
{
documentChangesBuilder.Add(textDocumentEdit);
}
}
else if (documentChanges.TryGetSecond(out var sumTypes))
{
foreach (var sumType in sumTypes)
{
documentChangesBuilder.Add(sumType);
}
}
}
}

if (changesBuilder.Count == 0 && documentChangesBuilder.Count == 0)
{
return null;
}

Contract.ThrowIfTrue(changesBuilder.Count > 0 && documentChangesBuilder.Count > 0, "Cannot have both changes and documentChanges in a WorkspaceEdit. Please honour the client capabilities.");

if (changesBuilder.Count > 0)
{
var changes = new Dictionary<string, TextEdit[]>();
foreach (var (path, editsBuilder) in changesBuilder)
{
changes[path] = editsBuilder.ToArrayAndFree();
}

return new WorkspaceEdit
{
Changes = changes
};
}

return new WorkspaceEdit
{
DocumentChanges = documentChangesBuilder.ToArray()
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ public TextDocumentSyncOptions? TextDocumentSync
/// </summary>
[JsonPropertyName("workspace")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public WorkspaceServerCapabilities? Workspace { get; init; }
public WorkspaceServerCapabilities? Workspace { get; set; }

/// <summary>
/// Gets or sets experimental server capabilities.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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.CodeAnalysis.LanguageServer.Handler;
using Roslyn.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;

internal abstract class AbstractRazorWillRenameListener : ILspWillRenameListener
{
Task<WorkspaceEdit?> ILspWillRenameListener.HandleWillRenameAsync(RenameFilesParams request, RequestContext context, CancellationToken cancellationToken)
{
var razorRequestContext = new RazorCohostRequestContext(context);
return HandleRequestAsync(request, razorRequestContext, cancellationToken);
}

protected abstract Task<WorkspaceEdit?> HandleRequestAsync(RenameFilesParams request, RazorCohostRequestContext razorRequestContext, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 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 Microsoft.CodeAnalysis.LanguageServer.Handler;

namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;

internal sealed class ExportRazorWillRenameListenerAttribute(string glob) : ExportLspWillRenameListenerAttribute(glob);
Loading