Skip to content

Commit

Permalink
Implement textDocument/prepareRename for better rename experience
Browse files Browse the repository at this point in the history
  • Loading branch information
dibarbet committed Nov 8, 2023
1 parent caa98cc commit d257e93
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities)

capabilities.DefinitionProvider = true;
capabilities.DocumentHighlightProvider = true;
capabilities.RenameProvider = true;
capabilities.RenameProvider = new RenameOptions
{
PrepareProvider = true,
};
capabilities.ImplementationProvider = true;
capabilities.CodeActionProvider = new CodeActionOptions { CodeActionKinds = [CodeActionKind.QuickFix, CodeActionKind.Refactor], ResolveProvider = true };
capabilities.CompletionProvider = new VisualStudio.LanguageServer.Protocol.CompletionOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler;

[ExportCSharpVisualBasicStatelessLspService(typeof(PrepareRenameHandler)), Shared]
[Method(Methods.TextDocumentPrepareRenameName)]
internal class PrepareRenameHandler : ILspServiceDocumentRequestHandler<PrepareRenameParams, DefaultBehaviorPrepareRename?>
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public PrepareRenameHandler()
{
}

public bool MutatesSolutionState => false;
public bool RequiresLSPSolution => true;

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

public async Task<DefaultBehaviorPrepareRename?> HandleRequestAsync(PrepareRenameParams request, RequestContext context, CancellationToken cancellationToken)
{
var document = context.Document;
Contract.ThrowIfNull(document);

var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);

var symbolicRenameInfo = await SymbolicRenameInfo.GetRenameInfoAsync(
document, position, cancellationToken).ConfigureAwait(false);
if (symbolicRenameInfo.IsError)
return null;

return new DefaultBehaviorPrepareRename
{
DefaultBehavior = true,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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.Threading;
using System.Threading.Tasks;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Rename
{
public class PrepareRenameTests : AbstractLanguageServerProtocolTests
{
public PrepareRenameTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
{
}

[Theory, CombinatorialData]
public async Task TestPrepareRenameValidLocationAsync(bool mutatingLspWorkspace)
{
var markup =
@"class A
{
void {|caret:|}M()
{
}
}";
await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace);
var renameLocation = testLspServer.GetLocations("caret").First();

var results = await RunPrepareRenameAsync(testLspServer, CreatePrepareRenameParams(renameLocation));
Assert.True(results?.DefaultBehavior);
}

[Theory, CombinatorialData]
public async Task TestPrepareRenameInvalidLocationAsync(bool mutatingLspWorkspace)
{
var markup =
@"class A
{
// Cannot rename {|caret:|} inside a comment.
void M()
{
}
}";
await using var testLspServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace);
var renameLocation = testLspServer.GetLocations("caret").First();

var results = await RunPrepareRenameAsync(testLspServer, CreatePrepareRenameParams(renameLocation));
Assert.Null(results);
}

private static LSP.PrepareRenameParams CreatePrepareRenameParams(LSP.Location location)
=> new LSP.PrepareRenameParams()
{
Position = location.Range.Start,
TextDocument = CreateTextDocumentIdentifier(location.Uri)
};

private static async Task<LSP.DefaultBehaviorPrepareRename?> RunPrepareRenameAsync(TestLspServer testLspServer, LSP.PrepareRenameParams prepareRenameParams)
{
return await testLspServer.ExecuteRequestAsync<LSP.PrepareRenameParams, LSP.DefaultBehaviorPrepareRename?>(LSP.Methods.TextDocumentPrepareRenameName, prepareRenameParams, CancellationToken.None);
}
}
}

0 comments on commit d257e93

Please sign in to comment.