Skip to content

Commit

Permalink
Merge pull request #47882 from davidwengier/LSPFindAllReferencesIcons
Browse files Browse the repository at this point in the history
Send back icons for FAR via LSP
  • Loading branch information
msftbot[bot] authored Oct 7, 2020
2 parents 4ed9289 + 9b05079 commit bf030da
Show file tree
Hide file tree
Showing 13 changed files with 141 additions and 43 deletions.
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<CodeStyleAnalyzerVersion>3.8.0-2.20414.4</CodeStyleAnalyzerVersion>
<VisualStudioEditorPackagesVersion>16.8.39</VisualStudioEditorPackagesVersion>
<ILToolsPackageVersion>5.0.0-alpha1.19409.1</ILToolsPackageVersion>
<MicrosoftVisualStudioLanguageServerProtocolPackagesVersion>16.8.103</MicrosoftVisualStudioLanguageServerProtocolPackagesVersion>
<MicrosoftVisualStudioLanguageServerProtocolPackagesVersion>16.8.120</MicrosoftVisualStudioLanguageServerProtocolPackagesVersion>
<MicrosoftVisualStudioShellPackagesVersion>16.8.30406.65-pre</MicrosoftVisualStudioShellPackagesVersion>
</PropertyGroup>
<!--
Expand Down
4 changes: 4 additions & 0 deletions src/EditorFeatures/Core/Shared/Extensions/GlyphExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.Tags;
using Microsoft.VisualStudio.Core.Imaging;
using Microsoft.VisualStudio.Imaging;
using Microsoft.VisualStudio.Text.Adornments;

namespace Microsoft.CodeAnalysis.Editor.Shared.Extensions
{
Expand Down Expand Up @@ -221,5 +222,8 @@ public static ImageId GetImageId(this Glyph glyph)
throw new ArgumentException(nameof(glyph));
}
}

public static ImageElement GetImageElement(this Glyph glyph)
=> new ImageElement(glyph.GetImageId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.Text.Adornments;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Roslyn.Utilities;
using Xunit;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
Expand Down Expand Up @@ -241,18 +242,18 @@ protected static LSP.VSCompletionItem CreateCompletionItem(
SortText = sortText ?? insertText,
InsertTextFormat = LSP.InsertTextFormat.Plaintext,
Kind = kind,
Data = new CompletionResolveData()
Data = JObject.FromObject(new CompletionResolveData()
{
DisplayText = insertText,
TextDocument = requestParameters.TextDocument,
Position = requestParameters.Position,
CompletionTrigger = new CompletionTrigger(ProtocolConversions.LSPToRoslynCompletionTriggerKind(requestParameters.Context.TriggerKind), char.Parse(requestParameters.Context.TriggerCharacter))
},
Preselect = preselect,
CompletionTrigger = ProtocolConversions.LSPToRoslynCompletionTrigger(requestParameters.Context)
}),
Preselect = preselect
};

if (tags != null)
item.Icon = new ImageElement(tags.ToImmutableArray().GetFirstGlyph().GetImageId());
item.Icon = tags.ToImmutableArray().GetFirstGlyph().GetImageElement();

if (commitCharacters != null)
item.CommitCharacters = commitCharacters.Value.Select(c => c.ToString()).ToArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.Finders;
Expand Down Expand Up @@ -93,7 +94,7 @@ public override async ValueTask OnDefinitionFoundAsync(DefinitionItem definition
var definitionItem = await GenerateVSReferenceItemAsync(
_id, definitionId: _id, _document, _position, definition.SourceSpans.FirstOrDefault(),
definition.DisplayableProperties, _metadataAsSourceFileService, definition.GetClassifiedText(),
symbolUsageInfo: null, CancellationToken).ConfigureAwait(false);
definition.Tags.GetFirstGlyph(), symbolUsageInfo: null, CancellationToken).ConfigureAwait(false);

if (definitionItem != null)
{
Expand Down Expand Up @@ -135,7 +136,7 @@ public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem refere
var referenceItem = await GenerateVSReferenceItemAsync(
_id, definitionId, _document, _position, reference.SourceSpan,
reference.AdditionalProperties, _metadataAsSourceFileService, definitionText: null,
reference.SymbolUsageInfo, CancellationToken).ConfigureAwait(false);
definitionGlyph: Glyph.None, reference.SymbolUsageInfo, CancellationToken).ConfigureAwait(false);

if (referenceItem != null)
{
Expand All @@ -153,6 +154,7 @@ public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem refere
ImmutableDictionary<string, string> properties,
IMetadataAsSourceFileService metadataAsSourceFileService,
ClassifiedTextElement? definitionText,
Glyph definitionGlyph,
SymbolUsageInfo? symbolUsageInfo,
CancellationToken cancellationToken)
{
Expand All @@ -176,6 +178,7 @@ public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem refere
{
DefinitionId = definitionId,
DefinitionText = definitionText, // Only definitions should have a non-null DefinitionText
DefinitionIcon = definitionGlyph.GetImageElement(),
DisplayPath = location.Uri.LocalPath,
Id = id,
Kind = symbolUsageInfo.HasValue ? ProtocolConversions.SymbolUsageInfoToReferenceKinds(symbolUsageInfo.Value) : Array.Empty<ReferenceKind>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.DocumentHighlighting;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Elfie.Diagnostics;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
Expand Down Expand Up @@ -70,18 +71,27 @@ internal static class ProtocolConversions

// TO-DO: More LSP.CompletionTriggerKind mappings are required to properly map to Roslyn CompletionTriggerKinds.
// https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1178726
public static Completion.CompletionTriggerKind LSPToRoslynCompletionTriggerKind(LSP.CompletionTriggerKind triggerKind)
public static Completion.CompletionTrigger LSPToRoslynCompletionTrigger(LSP.CompletionContext? context)
{
switch (triggerKind)
if (context == null)
{
case LSP.CompletionTriggerKind.Invoked:
return Completion.CompletionTriggerKind.Invoke;
case LSP.CompletionTriggerKind.TriggerCharacter:
return Completion.CompletionTriggerKind.Insertion;
default:
// LSP added a TriggerKind that we need to support.
Logger.Log(FunctionId.LSPCompletion_MissingLSPCompletionTriggerKind);
return Completion.CompletionTriggerKind.Invoke;
// Some LSP clients don't support sending extra context, so all we can do is invoke
return Completion.CompletionTrigger.Invoke;
}
else if (context.TriggerKind == LSP.CompletionTriggerKind.Invoked)
{
return Completion.CompletionTrigger.Invoke;
}
else if (context.TriggerKind == LSP.CompletionTriggerKind.TriggerCharacter)
{
Contract.ThrowIfNull(context.TriggerCharacter);
return Completion.CompletionTrigger.CreateInsertionTrigger(char.Parse(context.TriggerCharacter));
}
else
{
// LSP added a TriggerKind that we need to support.
Logger.Log(FunctionId.LSPCompletion_MissingLSPCompletionTriggerKind);
return Completion.CompletionTrigger.Invoke;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,18 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
[ExportLspMethod(LSP.Methods.TextDocumentCompletionName, mutatesSolutionState: false)]
internal class CompletionHandler : IRequestHandler<LSP.CompletionParams, LSP.CompletionList?>
{
private readonly ImmutableHashSet<string> _csharpTriggerCharacters;
private readonly ImmutableHashSet<string> _vbTriggerCharacters;
private readonly ImmutableHashSet<char> _csharpTriggerCharacters;
private readonly ImmutableHashSet<char> _vbTriggerCharacters;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CompletionHandler(
[ImportMany] IEnumerable<Lazy<CompletionProvider, CompletionProviderMetadata>> completionProviders)
{
_csharpTriggerCharacters = completionProviders.Where(lz => lz.Metadata.Language == LanguageNames.CSharp).SelectMany(
lz => GetTriggerCharacters(lz.Value)).Select(c => c.ToString()).ToImmutableHashSet();
lz => GetTriggerCharacters(lz.Value)).ToImmutableHashSet();
_vbTriggerCharacters = completionProviders.Where(lz => lz.Metadata.Language == LanguageNames.VisualBasic).SelectMany(
lz => GetTriggerCharacters(lz.Value)).Select(c => c.ToString()).ToImmutableHashSet();
lz => GetTriggerCharacters(lz.Value)).ToImmutableHashSet();
}

public LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(LSP.CompletionParams request) => request.TextDocument;
Expand All @@ -54,9 +54,11 @@ public CompletionHandler(
// C# and VB share the same LSP language server, and thus share the same default trigger characters.
// We need to ensure the trigger character is valid in the document's language. For example, the '{'
// character, while a trigger character in VB, is not a trigger character in C#.
char.TryParse(request.Context.TriggerCharacter, out var triggerCharacter);
if (request.Context.TriggerKind == LSP.CompletionTriggerKind.TriggerCharacter && !char.IsLetterOrDigit(triggerCharacter) &&
!IsValidTriggerCharacterForDocument(document, request.Context.TriggerCharacter))
if (request.Context != null &&
request.Context.TriggerKind == LSP.CompletionTriggerKind.TriggerCharacter &&
!char.TryParse(request.Context.TriggerCharacter, out var triggerCharacter) &&
!char.IsLetterOrDigit(triggerCharacter) &&
!IsValidTriggerCharacterForDocument(document, triggerCharacter))
{
return null;
}
Expand All @@ -82,8 +84,7 @@ public CompletionHandler(

// TO-DO: More LSP.CompletionTriggerKind mappings are required to properly map to Roslyn CompletionTriggerKinds.
// https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1178726
var triggerKind = ProtocolConversions.LSPToRoslynCompletionTriggerKind(request.Context.TriggerKind);
var completionTrigger = new CompletionTrigger(triggerKind, triggerCharacter);
var completionTrigger = ProtocolConversions.LSPToRoslynCompletionTrigger(request.Context);

var list = await completionService.GetCompletionsAsync(document, position, completionTrigger, options: completionOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
if (list == null)
Expand All @@ -97,11 +98,11 @@ public CompletionHandler(
return new LSP.VSCompletionList
{
Items = list.Items.Select(item => CreateLSPCompletionItem(request, item, lspVSClientCapability, completionTrigger, commitCharactersRuleCache)).ToArray(),
SuggesstionMode = list.SuggestionModeItem != null,
SuggestionMode = list.SuggestionModeItem != null,
};

// Local functions
bool IsValidTriggerCharacterForDocument(Document document, string triggerCharacter)
bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter)
{
if (document.Project.Language == LanguageNames.CSharp)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol;
using Microsoft.VisualStudio.Text.Adornments;
using Newtonsoft.Json.Linq;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler
Expand All @@ -29,12 +30,16 @@ public CompletionResolveHandler()
{
}

private static CompletionResolveData GetCompletionResolveData(LSP.CompletionItem request)
{
Contract.ThrowIfNull(request.Data);

return ((JToken)request.Data).ToObject<CompletionResolveData>();
}

public LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(LSP.CompletionItem request)
=> GetCompletionResolveData(request).TextDocument;

private static CompletionResolveData GetCompletionResolveData(LSP.CompletionItem completionItem)
=> completionItem.Data as CompletionResolveData ?? ((JToken)completionItem.Data).ToObject<CompletionResolveData>();

public async Task<LSP.CompletionItem> HandleRequestAsync(LSP.CompletionItem completionItem, RequestContext context, CancellationToken cancellationToken)
{
var document = context.Document;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ public FindAllReferencesHandler(IMetadataAsSourceFileService metadataAsSourceFil
return Array.Empty<LSP.VSReferenceItem>();
}

// We only support streaming results back, so no point doing any work if the client doesn't
if (referenceParams.PartialResultToken == null)
{
return Array.Empty<LSP.VSReferenceItem>();
}

var findUsagesService = document.GetRequiredLanguageService<IFindUsagesLSPService>();
var position = await document.GetPositionFromLinePositionAsync(
ProtocolConversions.PositionToLinePosition(referenceParams.Position), cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ void M()

var results = (LSP.VSCompletionList)await RunGetCompletionsAsync(workspace.CurrentSolution, completionParams).ConfigureAwait(false);
Assert.True(results.Items.Any());
Assert.True(results.SuggesstionMode);
Assert.True(results.SuggestionMode);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

#nullable disable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.VisualStudio.Text.Adornments;
using Roslyn.Test.Utilities;
using Xunit;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
Expand Down Expand Up @@ -45,7 +49,44 @@ void M2()
Assert.Equal("M", results[1].ContainingMember);
Assert.Equal("M2", results[3].ContainingMember);

AssertValidDefinitionProperties(results, 0);
AssertValidDefinitionProperties(results, 0, Glyph.FieldPublic);
}

[WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/43063")]
public async Task TestFindAllReferencesAsync_Class()
{
var markup =
@"class {|reference:A|}
{
public int someInt = 1;
void M()
{
var i = someInt + 1;
}
}
class B
{
int someInt = {|reference:A|}.someInt + 1;
void M2()
{
var j = someInt + {|caret:|}{|reference:A|}.someInt;
}
}";
using var workspace = CreateTestWorkspace(markup, out var locations);

var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First());
AssertLocationsEqual(locations["reference"], results.Select(result => result.Location));

var textElement = results[0].Text as ClassifiedTextElement;
Assert.NotNull(textElement);
var actualText = string.Concat(textElement.Runs.Select(r => r.Text));

Assert.Equal("class A", actualText);
Assert.Equal("B", results[1].ContainingType);
Assert.Equal("B", results[2].ContainingType);
Assert.Equal("M2", results[2].ContainingMember);

AssertValidDefinitionProperties(results, 0, Glyph.ClassInternal);
}

[WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/43063")]
Expand Down Expand Up @@ -80,7 +121,7 @@ void M2()
Assert.Equal("M", results[1].ContainingMember);
Assert.Equal("M2", results[3].ContainingMember);

AssertValidDefinitionProperties(results, 0);
AssertValidDefinitionProperties(results, 0, Glyph.FieldPublic);
}

[WpfFact]
Expand Down Expand Up @@ -116,12 +157,13 @@ void M()
Assert.NotNull(results[0].Location.Uri);
}

private static LSP.ReferenceParams CreateReferenceParams(LSP.Location caret) =>
private static LSP.ReferenceParams CreateReferenceParams(LSP.Location caret, IProgress<object> progress) =>
new LSP.ReferenceParams()
{
TextDocument = CreateTextDocumentIdentifier(caret.Uri),
Position = caret.Range.Start,
Context = new LSP.ReferenceContext(),
PartialResultToken = progress
};

private static async Task<LSP.VSReferenceItem[]> RunFindAllReferencesAsync(Solution solution, LSP.Location caret)
Expand All @@ -131,17 +173,23 @@ private static LSP.ReferenceParams CreateReferenceParams(LSP.Location caret) =>
SupportsVisualStudioExtensions = true
};

var progress = new ProgressCollector<LSP.VSReferenceItem>();

var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.ReferenceParams, LSP.VSReferenceItem[]>(queue, LSP.Methods.TextDocumentReferencesName,
CreateReferenceParams(caret), vsClientCapabilities, null, CancellationToken.None);
await GetLanguageServer(solution).ExecuteRequestAsync<LSP.ReferenceParams, LSP.VSReferenceItem[]>(queue, LSP.Methods.TextDocumentReferencesName,
CreateReferenceParams(caret, progress), vsClientCapabilities, null, CancellationToken.None);

return progress.GetItems();
}

private static void AssertValidDefinitionProperties(LSP.ReferenceItem[] referenceItems, int definitionIndex)
private static void AssertValidDefinitionProperties(LSP.VSReferenceItem[] referenceItems, int definitionIndex, Glyph definitionGlyph)
{
var definition = referenceItems[definitionIndex];
var definitionId = definition.DefinitionId;
Assert.NotNull(definition.DefinitionText);

Assert.Equal(definitionGlyph.GetImageId(), definition.DefinitionIcon.ImageId);

for (var i = 0; i < referenceItems.Length; i++)
{
if (i == definitionIndex)
Expand All @@ -150,9 +198,22 @@ private static void AssertValidDefinitionProperties(LSP.ReferenceItem[] referenc
}

Assert.Null(referenceItems[i].DefinitionText);
Assert.Equal(0, referenceItems[i].DefinitionIcon.ImageId.Id);
Assert.Equal(definitionId, referenceItems[i].DefinitionId);
Assert.NotEqual(definitionId, referenceItems[i].Id);
}
}

private sealed class ProgressCollector<T> : IProgress<object>
{
private readonly List<T> _items = new List<T>();

public T[] GetItems() => _items.ToArray();

public void Report(object value)
{
_items.AddRange((T[])value);
}
}
}
}
Loading

0 comments on commit bf030da

Please sign in to comment.