Skip to content

Commit 8d24cf8

Browse files
Merge pull request #1582 from dmgonch/dmgonch/fix/RequestHandlersExtensibility
Fixes bugs when there are multiple handlers are defined for a language for a given request
2 parents 0d90be9 + db413a2 commit 8d24cf8

10 files changed

Lines changed: 411 additions & 21 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace OmniSharp.Models
2+
{
3+
internal interface ICanBeEmptyResponse
4+
{
5+
bool IsEmpty { get; }
6+
}
7+
}

src/OmniSharp.Abstractions/Models/v1/GotoDefinition/GotoDefinitionResponse.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33

44
namespace OmniSharp.Models.GotoDefinition
55
{
6-
public class GotoDefinitionResponse
6+
public class GotoDefinitionResponse : ICanBeEmptyResponse
77
{
88
public string FileName { get; set; }
99
[JsonConverter(typeof(ZeroBasedIndexConverter))]
1010
public int Line { get; set; }
1111
[JsonConverter(typeof(ZeroBasedIndexConverter))]
1212
public int Column { get; set; }
1313
public MetadataSource MetadataSource { get; set; }
14+
public bool IsEmpty => FileName == null || FileName == string.Empty;
1415
}
1516
}

src/OmniSharp.Host/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
[assembly: InternalsVisibleTo("OmniSharp.Http.Tests")]
44
[assembly: InternalsVisibleTo("OmniSharp.MSBuild.Tests")]
55
[assembly: InternalsVisibleTo("OmniSharp.Roslyn.CSharp.Tests")]
6+
[assembly: InternalsVisibleTo("OmniSharp.Stdio.Tests")]
67
[assembly: InternalsVisibleTo("TestUtility")]

src/OmniSharp.Host/Endpoint/EndpointHandler.cs

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class EndpointHandler<TRequest, TResponse> : EndpointHandler
4848
{
4949
private readonly CompositionHost _host;
5050
private readonly IPredicateHandler _languagePredicateHandler;
51-
private readonly Lazy<Task<Dictionary<string, ExportHandler<TRequest, TResponse>>>> _exports;
51+
private readonly Lazy<Task<Dictionary<string, ExportHandler<TRequest, TResponse>[]>>> _exports;
5252
private readonly OmniSharpWorkspace _workspace;
5353
private readonly bool _hasLanguageProperty;
5454
private readonly bool _hasFileNameProperty;
@@ -71,10 +71,10 @@ public EndpointHandler(IPredicateHandler languagePredicateHandler, CompositionHo
7171
_canBeAggregated = typeof(IAggregateResponse).IsAssignableFrom(metadata.ResponseType);
7272
_updateBufferHandler = updateBufferHandler;
7373

74-
_exports = new Lazy<Task<Dictionary<string, ExportHandler<TRequest, TResponse>>>>(() => LoadExportHandlers(handlers));
74+
_exports = new Lazy<Task<Dictionary<string, ExportHandler<TRequest, TResponse>[]>>>(() => LoadExportHandlers(handlers));
7575
}
7676

77-
private Task<Dictionary<string, ExportHandler<TRequest, TResponse>>> LoadExportHandlers(IEnumerable<Lazy<IRequestHandler, OmniSharpRequestHandlerMetadata>> handlers)
77+
private Task<Dictionary<string, ExportHandler<TRequest, TResponse>[]>> LoadExportHandlers(IEnumerable<Lazy<IRequestHandler, OmniSharpRequestHandlerMetadata>> handlers)
7878
{
7979
var interfaceHandlers = handlers
8080
.Select(export => new RequestHandlerExportHandler<TRequest, TResponse>(export.Metadata.Language, (IRequestHandler<TRequest, TResponse>)export.Value))
@@ -84,9 +84,13 @@ private Task<Dictionary<string, ExportHandler<TRequest, TResponse>>> LoadExportH
8484
.Select(plugin => new PluginExportHandler<TRequest, TResponse>(EndpointName, plugin))
8585
.Cast<ExportHandler<TRequest, TResponse>>();
8686

87+
// Group handlers by language and sort each group for consistency
8788
return Task.FromResult(interfaceHandlers
88-
.Concat(plugins)
89-
.ToDictionary(export => export.Language));
89+
.Concat(plugins)
90+
.GroupBy(export => export.Language, StringComparer.OrdinalIgnoreCase)
91+
.ToDictionary(
92+
group => group.Key,
93+
group => group.OrderBy(g => g).ToArray()));
9094
}
9195

9296
public string EndpointName { get; }
@@ -142,18 +146,88 @@ private Task<object> HandleLanguageRequest(string language, TRequest request, Re
142146
{
143147
if (!string.IsNullOrEmpty(language))
144148
{
145-
return HandleSingleRequest(language, request, packet);
149+
return HandleRequestForLanguage(language, request, packet);
146150
}
147151

148152
return HandleAllRequest(request, packet);
149153
}
150154

151-
private async Task<object> HandleSingleRequest(string language, TRequest request, RequestPacket packet)
155+
private async Task<IAggregateResponse> AggregateResponsesFromLanguageHandlers(ExportHandler<TRequest, TResponse>[] handlers, TRequest request)
156+
{
157+
if (!_canBeAggregated)
158+
{
159+
throw new NotSupportedException($"Must be able to aggregate responses from all handlers for {EndpointName}");
160+
}
161+
162+
IAggregateResponse aggregateResponse = null;
163+
164+
if (handlers.Length == 1)
165+
{
166+
var response = handlers[0].Handle(request);
167+
return (IAggregateResponse)await response;
168+
}
169+
else
170+
{
171+
var responses = new List<Task<TResponse>>();
172+
foreach (var handler in handlers)
173+
{
174+
responses.Add(handler.Handle(request));
175+
}
176+
177+
foreach (IAggregateResponse response in await Task.WhenAll(responses))
178+
{
179+
if (aggregateResponse != null)
180+
{
181+
aggregateResponse = aggregateResponse.Merge(response);
182+
}
183+
else
184+
{
185+
aggregateResponse = response;
186+
}
187+
}
188+
}
189+
190+
return aggregateResponse;
191+
}
192+
193+
private async Task<object> GetFirstNotEmptyResponseFromHandlers(ExportHandler<TRequest, TResponse>[] handlers, TRequest request)
194+
{
195+
var responses = new List<Task<TResponse>>();
196+
foreach (var handler in handlers)
197+
{
198+
responses.Add(handler.Handle(request));
199+
}
200+
201+
foreach (object response in await Task.WhenAll(responses))
202+
{
203+
var canBeEmptyResponse = response as ICanBeEmptyResponse;
204+
if (canBeEmptyResponse != null)
205+
{
206+
if (!canBeEmptyResponse.IsEmpty)
207+
{
208+
return response;
209+
}
210+
}
211+
else if (response != null)
212+
{
213+
return response;
214+
}
215+
}
216+
217+
return null;
218+
}
219+
220+
private async Task<object> HandleRequestForLanguage(string language, TRequest request, RequestPacket packet)
152221
{
153222
var exports = await _exports.Value;
154-
if (exports.TryGetValue(language, out var handler))
223+
if (exports.TryGetValue(language, out var handlers))
155224
{
156-
return await handler.Handle(request);
225+
if (_canBeAggregated)
226+
{
227+
return await AggregateResponsesFromLanguageHandlers(handlers, request);
228+
}
229+
230+
return await GetFirstNotEmptyResponseFromHandlers(handlers, request);
157231
}
158232

159233
throw new NotSupportedException($"{language} does not support {EndpointName}");
@@ -169,11 +243,10 @@ private async Task<object> HandleAllRequest(TRequest request, RequestPacket pack
169243
var exports = await _exports.Value;
170244

171245
IAggregateResponse aggregateResponse = null;
172-
173-
var responses = new List<Task<TResponse>>();
174-
foreach (var handler in exports.Values)
246+
var responses = new List<Task<IAggregateResponse>>();
247+
foreach (var export in exports)
175248
{
176-
responses.Add(handler.Handle(request));
249+
responses.Add(AggregateResponsesFromLanguageHandlers(export.Value, request));
177250
}
178251

179252
foreach (IAggregateResponse exportResponse in await Task.WhenAll(responses))
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
using System;
12
using System.Threading.Tasks;
23

34
namespace OmniSharp.Endpoint.Exports
45
{
5-
abstract class ExportHandler<TRequest, TResponse>
6+
abstract class ExportHandler<TRequest, TResponse> : IComparable<ExportHandler<TRequest, TResponse>>
67
{
78
protected ExportHandler(string language)
89
{
910
Language = language;
1011
}
1112

1213
public string Language { get; }
14+
15+
public abstract int CompareTo(ExportHandler<TRequest, TResponse> other);
1316
public abstract Task<TResponse> Handle(TRequest request);
1417
}
1518
}

src/OmniSharp.Host/Endpoint/Exports/PluginExportHandler.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ public PluginExportHandler(string endpoint, Plugin plugin) : base(plugin.Config.
1414
_plugin = plugin;
1515
}
1616

17+
public override int CompareTo(ExportHandler<TRequest, TResponse> other)
18+
{
19+
var otherPlugin = other as PluginExportHandler<TRequest, TResponse>;
20+
if (otherPlugin == null)
21+
{
22+
return 1;
23+
}
24+
25+
return _plugin.Key.CompareTo(otherPlugin._plugin.Key);
26+
}
27+
1728
public override Task<TResponse> Handle(TRequest request)
1829
{
1930
return _plugin.Handle<TRequest, TResponse>(_endpoint, request);

src/OmniSharp.Host/Endpoint/Exports/RequestHandlerExportHandler.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ public RequestHandlerExportHandler(string language, IRequestHandler<TRequest, TR
1313
_handler = handler;
1414
}
1515

16+
public override int CompareTo(ExportHandler<TRequest, TResponse> other)
17+
{
18+
var otherHandler = other as RequestHandlerExportHandler<TRequest, TResponse>;
19+
if (otherHandler == null)
20+
{
21+
return 1;
22+
}
23+
24+
return _handler.GetType().ToString().CompareTo(otherHandler._handler.GetType().ToString());
25+
}
26+
1627
public override Task<TResponse> Handle(TRequest request)
1728
{
1829
return _handler.Handle(request);

src/OmniSharp.Host/Mef/MefValueProvider.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ namespace OmniSharp.Mef
77
internal class MefValueProvider<T> : ExportDescriptorProvider
88
{
99
private readonly T _item;
10+
private readonly IDictionary<string, object> _metadata;
1011

11-
public MefValueProvider(T item)
12+
public MefValueProvider(T item, IDictionary<string, object> metadata)
1213
{
1314
_item = item;
15+
_metadata = metadata;
1416
}
1517

1618
public override IEnumerable<ExportDescriptorPromise> GetExportDescriptors(CompositionContract contract, DependencyAccessor descriptorAccessor)
@@ -19,16 +21,16 @@ public override IEnumerable<ExportDescriptorPromise> GetExportDescriptors(Compos
1921
{
2022
yield return new ExportDescriptorPromise(contract, string.Empty, true,
2123
() => Enumerable.Empty<CompositionDependency>(),
22-
deps => ExportDescriptor.Create((context, operation) => _item, new Dictionary<string, object>()));
24+
deps => ExportDescriptor.Create((context, operation) => _item, _metadata ?? new Dictionary<string, object>()));
2325
}
2426
}
2527
}
2628

2729
internal static class MefValueProvider
2830
{
29-
public static MefValueProvider<T> From<T>(T value)
31+
public static MefValueProvider<T> From<T>(T value, IDictionary<string, object> metadata = null)
3032
{
31-
return new MefValueProvider<T>(value);
33+
return new MefValueProvider<T>(value, metadata);
3234
}
3335
}
3436
}

0 commit comments

Comments
 (0)