// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.Text.Adornments;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
///
/// Handle a completion request.
///
[Shared]
[ExportLspMethod(LSP.Methods.TextDocumentCompletionName)]
internal class CompletionHandler : IRequestHandler
{
public async Task HandleRequestAsync(Solution solution, LSP.CompletionParams request, LSP.ClientCapabilities clientCapabilities,
CancellationToken cancellationToken)
{
var document = solution.GetDocumentFromURI(request.TextDocument.Uri);
if (document == null)
{
return Array.Empty();
}
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
// Filter out unimported types for now as there are two issues with providing them:
// 1. LSP client does not currently provide a way to provide detail text on the completion item to show the namespace.
// https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1076759
// 2. We need to figure out how to provide the text edits along with the completion item or provide them in the resolve request.
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/985860/
var documentOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var completionOptions = documentOptions.WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, false);
var completionService = document.Project.LanguageServices.GetService();
var list = await completionService.GetCompletionsAsync(document, position, options: completionOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
if (list == null)
{
return Array.Empty();
}
var lspVSClientCapability = clientCapabilities?.HasVisualStudioLspCapability() == true;
return list.Items.Select(item => CreateLSPCompletionItem(request, item, lspVSClientCapability)).ToArray();
// local functions
static LSP.CompletionItem CreateLSPCompletionItem(LSP.CompletionParams request, CompletionItem item, bool useVSCompletionItem)
{
if (useVSCompletionItem)
{
var vsCompletionItem = CreateCompletionItem(request, item);
vsCompletionItem.Icon = new ImageElement(item.Tags.GetFirstGlyph().GetImageId());
return vsCompletionItem;
}
else
{
var roslynCompletionItem = CreateCompletionItem(request, item);
roslynCompletionItem.Tags = item.Tags.ToArray();
return roslynCompletionItem;
}
}
static TCompletionItem CreateCompletionItem(LSP.CompletionParams request, CompletionItem item) where TCompletionItem : LSP.CompletionItem, new()
=> new TCompletionItem
{
Label = item.DisplayTextPrefix + item.DisplayText + item.DisplayTextSuffix,
InsertText = item.Properties.ContainsKey("InsertionText") ? item.Properties["InsertionText"] : item.DisplayText,
SortText = item.SortText,
FilterText = item.FilterText,
Kind = GetCompletionKind(item.Tags),
Data = new CompletionResolveData { CompletionParams = request, DisplayText = item.DisplayText }
};
}
private static LSP.CompletionItemKind GetCompletionKind(ImmutableArray tags)
{
foreach (var tag in tags)
{
if (ProtocolConversions.RoslynTagToCompletionItemKind.TryGetValue(tag, out var completionItemKind))
{
return completionItemKind;
}
}
return LSP.CompletionItemKind.Text;
}
}
}