Skip to content

Commit 43b1ac9

Browse files
Routing via resolve!
1 parent 4d8f7d1 commit 43b1ac9

File tree

11 files changed

+425
-133
lines changed

11 files changed

+425
-133
lines changed

src/Protocol/Document/ICodeLensResolveHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ public static partial class DocumentNames
1212
}
1313

1414
[Parallel, Method(CodeLensResolve)]
15-
public interface ICodeLensResolveHandler : IRequestHandler<CodeLens, CodeLens> { }
15+
public interface ICodeLensResolveHandler : ICanBeResolvedHandler<CodeLens> { }
1616
}

src/Protocol/Document/ICompletionResolveHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ public static partial class DocumentNames
1212
}
1313

1414
[Serial, Method(CompletionResolve)]
15-
public interface ICompletionResolveHandler : IRequestHandler<CompletionItem, CompletionItem> { }
15+
public interface ICompletionResolveHandler : ICanBeResolvedHandler<CompletionItem> { }
1616
}

src/Protocol/Models/CompletionList.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,22 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol.Models
99
/// Represents a collection of [completion items](#CompletionItem) to be presented
1010
/// in the editor.
1111
/// </summary>
12-
public class CompletionList
12+
public class CompletionList : Container<CompletionItem>
1313
{
14-
public CompletionList(IEnumerable<CompletionItem> items)
14+
public CompletionList() : base(Enumerable.Empty<CompletionItem>()) { }
15+
public CompletionList(bool isIncomplete) : base(Enumerable.Empty<CompletionItem>())
1516
{
16-
Items = items;
17+
IsIncomplete = isIncomplete;
18+
}
19+
20+
public CompletionList(IEnumerable<CompletionItem> items) : base(items) { }
21+
public CompletionList(IEnumerable<CompletionItem> items, bool isIncomplete) : base(items)
22+
{
23+
IsIncomplete = isIncomplete;
1724
}
1825

19-
public CompletionList(IEnumerable<CompletionItem> items, bool isIncomplete) : this(items)
26+
public CompletionList(params CompletionItem[] items) : base(items) { }
27+
public CompletionList(bool isIncomplete, params CompletionItem[] items) : base(items)
2028
{
2129
IsIncomplete = isIncomplete;
2230
}
@@ -30,7 +38,7 @@ public CompletionList(IEnumerable<CompletionItem> items, bool isIncomplete) : th
3038
/// <summary>
3139
/// The completion items.
3240
/// </summary>
33-
public IEnumerable<CompletionItem> Items { get; }
41+
public IEnumerable<CompletionItem> Items => this;
3442

3543
public static implicit operator CompletionList(CompletionItem[] items)
3644
{
@@ -49,7 +57,7 @@ public static implicit operator CompletionList(List<CompletionItem> items)
4957

5058
public static implicit operator CompletionItem[] (CompletionList list)
5159
{
52-
return list.Items.ToArray();
60+
return list.ToArray();
5361
}
5462
}
5563
}

src/Protocol/Models/ICanBeResolved.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ public interface ICanBeResolved
1414
[Optional]
1515
JToken Data { get; set; }
1616
}
17-
}
17+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using OmniSharp.Extensions.JsonRpc;
2+
3+
namespace OmniSharp.Extensions.LanguageServer.Protocol.Models
4+
{
5+
/// <summary>
6+
/// Common interface for types that support resolution.
7+
/// </summary>
8+
/// <typeparam name="T"></typeparam>
9+
public interface ICanBeResolvedHandler<T> : IRequestHandler<T, T>
10+
where T : ICanBeResolved
11+
{
12+
/// <summary>
13+
/// Method that determines if a handler can be used to resolve this one
14+
/// </summary>
15+
/// <param name="value"></param>
16+
/// <returns></returns>
17+
bool CanResolve(T value);
18+
}
19+
}

src/Protocol/Serialization/Converters/CompletionListConverter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using Newtonsoft.Json;
45
using Newtonsoft.Json.Linq;
56
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
@@ -13,7 +14,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
1314
var v = value as CompletionList;
1415
if (!v.IsIncomplete)
1516
{
16-
serializer.Serialize(writer, v.Items);
17+
serializer.Serialize(writer, v.Items.ToArray());
1718
return;
1819
}
1920

src/Server/Abstractions/ILspHandlerDescriptor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using OmniSharp.Extensions.JsonRpc;
33
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
44

@@ -15,5 +15,6 @@ public interface ILspHandlerDescriptor : IHandlerDescriptor
1515
void SetCapability(object instance);
1616
bool IsDynamicCapability { get; }
1717
bool AllowsDynamicRegistration { get; }
18+
Type CanBeResolvedHandlerType { get; }
1819
}
1920
}

src/Server/HandlerDescriptor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ public HandlerDescriptor(string method, string key, IJsonRpcHandler handler, Typ
2323
Params = @params;
2424
RegistrationType = registrationType;
2525
CapabilityType = capabilityType;
26+
if (@params != null && typeof(ICanBeResolved).GetTypeInfo().IsAssignableFrom(@params))
27+
{
28+
CanBeResolvedHandlerType = typeof(ICanBeResolvedHandler<>).MakeGenericType(@params);
29+
}
2630
}
2731

2832
public IJsonRpcHandler Handler { get; }
@@ -80,6 +84,7 @@ public void SetCapability(object instance)
8084

8185
public bool IsDynamicCapability => typeof(DynamicCapability).GetTypeInfo().IsAssignableFrom(CapabilityType);
8286
public bool AllowsDynamicRegistration { get; private set; }
87+
public Type CanBeResolvedHandlerType { get; }
8388

8489
public void Dispose()
8590
{

src/Server/Matchers/ResolveCommandMatcher.cs

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Linq;
3+
using System.Reflection;
34
using Microsoft.Extensions.Logging;
45
using Newtonsoft.Json.Linq;
56
using OmniSharp.Extensions.LanguageServer.Protocol;
@@ -11,6 +12,7 @@ namespace OmniSharp.Extensions.LanguageServer.Server.Matchers
1112
public class ResolveCommandMatcher : IHandlerMatcher, IHandlerPostProcessorMatcher, IHandlerPostProcessor
1213
{
1314
private readonly ILogger _logger;
15+
internal static string PrivateHandlerTypeName = "$$___handlerType___$$";
1416

1517
public ResolveCommandMatcher(ILogger logger)
1618
{
@@ -27,17 +29,32 @@ public IEnumerable<ILspHandlerDescriptor> FindHandler(object parameters, IEnumer
2729
{
2830
if (parameters is ICanBeResolved canBeResolved)
2931
{
30-
var handlerType = canBeResolved.Data?.Value<string>("handlerType");
32+
var handlerType = canBeResolved.Data?.Value<string>(PrivateHandlerTypeName);
3133
if (string.IsNullOrWhiteSpace(handlerType))
3234
{
33-
var descriptor = descriptors.FirstOrDefault();
35+
foreach (var descriptor in descriptors)
36+
{
37+
if (descriptor.CanBeResolvedHandlerType?.GetTypeInfo().IsAssignableFrom(descriptor.Handler.GetType()) == true)
38+
{
39+
var method = typeof(ResolveCommandMatcher).GetTypeInfo()
40+
.GetMethod(nameof(CanResolve), BindingFlags.NonPublic | BindingFlags.Static)
41+
.MakeGenericMethod(descriptor.Params);
42+
if ((bool)method.Invoke(null, new[] { descriptor.Handler, parameters }))
43+
{
44+
yield return descriptor;
45+
yield break;
46+
}
47+
}
48+
}
49+
50+
var descriptor2 = descriptors.FirstOrDefault();
3451
_logger.LogTrace(
3552
"Resolve {Method} was called, but data did not have handle type defined. Using Handler {HandlerType}",
36-
descriptor?.Method,
37-
descriptor?.Handler.GetType().FullName
53+
descriptor2?.Method,
54+
descriptor2?.Handler.GetType().FullName
3855
);
3956

40-
yield return descriptor;
57+
yield return descriptor2;
4158
yield break;
4259
}
4360
foreach (var descriptor in descriptors)
@@ -54,6 +71,12 @@ public IEnumerable<ILspHandlerDescriptor> FindHandler(object parameters, IEnumer
5471
}
5572
}
5673

74+
private static bool CanResolve<T>(ICanBeResolvedHandler<T> handler, T value)
75+
where T : ICanBeResolved
76+
{
77+
return handler.CanResolve(value);
78+
}
79+
5780
public IEnumerable<IHandlerPostProcessor> FindPostProcessor(ILspHandlerDescriptor descriptor, object parameters, object response)
5881
{
5982
if (descriptor.Method == DocumentNames.CodeLens || descriptor.Method == DocumentNames.Completion)
@@ -67,7 +90,9 @@ public IEnumerable<IHandlerPostProcessor> FindPostProcessor(ILspHandlerDescripto
6790

6891
public object Process(ILspHandlerDescriptor descriptor, object parameters, object response)
6992
{
70-
if (response is IEnumerable<ICanBeResolved> canBeResolveds)
93+
// Only pin the handler type, if we know the source handler (codelens) is also the resolver.
94+
if (response is IEnumerable<ICanBeResolved> canBeResolveds &&
95+
descriptor.CanBeResolvedHandlerType.GetTypeInfo().IsAssignableFrom(descriptor.Handler.GetType()))
7196
{
7297
_logger.LogTrace("Updating Resolve items with wrapped data for {Method}:{Handler}",
7398
descriptor.Method,
@@ -77,10 +102,10 @@ public object Process(ILspHandlerDescriptor descriptor, object parameters, objec
77102
// Originally we were going to change Data to be a JObject instead of JToken
78103
// This allows us to leave data alone by simply wrapping it
79104
// Since we're always going to intercept these items, we can control this.
80-
item.Data = new JObject(new {
81-
data = item.Data,
82-
handlerType = descriptor.HandlerType.FullName
83-
});
105+
var data = new JObject();
106+
data["data"] = item.Data;
107+
data[PrivateHandlerTypeName] = descriptor.Handler.GetType().FullName;
108+
item.Data = data;
84109
}
85110
}
86111
return response;

test/Lsp.Tests/Matchers/ExecuteCommandHandlerMatcherTests.cs

Lines changed: 0 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Linq;
44
using FluentAssertions;
55
using Microsoft.Extensions.Logging;
6-
using Newtonsoft.Json.Linq;
76
using NSubstitute;
87
using OmniSharp.Extensions.LanguageServer;
98
using OmniSharp.Extensions.LanguageServer.Protocol;
@@ -78,115 +77,4 @@ public void Should_Return_Handler_Descriptor()
7877
result.Should().Contain(x => x.Method == "workspace/executeCommand");
7978
}
8079
}
81-
public class ResolveCommandMatcherTests
82-
{
83-
private readonly ILogger _logger;
84-
85-
public ResolveCommandMatcherTests()
86-
{
87-
_logger = Substitute.For<ILogger>();
88-
}
89-
90-
[Fact]
91-
public void Should_Not_Return_Null()
92-
{
93-
// Given
94-
var handlerDescriptors = Enumerable.Empty<ILspHandlerDescriptor>();
95-
var handlerMatcher = new ResolveCommandMatcher(_logger);
96-
97-
// When
98-
var result = handlerMatcher.FindHandler(1, handlerDescriptors);
99-
100-
// Then
101-
result.Should().NotBeNull();
102-
}
103-
104-
[Fact]
105-
public void Should_Return_Empty_Descriptor()
106-
{
107-
// Given
108-
var handlerDescriptors = Enumerable.Empty<ILspHandlerDescriptor>();
109-
var handlerMatcher = new ResolveCommandMatcher(_logger);
110-
111-
// When
112-
var result = handlerMatcher.FindHandler(1, handlerDescriptors);
113-
114-
// Then
115-
result.Should().BeEmpty();
116-
}
117-
118-
[Fact]
119-
public void Should_Return_CodeLensResolve_Descriptor()
120-
{
121-
// Given
122-
var handlerMatcher = new ResolveCommandMatcher(_logger);
123-
var resolveHandler = Substitute.For<ICodeLensResolveHandler>();
124-
var resolveHandler2 = Substitute.For<ICodeLensResolveHandler>();
125-
126-
// When
127-
var result = handlerMatcher.FindHandler(new CodeLens() {
128-
Data = JToken.FromObject(new { handlerType = typeof(ICodeLensResolveHandler).FullName, data = new { a = 1 } })
129-
},
130-
new List<HandlerDescriptor> {
131-
new HandlerDescriptor(DocumentNames.CodeLensResolve,
132-
"Key",
133-
resolveHandler,
134-
resolveHandler.GetType(),
135-
typeof(CodeLens),
136-
null,
137-
null,
138-
() => { }),
139-
new HandlerDescriptor(DocumentNames.CodeLensResolve,
140-
"Key2",
141-
resolveHandler2,
142-
typeof(ICodeLensResolveHandler),
143-
typeof(CodeLens),
144-
null,
145-
null,
146-
() => { }),
147-
})
148-
.ToArray();
149-
150-
// Then
151-
result.Should().NotBeNullOrEmpty();
152-
result.Should().Contain(x => x.Handler == resolveHandler2);
153-
}
154-
155-
[Fact]
156-
public void Should_Return_CompletionResolve_Descriptor()
157-
{
158-
// Given
159-
var handlerMatcher = new ResolveCommandMatcher(_logger);
160-
var resolveHandler = Substitute.For<ICompletionResolveHandler>();
161-
var resolveHandler2 = Substitute.For<ICompletionResolveHandler>();
162-
163-
// When
164-
var result = handlerMatcher.FindHandler(new CodeLens() {
165-
Data = JToken.FromObject(new { handlerType = typeof(ICompletionResolveHandler).FullName, data = new { a = 1 } })
166-
},
167-
new List<HandlerDescriptor> {
168-
new HandlerDescriptor(DocumentNames.CompletionResolve,
169-
"Key",
170-
resolveHandler,
171-
resolveHandler.GetType(),
172-
typeof(CompletionItem),
173-
null,
174-
null,
175-
() => { }),
176-
new HandlerDescriptor(DocumentNames.CompletionResolve,
177-
"Key2",
178-
resolveHandler2,
179-
typeof(ICompletionResolveHandler),
180-
typeof(CompletionItem),
181-
null,
182-
null,
183-
() => { }),
184-
})
185-
.ToArray();
186-
187-
// Then
188-
result.Should().NotBeNullOrEmpty();
189-
result.Should().Contain(x => x.Handler == resolveHandler2);
190-
}
191-
}
19280
}

0 commit comments

Comments
 (0)