diff --git a/src/EditorFeatures/CSharp/FindUsages/CSharpFindUsagesLSPService.cs b/src/EditorFeatures/CSharp/FindUsages/CSharpFindUsagesLSPService.cs new file mode 100644 index 0000000000000000000000000000000000000000..b513b76c16ef03419330dea43c873bdfa4bb4c3c --- /dev/null +++ b/src/EditorFeatures/CSharp/FindUsages/CSharpFindUsagesLSPService.cs @@ -0,0 +1,23 @@ +// 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.Composition; +using Microsoft.CodeAnalysis.Editor.FindUsages; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.FindUsages +{ + [ExportLanguageService(typeof(IFindUsagesLSPService), LanguageNames.CSharp), Shared] + internal class CSharpFindUsagesLSPService : AbstractFindUsagesService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpFindUsagesLSPService(IThreadingContext threadingContext) + : base(threadingContext) + { + } + } +} diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs index 6056ce5cc41c2b22e13c128722571b585e44a069..09b272bebbece809c22e73606bd386134902b999 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.cs @@ -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) { diff --git a/src/EditorFeatures/Core/FindUsages/IFindUsagesLSPService.cs b/src/EditorFeatures/Core/FindUsages/IFindUsagesLSPService.cs new file mode 100644 index 0000000000000000000000000000000000000000..654199a61d5d01034f76cbac5830ff2f000b2c0d --- /dev/null +++ b/src/EditorFeatures/Core/FindUsages/IFindUsagesLSPService.cs @@ -0,0 +1,25 @@ +// 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 + { + /// + /// Finds the references for the symbol at the specific position in the document, + /// pushing the results into the context instance. + /// + Task FindReferencesAsync(Document document, int position, IFindUsagesContext context); + + /// + /// Finds the implementations for the symbol at the specific position in the document, + /// pushing the results into the context instance. + /// + Task FindImplementationsAsync(Document document, int position, IFindUsagesContext context); + } +} diff --git a/src/EditorFeatures/VisualBasic/FindUsages/VisualBasicFindUsagesLSPService.vb b/src/EditorFeatures/VisualBasic/FindUsages/VisualBasicFindUsagesLSPService.vb new file mode 100644 index 0000000000000000000000000000000000000000..73fd13b6d4a322fc5bafbf56d1ad0caf45027d4c --- /dev/null +++ b/src/EditorFeatures/VisualBasic/FindUsages/VisualBasicFindUsagesLSPService.vb @@ -0,0 +1,21 @@ +' 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 + + Friend Class VisualBasicFindUsagesLSPService + Inherits AbstractFindUsagesService + + + + Public Sub New(threadingContext As IThreadingContext) + MyBase.New(threadingContext) + End Sub + End Class +End Namespace diff --git a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index d0b73e0a453764638940ab9afc73c52c2fb799ec..cb9badd814966cdf72789e926c07592a944ac902 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -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.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(); + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Initialize/InitializeHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Initialize/InitializeHandler.cs index a6602a1ede0524ee426992a698b269a12c6b7a37..31b7ed25b9514951b278486d336f8ca5151f27b5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Initialize/InitializeHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Initialize/InitializeHandler.cs @@ -50,6 +50,7 @@ public Task HandleRequestAsync(Solution solution, LSP.Init DocumentRangeFormattingProvider = true, DocumentOnTypeFormattingProvider = new LSP.DocumentOnTypeFormattingOptions { FirstTriggerCharacter = "}", MoreTriggerCharacter = new[] { ";", "\n" } }, DocumentHighlightProvider = true, + ReferencesProvider = true, } }); } diff --git a/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs b/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..5738024cc1a990ae06ef254e7c5e4ec5100f54fb --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/References/FindAllReferencesHandler.cs @@ -0,0 +1,208 @@ +// 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 + { + 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 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(); + } + + var findUsagesService = document.GetRequiredLanguageService(); + 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 GetReferenceItemsAsync( + Document document, + int position, + SimpleFindUsagesContext context, + CancellationToken cancellationToken) + { + // Mapping each reference to its definition + var definitionMap = new Dictionary>(); + foreach (var reference in context.GetReferences()) + { + if (!definitionMap.ContainsKey(reference.Definition)) + { + definitionMap.Add(reference.Definition, new List()); + } + + 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.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 GenerateReferenceItem( + int id, + int? definitionId, + Document originalDocument, + int originalPosition, + DocumentSpan documentSpan, + SimpleFindUsagesContext context, + ImmutableDictionary 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; + } + } + } +} diff --git a/src/Features/LanguageServer/Protocol/LanguageServerProtocol.cs b/src/Features/LanguageServer/Protocol/LanguageServerProtocol.cs index 9f2370bbc7fd6a117cf30f1479b5fb0d4fcb80ec..b7bddb0b886c3fd51b8d6e6d6075c8459d864b1a 100644 --- a/src/Features/LanguageServer/Protocol/LanguageServerProtocol.cs +++ b/src/Features/LanguageServer/Protocol/LanguageServerProtocol.cs @@ -153,6 +153,18 @@ public Task FormatDocumentRangeAsync(Solution solution, LSP.Docu public Task GetDocumentHighlightAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken) => ExecuteRequestAsync(LSP.Methods.TextDocumentDocumentHighlightName, solution, request, clientCapabilities, cancellationToken); + /// + /// 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 + /// + /// the solution containing the request document. + /// the references request. + /// the client capabilities for the request. + /// a cancellation token. + /// references information associated with the symbol at the given document location. + public Task GetDocumentReferencesAsync(Solution solution, LSP.ReferenceParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken) + => ExecuteRequestAsync(LSP.Methods.TextDocumentReferencesName, solution, request, clientCapabilities, cancellationToken); + /// /// 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 GetFoldingRangeAsync(Solution solution, LSP.Fold /// https://microsoft.github.io/language-server-protocol/specification#textDocument_hover /// /// the solution containing any documents in the request. - /// the hover requesst. + /// the hover request. /// the client capabilities for the request. /// a cancellation token. /// the Hover using MarkupContent. @@ -225,18 +237,6 @@ public Task GetWorkspaceSymbolsAsync(Solution solution, public Task?> GoToDefinitionAsync(Solution solution, LSP.TextDocumentPositionParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken) => ExecuteRequestAsync?>(LSP.Methods.TextDocumentDefinitionName, solution, request, clientCapabilities, cancellationToken); - /// - /// Answers a rename request by returning the workspace edit for a given symbol. - /// https://microsoft.github.io/language-server-protocol/specification#textDocument_rename - /// - /// the solution containing the request. - /// the document position of the symbol to rename. - /// the client capabilities for the request. - /// a cancellation token. - /// the workspace edits to rename the given symbol - public Task RenameAsync(Solution solution, LSP.RenameParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken) - => ExecuteRequestAsync(LSP.Methods.TextDocumentRenameName, solution, request, clientCapabilities, cancellationToken); - /// /// 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 GoToTypeDefinitionAsync(Solution solution, LSP.TextD public Task InitializeAsync(Solution solution, LSP.InitializeParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken) => ExecuteRequestAsync(LSP.Methods.InitializeName, solution, request, clientCapabilities, cancellationToken); + /// + /// Answers a rename request by returning the workspace edit for a given symbol. + /// https://microsoft.github.io/language-server-protocol/specification#textDocument_rename + /// + /// the solution containing the request. + /// the document position of the symbol to rename. + /// the client capabilities for the request. + /// a cancellation token. + /// the workspace edits to rename the given symbol + public Task RenameAsync(Solution solution, LSP.RenameParams request, LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken) + => ExecuteRequestAsync(LSP.Methods.TextDocumentRenameName, solution, request, clientCapabilities, cancellationToken); + /// /// Answers a request to resolve a completion item. /// https://microsoft.github.io/language-server-protocol/specification#completionItem_resolve diff --git a/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..fe8ff0f4cfbe564db009c44b939c58f16a7c2901 --- /dev/null +++ b/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs @@ -0,0 +1,154 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Test.Utilities; +using Xunit; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.References +{ + public class FindAllReferencesHandlerTests : AbstractLanguageServerProtocolTests + { + [WpfFact] + public async Task TestFindAllReferencesAsync() + { + var markup = +@"class A +{ + public int {|reference: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); + + 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] + public async Task TestFindAllReferencesAsync_MultipleDocuments() + { + var markups = new string[] { +@"class A +{ + public int {|reference: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(markups, out var locations); + + 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] + public async Task TestFindAllReferencesAsync_InvalidLocation() + { + var markup = +@"class A +{ + {|caret:|} +}"; + using var workspace = CreateTestWorkspace(markup, out var locations); + + var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First()); + Assert.Empty(results); + } + + [WpfFact] + public async Task TestFindAllReferencesMetadataDefinitionAsync() + { + 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(), + }; + + private static async Task 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) + { + continue; + } + + Assert.Null(referenceItems[i].DefinitionText); + Assert.Equal(definitionId, referenceItems[i].DefinitionId); + Assert.NotEqual(definitionId, referenceItems[i].Id); + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs index fede3d013a304023941f3ea1bcade60c3e5ce54c..0478ed6d63e8441bcc07bcb890543c6b05cfeb50 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs @@ -126,10 +126,17 @@ public void Exit() [JsonRpcMethod(Methods.TextDocumentRenameName)] public Task GetTextDocumentRenameAsync(JToken input, CancellationToken cancellationToken) { - var renameParams = input.ToObject(); + var renameParams = input.ToObject(JsonSerializer); return _protocol.RenameAsync(_workspace.CurrentSolution, renameParams, _clientCapabilities, cancellationToken); } + [JsonRpcMethod(Methods.TextDocumentReferencesName)] + public Task GetTextDocumentReferencesAsync(JToken input, CancellationToken cancellationToken) + { + var referencesParams = input.ToObject(JsonSerializer); + return _protocol.GetDocumentReferencesAsync(_workspace.CurrentSolution, referencesParams, _clientCapabilities, cancellationToken); + } + [JsonRpcMethod(Methods.TextDocumentCompletionName)] public Task?> GetTextDocumentCompletionAsync(JToken input, CancellationToken cancellationToken) { diff --git a/src/VisualStudio/LiveShare/Impl/Client/References/RoslynFindUsagesService.Exports.cs b/src/VisualStudio/LiveShare/Impl/Client/References/RoslynFindUsagesService.Exports.cs deleted file mode 100644 index 32bbfac1f8d5832a785973021499a84dd2389135..0000000000000000000000000000000000000000 --- a/src/VisualStudio/LiveShare/Impl/Client/References/RoslynFindUsagesService.Exports.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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.Composition; -using Microsoft.CodeAnalysis.Editor.FindUsages; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.VisualStudio.LanguageServices.LiveShare.Client.References -{ - [ExportLanguageService(typeof(IFindUsagesService), StringConstants.CSharpLspLanguageName), Shared] - internal class CSharpLspFindUsagesService : RoslynFindUsagesService - { - [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) - { - } - } -} diff --git a/src/VisualStudio/LiveShare/Impl/Client/References/RoslynFindUsagesService.cs b/src/VisualStudio/LiveShare/Impl/Client/References/RoslynFindUsagesService.cs deleted file mode 100644 index b773a95843a83a265f0ab0c09659d3209ed6003e..0000000000000000000000000000000000000000 --- a/src/VisualStudio/LiveShare/Impl/Client/References/RoslynFindUsagesService.cs +++ /dev/null @@ -1,73 +0,0 @@ -// 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.Empty, ImmutableArray.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); - } - } - } -} diff --git a/src/VisualStudio/LiveShare/Impl/Client/RoslynLSPClientService.cs b/src/VisualStudio/LiveShare/Impl/Client/RoslynLSPClientService.cs index 343d32125888753665ec11d0f756dc9b4331a132..5ede20193d386b324091e95c9506172df9243f25 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/RoslynLSPClientService.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/RoslynLSPClientService.cs @@ -97,7 +97,6 @@ public Task CreateServiceAsync(CollaborationSession colla // Uses Roslyn client. CodeActionProvider = true, ExecuteCommandProvider = new ExecuteCommandOptions(), - ReferencesProvider = true, }))); var lifeTimeService = LspClientLifeTimeService; diff --git a/src/VisualStudio/LiveShare/Impl/FindAllReferencesHandler.Exports.cs b/src/VisualStudio/LiveShare/Impl/FindAllReferencesHandler.Exports.cs deleted file mode 100644 index 7a126fabd20e8b8d4d2edc6ce0582518447415f2..0000000000000000000000000000000000000000 --- a/src/VisualStudio/LiveShare/Impl/FindAllReferencesHandler.Exports.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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) - { - } - } -} diff --git a/src/VisualStudio/LiveShare/Impl/FindAllReferencesHandler.cs b/src/VisualStudio/LiveShare/Impl/FindAllReferencesHandler.cs deleted file mode 100644 index 5e2ac01ffa8cc9f1b0a007f857559a6d3eec4b2c..0000000000000000000000000000000000000000 --- a/src/VisualStudio/LiveShare/Impl/FindAllReferencesHandler.cs +++ /dev/null @@ -1,149 +0,0 @@ -// 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 on various requests. - // However, this is not yet supported through Live Share, so deserialization fails on the IProgress property. - // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1043376 tracks Live Share support for this (committed for 16.6). - internal class FindAllReferencesHandler : ILspRequestHandler - { - 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 HandleAsync(object request, RequestContext requestContext, CancellationToken cancellationToken) - { - // The VS LSP client supports streaming using IProgress on various requests. - // However, this is not yet supported through Live Share, so deserialization fails on the IProgress property. - // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1043376 tracks Live Share support for this (committed for 16.6). - var referenceParams = ((JObject)request).ToObject(InProcLanguageServer.JsonSerializer); - var locations = ArrayBuilder.GetInstance(); - var solution = requestContext.Context; - var document = solution.GetDocumentFromURI(referenceParams.TextDocument.Uri); - if (document == null) - { - return locations.ToArrayAndFree(); - } - - var findUsagesService = document.Project.LanguageServices.GetService(); - 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()?.HasVisualStudioLspCapability() == true) - { - return await GetReferenceGroupsAsync(referenceParams, context, cancellationToken).ConfigureAwait(false); - } - else - { - return await GetLocationsAsync(referenceParams, context, cancellationToken).ConfigureAwait(false); - } - } - - [System.Obsolete] - private async Task GetReferenceGroupsAsync(LSP.ReferenceParams request, SimpleFindUsagesContext context, CancellationToken cancellationToken) - { - var definitionMap = new Dictionary>(); - - foreach (var reference in context.GetReferences()) - { - if (!definitionMap.ContainsKey(reference.Definition)) - { - definitionMap.Add(reference.Definition, new List()); - } - - definitionMap[reference.Definition].Add(reference); - } - - var referenceGroups = ArrayBuilder.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(); - 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 GetLocationsAsync(LSP.ReferenceParams request, SimpleFindUsagesContext context, CancellationToken cancellationToken) - { - var locations = ArrayBuilder.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(); - } - } -} diff --git a/src/VisualStudio/LiveShare/Test/FindAllReferencesHandlerTests.cs b/src/VisualStudio/LiveShare/Test/FindAllReferencesHandlerTests.cs deleted file mode 100644 index f8ce61af2b6738f3ba7ca104402958062675291b..0000000000000000000000000000000000000000 --- a/src/VisualStudio/LiveShare/Test/FindAllReferencesHandlerTests.cs +++ /dev/null @@ -1,128 +0,0 @@ -// 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.Linq; -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 -{ - public class FindAllReferencesHandlerTests : AbstractLiveShareRequestHandlerTests - { - [WpfFact] - public async Task TestFindAllReferencesAsync() - { - var markup = -@"class A -{ - public int {|reference: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); - - var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First(), true); - AssertLocationsEqual(locations["reference"], results); - } - - [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); - - var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First(), false); - AssertLocationsEqual(locations["reference"], results); - } - - [WpfFact] - public async Task TestFindAllReferencesAsync_MultipleDocuments() - { - var markups = new string[] { -@"class A -{ - public int {|reference: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(markups, out var locations); - - var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First(), true); - AssertLocationsEqual(locations["reference"], results); - } - - [WpfFact] - public async Task TestFindAllReferencesAsync_InvalidLocation() - { - var markup = -@"class A -{ - {|caret:|} -}"; - using var workspace = CreateTestWorkspace(markup, out var locations); - - var results = await RunFindAllReferencesAsync(workspace.CurrentSolution, locations["caret"].First(), true); - Assert.Empty(results); - } - - private static async Task RunFindAllReferencesAsync(Solution solution, LSP.Location caret, bool includeDeclaration) - { - var request = (JObject)JToken.FromObject(new LSP.ReferenceParams() - { - TextDocument = CreateTextDocumentIdentifier(caret.Uri), - Position = caret.Range.Start, - Context = new LSP.ReferenceContext() - { - IncludeDeclaration = includeDeclaration - } - }); - - var references = await TestHandleAsync(solution, request, Methods.TextDocumentReferencesName); - return references.Select(o => (LSP.Location)o).ToArray(); - } - } -}