代码之家  ›  专栏  ›  技术社区  ›  Adam Plocher

VS扩展名:TextPoint。对于大文件,大于/小于非常慢

  •  6
  • Adam Plocher  · 技术社区  · 7 年前

    我正在开发一个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<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;
        }
    }
    

    Carlos' blog 并从VB中移植了它)。

    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.GreaterThan TestPoint.LessThan 方法表现更好?
    • 或者,我是S.O.L。?

    不管是什么方法,它只需要支持VS2015或更高版本。

    非常感谢。

    更新:回答谢尔盖的评论——这确实似乎是由 .GreaterThan / .LessThan() element.StartPoint element.EndPoint .

    enter image description here

    2 回复  |  直到 7 年前
        1
  •  2
  •   Sergey Vlasov    7 年前

    使用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);
        }
    
        2
  •  0
  •   Adam Plocher    7 年前

    我将此标记为答案,但由于Sergey在他的答案中非常有帮助,加上我的Roslyn代码的灵感实际上是 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 Microsoft.CodeAnalysis.EditorFeatures.Text , Microsoft.CodeAnalysis.CSharp

    VS2015/2017的目标版本和所需的.NET版本

    here's a SO answer 具有用于不同VS目标的版本。最早的目标似乎是VS2015(RTM)。我个人使用的是v1.3.2,它应该支持VS2015更新3或更高版本。

    表演

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

    (与问题稍微无关,但可能对某人有所帮助……)

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