问题描述
我有一个从 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
拥有你需要的一切,但事实并非如此,你需要引用 Document
的 SemanticModel
才能获得所有签名当用户在代码完成期间在方法上键入 (
时方法的重载。
直到我开始查看 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 };
}