-
Notifications
You must be signed in to change notification settings - Fork 416
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
Use CompletionService in IntellisenseService #840
Changes from 9 commits
dbdc713
8f233f0
73c78c4
e82f2e8
5a34a46
d8e066a
ab55def
b179067
5643a01
62fe53a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Completion; | ||
using OmniSharp.Utilities; | ||
|
||
namespace OmniSharp.Roslyn.CSharp.Services.Intellisense | ||
{ | ||
internal static class CompletionItemExtensions | ||
{ | ||
private static MethodInfo _getSymbolsAsync; | ||
|
||
static CompletionItemExtensions() | ||
{ | ||
var symbolCompletionItemType = typeof(CompletionItem).GetTypeInfo().Assembly.GetType("Microsoft.CodeAnalysis.Completion.Providers.SymbolCompletionItem"); | ||
_getSymbolsAsync = symbolCompletionItemType.GetMethod("GetSymbolsAsync", BindingFlags.Public | BindingFlags.Static); | ||
} | ||
|
||
public static async Task<IEnumerable<ISymbol>> GetCompletionSymbolsAsync(this CompletionItem completionItem, IEnumerable<ISymbol> recommendedSymbols, Document document) | ||
{ | ||
// for SymbolCompletionProvider, use the logic of extracting information from recommended symbols | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? Isn't call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kind of hate that we pre-compute recommendedSymbols (which is essentially like calling the CompletionService twice) just to handle this special case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unfortunately since this change dotnet/roslyn#15688, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yes, I'd forgotten about that. Computing all symbol IDs was simply too expensive. |
||
if (completionItem.Properties.ContainsKey("Provider") && completionItem.Properties["Provider"] == "Microsoft.CodeAnalysis.CSharp.Completion.Providers.SymbolCompletionProvider") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be good to throw an exception if the type |
||
{ | ||
var symbols = recommendedSymbols.Where(x => x.Name == completionItem.Properties["SymbolName"] && (int)x.Kind == int.Parse(completionItem.Properties["SymbolKind"])).Distinct(); | ||
return symbols ?? Enumerable.Empty<ISymbol>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you are right 😊 |
||
} | ||
|
||
// if the completion provider encoded symbols into Properties, we can return them | ||
if (completionItem.Properties.ContainsKey("Symbols")) | ||
{ | ||
// the API to decode symbols is not public at the moment | ||
// http://source.roslyn.io/#Microsoft.CodeAnalysis.Features/Completion/Providers/SymbolCompletionItem.cs,93 | ||
var decodedSymbolsTask = _getSymbolsAsync.InvokeStatic<Task<ImmutableArray<ISymbol>>>(new object[] { completionItem, document, default(CancellationToken) }); | ||
if (decodedSymbolsTask != null) | ||
{ | ||
return await decodedSymbolsTask; | ||
} | ||
} | ||
|
||
return Enumerable.Empty<ISymbol>(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Composition; | ||
using System.Linq; | ||
|
@@ -37,54 +38,60 @@ public async Task<IEnumerable<AutoCompleteResponse>> Handle(AutoCompleteRequest | |
{ | ||
var sourceText = await document.GetTextAsync(); | ||
var position = sourceText.Lines.GetPosition(new LinePosition(request.Line, request.Column)); | ||
|
||
var service = CompletionService.GetService(document); | ||
var completionList = await service.GetCompletionsAsync(document, position); | ||
|
||
// Add keywords from the completion list. We'll use the recommender service to get symbols | ||
// to create snippets from. | ||
|
||
if (completionList != null) | ||
{ | ||
// get recommened symbols to match them up later with SymbolCompletionProvider | ||
var semanticModel = await document.GetSemanticModelAsync(); | ||
var recommendedSymbols = await Recommender.GetRecommendedSymbolsAtPositionAsync(semanticModel, position, _workspace); | ||
|
||
foreach (var item in completionList.Items) | ||
{ | ||
if (item.Tags.Contains(CompletionTags.Keyword)) | ||
var completionText = item.DisplayText; | ||
if (completionText.IsValidCompletionFor(wordToComplete)) | ||
{ | ||
// Note: For keywords, we'll just assume that the completion text is the same | ||
// as the display text. | ||
var keyword = item.DisplayText; | ||
if (keyword.IsValidCompletionFor(wordToComplete)) | ||
var symbols = await item.GetCompletionSymbolsAsync(recommendedSymbols, document); | ||
if (symbols.Any()) | ||
{ | ||
var response = new AutoCompleteResponse() | ||
foreach (var symbol in symbols) | ||
{ | ||
CompletionText = item.DisplayText, | ||
DisplayText = item.DisplayText, | ||
Snippet = item.DisplayText, | ||
Kind = request.WantKind ? "Keyword" : null | ||
}; | ||
|
||
completions.Add(response); | ||
if (symbol != null) | ||
{ | ||
if (request.WantSnippet) | ||
{ | ||
foreach (var completion in MakeSnippetedResponses(request, symbol, item.DisplayText)) | ||
{ | ||
completions.Add(completion); | ||
} | ||
} | ||
else | ||
{ | ||
completions.Add(MakeAutoCompleteResponse(request, symbol, item.DisplayText)); | ||
} | ||
} | ||
} | ||
|
||
// if we had any symbols from the completion, we can continue, otherwise it means | ||
// the completion didn't have an associated symbol so we'll add it manually | ||
continue; | ||
} | ||
} | ||
} | ||
} | ||
|
||
var model = await document.GetSemanticModelAsync(); | ||
var symbols = await Recommender.GetRecommendedSymbolsAtPositionAsync(model, position, _workspace); | ||
// for other completions, i.e. keywords, create a simple AutoCompleteResponse | ||
// we'll just assume that the completion text is the same | ||
// as the display text. | ||
var response = new AutoCompleteResponse() | ||
{ | ||
CompletionText = item.DisplayText, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This probably isn't good enough for override completion. Completions on override will just be wrong until we can properly support CompletionService because the completion is multi-line, moves the caret, and may add usings above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Completion on partial will also be broken. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI that this is handled by calling when before an item is committed. http://source.roslyn.io/#Microsoft.CodeAnalysis.Features/Completion/CompletionService.cs,bfe3e3a9f66443fb. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To support completion properly we need a new api similar to what LSP provides with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, but we'll want to be more of a "resolve" API, so we don't have to go computing this for every completion item. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually overrides and partials won't even reach that specific line because they'd branch out here since these providers encode symbols. So the formatting/presentation of i.e. symbol information will be consistent with the rest of the experience, but yeah ultimately the From what I experimented, even the simple It's definitely sub par experience compared to VS but at the same time maybe it's better than not having anything? |
||
DisplayText = item.DisplayText, | ||
Snippet = item.DisplayText, | ||
Kind = request.WantKind ? item.Tags.First() : null | ||
}; | ||
|
||
foreach (var symbol in symbols.Where(s => s.Name.IsValidCompletionFor(wordToComplete))) | ||
{ | ||
if (request.WantSnippet) | ||
{ | ||
foreach (var completion in MakeSnippetedResponses(request, symbol)) | ||
{ | ||
completions.Add(completion); | ||
completions.Add(response); | ||
} | ||
} | ||
else | ||
{ | ||
completions.Add(MakeAutoCompleteResponse(request, symbol)); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -93,10 +100,11 @@ public async Task<IEnumerable<AutoCompleteResponse>> Handle(AutoCompleteRequest | |
.ThenByDescending(c => c.CompletionText.IsValidCompletionStartsWithIgnoreCase(wordToComplete)) | ||
.ThenByDescending(c => c.CompletionText.IsCamelCaseMatch(wordToComplete)) | ||
.ThenByDescending(c => c.CompletionText.IsSubsequenceMatch(wordToComplete)) | ||
.ThenBy(c => c.CompletionText); | ||
.ThenBy(c => c.DisplayText, StringComparer.OrdinalIgnoreCase) | ||
.ThenBy(c => c.CompletionText, StringComparer.OrdinalIgnoreCase); | ||
} | ||
|
||
private IEnumerable<AutoCompleteResponse> MakeSnippetedResponses(AutoCompleteRequest request, ISymbol symbol) | ||
private IEnumerable<AutoCompleteResponse> MakeSnippetedResponses(AutoCompleteRequest request, ISymbol symbol, string displayName) | ||
{ | ||
var completions = new List<AutoCompleteResponse>(); | ||
|
||
|
@@ -105,41 +113,41 @@ private IEnumerable<AutoCompleteResponse> MakeSnippetedResponses(AutoCompleteReq | |
{ | ||
if (methodSymbol.Parameters.Any(p => p.IsOptional)) | ||
{ | ||
completions.Add(MakeAutoCompleteResponse(request, symbol, false)); | ||
completions.Add(MakeAutoCompleteResponse(request, symbol, displayName, false)); | ||
} | ||
|
||
completions.Add(MakeAutoCompleteResponse(request, symbol)); | ||
completions.Add(MakeAutoCompleteResponse(request, symbol, displayName)); | ||
|
||
return completions; | ||
} | ||
|
||
var typeSymbol = symbol as INamedTypeSymbol; | ||
if (typeSymbol != null) | ||
{ | ||
completions.Add(MakeAutoCompleteResponse(request, symbol)); | ||
completions.Add(MakeAutoCompleteResponse(request, symbol, displayName)); | ||
|
||
if (typeSymbol.TypeKind != TypeKind.Enum) | ||
{ | ||
foreach (var ctor in typeSymbol.InstanceConstructors) | ||
{ | ||
completions.Add(MakeAutoCompleteResponse(request, ctor)); | ||
completions.Add(MakeAutoCompleteResponse(request, ctor, displayName)); | ||
} | ||
} | ||
|
||
return completions; | ||
} | ||
|
||
return new[] { MakeAutoCompleteResponse(request, symbol) }; | ||
return new[] { MakeAutoCompleteResponse(request, symbol, displayName) }; | ||
} | ||
|
||
private AutoCompleteResponse MakeAutoCompleteResponse(AutoCompleteRequest request, ISymbol symbol, bool includeOptionalParams = true) | ||
private AutoCompleteResponse MakeAutoCompleteResponse(AutoCompleteRequest request, ISymbol symbol, string displayName, bool includeOptionalParams = true) | ||
{ | ||
var displayNameGenerator = new SnippetGenerator(); | ||
displayNameGenerator.IncludeMarkers = false; | ||
displayNameGenerator.IncludeOptionalParameters = includeOptionalParams; | ||
|
||
var response = new AutoCompleteResponse(); | ||
response.CompletionText = symbol.Name; | ||
response.CompletionText = displayName; | ||
|
||
// TODO: Do something more intelligent here | ||
response.DisplayText = displayNameGenerator.Generate(symbol); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've got some extension methods for this sort of thing (used by
MetadataHelper
) that throw exceptions nicely when they fail. I'd recommend using those to ensure we can easily spot failures if internal API names change.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes good point 👍