diff --git a/src/Protocol/Serialization/Converters/RangeOrPlaceholderRangeConverter.cs b/src/Protocol/Serialization/Converters/RangeOrPlaceholderRangeConverter.cs index fe8658cb2..f0d1e2504 100644 --- a/src/Protocol/Serialization/Converters/RangeOrPlaceholderRangeConverter.cs +++ b/src/Protocol/Serialization/Converters/RangeOrPlaceholderRangeConverter.cs @@ -1,5 +1,6 @@ using System; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; @@ -13,16 +14,29 @@ public override void WriteJson(JsonWriter writer, RangeOrPlaceholderRange value, { serializer.Serialize(writer, value.Range); } - else + else if (value.IsPlaceholderRange) { serializer.Serialize(writer, value.PlaceholderRange); } + else + { + writer.WriteNull(); + } } public override RangeOrPlaceholderRange ReadJson( JsonReader reader, Type objectType, RangeOrPlaceholderRange existingValue, bool hasExistingValue, JsonSerializer serializer - ) => new RangeOrPlaceholderRange((Range) null); + ) + { + if (reader.TokenType is JsonToken.StartObject) + { + var obj = JToken.ReadFrom(reader) as JObject; + return obj.ContainsKey("placeholder") + ? new RangeOrPlaceholderRange(obj.ToObject()) + : new RangeOrPlaceholderRange(obj.ToObject()); + } - public override bool CanRead => false; + return null; + } } } diff --git a/test/Lsp.Tests/Integration/RenameTests.cs b/test/Lsp.Tests/Integration/RenameTests.cs new file mode 100644 index 000000000..d7e4061dd --- /dev/null +++ b/test/Lsp.Tests/Integration/RenameTests.cs @@ -0,0 +1,180 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using NSubstitute; +using OmniSharp.Extensions.JsonRpc.Testing; +using OmniSharp.Extensions.LanguageProtocol.Testing; +using OmniSharp.Extensions.LanguageServer.Client; +using OmniSharp.Extensions.LanguageServer.Protocol; +using OmniSharp.Extensions.LanguageServer.Protocol.Document; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; +using OmniSharp.Extensions.LanguageServer.Server; +using Serilog.Events; +using Xunit; +using Xunit.Abstractions; +using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range; + +namespace Lsp.Tests.Integration +{ + public class RenameTests : LanguageProtocolTestBase + { + private readonly Func> _prepareRename; + private readonly Func> _rename; + + public RenameTests(ITestOutputHelper outputHelper) : base(new JsonRpcTestOptions().ConfigureForXUnit(outputHelper, LogEventLevel.Verbose)) + { + _prepareRename = Substitute.For>>(); + _rename = Substitute.For>>(); + } + + [Fact] + public async Task Should_Handle_Rename_With_No_Value() + { + _prepareRename.Invoke(Arg.Any(), Arg.Any()) + .Returns( + call => { + var pos = call.Arg().Position; + return new RangeOrPlaceholderRange( + new Range( + pos, + ( pos.Line, pos.Character + 2 ) + ) + ); + } + ); + + _rename.Invoke(Arg.Any(), Arg.Any()) + .Returns( + new WorkspaceEdit() { + DocumentChanges = new Container(new WorkspaceEditDocumentChange(new CreateFile() { + Uri = DocumentUri.FromFileSystemPath("/abcd/create.cs") + })) + } + ); + + var (client, server) = await Initialize(ClientOptionsAction, ServerOptionsAction); + + var result = await client.PrepareRename( + new PrepareRenameParams() { + Position = ( 1, 1 ), + TextDocument = DocumentUri.FromFileSystemPath("/abcd/file.cs") + }, + CancellationToken + ); + + result.Should().NotBeNull(); + + var renameResponse = await client.RequestRename( + new RenameParams() { + Position = result.Range.Start, + NewName = "newname", + TextDocument = DocumentUri.FromFileSystemPath("/abcd/file.cs") + } + ); + + renameResponse.DocumentChanges.Should().HaveCount(1); + renameResponse.DocumentChanges.Should().Match(z => z.Any(x => x.IsCreateFile)); + } + + [Fact] + public async Task Should_Handle_Prepare_Rename_With_No_Value() + { + _prepareRename.Invoke(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(null)); + var (client, server) = await Initialize(ClientOptionsAction, ServerOptionsAction); + + var result = await client.PrepareRename( + new PrepareRenameParams() { + Position = ( 1, 1 ), + TextDocument = DocumentUri.FromFileSystemPath("/abcd/file.cs") + }, + CancellationToken + ); + + result.Should().BeNull(); + } + + [Fact] + public async Task Should_Handle_Prepare_Rename_With_Range() + { + _prepareRename.Invoke(Arg.Any(), Arg.Any()) + .Returns( + call => { + var pos = call.Arg().Position; + return new RangeOrPlaceholderRange( + new Range( + pos, + ( pos.Line, pos.Character + 2 ) + ) + ); + } + ); + + var (client, server) = await Initialize(ClientOptionsAction, ServerOptionsAction); + + var result = await client.PrepareRename( + new PrepareRenameParams() { + Position = ( 1, 1 ), + TextDocument = DocumentUri.FromFileSystemPath("/abcd/file.cs") + }, + CancellationToken + ); + + result.IsRange.Should().BeTrue(); + } + + [Fact] + public async Task Should_Handle_Prepare_Rename_With_PlaceholderRange() + { + _prepareRename.Invoke(Arg.Any(), Arg.Any()) + .Returns( + call => { + var pos = call.Arg().Position; + return new RangeOrPlaceholderRange( + new PlaceholderRange() { + Range = + new Range( + pos, + ( pos.Line, pos.Character + 2 ) + ), + Placeholder = "Bogus" + } + ); + } + ); + + var (client, server) = await Initialize(ClientOptionsAction, ServerOptionsAction); + + var result = await client.PrepareRename( + new PrepareRenameParams() { + Position = ( 1, 1 ), + TextDocument = DocumentUri.FromFileSystemPath("/abcd/file.cs") + }, + CancellationToken + ); + + result.IsPlaceholderRange.Should().BeTrue(); + } + + private void ServerOptionsAction(LanguageServerOptions obj) + { + obj.OnPrepareRename( + _prepareRename, new TextDocumentRegistrationOptions() { + DocumentSelector = DocumentSelector.ForLanguage("csharp") + } + ); + obj.OnRename( + _rename, new RenameRegistrationOptions() { + DocumentSelector = DocumentSelector.ForLanguage("csharp"), + PrepareProvider = true + } + ); + } + + private void ClientOptionsAction(LanguageClientOptions obj) + { + } + } +}