Skip to content

Commit

Permalink
Merge pull request #1970 from arjenwitteveen/lsp-find-implementations
Browse files Browse the repository at this point in the history
Add support for textDocument/implementation in LSP mode
  • Loading branch information
david-driscoll committed Oct 8, 2020
2 parents 5b95432 + b3b4f2e commit 73292ab
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Models;
using OmniSharp.Models.FindImplementations;
using static OmniSharp.LanguageServerProtocol.Helpers;

namespace OmniSharp.LanguageServerProtocol.Handlers
{
internal sealed class OmniSharpImplementationHandler : ImplementationHandler
{
public static IEnumerable<IJsonRpcHandler> Enumerate(RequestHandlers handlers)
{
foreach (var (selector, handler) in handlers
.OfType<Mef.IRequestHandler<FindImplementationsRequest, QuickFixResponse>>())
if (handler != null)
yield return new OmniSharpImplementationHandler(handler, selector);
}

private readonly Mef.IRequestHandler<FindImplementationsRequest, QuickFixResponse> _findImplementationsHandler;

public OmniSharpImplementationHandler(Mef.IRequestHandler<FindImplementationsRequest, QuickFixResponse> findImplementationsHandler, DocumentSelector documentSelector)
: base(new ImplementationRegistrationOptions()
{
DocumentSelector = documentSelector
})
{
_findImplementationsHandler = findImplementationsHandler;
}

public async override Task<LocationOrLocationLinks> Handle(ImplementationParams request, CancellationToken token)
{
var omnisharpRequest = new FindImplementationsRequest()
{
FileName = FromUri(request.TextDocument.Uri),
Column = Convert.ToInt32(request.Position.Character),
Line = Convert.ToInt32(request.Position.Line)
};

var omnisharpResponse = await _findImplementationsHandler.Handle(omnisharpRequest);

return omnisharpResponse?.QuickFixes?.Select(x => new LocationOrLocationLink(new Location
{
Uri = ToUri(x.FileName),
Range = ToRange((x.Column, x.Line))
})).ToArray() ?? Array.Empty<LocationOrLocationLink>();
}
}
}
1 change: 1 addition & 0 deletions src/OmniSharp.LanguageServerProtocol/LanguageServerHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ internal static void RegisterHandlers(ILanguageServer server, CompositionHost co
.Concat(OmniSharpWorkspaceSymbolsHandler.Enumerate(handlers))
.Concat(OmniSharpDocumentSymbolHandler.Enumerate(handlers))
.Concat(OmniSharpReferencesHandler.Enumerate(handlers))
.Concat(OmniSharpImplementationHandler.Enumerate(handlers))
.Concat(OmniSharpCodeLensHandler.Enumerate(handlers))
.Concat(OmniSharpCodeActionHandler.Enumerate(handlers, serializer, server, documentVersions))
.Concat(OmniSharpDocumentFormattingHandler.Enumerate(handlers))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public async Task<QuickFixResponse> Handle(FindImplementationsRequest request)
var quickFixes = new List<QuickFix>();
var symbol = await SymbolFinder.FindSymbolAtPositionAsync(semanticModel, position, _workspace);

if (symbol == null)
{
return response;
}

if (symbol.IsInterfaceType() || symbol.IsImplementableMember())
{
// SymbolFinder.FindImplementationsAsync will not include the method overrides
Expand Down
227 changes: 227 additions & 0 deletions tests/OmniSharp.Lsp.Tests/OmniSharpImplementationHandlerFacts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using TestUtility;
using Xunit;
using Xunit.Abstractions;

namespace OmniSharp.Lsp.Tests
{
public class OmniSharpImplementationHandlerFacts : AbstractLanguageServerTestBase
{
public OmniSharpImplementationHandlerFacts(ITestOutputHelper output)
: base(output)
{
}

[Fact]
public async Task CanFindImplementationsOfClass()
{
const string code = @"
public class Foo$$Base
{
}
public class FooDerivedA : FooBase
{
}
public class FooDerivedB : FooBase
{
}";

var implementations = await FindImplementationsAsync(code);
Assert.Equal(3, implementations.Count());
}

[Fact]
public async Task CanFindImplementationsOfInterface()
{
const string code = @"
public interface IF$$oo
{
}
public class FooA : IFoo
{
}
public class FooB : IFoo
{
}";

var implementations = await FindImplementationsAsync(code);
Assert.Equal(2, implementations.Count());
}

[Fact]
public async Task CanFindImplementationsOfVirtualFunction()
{
const string code = @"
public class FooBase
{
public virtual int B$$ar() { return 1; }
}
public class FooDerivedA : FooBase
{
public override int Bar() { return 2; }
}
public class FooDerivedB : FooBase
{
public override int Bar() { return 3; }
}";

var implementations = await FindImplementationsAsync(code);
Assert.Equal(3, implementations.Count());
}

[Fact]
public async Task CanFindImplementationsOfAbstractFunction()
{
const string code = @"
public abstract class FooBase
{
public abstract int B$$ar();
}
public class FooDerivedA : FooBase
{
public override int Bar() { return 2; }
}
public class FooDerivedB : FooBase
{
public override int Bar() { return 3; }
}";

var implementations = await FindImplementationsAsync(code);
Assert.Equal(2, implementations.Count());
}

[Fact]
public async Task CanFindImplementationsOfVirtualProperty()
{
const string code = @"
public class FooBase
{
public virtual int B$$ar => 1;
}
public class FooDerivedA : FooBase
{
public override int Bar => 2;
}
public class FooDerivedB : FooBase
{
public override int Bar => 3;
}";

var implementations = await FindImplementationsAsync(code);
Assert.Equal(3, implementations.Count());
}

[Fact]
public async Task CanFindImplementationsOfAbstractProperty()
{
const string code = @"
public abstract class FooBase
{
public abstract int B$$ar { get; }
}
public class FooDerivedA : FooBase
{
public override int Bar => 2;
}
public class FooDerivedB : FooBase
{
public override int Bar => 3;
}";

var implementations = await FindImplementationsAsync(code);
Assert.Equal(2, implementations.Count());
}

[Fact]
public async Task CannotFindImplementationsWithoutSymbol()
{
const string code = @"
public class Foo
{
$$
}";

var implementations = await FindImplementationsAsync(code);
Assert.Empty(implementations);
}

[Fact]
public async Task CannotFindImplementationsForUnsupportedSymbol()
{
const string code = @"
pub$$lic class Foo
{
}";

var implementations = await FindImplementationsAsync(code);
Assert.Empty(implementations);
}

[Fact]
public async Task CannotFindImplementationsForEmptyFiles()
{
var response = await Client.TextDocument.RequestImplementation(new ImplementationParams
{
Position = (0, 0),
TextDocument = "notfound.cs"
}, CancellationToken);

Assert.Empty(response);
}

private Task<LocationOrLocationLinks> FindImplementationsAsync(string code)
{
return FindImplementationsAsync(new[] { new TestFile("dummy.cs", code) });
}

private async Task<LocationOrLocationLinks> FindImplementationsAsync(TestFile[] testFiles)
{
OmniSharpTestHost.AddFilesToWorkspace(testFiles
.Select(f =>
new TestFile(
((f.FileName.StartsWith("/") || f.FileName.StartsWith("\\")) ? f.FileName : ("/" + f.FileName))
.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), f.Content))
.ToArray()
);

var file = testFiles.Single(tf => tf.Content.HasPosition);
var point = file.Content.GetPointFromPosition();

Client.TextDocument.DidChangeTextDocument(new DidChangeTextDocumentParams
{
ContentChanges = new Container<TextDocumentContentChangeEvent>(new TextDocumentContentChangeEvent
{
Text = file.Content.Code
}),
TextDocument = new VersionedTextDocumentIdentifier
{
Uri = DocumentUri.From(file.FileName),
Version = 1
}
});

return await Client.TextDocument.RequestImplementation(new ImplementationParams
{
Position = new Position(point.Line, point.Offset),
TextDocument = new TextDocumentIdentifier(DocumentUri.From(file.FileName))
}, CancellationToken);
}
}
}

0 comments on commit 73292ab

Please sign in to comment.