-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathCompletionHandler.cs
259 lines (225 loc) · 9.8 KB
/
CompletionHandler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Server;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MSBuildProjectTools.LanguageServer.Handlers
{
using CompletionProviders;
using Documents;
using LanguageServer.CustomProtocol;
using SemanticModel;
using Utilities;
using Position = Position;
/// <summary>
/// Handler for completion requests.
/// </summary>
public sealed class CompletionHandler
: Handler, ICustomCompletionHandler
{
private readonly IEnumerable<ICompletionProvider> _completionProviders;
/// <summary>
/// Create a new <see cref="CompletionHandler"/>.
/// </summary>
/// <param name="server">
/// The language server.
/// </param>
/// <param name="completionProviders">
/// Available completion providers.
/// </param>
/// <param name="workspace">
/// The document workspace.
/// </param>
/// <param name="logger">
/// The application logger.
/// </param>
public CompletionHandler(ILanguageServer server, IEnumerable<ICompletionProvider> completionProviders, Workspace workspace, ILogger logger)
: base(server, logger)
{
if (workspace == null)
throw new ArgumentNullException(nameof(workspace));
_completionProviders = completionProviders;
Workspace = workspace;
}
/// <summary>
/// The document workspace.
/// </summary>
Workspace Workspace { get; }
/// <summary>
/// The LSP document selector that describes documents the handler is interested in.
/// </summary>
DocumentSelector DocumentSelector { get; } = new DocumentSelector(
new DocumentFilter
{
Pattern = "**/*.*",
Language = "msbuild",
Scheme = "file"
},
new DocumentFilter
{
Pattern = "**/*.*proj",
Language = "xml",
Scheme = "file"
},
new DocumentFilter
{
Pattern = "**/*.props",
Language = "xml",
Scheme = "file"
},
new DocumentFilter
{
Pattern = "**/*.targets",
Language = "xml",
Scheme = "file"
}
);
/// <summary>
/// Registration options for handling completion-request events.
/// </summary>
CompletionRegistrationOptions CompletionRegistrationOptions
{
get => new CompletionRegistrationOptions
{
DocumentSelector = DocumentSelector,
TriggerCharacters = new string[] {
"<", // Element
},
ResolveProvider = false
};
}
/// <summary>
/// Should the handler return an empty <see cref="CompletionList"/>s instead of <c>null</c>?
/// </summary>
bool ReturnEmptyCompletionLists => Workspace.Configuration.EnableExperimentalFeatures.Contains("empty-completion-lists");
/// <summary>
/// A <see cref="CompletionList"/> (or <c>null</c>) representing no completions.
/// </summary>
CompletionList NoCompletions => ReturnEmptyCompletionLists ? new CompletionList(Enumerable.Empty<CompletionItem>(), isIncomplete: false) : null;
/// <summary>
/// Called when completions are requested.
/// </summary>
/// <param name="parameters">
/// The request parameters.
/// </param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> that can be used to cancel the request.
/// </param>
/// <returns>
/// A <see cref="Task"/> representing the operation whose result is the completion list or <c>null</c> if no completions are provided.
/// </returns>
async Task<CompletionList> OnCompletion(CompletionParams parameters, CancellationToken cancellationToken)
{
ProjectDocument projectDocument = await Workspace.GetProjectDocument(parameters.TextDocument.Uri);
XmlLocation location;
bool isIncomplete = false;
var completionItems = new List<CompletionItem>();
using (await projectDocument.Lock.ReaderLockAsync(cancellationToken))
{
Position position = parameters.Position.ToNative();
Log.Verbose("Completion requested for {Position:l}", position);
if (!projectDocument.HasXml)
{
Log.Verbose("Completion short-circuited; project document does not have valid XML.");
return NoCompletions;
}
location = projectDocument.XmlLocator.Inspect(position);
if (location == null)
{
Log.Verbose("Completion short-circuited; nothing interesting at {Position:l}", position);
return NoCompletions;
}
Log.Verbose("Completion will target {XmlLocation:l}", location);
string triggerCharacters = null;
if (parameters.Context != null && parameters.Context.TriggerKind == CompletionTriggerKind.TriggerCharacter)
triggerCharacters = parameters.Context.TriggerCharacter;
var allProviderCompletions =
_completionProviders.Select(
provider => provider.ProvideCompletionsAsync(location, projectDocument, triggerCharacters, cancellationToken)
)
.ToList();
while (allProviderCompletions.Count > 0)
{
var providerCompletionTask = await Task.WhenAny(allProviderCompletions);
allProviderCompletions.Remove(providerCompletionTask);
try
{
CompletionList providerCompletions = await providerCompletionTask;
if (providerCompletions != null)
{
completionItems.AddRange(providerCompletions.Items);
isIncomplete |= providerCompletions.IsIncomplete; // If any provider returns incomplete results, VSCode will need to ask again as the user continues to type.
}
}
catch (AggregateException aggregateSuggestionError)
{
foreach (Exception suggestionError in aggregateSuggestionError.Flatten().InnerExceptions)
Log.Error(suggestionError, "Failed to provide completions.");
return NoCompletions;
}
catch (Exception suggestionError)
{
Log.Error(suggestionError, "Failed to provide completions.");
return NoCompletions;
}
}
}
Log.Verbose("Offering a total of {CompletionCount} completions for {Location:l} (Exhaustive: {Exhaustive}).", completionItems.Count, location, !isIncomplete);
if (completionItems.Count == 0 && !isIncomplete)
return NoCompletions;
var completionList = new CompletionList(completionItems, isIncomplete);
return completionList;
}
/// <summary>
/// Handle a request for completion.
/// </summary>
/// <param name="parameters">
/// The request parameters.
/// </param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> that can be used to cancel the request.
/// </param>
/// <returns>
/// A <see cref="Task"/> representing the operation whose result is the completion list or <c>null</c> if no completions are provided.
/// </returns>
async Task<CompletionList> IRequestHandler<CompletionParams, CompletionList>.Handle(CompletionParams parameters, CancellationToken cancellationToken)
{
if (parameters == null)
throw new ArgumentNullException(nameof(parameters));
using (BeginOperation("OnCompletion"))
{
try
{
return await OnCompletion(parameters, cancellationToken);
}
catch (Exception unexpectedError)
{
Log.Error(unexpectedError, "Unhandled exception in {Method:l}.", "OnCompletion");
return null;
}
}
}
/// <summary>
/// Get registration options for handling completion requests.
/// </summary>
/// <returns>
/// The registration options.
/// </returns>
CompletionRegistrationOptions IRegistration<CompletionRegistrationOptions>.GetRegistrationOptions() => CompletionRegistrationOptions;
/// <summary>
/// Called to inform the handler of the language server's completion capabilities.
/// </summary>
/// <param name="capabilities">
/// A <see cref="CompletionCapability"/> data structure representing the capabilities.
/// </param>
void ICapability<CompletionCapability>.SetCapability(CompletionCapability capabilities)
{
}
}
}