Пользовательская раскраска ключевых слов в Visual Studio 2010+

Я пытаюсь добавить пользовательскую раскраску только для определенных ключевых слов в редакторе Visual Studio для кода С#. Я хочу, чтобы иметь возможность окраски любого типа, который реализует IDisposable как другой цвет. В идеале я бы хотел создать простой список классов/интерфейсов, которые вытекают из IDisposable в какой-то конфигурации, которую я могу редактировать. (Хотя, если вы сказали, что существует метод/плагин, который автоматически будет находить все одноразовые типы и их цвет независимо друг от друга, это будет Святой Грааль).

Я провел много исследований, и похоже, что расширение "editor classifier" может сделать трюк. Однако я создал тот, который просто пытается покрасить слово "поток", и хотя он ударил мой код, который пытается выделить это слово, он не заканчивается выделенным в редакторе.

Я добавил расширение VS к Github здесь

Это действительно похоже на то, что это должно быть довольно просто, но я спустился по многим переулкам, только чтобы найти тупики. Есть ли более простой способ сделать это, или мое расширение нарушено?

Обновление

Очень странно. Я только что запустил расширение и, хотя оно не выделяет текст в редакторе, выделяет все экземпляры "Stream" во всплывающем тексте, когда вы наводите курсор на тип/переменную! Есть ли способ заставить его обратиться к редактору?

enter image description here

Ответ 1

В зависимости от того, вы используете Jetbrains Resharper или нет, вы можете написать для этого плагин. Таким образом, вы можете не только добавлять визуальное уведомление о IDisposable для переменной, но также предоставлять fastfixes, если и только если он не вызван, это то, что я предполагаю, что вы хотите поймать. Имейте в виду, что я могу представить, что для этого уже есть плагин R #. Я знаю, что тоже это рассмотрел, но мне было слишком лениво написать плагин для этого.

Не поймите меня неправильно btw - Если вы не используете r #, но вы должны попробовать попробовать.

Среди других вы будете работать с этим: API-QuickFix

Существуют также способы определения пользовательских ключевых слов, как это делает resharper, заданных с помощью специальной разметки и применяемых к ним fastfixes.

PS: Нет, я не работаю на реактивных мозгах. это просто так хорошо:)

ОБНОВЛЕНИЕ:

потенциальное расширение VS Extension?

проверьте это: Ссылка MSDN, выделяющая текст

Я попытался открыть ваш проект github, но не мог, поэтому я подумал, что я просто проверю msdn. кажется, что вы извлекаете из неправильного класса для удовлетворения ваших потребностей?

Ключевое слово MSDN "Редакторы - Расширение редактора - Пошаговое руководство: выделение текста"

Я знаю, что SO хочет код на сайте, но ссылки msdn, идущие вниз, маловероятны, и с данной информацией контент можно найти достаточно легко:)

Ответ 2

Я немного опаздываю на вечеринку, но эй, почему бы не выбросить 2 цента.

Как вы объяснили в своем вопросе, ваш проект состоит из двух основных частей:

  • Поиск классов, реализующих IDisposable
  • Выделение их

Первое, безусловно, самое сложное, хотя и не исключение. Возможно, подход на основе словарного списка является самым простым, хотя должно быть возможно с Roslyn, чтобы выяснить "на лету", какие наследуемые классы IDisposible.

Вы также можете всегда прибегать к загрузке скомпилированного проекта .exe/.dll в фоновом режиме после сборки и выяснении того, какие типы существуют, но вам все равно придется написать какой-нибудь волшебный код для определения того, что имена классов в коде ссылаются на то, какие фактические классы с полным именем в сборке.

Вторая часть, выделяющая, довольно проста, когда вы знаете, как это сделать (это помогает, что я провел последние несколько месяцев, работая полный рабочий день по расширению VS). Конечно, с Visual Studio ничего не так просто, как кажется (несмотря на усилия Microsoft, чтобы сделать ее удобной для пользователя). Итак, я создал образец расширения, в котором выделяются только классы с именем "Stream" в файлах С#, чтобы вы начали.

highlighting at work

Ниже приведен соответствующий код, а полный проект находится в GitHub). Он начинается с поставщика классификатора-метки:

[Export(typeof(ITaggerProvider))]
[ContentType("CSharp")]
[TagType(typeof(ClassificationTag))]
[Name("HighlightDisposableTagger")]
public class HighlightDisposableTaggerProvider : ITaggerProvider
{
    [Import]
    private IClassificationTypeRegistryService _classificationRegistry = null;

    [Import]
    private IClassifierAggregatorService _classifierAggregator = null;

    private bool _reentrant;

    public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag
    {
        if (_reentrant)
            return null;

        try {
            _reentrant = true;
            var classifier = _classifierAggregator.GetClassifier(buffer);
            return new HighlightDisposableTagger(buffer, _classificationRegistry, classifier) as ITagger<T>;
        }
        finally {
            _reentrant = false;
        }
    }
}

Тогда сам теггер:

public class HighlightDisposableTagger : ITagger<ClassificationTag>
{
    private const string DisposableFormatName = "HighlightDisposableFormat";

    [Export]
    [Name(DisposableFormatName)]
    public static ClassificationTypeDefinition DisposableFormatType = null;

    [Export(typeof(EditorFormatDefinition))]
    [Name(DisposableFormatName)]
    [ClassificationType(ClassificationTypeNames = DisposableFormatName)]
    [UserVisible(true)]
    public class DisposableFormatDefinition : ClassificationFormatDefinition
    {
        public DisposableFormatDefinition()
        {
            DisplayName = "Disposable Format";
            ForegroundColor = Color.FromRgb(0xFF, 0x00, 0x00);
        }
    }

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged = delegate { };

    private ITextBuffer _subjectBuffer;
    private ClassificationTag _tag;
    private IClassifier _classifier;
    private bool _reentrant;

    public HighlightDisposableTagger(ITextBuffer subjectBuffer, IClassificationTypeRegistryService typeService, IClassifier classifier)
    {
        _subjectBuffer = subjectBuffer;

        var classificationType = typeService.GetClassificationType(DisposableFormatName);
        _tag = new ClassificationTag(classificationType);
        _classifier = classifier;
    }

    public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans)
    {
        if (_reentrant) {
            return Enumerable.Empty<ITagSpan<ClassificationTag>>();
        }

        var tags = new List<ITagSpan<ClassificationTag>>();
        try {
            _reentrant = true;

            foreach (var span in spans) {
                if (span.IsEmpty)
                    continue;

                foreach (var token in _classifier.GetClassificationSpans(span)) {
                    if (token.ClassificationType.IsOfType(/*PredefinedClassificationTypeNames.Identifier*/ "User Types")) {
                        // TODO: Somehow figure out if this refers to a class which implements IDisposable
                        if (token.Span.GetText() == "Stream") {
                            tags.Add(new TagSpan<ClassificationTag>(token.Span, _tag));
                        }
                    }
                }
            }
            return tags;
        }
        finally {
            _reentrant = false;
        }
    }
}

Я тестировал это только на VS2010, но он тоже должен работать на VS2013 (единственное, что может быть другим, - это классифицированное имя класса, но это легко обнаружить с хорошо поставленной точкой останова). Я никогда не писал расширение для VS2012, поэтому я не могу комментировать это, но я знаю, что он очень близок к VS2013 в большинстве случаев.

Ответ 3

Итак, одно возможное решение (я считаю, что это работает):

1) Создайте свой собственный тип контента, который наследуется от csharp.

2) Создайте новый TextViewCreationListener, который поменяет все типы контента "csharp" на ваш собственный, что потенциально "обезоруживает" все остальные классификаторы.

3) Зарегистрируйте свой классификатор для обработки вашего собственного типа контента.

Вот некоторые из кода:

[Export(typeof(IVsTextViewCreationListener))]
[ContentType("csharp")]
[TextViewRole(PredefinedTextViewRoles.Editable)]
class TextViewCreationListener : IVsTextViewCreationListener {
    internal readonly IVsEditorAdaptersFactoryService _adaptersFactory;

    [Import] internal IContentTypeRegistryService ContentTypeRegistryService = null;

    [ImportingConstructor]
    public TextViewCreationListener(IVsEditorAdaptersFactoryService adaptersFactory) {
        _adaptersFactory = adaptersFactory;
    }

    #region IVsTextViewCreationListener Members

    public void VsTextViewCreated(VisualStudio.TextManager.Interop.IVsTextView textViewAdapter) {
        var textView = _adaptersFactory.GetWpfTextView(textViewAdapter);

        var myContent = ContentTypeRegistryService.GetContentType(MyContentType);
        if(myContent == null)
        {
            ContentTypeRegistryService.AddContentType(MyContentType, new[] {"csharp"});
            myContent = ContentTypeRegistryService.GetContentType(MyContentType);
        }

        // some kind of check if the content type is not already MyContentType. 
        textView.TextBuffer.ChangeContentType(myContent, null);
    }

    #endregion
}

И теперь просто измените свой IClassifierProvider для регистрации с вашим собственным типом контента, как таковой: [ContentType(MyContentType)]

В свой собственный классификатор, вы можете в основном сделать свой собственный расчет, и как только вы подумаете, что не можете справиться с этим материалом, вы можете передать управление другим классификаторам.

Если вы используете MEF и импортируете IClassifierAggregatorService, вы можете получить "MASTER-классификатор", который будет запускать всю логику для вас. Я еще не реализовал его, но в прошлом я предлагал что-то подобное, и это, казалось, сработало. Или вы можете использовать [ImportMany] с List<IClassifier> и отфильтровать csharp??