SignatureHelpHandler.cs 7.0 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 13 14 15 16 17 18 19 20 21 22 23 24 25 26

using System;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.SignatureHelp;
using Microsoft.VisualStudio.Text.Adornments;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
    [Shared]
    [ExportLspMethod(LSP.Methods.TextDocumentSignatureHelpName)]
    internal class SignatureHelpHandler : IRequestHandler<LSP.TextDocumentPositionParams, LSP.SignatureHelp>
    {
        private readonly IEnumerable<Lazy<ISignatureHelpProvider, OrderableLanguageMetadata>> _allProviders;

        [ImportingConstructor]
27
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
28 29 30 31 32 33
        public SignatureHelpHandler([ImportMany] IEnumerable<Lazy<ISignatureHelpProvider, OrderableLanguageMetadata>> allProviders)
        {
            _allProviders = allProviders;
        }

        public async Task<LSP.SignatureHelp> HandleRequestAsync(Solution solution, LSP.TextDocumentPositionParams request,
34
            LSP.ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
35 36 37 38 39 40 41
        {
            var document = solution.GetDocumentFromURI(request.TextDocument.Uri);
            if (document == null)
            {
                return new LSP.SignatureHelp();
            }

42
            var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false);
43 44 45 46 47 48

            var providers = _allProviders.Where(p => p.Metadata.Language == document.Project.Language);
            var triggerInfo = new SignatureHelpTriggerInfo(SignatureHelpTriggerReason.InvokeSignatureHelpCommand);

            foreach (var provider in providers)
            {
49
                var items = await provider.Value.GetItemsAsync(document, position, triggerInfo, cancellationToken).ConfigureAwait(false);
50 51 52 53 54 55 56 57 58 59 60 61 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

                if (items != null)
                {
                    var sigInfos = new ArrayBuilder<LSP.SignatureInformation>();

                    foreach (var item in items.Items)
                    {
                        LSP.SignatureInformation sigInfo;
                        if (clientCapabilities?.HasVisualStudioLspCapability() == true)
                        {
                            sigInfo = new LSP.VSSignatureInformation
                            {
                                ColorizedLabel = GetSignatureClassifiedText(item)
                            };
                        }
                        else
                        {
                            sigInfo = new LSP.SignatureInformation();
                        }

                        sigInfo.Label = GetSignatureText(item);
                        sigInfo.Documentation = new LSP.MarkupContent { Kind = LSP.MarkupKind.PlainText, Value = item.DocumentationFactory(cancellationToken).GetFullText() };
                        sigInfo.Parameters = item.Parameters.Select(p => new LSP.ParameterInformation
                        {
                            Label = p.Name,
                            Documentation = new LSP.MarkupContent { Kind = LSP.MarkupKind.PlainText, Value = p.DocumentationFactory(cancellationToken).GetFullText() }
                        }).ToArray();
                        sigInfos.Add(sigInfo);
                    }

                    var sigHelp = new LSP.SignatureHelp
                    {
                        ActiveSignature = GetActiveSignature(items),
                        ActiveParameter = items.ArgumentIndex,
                        Signatures = sigInfos.ToArrayAndFree()
                    };

                    return sigHelp;
                }
            }

            return new LSP.SignatureHelp();
        }


        private static int GetActiveSignature(SignatureHelpItems items)
        {
            if (items.SelectedItemIndex.HasValue)
            {
                return items.SelectedItemIndex.Value;
            }

            // From Roslyn's doc comments for SelectedItemIndex:
            //   If this is null, then the controller will pick the first item that has enough arguments
            //   to be viable based on what argument position the user is currently inside of.
            // However, the LSP spec expects the language server to make this decision.
            // So implement the logic of picking a signature that has enough arguments here.

            var matchingSignature = items.Items.FirstOrDefault(sig => sig.Parameters.Length > items.ArgumentIndex);
            return matchingSignature != null ? items.Items.IndexOf(matchingSignature) : 0;
        }

        /// <summary>
N
nnpcYvIVl 已提交
113
        /// The <see cref="SignatureHelpItem"/> contains a prefix, parameters separated by a
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
        /// separator and a suffix. Parameters themselves have a prefix, display and suffix.
        /// Concatenate them all to get the text.
        /// </summary>
        private string GetSignatureText(SignatureHelpItem item)
        {
            var sb = new StringBuilder();

            sb.Append(item.PrefixDisplayParts.GetFullText());

            var separators = item.SeparatorDisplayParts.GetFullText();
            for (var i = 0; i < item.Parameters.Length; i++)
            {
                var param = item.Parameters[i];

                if (i > 0)
                {
                    sb.Append(separators);
                }

                sb.Append(param.PrefixDisplayParts.GetFullText());
                sb.Append(param.DisplayParts.GetFullText());
                sb.Append(param.SuffixDisplayParts.GetFullText());
            }

            sb.Append(item.SuffixDisplayParts.GetFullText());
            sb.Append(item.DescriptionParts.GetFullText());

            return sb.ToString();
        }
        private ClassifiedTextElement GetSignatureClassifiedText(SignatureHelpItem item)
        {
            var taggedTexts = new ArrayBuilder<TaggedText>();

            taggedTexts.AddRange(item.PrefixDisplayParts);

            var separators = item.SeparatorDisplayParts;
            for (var i = 0; i < item.Parameters.Length; i++)
            {
                var param = item.Parameters[i];

                if (i > 0)
                {
                    taggedTexts.AddRange(separators);
                }

                taggedTexts.AddRange(param.PrefixDisplayParts);
                taggedTexts.AddRange(param.DisplayParts);
                taggedTexts.AddRange(param.SuffixDisplayParts);
            }

            taggedTexts.AddRange(item.SuffixDisplayParts);
            taggedTexts.AddRange(item.DescriptionParts);

            return new ClassifiedTextElement(taggedTexts.ToArrayAndFree().Select(part => new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text)));
        }
    }
}