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

时间:2017-07-27 19:08:32

标签: c# visual-studio visual-studio-extensions vsix envdte

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

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

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<CodeElement> GetCodeElementAtTextPoint(CodeElements codeElements, TextPoint objTextPoint)
    {
        var returnValue = new List<CodeElement>();

        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' blog获取它并从VB移植它。)

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

private static List<CodeElement> GetCodeElementAtTextPoint(CodeElements codeElements, TextPoint objTextPoint)
{
    foreach (CodeElement element in codeElements)
    {
        ...
/*-->*/ if (element.StartPoint.GreaterThan(objTextPoint)) // HERE <---
        {
            // The code element starts beyond the point
        }
/*-->*/ else if (element.EndPoint.LessThan(objTextPoint)) // HERE <----
        {
            // The code element ends before the point
        }
        else
        {
            ...
            var memberElements = GetCodeElementMembers(element);
/*-->*/     var objMemberCodeElement = GetCodeElementAtTextPoint(memberElements, objTextPoint); // AND, HERE <---

            ...
        }
    }

    return returnValue;
}

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

  • 我可以使用另一种方法来检索我的光标所在的成员类型(类,方法,道具等),名称,行号和父母吗?
  • 我能做些什么来使TextPoint.GreaterThanTestPoint.LessThan方法表现得更好?
  • 或者,我是S.O.L。?

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

谢谢!

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

enter image description here

2 个答案:

答案 0 :(得分:2)

使用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);
    }

答案 1 :(得分:0)

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

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

代码

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.TextMicrosoft.CodeAnalysis.CSharp以及所有依赖项。

定位VS2015 / 2017版本和所需的.NET版本

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

<强>性能

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

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

任何使用CaretChanged事件来驱动此类功能的人都有一个提示,他们遇到了性能问题:我建议使用调度程序并限制调用次数。下面的代码将为呼叫增加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);
    ...
}

...