VS Extension:TextPoint.GreaterThan / LessThan对于大文件来说非常慢

我正在研究VS扩展,它需要知道文本光标当前位于哪个类成员(方法,属性等)。 它还需要父母的意识(例如,类,嵌套类等)。 它需要知道成员或类的类型,名称和行号。 当我说“类型”时我的意思是“方法”或“属性”不一定是“.NET类型”。

目前我在这里使用此代码:

public static class CodeElementHelper { public static CodeElement[] GetCodeElementAtCursor(DTE2 dte) { try { var cursorTextPoint = GetCursorTextPoint(dte); if (cursorTextPoint != null) { var activeDocument = dte.ActiveDocument; var projectItem = activeDocument.ProjectItem; var codeElements = projectItem.FileCodeModel.CodeElements; return GetCodeElementAtTextPoint(codeElements, cursorTextPoint).ToArray(); } } catch (Exception ex) { Debug.WriteLine("[DBG][EXC] - " + ex.Message + " " + ex.StackTrace); } return null; } private static TextPoint GetCursorTextPoint(DTE2 dte) { var cursorTextPoint = default(TextPoint); try { var objTextDocument = (TextDocument)dte.ActiveDocument.Object(); cursorTextPoint = objTextDocument.Selection.ActivePoint; } catch (Exception ex) { Debug.WriteLine("[DBG][EXC] - " + ex.Message + " " + ex.StackTrace); } return cursorTextPoint; } private static List GetCodeElementAtTextPoint(CodeElements codeElements, TextPoint objTextPoint) { var returnValue = new List(); if (codeElements == null) return null; int count = 0; foreach (CodeElement element in codeElements) { if (element.StartPoint.GreaterThan(objTextPoint)) { // The code element starts beyond the point } else if (element.EndPoint.LessThan(objTextPoint)) { // The code element ends before the point } else { if (element.Kind == vsCMElement.vsCMElementClass || element.Kind == vsCMElement.vsCMElementProperty || element.Kind == vsCMElement.vsCMElementPropertySetStmt || element.Kind == vsCMElement.vsCMElementFunction) { returnValue.Add(element); } var memberElements = GetCodeElementMembers(element); var objMemberCodeElement = GetCodeElementAtTextPoint(memberElements, objTextPoint); if (objMemberCodeElement != null) { returnValue.AddRange(objMemberCodeElement); } break; } } return returnValue; } private static CodeElements GetCodeElementMembers(CodeElement codeElement) { CodeElements codeElements = null; if (codeElement is CodeNamespace) { codeElements = (codeElement as CodeNamespace).Members; } else if (codeElement is CodeType) { codeElements = (codeElement as CodeType).Members; } else if (codeElement is CodeFunction) { codeElements = (codeElement as CodeFunction).Parameters; } return codeElements; } } 

所以当前有效,如果我调用GetCodeElementAtCursor,我将获得该成员,并且它的父母会回来。 (这是一个古老的代码,但我相信我最初从Carlos的博客中获取它并从VB移植它)。

我的问题是,当我的扩展用于非常大的代码时,例如具有几千行的自动生成的文件,它会使VS进行爬行。 几乎无法使用。 运行探查器显示热线是

 private static List GetCodeElementAtTextPoint(CodeElements codeElements, TextPoint objTextPoint) { foreach (CodeElement element in codeElements) { ... /*-->*/ if (element.StartPoint.GreaterThan(objTextPoint)) // HERE */ else if (element.EndPoint.LessThan(objTextPoint)) // HERE */ var objMemberCodeElement = GetCodeElementAtTextPoint(memberElements, objTextPoint); // AND, HERE <--- ... } } return returnValue; } 

所以第三个是显而易见的,它是对自身的递归调用,因此影响它的任何因素都会影响对自身的调用。 然而,前两个,我不知道如何解决。

  • 是否有一种替代方法可以用来检索我的光标所在的成员类型(类,方法,道具等),名称,行号和父母?
  • 有什么办法可以让TextPoint.GreaterThanTestPoint.LessThan方法表现TextPoint.GreaterThan好吗?
  • 或者,我是SOL吗?

无论方法是什么,它只需要支持VS2015或更新版本。

谢谢!

更新:回答谢尔盖的评论 – 它确实似乎是由.GreaterThan / .LessThan()引起的。 我已经分离了代码,并且减速肯定发生在那些方法调用上,而不是element.StartPointelement.EndPoint的属性访问器。

在此处输入图像描述

使用GetCursorTextPoint获取TextPoint后,可以使用TextPoint.CodeElement属性查找当前代码元素:

  EnvDTE.TextPoint p = GetCursorTextPoint(DTE); foreach (EnvDTE.vsCMElement i in Enum.GetValues(typeof(EnvDTE.vsCMElement))) { EnvDTE.CodeElement e = p.CodeElement[i]; if (e != null) System.Windows.MessageBox.Show(i.ToString() + " " + e.FullName); } 

我最终走上了使用一些新的roslyn东西的路线。 下面的代码(几乎)与问题上面的代码完全一样,添加了返回Moniker。

我将此标记为答案,但由于谢尔盖对他的答案非常有帮助,加上我的罗斯林代码的灵感实际上来自这个SO答案 ,这也是他的答案,他绝对值得分数:)。

代码

 public static (string, ImageMoniker)[] GetSyntaxHierarchyAtCaret(IWpfTextView textView) { var caretPosition = textView.Caret.Position.BufferPosition; var document = caretPosition.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); var syntaxRoot = document.GetSyntaxRootAsync().Result; var caretParent = syntaxRoot.FindToken(caretPosition).Parent; var returnValue = new List<(string, ImageMoniker)>(); while (caretParent != null) { var kind = caretParent.Kind(); switch (kind) { case SyntaxKind.ClassDeclaration: { var dec = caretParent as ClassDeclarationSyntax; returnValue.Add((dec.Identifier.ToString(),KnownMonikers.Class)); break; } case SyntaxKind.MethodDeclaration: { var dec = caretParent as MethodDeclarationSyntax; returnValue.Add((dec.Identifier.ToString(),KnownMonikers.Method)); break; } case SyntaxKind.PropertyDeclaration: { var dec = caretParent as PropertyDeclarationSyntax; returnValue.Add((dec.Identifier.ToString(), KnownMonikers.Property)); break; } } caretParent = caretParent.Parent; } return returnValue.ToArray(); } 

依赖

由于我正在返回一个元组,你需要System.ValueTuple ,而Roslyn的东西需要Microsoft.CodeAnalysis.EditorFeatures.Text , Microsoft.CodeAnalysis.CSharp以及所有依赖项。

针对VS2015 / 2017的版本和所需的.NET版本

CodeAnalysis程序集要求您定位(我认为).NET 4.6.1或更高版本。 CodeAnalysis程序集的版本也直接与它可以支持的VS版本相关。 我没有看到任何关于此的官方文档(我认为应该在每个msdn页面顶部用大胆的红色字母发布关于这个!)但是这里有一个SO答案 ,用于不同VS目标的版本。 你最早可以瞄准的目标似乎是VS2015(RTM)。 我个人使用v1.3.2,它应该支持VS2015 Update 3或更高版本。

性能

我没有通过分析器运行它,但它运行得相当顺畅。 一开始有几秒钟,在大文件上,它不起作用(我假设文件被索引) – 但如果仔细观察,VS中的许多function在索引之前都不起作用(或无论如何)是完整的。 你几乎没有注意到它。 在一个小文件上,它是微不足道的。

(与问题略有不同,但可能对某人有帮助……)

任何使用CaretChanged事件来驱动这样的function的人都有一个提示,他们遇到性能问题:我建议使用调度程序并限制调用次数。 下面的代码将为呼叫增加200ms的延迟,并且每200ms不允许多于一次呼叫。 好吧,至少200毫秒。 它是不可预测的,但它会在能够 – 低优先级(DispatcherPriority.ApplicationIdle)时运行:

 private readonly IWpfTextView _textView; private readonly DispatcherTimer _throttleCursorMove; ... // constructor { _textView.Caret.PositionChanged += HandleCaretPositionChanged; _throttleCursorMove = new DispatcherTimer(DispatcherPriority.ApplicationIdle); _throttleCursorMove.Tick += (sender, args) => CaretPositionChanged(); _throttleCursorMove.Interval = new TimeSpan(0, 0, 0, 0, 200); } private void HandleCaretPositionChanged(object sender, CaretPositionChangedEventArgs e) { if (!_throttleCursorMove.IsEnabled) _throttleCursorMove.Start(); } private void CaretPositionChanged() { _throttleCursorMove.Stop(); ... var hierarchy = CodeHierarchyHelper.GetSyntaxHierarchyAtCaret(_textView); ... } ...