DocumentSymbolsHandler.cs 8.6 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// 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.
4 5 6 7 8 9 10 11 12

using System;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar;
13
using Microsoft.CodeAnalysis.Host.Mef;
14 15 16 17 18 19 20 21 22 23
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
    [Shared]
    [ExportLspMethod(Methods.TextDocumentDocumentSymbolName)]
D
David Barbet 已提交
24
    internal class DocumentSymbolsHandler : AbstractRequestHandler<DocumentSymbolParams, object[]>
25
    {
26
        [ImportingConstructor]
27
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
28
        public DocumentSymbolsHandler(ILspSolutionProvider solutionProvider) : base(solutionProvider)
29 30 31
        {
        }

32 33
        public override async Task<object[]> HandleRequestAsync(DocumentSymbolParams request, ClientCapabilities clientCapabilities,
            string clientName, CancellationToken cancellationToken)
34
        {
D
David Barbet 已提交
35
            var document = SolutionProvider.GetDocument(request.TextDocument, clientName);
36 37 38 39 40 41 42
            if (document == null)
            {
                return Array.Empty<SymbolInformation>();
            }

            var symbols = ArrayBuilder<object>.GetInstance();

43
            var navBarService = document.Project.LanguageServices.GetRequiredService<INavigationBarItemService>();
44
            var navBarItems = await navBarService.GetItemsAsync(document, cancellationToken).ConfigureAwait(false);
45 46 47 48 49
            if (navBarItems.Count == 0)
            {
                return symbols.ToArrayAndFree();
            }

50 51 52
            var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
            var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
53 54 55 56 57 58 59 60

            // TODO - Return more than 2 levels of symbols.
            // https://github.com/dotnet/roslyn/projects/45#card-20033869
            if (clientCapabilities?.TextDocument?.DocumentSymbol?.HierarchicalDocumentSymbolSupport == true)
            {
                foreach (var item in navBarItems)
                {
                    // only top level ones
61
                    symbols.Add(await GetDocumentSymbolAsync(item, compilation, tree, text, cancellationToken).ConfigureAwait(false));
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
                }
            }
            else
            {
                foreach (var item in navBarItems)
                {
                    symbols.Add(GetSymbolInformation(item, compilation, tree, document, text, cancellationToken, containerName: null));

                    foreach (var childItem in item.ChildItems)
                    {
                        symbols.Add(GetSymbolInformation(childItem, compilation, tree, document, text, cancellationToken, item.Text));
                    }
                }
            }

            var result = symbols.WhereNotNull().ToArray();
            symbols.Free();
            return result;
        }

        /// <summary>
        /// Get a symbol information from a specified nav bar item.
        /// </summary>
        private static SymbolInformation GetSymbolInformation(NavigationBarItem item, Compilation compilation, SyntaxTree tree, Document document,
            SourceText text, CancellationToken cancellationToken, string containerName = null)
        {
            if (item.Spans.Count == 0)
            {
                return null;
            }

            var location = GetLocation(item, compilation, tree, cancellationToken);

            if (location == null)
            {
                return Create(item, item.Spans.First(), containerName, document, text);
            }

            return Create(item, location.SourceSpan, containerName, document, text);

            static SymbolInformation Create(NavigationBarItem item, TextSpan span, string containerName, Document document, SourceText text)
            {
                return new SymbolInformation
                {
                    Name = item.Text,
                    Location = new LSP.Location
                    {
                        Uri = document.GetURI(),
                        Range = ProtocolConversions.TextSpanToRange(span, text),
                    },
                    Kind = ProtocolConversions.GlyphToSymbolKind(item.Glyph),
                    ContainerName = containerName,
                };
            }
        }

        /// <summary>
        /// Get a document symbol from a specified nav bar item.
        /// </summary>
        private static async Task<DocumentSymbol> GetDocumentSymbolAsync(NavigationBarItem item, Compilation compilation, SyntaxTree tree,
122
            SourceText text, CancellationToken cancellationToken)
123 124 125 126 127 128 129 130
        {
            // it is actually symbol location getter. but anyway.
            var location = GetLocation(item, compilation, tree, cancellationToken);
            if (location == null)
            {
                return null;
            }

131
            var symbol = await GetSymbolAsync(location, compilation, cancellationToken).ConfigureAwait(false);
132 133 134 135 136 137 138 139 140 141 142 143 144
            if (symbol == null)
            {
                return null;
            }

            return new DocumentSymbol
            {
                Name = symbol.Name,
                Detail = item.Text,
                Kind = ProtocolConversions.GlyphToSymbolKind(item.Glyph),
                Deprecated = symbol.GetAttributes().Any(x => x.AttributeClass.MetadataName == "ObsoleteAttribute"),
                Range = ProtocolConversions.TextSpanToRange(item.Spans.First(), text),
                SelectionRange = ProtocolConversions.TextSpanToRange(location.SourceSpan, text),
145
                Children = await GetChildrenAsync(item.ChildItems, compilation, tree, text, cancellationToken).ConfigureAwait(false),
146 147 148
            };

            static async Task<DocumentSymbol[]> GetChildrenAsync(IEnumerable<NavigationBarItem> items, Compilation compilation, SyntaxTree tree,
149
                SourceText text, CancellationToken cancellationToken)
150 151 152 153
            {
                var list = new ArrayBuilder<DocumentSymbol>();
                foreach (var item in items)
                {
154
                    list.Add(await GetDocumentSymbolAsync(item, compilation, tree, text, cancellationToken).ConfigureAwait(false));
155 156 157 158 159
                }

                return list.ToArrayAndFree();
            }

160
            static async Task<ISymbol> GetSymbolAsync(Location location, Compilation compilation, CancellationToken cancellationToken)
161 162
            {
                var model = compilation.GetSemanticModel(location.SourceTree);
163
                var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
164 165 166 167
                var node = root.FindNode(location.SourceSpan);

                while (node != null)
                {
J
Jonathon Marolf 已提交
168
                    var symbol = model.GetDeclaredSymbol(node, cancellationToken);
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
                    if (symbol != null)
                    {
                        return symbol;
                    }

                    node = node.Parent;
                }

                return null;
            }
        }

        /// <summary>
        /// Get a location for a particular nav bar item.
        /// </summary>
        private static Location GetLocation(NavigationBarItem item, Compilation compilation, SyntaxTree tree, CancellationToken cancellationToken)
        {
            if (!(item is NavigationBarSymbolItem symbolItem))
            {
                return null;
            }

            var symbols = symbolItem.NavigationSymbolId.Resolve(compilation, cancellationToken: cancellationToken);
            var symbol = symbols.Symbol;

            if (symbol == null)
            {
                if (symbolItem.NavigationSymbolIndex < symbols.CandidateSymbols.Length)
                {
                    symbol = symbols.CandidateSymbols[symbolItem.NavigationSymbolIndex.Value];
                }
                else
                {
                    return null;
                }
            }

            var location = symbol.Locations.FirstOrDefault(l => l.SourceTree.Equals(tree));
            return location ?? symbol.Locations.FirstOrDefault();
        }
    }
}