未验证 提交 ed030c6b 编写于 作者: A Allison Chou 提交者: GitHub

Merge pull request #42854 from allisonchou/LSP_FAR

Move Find All References to LSP
......@@ -5,28 +5,18 @@
using System;
using System.Composition;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.VisualStudio.LanguageServices.LiveShare.Client.References
namespace Microsoft.CodeAnalysis.Editor.CSharp.FindUsages
{
[ExportLanguageService(typeof(IFindUsagesService), StringConstants.CSharpLspLanguageName), Shared]
internal class CSharpLspFindUsagesService : RoslynFindUsagesService
[ExportLanguageService(typeof(IFindUsagesLSPService), LanguageNames.CSharp), Shared]
internal class CSharpFindUsagesLSPService : AbstractFindUsagesService
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CSharpLspFindUsagesService(CSharpLspClientServiceFactory csharpLspClientServiceFactory, RemoteLanguageServiceWorkspace remoteLanguageServiceWorkspace)
: base(csharpLspClientServiceFactory, remoteLanguageServiceWorkspace)
{
}
}
[ExportLanguageService(typeof(IFindUsagesService), StringConstants.VBLspLanguageName), Shared]
internal class VBLspFindUsagesService : RoslynFindUsagesService
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public VBLspFindUsagesService(VisualBasicLspClientServiceFactory vbLspClientServiceFactory, RemoteLanguageServiceWorkspace remoteLanguageServiceWorkspace)
: base(vbLspClientServiceFactory, remoteLanguageServiceWorkspace)
public CSharpFindUsagesLSPService(IThreadingContext threadingContext)
: base(threadingContext)
{
}
}
......
......@@ -18,7 +18,7 @@
namespace Microsoft.CodeAnalysis.Editor.FindUsages
{
internal abstract partial class AbstractFindUsagesService : IFindUsagesService
internal abstract partial class AbstractFindUsagesService : IFindUsagesService, IFindUsagesLSPService
{
private readonly IThreadingContext _threadingContext;
......@@ -60,7 +60,7 @@ protected AbstractFindUsagesService(IThreadingContext threadingContext)
}
}
public async Task FindReferencesAsync(
async Task IFindUsagesService.FindReferencesAsync(
Document document, int position, IFindUsagesContext context)
{
var definitionTrackingContext = new DefinitionTrackingContext(context);
......@@ -88,6 +88,18 @@ protected AbstractFindUsagesService(IThreadingContext threadingContext)
}
}
async Task IFindUsagesLSPService.FindReferencesAsync(
Document document, int position, IFindUsagesContext context)
{
// We don't need to get third party definitions when finding references in LSP.
// Currently, 3rd party definitions = XAML definitions, and XAML will provide
// references via LSP instead of hooking into Roslyn.
// This also means that we don't need to be on the UI thread.
var definitionTrackingContext = new DefinitionTrackingContext(context);
await FindLiteralOrSymbolReferencesAsync(
document, position, definitionTrackingContext).ConfigureAwait(false);
}
private async Task FindLiteralOrSymbolReferencesAsync(
Document document, int position, IFindUsagesContext context)
{
......
// 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.Threading.Tasks;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.Editor.FindUsages
{
internal interface IFindUsagesLSPService : ILanguageService
{
/// <summary>
/// Finds the references for the symbol at the specific position in the document,
/// pushing the results into the context instance.
/// </summary>
Task FindReferencesAsync(Document document, int position, IFindUsagesContext context);
/// <summary>
/// Finds the implementations for the symbol at the specific position in the document,
/// pushing the results into the context instance.
/// </summary>
Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context);
}
}
' 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.
Imports System.Composition
Imports Microsoft.CodeAnalysis.Editor.FindUsages
Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities
Imports Microsoft.CodeAnalysis.Host.Mef
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.FindUsages
<ExportLanguageService(GetType(IFindUsagesLSPService), LanguageNames.VisualBasic), [Shared]>
Friend Class VisualBasicFindUsagesLSPService
Inherits AbstractFindUsagesService
<ImportingConstructor>
<Obsolete(MefConstruction.ImportingConstructorMessage, True)>
Public Sub New(threadingContext As IThreadingContext)
MyBase.New(threadingContext)
End Sub
End Class
End Namespace
......@@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis.Tags;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
......@@ -381,5 +382,77 @@ public static Glyph CompletionItemKindToGlyph(LSP.CompletionItemKind kind)
return Glyph.None;
}
}
// The mappings here are roughly based off of SymbolUsageInfoExtensions.ToSymbolReferenceKinds.
public static LSP.ReferenceKind[] SymbolUsageInfoToReferenceKinds(SymbolUsageInfo symbolUsageInfo)
{
var referenceKinds = ArrayBuilder<LSP.ReferenceKind>.GetInstance();
if (symbolUsageInfo.ValueUsageInfoOpt.HasValue)
{
var usageInfo = symbolUsageInfo.ValueUsageInfoOpt.Value;
if (usageInfo.IsReadFrom())
{
referenceKinds.Add(LSP.ReferenceKind.Read);
}
if (usageInfo.IsWrittenTo())
{
referenceKinds.Add(LSP.ReferenceKind.Write);
}
if (usageInfo.IsReference())
{
referenceKinds.Add(LSP.ReferenceKind.Reference);
}
if (usageInfo.IsNameOnly())
{
referenceKinds.Add(LSP.ReferenceKind.Name);
}
}
if (symbolUsageInfo.TypeOrNamespaceUsageInfoOpt.HasValue)
{
var usageInfo = symbolUsageInfo.TypeOrNamespaceUsageInfoOpt.Value;
if ((usageInfo & TypeOrNamespaceUsageInfo.Qualified) != 0)
{
referenceKinds.Add(LSP.ReferenceKind.Qualified);
}
if ((usageInfo & TypeOrNamespaceUsageInfo.TypeArgument) != 0)
{
referenceKinds.Add(LSP.ReferenceKind.TypeArgument);
}
if ((usageInfo & TypeOrNamespaceUsageInfo.TypeConstraint) != 0)
{
referenceKinds.Add(LSP.ReferenceKind.TypeConstraint);
}
if ((usageInfo & TypeOrNamespaceUsageInfo.Base) != 0)
{
referenceKinds.Add(LSP.ReferenceKind.BaseType);
}
// Preserving the same mapping logic that SymbolUsageInfoExtensions.ToSymbolReferenceKinds uses
if ((usageInfo & TypeOrNamespaceUsageInfo.ObjectCreation) != 0)
{
referenceKinds.Add(LSP.ReferenceKind.Constructor);
}
if ((usageInfo & TypeOrNamespaceUsageInfo.Import) != 0)
{
referenceKinds.Add(LSP.ReferenceKind.Import);
}
// Preserving the same mapping logic that SymbolUsageInfoExtensions.ToSymbolReferenceKinds uses
if ((usageInfo & TypeOrNamespaceUsageInfo.NamespaceDeclaration) != 0)
{
referenceKinds.Add(LSP.ReferenceKind.Declaration);
}
}
return referenceKinds.ToArrayAndFree();
}
}
}
......@@ -50,6 +50,7 @@ public Task<LSP.InitializeResult> HandleRequestAsync(Solution solution, LSP.Init
DocumentRangeFormattingProvider = true,
DocumentOnTypeFormattingProvider = new LSP.DocumentOnTypeFormattingOptions { FirstTriggerCharacter = "}", MoreTriggerCharacter = new[] { ";", "\n" } },
DocumentHighlightProvider = true,
ReferencesProvider = true,
}
});
}
......
// 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.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.Finders;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Text.Adornments;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
[ExportLspMethod(LSP.Methods.TextDocumentReferencesName), Shared]
internal class FindAllReferencesHandler : IRequestHandler<LSP.ReferenceParams, LSP.VSReferenceItem[]>
{
private readonly IThreadingContext _threadingContext;
private readonly IMetadataAsSourceFileService _metadataAsSourceFileService;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public FindAllReferencesHandler(IThreadingContext threadingContext, IMetadataAsSourceFileService metadataAsSourceFileService)
{
_threadingContext = threadingContext;
_metadataAsSourceFileService = metadataAsSourceFileService;
}
public async Task<LSP.VSReferenceItem[]> HandleRequestAsync(
Solution solution,
ReferenceParams referenceParams,
ClientCapabilities clientCapabilities,
CancellationToken cancellationToken)
{
Debug.Assert(clientCapabilities.HasVisualStudioLspCapability());
var document = solution.GetDocumentFromURI(referenceParams.TextDocument.Uri);
if (document == null)
{
return Array.Empty<LSP.VSReferenceItem>();
}
var findUsagesService = document.GetRequiredLanguageService<IFindUsagesLSPService>();
var position = await document.GetPositionFromLinePositionAsync(
ProtocolConversions.PositionToLinePosition(referenceParams.Position), cancellationToken).ConfigureAwait(false);
var context = new SimpleFindUsagesContext(cancellationToken);
// Finds the references for the symbol at the specific position in the document, pushing the results to the context instance.
await findUsagesService.FindReferencesAsync(document, position, context).ConfigureAwait(false);
return await GetReferenceItemsAsync(document, position, context, cancellationToken).ConfigureAwait(false);
}
private async Task<LSP.VSReferenceItem[]> GetReferenceItemsAsync(
Document document,
int position,
SimpleFindUsagesContext context,
CancellationToken cancellationToken)
{
// Mapping each reference to its definition
var definitionMap = new Dictionary<DefinitionItem, List<SourceReferenceItem>>();
foreach (var reference in context.GetReferences())
{
if (!definitionMap.ContainsKey(reference.Definition))
{
definitionMap.Add(reference.Definition, new List<SourceReferenceItem>());
}
definitionMap[reference.Definition].Add(reference);
}
// NOTE: Parts of FAR currently do not display correctly due to a bug in LSP.VSReferenceItem.
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1088938/
using var _ = ArrayBuilder<LSP.VSReferenceItem>.GetInstance(out var referenceItems);
// Each reference is assigned its own unique id
var id = 0;
foreach (var (definition, references) in definitionMap)
{
// Creating the reference item that corresponds to the definition.
var definitionItem = await GenerateReferenceItem(
id, definitionId: id, document, position, definition.SourceSpans.FirstOrDefault(), context, definition.DisplayableProperties,
definition.GetClassifiedText(), symbolUsageInfo: null, cancellationToken).ConfigureAwait(false);
// If we have an empty location, skip this definition and its references.
if (definitionItem.Location == null)
{
continue;
}
referenceItems.Add(definitionItem);
var definitionId = id;
id++;
// Creating a reference item for each reference.
foreach (var reference in references)
{
var referenceItem = await GenerateReferenceItem(
id, definitionId, document, position, reference.SourceSpan, context, reference.AdditionalProperties,
definitionText: null, reference.SymbolUsageInfo, cancellationToken).ConfigureAwait(false);
// If we have an empty location, skip this reference.
if (referenceItem.Location == null)
{
continue;
}
referenceItems.Add(referenceItem);
id++;
};
}
return referenceItems.ToArray();
async Task<LSP.VSReferenceItem> GenerateReferenceItem(
int id,
int? definitionId,
Document originalDocument,
int originalPosition,
DocumentSpan documentSpan,
SimpleFindUsagesContext context,
ImmutableDictionary<string, string> properties,
ClassifiedTextElement? definitionText,
SymbolUsageInfo? symbolUsageInfo,
CancellationToken cancellationToken)
{
LSP.Location? location = null;
// If we have no source span, our location may be in metadata.
if (documentSpan == default)
{
var symbol = await SymbolFinder.FindSymbolAtPositionAsync(originalDocument, originalPosition, cancellationToken).ConfigureAwait(false);
if (symbol != null && symbol.Locations != null && !symbol.Locations.IsEmpty && symbol.Locations.First().IsInMetadata)
{
var declarationFile = await _metadataAsSourceFileService.GetGeneratedFileAsync(
originalDocument.Project, symbol, allowDecompilation: false, cancellationToken).ConfigureAwait(false);
var linePosSpan = declarationFile.IdentifierLocation.GetLineSpan().Span;
location = new LSP.Location
{
Uri = new Uri(declarationFile.FilePath),
Range = ProtocolConversions.LinePositionToRange(linePosSpan),
};
}
}
else
{
location = await ProtocolConversions.DocumentSpanToLocationAsync(documentSpan, cancellationToken).ConfigureAwait(false);
}
// TO-DO: The Origin property should be added once Rich-Nav is completed.
// https://github.com/dotnet/roslyn/issues/42847
var result = new LSP.VSReferenceItem
{
ContainingMember = properties.TryGetValue(
AbstractReferenceFinder.ContainingMemberInfoPropertyName, out var referenceContainingMember) ? referenceContainingMember : null,
ContainingType = properties.TryGetValue(
AbstractReferenceFinder.ContainingTypeInfoPropertyName, out var referenceContainingType) ? referenceContainingType : null,
DefinitionId = definitionId,
DefinitionText = definitionText, // Only definitions should have a non-null DefinitionText
DisplayPath = location?.Uri.LocalPath,
DocumentName = documentSpan == default ? null : documentSpan.Document.Name,
Id = id,
Kind = symbolUsageInfo.HasValue ? ProtocolConversions.SymbolUsageInfoToReferenceKinds(symbolUsageInfo.Value) : new ReferenceKind[] { },
Location = location,
ProjectName = documentSpan == default ? null : documentSpan.Document.Project.Name,
ResolutionStatus = ResolutionStatusKind.ConfirmedAsReference,
};
// Properly assigning the text property.
if (id == definitionId)
{
result.Text = definitionText;
}
else if (documentSpan != default)
{
var classifiedSpansAndHighlightSpan = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(
documentSpan, cancellationToken).ConfigureAwait(false);
var classifiedSpans = classifiedSpansAndHighlightSpan.ClassifiedSpans;
var docText = await documentSpan.Document.GetTextAsync(cancellationToken).ConfigureAwait(false);
result.Text = new ClassifiedTextElement(
classifiedSpans.Select(cspan => new ClassifiedTextRun(cspan.ClassificationType, docText.ToString(cspan.TextSpan))));
}
return result;
}
}
}
}
......@@ -153,6 +153,18 @@ public Task<LSP.TextEdit[]> FormatDocumentRangeAsync(Solution solution, LSP.Docu
public Task<LSP.DocumentHighlight[]> GetDocumentHighlightAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.DocumentHighlight[]>(LSP.Methods.TextDocumentDocumentHighlightName, solution, request, clientCapabilities, cancellationToken);
/// <summary>
/// Answers a document references request by returning references information associated with the symbol at a given document location.
/// https://microsoft.github.io/language-server-protocol/specification#textDocument_references
/// </summary>
/// <param name="solution">the solution containing the request document.</param>
/// <param name="request">the references request.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>references information associated with the symbol at the given document location.</returns>
public Task<LSP.VSReferenceItem[]> GetDocumentReferencesAsync(Solution solution, LSP.ReferenceParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.ReferenceParams, LSP.VSReferenceItem[]>(LSP.Methods.TextDocumentReferencesName, solution, request, clientCapabilities, cancellationToken);
/// <summary>
/// Answers a document symbols request by returning a list of symbols in the document.
/// https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol
......@@ -182,7 +194,7 @@ public Task<LSP.FoldingRange[]> GetFoldingRangeAsync(Solution solution, LSP.Fold
/// https://microsoft.github.io/language-server-protocol/specification#textDocument_hover
/// </summary>
/// <param name="solution">the solution containing any documents in the request.</param>
/// <param name="request">the hover requesst.</param>
/// <param name="request">the hover request.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the Hover using MarkupContent.</returns>
......@@ -225,18 +237,6 @@ public Task<LSP.SymbolInformation[]> GetWorkspaceSymbolsAsync(Solution solution,
public Task<LSP.SumType<LSP.Location, LSP.Location[]>?> GoToDefinitionAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.SumType<LSP.Location, LSP.Location[]>?>(LSP.Methods.TextDocumentDefinitionName, solution, request, clientCapabilities, cancellationToken);
/// <summary>
/// Answers a rename request by returning the workspace edit for a given symbol.
/// https://microsoft.github.io/language-server-protocol/specification#textDocument_rename
/// </summary>
/// <param name="solution">the solution containing the request.</param>
/// <param name="request">the document position of the symbol to rename.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the workspace edits to rename the given symbol</returns>
public Task<WorkspaceEdit> RenameAsync(Solution solution, LSP.RenameParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.RenameParams, WorkspaceEdit>(LSP.Methods.TextDocumentRenameName, solution, request, clientCapabilities, cancellationToken);
/// <summary>
/// Answers a goto type definition request by returning the location of a given type definition.
/// https://microsoft.github.io/language-server-protocol/specification#textDocument_typeDefinition
......@@ -261,6 +261,18 @@ public Task<LSP.Location[]> GoToTypeDefinitionAsync(Solution solution, LSP.TextD
public Task<LSP.InitializeResult> InitializeAsync(Solution solution, LSP.InitializeParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.InitializeParams, LSP.InitializeResult>(LSP.Methods.InitializeName, solution, request, clientCapabilities, cancellationToken);
/// <summary>
/// Answers a rename request by returning the workspace edit for a given symbol.
/// https://microsoft.github.io/language-server-protocol/specification#textDocument_rename
/// </summary>
/// <param name="solution">the solution containing the request.</param>
/// <param name="request">the document position of the symbol to rename.</param>
/// <param name="clientCapabilities">the client capabilities for the request.</param>
/// <param name="cancellationToken">a cancellation token.</param>
/// <returns>the workspace edits to rename the given symbol</returns>
public Task<WorkspaceEdit> RenameAsync(Solution solution, LSP.RenameParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
=> ExecuteRequestAsync<LSP.RenameParams, WorkspaceEdit>(LSP.Methods.TextDocumentRenameName, solution, request, clientCapabilities, cancellationToken);
/// <summary>
/// Answers a request to resolve a completion item.
/// https://microsoft.github.io/language-server-protocol/specification#completionItem_resolve
......
......@@ -3,17 +3,15 @@
// See the LICENSE file in the project root for more information.
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Newtonsoft.Json.Linq;
using Roslyn.Test.Utilities;
using Xunit;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.VisualStudio.LanguageServices.LiveShare.UnitTests
namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.References
{
public class FindAllReferencesHandlerTests : AbstractLiveShareRequestHandlerTests
public class FindAllReferencesHandlerTests : AbstractLanguageServerProtocolTests
{
[WpfFact]
public async Task TestFindAllReferencesAsync()
......@@ -37,34 +35,15 @@ void M2()
}";
using var workspace = CreateTestWorkspace(markup, out var locations);
var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First(), true);
AssertLocationsEqual(locations["reference"], results);
}
var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First());
AssertLocationsEqual(locations["reference"], results.Select(result => result.Location));
[WpfFact]
public async Task TestFindAllReferencesAsync_DoNotIncludeDeclarations()
{
var markup =
@"class A
{
public int someInt = 1;
void M()
{
var i = {|reference:someInt|} + 1;
}
}
class B
{
int someInt = A.{|reference:someInt|} + 1;
void M2()
{
var j = someInt + A.{|caret:|}{|reference:someInt|};
}
}";
using var workspace = CreateTestWorkspace(markup, out var locations);
Assert.Equal("A", results[0].ContainingType);
Assert.Equal("B", results[2].ContainingType);
Assert.Equal("M", results[1].ContainingMember);
Assert.Equal("M2", results[3].ContainingMember);
var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First(), false);
AssertLocationsEqual(locations["reference"], results);
AssertValidDefinitionProperties(results, 0);
}
[WpfFact]
......@@ -91,8 +70,15 @@ void M2()
using var workspace = CreateTestWorkspace(markups, out var locations);
var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First(), true);
AssertLocationsEqual(locations["reference"], results);
var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First());
AssertLocationsEqual(locations["reference"], results.Select(result => result.Location));
Assert.Equal("A", results[0].ContainingType);
Assert.Equal("B", results[2].ContainingType);
Assert.Equal("M", results[1].ContainingMember);
Assert.Equal("M2", results[3].ContainingMember);
AssertValidDefinitionProperties(results, 0);
}
[WpfFact]
......@@ -105,24 +91,64 @@ public async Task TestFindAllReferencesAsync_InvalidLocation()
}";
using var workspace = CreateTestWorkspace(markup, out var locations);
var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First(), true);
var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First());
Assert.Empty(results);
}
private static async Task<LSP.Location[]> RunFindAllReferencesAsync(Solution solution, LSP.Location caret, bool includeDeclaration)
[WpfFact]
public async Task TestFindAllReferencesMetadataDefinitionAsync()
{
var request = (JObject)JToken.FromObject(new LSP.ReferenceParams()
var markup =
@"using System;
class A
{
void M()
{
Console.{|caret:|}{|reference:WriteLine|}(""text"");
}
}";
using var workspace = CreateTestWorkspace(markup, out var locations);
var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First());
Assert.NotNull(results[0].Location.Uri);
}
private static LSP.ReferenceParams CreateReferenceParams(LSP.Location caret) =>
new LSP.ReferenceParams()
{
TextDocument = CreateTextDocumentIdentifier(caret.Uri),
Position = caret.Range.Start,
Context = new LSP.ReferenceContext()
Context = new LSP.ReferenceContext(),
};
private static async Task<LSP.VSReferenceItem[]> RunFindAllReferencesAsync(Solution solution, LSP.Location caret)
{
var vsClientCapabilities = new LSP.VSClientCapabilities
{
SupportsVisualStudioExtensions = true
};
return await GetLanguageServer(solution).GetDocumentReferencesAsync(solution, CreateReferenceParams(caret), vsClientCapabilities, CancellationToken.None);
}
private static void AssertValidDefinitionProperties(LSP.ReferenceItem[] referenceItems, int definitionIndex)
{
var definition = referenceItems[definitionIndex];
var definitionId = definition.DefinitionId;
Assert.NotNull(definition.DefinitionText);
for (var i = 0; i < referenceItems.Length; i++)
{
if (i == definitionIndex)
{
IncludeDeclaration = includeDeclaration
continue;
}
});
var references = await TestHandleAsync<object, object[]>(solution, request, Methods.TextDocumentReferencesName);
return references.Select(o => (LSP.Location)o).ToArray();
Assert.Null(referenceItems[i].DefinitionText);
Assert.Equal(definitionId, referenceItems[i].DefinitionId);
Assert.NotEqual(definitionId, referenceItems[i].Id);
}
}
}
}
......@@ -126,10 +126,17 @@ public void Exit()
[JsonRpcMethod(Methods.TextDocumentRenameName)]
public Task<WorkspaceEdit> GetTextDocumentRenameAsync(JToken input, CancellationToken cancellationToken)
{
var renameParams = input.ToObject<RenameParams>();
var renameParams = input.ToObject<RenameParams>(JsonSerializer);
return _protocol.RenameAsync(_workspace.CurrentSolution, renameParams, _clientCapabilities, cancellationToken);
}
[JsonRpcMethod(Methods.TextDocumentReferencesName)]
public Task<VSReferenceItem[]> GetTextDocumentReferencesAsync(JToken input, CancellationToken cancellationToken)
{
var referencesParams = input.ToObject<ReferenceParams>(JsonSerializer);
return _protocol.GetDocumentReferencesAsync(_workspace.CurrentSolution, referencesParams, _clientCapabilities, cancellationToken);
}
[JsonRpcMethod(Methods.TextDocumentCompletionName)]
public Task<SumType<CompletionItem[], CompletionList>?> GetTextDocumentCompletionAsync(JToken input, CancellationToken cancellationToken)
{
......
// 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.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.VisualStudio.LanguageServices.LiveShare.Protocol;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.VisualStudio.LanguageServices.LiveShare.Client.References
{
internal class RoslynFindUsagesService : IFindUsagesService
{
private readonly AbstractLspClientServiceFactory _roslynLspClientServiceFactory;
private readonly RemoteLanguageServiceWorkspace _remoteLanguageServiceWorkspace;
public RoslynFindUsagesService(AbstractLspClientServiceFactory roslynLspClientServiceFactory, RemoteLanguageServiceWorkspace remoteLanguageServiceWorkspace)
{
_roslynLspClientServiceFactory = roslynLspClientServiceFactory ?? throw new ArgumentNullException(nameof(roslynLspClientServiceFactory));
_remoteLanguageServiceWorkspace = remoteLanguageServiceWorkspace ?? throw new ArgumentNullException(nameof(remoteLanguageServiceWorkspace));
}
public Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context)
{
// Find implementations is now handled by the VS LSP client.
return Task.CompletedTask;
}
public async Task FindReferencesAsync(Document document, int position, IFindUsagesContext context)
{
var text = await document.GetTextAsync().ConfigureAwait(false);
var lspClient = _roslynLspClientServiceFactory.ActiveLanguageServerClient;
if (lspClient == null)
{
return;
}
var referenceParams = new LSP.ReferenceParams
{
Context = new LSP.ReferenceContext { IncludeDeclaration = false },
TextDocument = ProtocolConversions.DocumentToTextDocumentIdentifier(document),
Position = ProtocolConversions.LinePositionToPosition(text.Lines.GetLinePosition(position))
};
var locations = await lspClient.RequestAsync(LSP.Methods.TextDocumentReferences.ToLSRequest(), referenceParams, context.CancellationToken).ConfigureAwait(false);
if (locations == null)
{
return;
}
// TODO: Need to get real definition data from the server.
var dummyDef = DefinitionItem.CreateNonNavigableItem(ImmutableArray<string>.Empty, ImmutableArray<TaggedText>.Empty);
await context.OnDefinitionFoundAsync(dummyDef).ConfigureAwait(false);
foreach (var location in locations)
{
var documentSpan = await _remoteLanguageServiceWorkspace.GetDocumentSpanFromLocationAsync(location, context.CancellationToken).ConfigureAwait(false);
if (documentSpan == null)
{
continue;
}
await context.OnReferenceFoundAsync(new SourceReferenceItem(dummyDef, documentSpan.Value)).ConfigureAwait(false);
}
}
}
}
......@@ -97,7 +97,6 @@ public Task<ICollaborationService> CreateServiceAsync(CollaborationSession colla
// Uses Roslyn client.
CodeActionProvider = true,
ExecuteCommandProvider = new ExecuteCommandOptions(),
ReferencesProvider = true,
})));
var lifeTimeService = LspClientLifeTimeService;
......
// 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.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.LiveShare.LanguageServices;
namespace Microsoft.VisualStudio.LanguageServices.LiveShare
{
[ExportLspRequestHandler(LiveShareConstants.RoslynContractName, Methods.TextDocumentReferencesName)]
[Obsolete("Used for backwards compatibility with old liveshare clients.")]
internal class RoslynFindAllReferencesHandler : FindAllReferencesHandler
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public RoslynFindAllReferencesHandler(IThreadingContext threadingContext) : base(threadingContext)
{
}
}
[ExportLspRequestHandler(LiveShareConstants.CSharpContractName, Methods.TextDocumentReferencesName)]
internal class CSharpFindAllReferencesHandler : FindAllReferencesHandler
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CSharpFindAllReferencesHandler(IThreadingContext threadingContext) : base(threadingContext)
{
}
}
[ExportLspRequestHandler(LiveShareConstants.VisualBasicContractName, Methods.TextDocumentReferencesName)]
internal class VisualBasicFindAllReferencesHandler : FindAllReferencesHandler
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public VisualBasicFindAllReferencesHandler(IThreadingContext threadingContext) : base(threadingContext)
{
}
}
[ExportLspRequestHandler(LiveShareConstants.TypeScriptContractName, Methods.TextDocumentReferencesName)]
internal class TypeScriptFindAllReferencesHandler : FindAllReferencesHandler
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public TypeScriptFindAllReferencesHandler(IThreadingContext threadingContext) : base(threadingContext)
{
}
}
}
// 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.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
using Microsoft.VisualStudio.LiveShare.LanguageServices;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text.Adornments;
using Newtonsoft.Json.Linq;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.VisualStudio.LanguageServices.LiveShare
{
// TODO - This should move to the ILanguageClient when we remove the UI thread dependency.
// https://github.com/dotnet/roslyn/issues/38477
// The VS LSP client supports streaming using IProgress<T> on various requests.
// However, this is not yet supported through Live Share, so deserialization fails on the IProgress<T> property.
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1043376 tracks Live Share support for this (committed for 16.6).
internal class FindAllReferencesHandler : ILspRequestHandler<object, object[], Solution>
{
private readonly IThreadingContext _threadingContext;
public FindAllReferencesHandler(IThreadingContext threadingContext)
=> _threadingContext = threadingContext;
// TODO - When FAR moves to ILanguageClient, we should switch from using ReferenceGroup (now obsolete) to ReferenceItem.
// https://github.com/dotnet/roslyn/issues/42581
[System.Obsolete]
public async Task<object[]> HandleAsync(object request, RequestContext<Solution> requestContext, CancellationToken cancellationToken)
{
// The VS LSP client supports streaming using IProgress<T> on various requests.
// However, this is not yet supported through Live Share, so deserialization fails on the IProgress<T> property.
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1043376 tracks Live Share support for this (committed for 16.6).
var referenceParams = ((JObject)request).ToObject<ReferenceParams>(InProcLanguageServer.JsonSerializer);
var locations = ArrayBuilder<LSP.Location>.GetInstance();
var solution = requestContext.Context;
var document = solution.GetDocumentFromURI(referenceParams.TextDocument.Uri);
if (document == null)
{
return locations.ToArrayAndFree();
}
var findUsagesService = document.Project.LanguageServices.GetService<IFindUsagesService>();
var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(referenceParams.Position), cancellationToken).ConfigureAwait(false);
var context = new SimpleFindUsagesContext(cancellationToken);
// Roslyn calls into third party extensions to compute reference results and needs to be on the UI thread to compute results.
// This is not great for us and ideally we should ask for a Roslyn API where we can make this call without blocking the UI.
if (VsTaskLibraryHelper.ServiceInstance != null)
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
}
await findUsagesService.FindReferencesAsync(document, position, context).ConfigureAwait(false);
if (requestContext?.ClientCapabilities?.ToObject<VSClientCapabilities>()?.HasVisualStudioLspCapability() == true)
{
return await GetReferenceGroupsAsync(referenceParams, context, cancellationToken).ConfigureAwait(false);
}
else
{
return await GetLocationsAsync(referenceParams, context, cancellationToken).ConfigureAwait(false);
}
}
[System.Obsolete]
private async Task<LSP.ReferenceGroup[]> GetReferenceGroupsAsync(LSP.ReferenceParams request, SimpleFindUsagesContext context, CancellationToken cancellationToken)
{
var definitionMap = new Dictionary<DefinitionItem, List<SourceReferenceItem>>();
foreach (var reference in context.GetReferences())
{
if (!definitionMap.ContainsKey(reference.Definition))
{
definitionMap.Add(reference.Definition, new List<SourceReferenceItem>());
}
definitionMap[reference.Definition].Add(reference);
}
var referenceGroups = ArrayBuilder<LSP.ReferenceGroup>.GetInstance();
foreach (var keyValuePair in definitionMap)
{
var definition = keyValuePair.Key;
var references = keyValuePair.Value;
var referenceGroup = new LSP.ReferenceGroup();
var text = definition.GetClassifiedText();
referenceGroup.Definition = await ProtocolConversions.DocumentSpanToLocationWithTextAsync(definition.SourceSpans.First(), text, cancellationToken).ConfigureAwait(false);
referenceGroup.DefinitionIcon = new ImageElement(definition.Tags.GetFirstGlyph().GetImageId());
var locationWithTexts = new ArrayBuilder<LSP.LocationWithText>();
foreach (var reference in references)
{
var classifiedSpansAndHighlightSpan = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(reference.SourceSpan, context.CancellationToken).ConfigureAwait(false);
var classifiedSpans = classifiedSpansAndHighlightSpan.ClassifiedSpans;
var referenceLocation = await ProtocolConversions.DocumentSpanToLocationAsync(reference.SourceSpan, cancellationToken).ConfigureAwait(false);
var docText = await reference.SourceSpan.Document.GetTextAsync(context.CancellationToken).ConfigureAwait(false);
var classifiedText = new ClassifiedTextElement(classifiedSpans.Select(cspan => new ClassifiedTextRun(cspan.ClassificationType, docText.ToString(cspan.TextSpan))));
var locationWithText = new LSP.LocationWithText { Range = referenceLocation.Range, Uri = referenceLocation.Uri, Text = classifiedText };
locationWithTexts.Add(locationWithText);
}
referenceGroup.References = locationWithTexts.ToArrayAndFree();
referenceGroups.Add(referenceGroup);
}
return referenceGroups.ToArrayAndFree();
}
private static async Task<LSP.Location[]> GetLocationsAsync(LSP.ReferenceParams request, SimpleFindUsagesContext context, CancellationToken cancellationToken)
{
var locations = ArrayBuilder<LSP.Location>.GetInstance();
if (request.Context.IncludeDeclaration)
{
foreach (var definition in context.GetDefinitions())
{
foreach (var docSpan in definition.SourceSpans)
{
locations.Add(await ProtocolConversions.DocumentSpanToLocationAsync(docSpan, cancellationToken).ConfigureAwait(false));
}
}
}
foreach (var reference in context.GetReferences())
{
locations.Add(await ProtocolConversions.DocumentSpanToLocationAsync(reference.SourceSpan, cancellationToken).ConfigureAwait(false));
}
return locations.ToArrayAndFree();
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册