Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move LSP Completion to the new CompletionService #2074

Merged
merged 4 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class CompletionItem
/// The format of <see cref="InsertText"/>. This applies to both <see cref="InsertText"/> and
/// <see cref="TextEdit"/>.<see cref="LinePositionSpanTextChange.NewText"/>.
/// </summary>
public InsertTextFormat? InsertTextFormat { get; set; }
public InsertTextFormat InsertTextFormat { get; set; }

/// <summary>
/// An edit which is applied to a document when selecting this completion. When an edit is provided the value of
Expand Down
Original file line number Diff line number Diff line change
@@ -1,149 +1,163 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Document;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
using OmniSharp.Models.AutoComplete;
using OmniSharp.Models.v1.Completion;

using CompletionTriggerKind = OmniSharp.Extensions.LanguageServer.Protocol.Models.CompletionTriggerKind;
using OmnisharpCompletionTriggerKind = OmniSharp.Models.v1.Completion.CompletionTriggerKind;
using CompletionItemKind = OmniSharp.Extensions.LanguageServer.Protocol.Models.CompletionItemKind;
using OmnisharpCompletionItemKind = OmniSharp.Models.v1.Completion.CompletionItemKind;
using CompletionItem = OmniSharp.Extensions.LanguageServer.Protocol.Models.CompletionItem;
using OmnisharpCompletionItem = OmniSharp.Models.v1.Completion.CompletionItem;
using CompletionItemTag = OmniSharp.Extensions.LanguageServer.Protocol.Models.CompletionItemTag;
using OmnisharpCompletionItemTag = OmniSharp.Models.v1.Completion.CompletionItemTag;
using InsertTextFormat = OmniSharp.Extensions.LanguageServer.Protocol.Models.InsertTextFormat;
using OmnisharpInsertTextFormat = OmniSharp.Models.v1.Completion.InsertTextFormat;

#nullable enable

namespace OmniSharp.LanguageServerProtocol.Handlers
{
class OmniSharpCompletionHandler : CompletionHandlerBase
{
public static IEnumerable<IJsonRpcHandler> Enumerate(RequestHandlers handlers)
{

foreach (var (selector, handler) in handlers
.OfType<Mef.IRequestHandler<AutoCompleteRequest, IEnumerable<AutoCompleteResponse>>>())
if (handler != null)
yield return new OmniSharpCompletionHandler(handler, selector);
}

private readonly Mef.IRequestHandler<AutoCompleteRequest, IEnumerable<AutoCompleteResponse>> _autoCompleteHandler;
private readonly DocumentSelector _documentSelector;

private static readonly IDictionary<string, CompletionItemKind> _kind = new Dictionary<string, CompletionItemKind>{
// types
{ "Class", CompletionItemKind.Class },
{ "Delegate", CompletionItemKind.Function },
{ "Enum", CompletionItemKind.Enum },
{ "Interface", CompletionItemKind.Interface },
{ "Struct", CompletionItemKind.Struct },

// variables
{ "Local", CompletionItemKind.Variable },
{ "Parameter", CompletionItemKind.Variable },
{ "RangeVariable", CompletionItemKind.Variable },

// members
{ "Const", CompletionItemKind.Constant },
{ "EnumMember", CompletionItemKind.Enum },
{ "Event", CompletionItemKind.Event },
{ "Field", CompletionItemKind.Field },
{ "Method", CompletionItemKind.Method },
{ "Property", CompletionItemKind.Property },

// other stuff
{ "Label", CompletionItemKind.Text },
{ "Keyword", CompletionItemKind.Keyword },
{ "Namespace", CompletionItemKind.Module }
};

private static CompletionItemKind GetCompletionItemKind(string key)
{
if (string.IsNullOrEmpty(key))
{
return CompletionItemKind.Property;
}
if (_kind.TryGetValue(key, out var completionItemKind))
foreach (var (selector, completionHandler, completionResolveHandler) in handlers
.OfType<Mef.IRequestHandler<CompletionRequest, CompletionResponse>,
Mef.IRequestHandler<CompletionResolveRequest, CompletionResolveResponse>>())
{
return completionItemKind;
if (completionHandler != null && completionResolveHandler != null)
yield return new OmniSharpCompletionHandler(completionHandler, completionResolveHandler, selector);
}
return CompletionItemKind.Property;
}

public OmniSharpCompletionHandler(Mef.IRequestHandler<AutoCompleteRequest, IEnumerable<AutoCompleteResponse>> autoCompleteHandler, DocumentSelector documentSelector)
private readonly Mef.IRequestHandler<CompletionRequest, CompletionResponse> _completionHandler;
private readonly Mef.IRequestHandler<CompletionResolveRequest, CompletionResolveResponse> _completionResolveHandler;
private readonly DocumentSelector _documentSelector;

public OmniSharpCompletionHandler(
Mef.IRequestHandler<CompletionRequest, CompletionResponse> completionHandler,
Mef.IRequestHandler<CompletionResolveRequest, CompletionResolveResponse> completionResolveHandler,
DocumentSelector documentSelector)
{
_autoCompleteHandler = autoCompleteHandler;
_completionHandler = completionHandler;
_completionResolveHandler = completionResolveHandler;
_documentSelector = documentSelector;
}

public async override Task<CompletionList> Handle(CompletionParams request, CancellationToken token)
public override async Task<CompletionList> Handle(CompletionParams request, CancellationToken token)
{
var omnisharpRequest = new AutoCompleteRequest()
var omnisharpRequest = new CompletionRequest()
{
FileName = Helpers.FromUri(request.TextDocument.Uri),
Column = Convert.ToInt32(request.Position.Character),
Line = Convert.ToInt32(request.Position.Line),
WantKind = true,
WantDocumentationForEveryCompletionResult = true,
WantReturnType = true,
WantSnippet = Capability.CompletionItem?.SnippetSupport ?? false
CompletionTrigger = ConvertEnum<CompletionTriggerKind, OmnisharpCompletionTriggerKind>(request.Context?.TriggerKind ?? CompletionTriggerKind.Invoked),
TriggerCharacter = request.Context?.TriggerCharacter is { Length: > 0 } str ? str[0] : null
};

var omnisharpResponse = await _autoCompleteHandler.Handle(omnisharpRequest);
var omnisharpResponse = await _completionHandler.Handle(omnisharpRequest);

var completions = new Dictionary<string, List<CompletionItem>>();
foreach (var response in omnisharpResponse)
{
var isSnippet = !string.IsNullOrEmpty(response.Snippet);
var text = isSnippet ? response.Snippet : response.CompletionText;
var textFormat = isSnippet ? InsertTextFormat.Snippet : InsertTextFormat.PlainText;

var completionItem = new CompletionItem
{
Label = response.CompletionText,
Detail = string.IsNullOrEmpty(response.ReturnType) ?
response.DisplayText :
$"{response.ReturnType} {response.DisplayText}",
Documentation = response.Description,
Kind = GetCompletionItemKind(response.Kind),
InsertText = text,
InsertTextFormat = textFormat,
};

if (!completions.ContainsKey(completionItem.Label))
{
completions[completionItem.Label] = new List<CompletionItem>();
}
completions[completionItem.Label].Add(completionItem);
}
return new CompletionList(omnisharpResponse.Items.Select(ToLSPCompletionItem), isIncomplete: omnisharpResponse.IsIncomplete);
}

var result = new List<CompletionItem>();
foreach (var key in completions.Keys)
public override async Task<CompletionItem> Handle(CompletionItem request, CancellationToken cancellationToken)
{
var resolveRequest = new CompletionResolveRequest
{
var suggestion = completions[key][0];
var overloadCount = completions[key].Count - 1;

if (overloadCount > 0)
{
// indicate that there is more
suggestion = suggestion with { Detail = $"{suggestion.Detail} (+ {overloadCount} overload(s))" };
}

result.Add(suggestion);
}
Item = ToOmnisharpCompletionItem(request)
};

return new CompletionList(result);
}
var result = await _completionResolveHandler.Handle(resolveRequest);

public override Task<CompletionItem> Handle(CompletionItem request, CancellationToken cancellationToken)
{
return Task.FromResult(request);
Debug.Assert(result.Item != null);
return ToLSPCompletionItem(result.Item!);
}

protected override CompletionRegistrationOptions CreateRegistrationOptions(CompletionCapability capability, ClientCapabilities clientCapabilities)
{
return new CompletionRegistrationOptions()
{
DocumentSelector = _documentSelector,
// TODO: Come along and add a service for getting autocompletion details after the fact.
ResolveProvider = false,
TriggerCharacters = new[] {".",},
ResolveProvider = true,
TriggerCharacters = new[] { ".", " " },
};
}

private static T2 ConvertEnum<T1, T2>(T1 t1)
where T1 : struct, Enum
where T2 : struct, Enum
{
VerifyEnumsInSync(typeof(T1), typeof(T2));
// The JIT will optimize this box away
return (T2)(object)t1;
}

[Conditional("DEBUG")]
private static void VerifyEnumsInSync(Type enum1, Type enum2)
{
Debug.Assert(enum1.IsEnum);
Debug.Assert(enum2.IsEnum);

var lspValues = Enum.GetValues(enum1);
var modelValues = Enum.GetValues(enum2);
Debug.Assert(lspValues.Length == modelValues.Length);
for (int i = 0; i < lspValues.Length; i++)
{
Debug.Assert((int)lspValues.GetValue(i) == (int)modelValues.GetValue(i));
}
}

private CompletionItem ToLSPCompletionItem(OmnisharpCompletionItem omnisharpCompletionItem)
=> new CompletionItem
{
Label = omnisharpCompletionItem.Label,
Kind = ConvertEnum<OmnisharpCompletionItemKind, CompletionItemKind>(omnisharpCompletionItem.Kind),
Tags = omnisharpCompletionItem.Tags is { } tags
? Container<CompletionItemTag>.From(tags.Select(ConvertEnum<OmnisharpCompletionItemTag, CompletionItemTag>))
: null,
Detail = omnisharpCompletionItem.Detail,
Documentation = omnisharpCompletionItem.Documentation is null
? (StringOrMarkupContent?)null
: new MarkupContent { Value = omnisharpCompletionItem.Documentation, Kind = MarkupKind.Markdown },
Preselect = omnisharpCompletionItem.Preselect,
SortText = omnisharpCompletionItem.SortText,
FilterText = omnisharpCompletionItem.FilterText,
InsertTextFormat = ConvertEnum<OmnisharpInsertTextFormat, InsertTextFormat>(omnisharpCompletionItem.InsertTextFormat),
TextEdit = Helpers.ToTextEdit(omnisharpCompletionItem.TextEdit),
CommitCharacters = omnisharpCompletionItem.CommitCharacters is { } chars
? Container<string>.From(chars.Select(i => i.ToString()))
: null,
AdditionalTextEdits = omnisharpCompletionItem.AdditionalTextEdits is { } edits
? TextEditContainer.From(edits.Select(e => Helpers.ToTextEdit(e)))
: null,
Data = JToken.FromObject(omnisharpCompletionItem.Data)
};

private OmnisharpCompletionItem ToOmnisharpCompletionItem(CompletionItem completionItem)
=> new OmnisharpCompletionItem
{
Label = completionItem.Label,
Kind = ConvertEnum<CompletionItemKind, OmnisharpCompletionItemKind>(completionItem.Kind),
Tags = completionItem.Tags?.Select(ConvertEnum<CompletionItemTag, OmnisharpCompletionItemTag>).ToList(),
Detail = completionItem.Detail,
Documentation = completionItem.Documentation?.MarkupContent!.Value,
Preselect = completionItem.Preselect,
SortText = completionItem.SortText,
FilterText = completionItem.FilterText,
InsertTextFormat = ConvertEnum<InsertTextFormat, OmnisharpInsertTextFormat>(completionItem.InsertTextFormat),
TextEdit = Helpers.FromTextEdit(completionItem.TextEdit!.TextEdit),
CommitCharacters = completionItem.CommitCharacters?.Select(i => i[0]).ToList(),
AdditionalTextEdits = completionItem.AdditionalTextEdits?.Select(e => Helpers.FromTextEdit(e)).ToList(),
Data = completionItem.Data!.ToObject<int>()
};
}
}
20 changes: 15 additions & 5 deletions src/OmniSharp.LanguageServerProtocol/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,16 @@ public static TextEdit ToTextEdit(LinePositionSpanTextChange textChange)
)
};
}

public static LinePositionSpanTextChange FromTextEdit(TextEdit textEdit)
=> new LinePositionSpanTextChange
{
NewText = textEdit.NewText,
StartLine = textEdit.Range.Start.Line,
EndLine = textEdit.Range.End.Line,
StartColumn = textEdit.Range.Start.Character,
EndColumn = textEdit.Range.End.Character
};
}

public static class CommandExtensions
Expand Down Expand Up @@ -353,7 +363,7 @@ private static (T arg1, T2 arg2) ExtractArguments<T, T2>(JArray args, ISerialize
T2 arg2 = default;
if (args.Count > 1) arg2 = args[1].ToObject<T2>(serializer.JsonSerializer);

return ( arg1!, arg2! );
return (arg1!, arg2!);
}

private static (T arg1, T2 arg2, T3 arg3) ExtractArguments<T, T2, T3>(JArray args, ISerializer serializer)
Expand All @@ -369,7 +379,7 @@ private static (T arg1, T2 arg2, T3 arg3) ExtractArguments<T, T2, T3>(JArray arg
T3 arg3 = default;
if (args.Count > 2) arg3 = args[2].ToObject<T3>(serializer.JsonSerializer);

return ( arg1!, arg2!, arg3! );
return (arg1!, arg2!, arg3!);
}

private static (T arg1, T2 arg2, T3 arg3, T4 arg4) ExtractArguments<T, T2, T3, T4>(JArray args, ISerializer serializer)
Expand All @@ -388,7 +398,7 @@ private static (T arg1, T2 arg2, T3 arg3, T4 arg4) ExtractArguments<T, T2, T3, T
T4 arg4 = default;
if (args.Count > 3) arg4 = args[3].ToObject<T4>(serializer.JsonSerializer);

return ( arg1!, arg2!, arg3!, arg4! );
return (arg1!, arg2!, arg3!, arg4!);
}

private static (T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) ExtractArguments<T, T2, T3, T4, T5>(JArray args, ISerializer serializer)
Expand All @@ -410,7 +420,7 @@ private static (T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) ExtractArguments<T,
T5 arg5 = default;
if (args.Count > 4) arg5 = args[4].ToObject<T5>(serializer.JsonSerializer);

return ( arg1!, arg2!, arg3!, arg4!, arg5! );
return (arg1!, arg2!, arg3!, arg4!, arg5!);
}

private static (T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) ExtractArguments<T, T2, T3, T4, T5, T6>(JArray args, ISerializer serializer)
Expand All @@ -435,7 +445,7 @@ private static (T arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) ExtractArgu
T6 arg6 = default;
if (args.Count > 5) arg6 = args[5].ToObject<T6>(serializer.JsonSerializer);

return ( arg1!, arg2!, arg3!, arg4!, arg5!, arg6! );
return (arg1!, arg2!, arg3!, arg4!, arg5!, arg6!);
}
}
}
Loading