Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VS Extension: TextPoint.GreaterThan / LessThan very slow for large files

I'm working on a VS Extension that needs to be aware of which class member the text-cursor is currently located in (methods, properties, etc). It also needs an awareness of the parents (e.g. class, nested classes, etc). It needs to know the type, name, and line number of the member or class. When I say "Type" I mean "method" or "property" not necessarily a ".NET Type".

Currently I have it working with this code here:

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;
    }
}

So that currently works, if I call GetCodeElementAtCursor I will get the member and it's parents back. (This is kinda old code, but I believe I originally snagged it from Carlos' blog and ported it from VB).

My problem is that when my extension is used on code that is very large, like auto-generated files with a couple thousand lines, for example, it brings VS to a crawl. Almost unusable. Running a profiler shows that the hot lines are

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;
}

So the third one is obvious, it's a recursive call to itself so whatever is affecting it will affect a call to itself. The first two, however, I'm not sure how to fix.

  • Is there an alternative method I could use to retrieve the type of member my cursor is on (class, method, prop, etc), the name, line #, and the parents?
  • Is there something that I could do to make the TextPoint.GreaterThan and TestPoint.LessThan methods perform better?
  • Or, am I S.O.L.?

Whatever the method is, it just needs to support VS2015 or newer.

Thank you!

UPDATE: To answer Sergey's comment - it does indeed seem to be caused by .GreaterThan / .LessThan(). I've separated the code and the slow-down is definitely occurring on those method calls, NOT the property accessor for element.StartPoint and element.EndPoint.

enter image description here

like image 501
Adam Plocher Avatar asked Jul 27 '17 19:07

Adam Plocher


1 Answers

After you get a TextPoint using GetCursorTextPoint, you can use TextPoint.CodeElement property to find current code elements:

    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);
    }
like image 62
Sergey Vlasov Avatar answered Oct 06 '22 00:10

Sergey Vlasov