C# Roslyn CompletionService 从哪里获取方法重载信息

问题描述

我有一个CompletionService.GetDescriptionAsync(Document,CompletionItem) 返回的方法给了我以下描述:

void sql.GetsqliteDB(string url) (+ 1 overload)

这是我在 Xamarin 项目中制作的一个方法,这里有两个方法签名:

public static void GetsqliteDB(string url);
public static string GetsqliteDB(string url,string name);

Roslyn 获取两者信息的方式是什么?

以下是我设置完成的方式:

    async Task InitCodeCompletion()
    {
        host = MefHostServices.Create(MefHostServices.DefaultAssemblies);
        workspace = new AdhocWorkspace(host);

        Type[] types =
        {
            typeof(object),typeof(System.Linq.Enumerable),typeof(System.Collections.IEnumerable),typeof(Console),typeof(System.Reflection.Assembly),typeof(List<>),typeof(Type),typeof(sql)
        };

        imports = types.Select(x => x.Namespace).distinct().ToImmutableArray();
        assemblies = types.Select(x => x.Assembly).distinct().ToImmutableArray();
        references = assemblies.Select(t => MetadataReference.CreateFromFile(t.Location) as MetadataReference).ToImmutableArray();

        compilationoptions = new CSharpCompilationoptions(
           OutputKind.DynamicallyLinkedLibrary,usings: imports);

        projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(),VersionStamp.Create(),"Script",LanguageNames.CSharp,isSubmission: true)
           .WithMetadataReferences(references).WithCompilationoptions(compilationoptions);
        project = workspace.AddProject(projectInfo);
        documentInfo = DocumentInfo.Create(DocumentId.CreateNewId(project.Id),sourceCodeKind: SourceCodeKind.Script,loader: TextLoader.From(TextAndVersion.Create(SourceText.From(""),VersionStamp.Create())));
        document = workspace.AddDocument(documentInfo);

        var services = workspace.Services;

        completionService = CompletionService.GetService(document);
    }



    async Task<CodeCompletionResults> GetCompletions(string code)
    {
        string codemodified = "using sql = XamTestNET5.Services.sqliteGeneratorService; " + Environment.NewLine;
        codemodified += "using HtmlSvc = XamTestNET5.Services.HtmlRetrievalService;" + Environment.NewLine;
        // ^^^ The above two lines set up some simple namespace aliases in my project,if you kNow how to put this in a separate project document and use it in code completion please let me kNow in comments as otherwise doing so gives me an exception that you can't have multiple Syntax trees
        codemodified += code;
        var source = SourceText.From(codemodified);
        document = document.WithText(source);

        // cursor position is at the end
        var position = source.Length;

        var completions = await completionService.GetCompletionsAsync(document,position);
        return new CodeCompletionResults() { InputCode = code,ModifiedCode = codemodified,Completions = completions }; 
    }

这是我现在获取它们并将它们放入浏览器控件的方法

    private async void CSharpShellEnvironment_EntryCodeCompletionEntry(object sender,CSharpShellEnvironment.EntryEventArgs e)
    {
        if (e.Value != "")
        {
            CodeCompletionResults results = await GetCompletions(e.Value);
            CompletionList list = results.Completions;

            if (list != null)
            {
                if (list.Items != null)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (var item in list.Items)
                    {
                        string spanText = (item.Span.Start != item.Span.End) ? results.ModifiedCode.Substring(item.Span.Start,item.Span.Length) : "";
                        bool recommended = spanText == "" ? true : item.displayText.StartsWith(spanText);
                        if (recommended)
                        {
                            string fText = item.displayText.Substring(spanText.Length);
                            string props = "";
                            foreach(var p in item.Properties)
                            {
                                props += $"<span data-key=\"{p.Key}\" data-value=\"{p.Value}\"></span>";
                            }
                            string tags = "";
                            foreach(var t in item.Tags)
                            {
                                tags += $"<span data-tag=\"{t}\"></span>";
                            }
                            string descStr = "";
                            if (item.Tags != null)
                            {
                                if (item.Tags.Where(x => x.ToLower() == "method").FirstOrDefault() != null && item.Tags.Where(x => x.ToLower() == "public").FirstOrDefault() != null)
                                {
                                    var desc = await completionService.GetDescriptionAsync(document,item);
                                    descStr += $"<span data-desc=\"{desc.Text}\">";
                                    foreach(var part in desc.TaggedParts)
                                    {
                                        descStr += $"<span data-desc-part-tag=\"{part.Tag}\" data-desc-part-text=\"{part.Text}\"></span>";
                                    }
                                    descStr += "</span>";
                                }
                            }
                            sb.AppendLine($"<div class=\"codecompleteentry\" data-display-text=\"{item.displayText}\" data-span-text=\"{spanText}\" data-final-text=\"{fText}\">{props}{tags}{descStr}{fText}</div>");
                        }
                    }
                    string scriptInputClick = "Array.prototype.forEach.call(document.getElementsByClassName('codecompleteentry'),function(el) { el.addEventListener('click',function(elem) { var text = { MessageType: 'CodeCompletion',Parameters: JSON.stringify({ DatadisplayText: el.getAttribute('data-display-text'),DataSpanText: el.getAttribute('data-span-text'),DataFinalText: el.getAttribute('data-final-text') }),Message: el.innerText }; window.chrome.webview.postMessage(text); } ); });";
                    sb.AppendLine($"<script type=\"text/javascript\">{scriptInputClick}</script>");
                    env.EnterCodeCompletionResponse(sb.ToString());
                }
                else
                {
                    env.EnterCodeCompletionResponse(strNoSuggestions);
                }
            }
            else
            {
                env.EnterCodeCompletionResponse(strNoSuggestions);
            }
        }
        else
        {
            env.EnterCodeCompletionResponse(strNoSuggestions);
        }
    }

解决方法

从表面上看,CompletionSurface 拥有你需要的一切,但事实并非如此,你需要引用 DocumentSemanticModel 才能获得所有签名当用户在代码完成期间在方法上键入 ( 时方法的重载。

直到我开始查看 RoslynPad 源代码后,我才意识到这一点,我建议这样做作为一个实际示例:https://github.com/aelij/RoslynPad

    List<IEnumerable<ReferencedSymbol>> allMethodRefs = new List<IEnumerable<ReferencedSymbol>>();

    async Task<CodeCompletionResults> GetCompletions(string code)
    {
        string codeModified = "using SQL = XamTestNET5.Services.SQLiteGeneratorService; " + Environment.NewLine;
        codeModified += "using HtmlSvc = XamTestNET5.Services.HtmlRetrievalService;" + Environment.NewLine;
        // ^^^ I put my namespace aliases in the same SyntaxTree for now,//     I'd like a better solution though.
        codeModified += code;
        var source = SourceText.From(codeModified);
        document = document.WithText(source);

        // cursor position is at the end
        var position = source.Length;

        var completions = await completionService.GetCompletionsAsync(document,position);
        syntaxRoot = await document.GetSyntaxRootAsync();
        semanticModel = await document.GetSemanticModelAsync();
        var methods = syntaxRoot.DescendantNodes().OfType<InvocationExpressionSyntax>();

        allMethodRefs = new List<IEnumerable<ReferencedSymbol>>();

        if (methods != null)
        {
            if (methods.Count() > 0)
            {
                foreach(var m in methods)
                {
                    var info = semanticModel.GetSymbolInfo(m);
                    if (info.Symbol != null)
                    {
                        allMethodRefs.Add(await SymbolFinder.FindReferencesAsync(info.Symbol,solution));
                    }
                    else
                    {
                        foreach(var symbol in info.CandidateSymbols)
                        {
                            allMethodRefs.Add(await SymbolFinder.FindReferencesAsync(symbol,solution));
                        }
                    }
                }
            }
        }
        return new CodeCompletionResults() { InputCode = code,ModifiedCode = codeModified,Completions = completions }; 
    }