VS расширение: TextPoint.Больше / меньше, чем очень медленно для больших файлов
Я работаю над расширением 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;
}
}
так что в настоящее время работает, если я позвоню GetCodeElementAtCursor я получу член, и это родители обратно. (Это своего рода старый код, но я считаю, что я изначально зацепил его из Карлос блог и портировал его с 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;
}
так третий очевиден, это рекурсивный вызов самому себе, поэтому все, что влияет на него, повлияет на вызов самому себе. Первые два, однако, я не уверен, как исправить.
- есть ли альтернативный метод, который я мог бы использовать для извлечения типа члена, на котором находится мой курсор (класс, метод, prop и т. д.), имя, строка # и родители?
- есть ли что-то, что я мог бы сделать, чтобы сделать
TextPoint.GreaterThan
иTestPoint.LessThan
методы лучше? - или я С. О. Л.?
каким бы ни был метод, он просто должен поддерживать VS2015 или новее.
спасибо!
UPDATE: чтобы ответить на комментарий Сергея - это действительно, похоже, вызвано .GreaterThan
/ .LessThan()
. Я разделил код, и замедление определенно происходит при вызовах этих методов, а не метод доступа к свойствам для element.StartPoint
и element.EndPoint
.
2 ответов
после того, как вы получите TextPoint, используя GetCursorTextPoint, вы можете использовать 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. Код ниже делает (в значительной степени) все то же самое, что и мой код выше в вопросе, с добавлением возврата прозвища.
Я отмечаю это как ответ, но так как Сергей был очень полезен в своем ответе, плюс вдохновение для моего кода Roslyn было на самом деле из этого так ответь, что также было его ответом, он определенно заслуживает очков :).
в код
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();
}
зависимости
Так как я возвращаю Кортеж, вам понадобится с версиями для использования для различных целей VS. Раньше всего вы можете нацелиться на VS2015 (RTM). Я лично использую v1.3.2 который должен поддерживать обновление VS2015 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);
...
}
...