Visual Studio

индекс
38,32

VS2010 — делаем свою подсветку синтаксиса

Поставил у себя VS2010 RC1. На винчестере семь гигов занимает, однако.

Посмотрел как делаются аддоны и в частности раскраска синтаксиса. Читал я об этом ещё год назад, а тут в связи с выходом RC решил опробовать в деле.

Для примера решил сделать подсветку синтаксиса для ассемблера Z80.
Первые результаты удалось получить через пару часов, ещё за пару часов сделал однострочный разбор — метки, комментарии, операторы и псевдо-операторы:

image

В Visual Studio 10 применяется новая модель расширений — весь код аддонов пишется исключительно на managed-коде, никаких COM. Это очень серьёзно облегчает жизнь, да и на общей стабильности системы сказывается очень положительно.

Собственно, к делу. Во-первых, существует несколько способов распространения расширений студии, я использовал VSIX — наиболее удобно для отладки. Имеет смысл поставить Microsoft Visual Studio 2010 SDK — студия дополняется шаблонами проектов для быстрого создания расширений. Но насколько я понимаю создавать их можно и так. С использованием VS2010 SDK новый проект для кастомной подсветки синтаксиса создаётся через пункт Extensibility > Editor Classifier.

Подсветка синтаксиса определяется классом, реализующим интерфейс IClassifier — этот класс задаёт логику разбора текста. Для использования классификатора нужен провайдер — реализующий IClassifierProvider и определяющий как наш классификатор будет использоваться.

    [Export(typeof(IClassifierProvider))]
    [ContentType("text")]
    internal class Z80EditorClassifierProvider : IClassifierProvider
    {
      [Import]
      internal IClassificationTypeRegistryService ClassificationRegistry = null; // Set via MEF

      public IClassifier GetClassifier(ITextBuffer buffer)
      {
        return buffer.Properties.GetOrCreateSingletonProperty<Z80EditorClassifier>(
          delegate { return new Z80EditorClassifier(ClassificationRegistry); });
      }
    }

* This source code was highlighted with Source Code Highlighter.

Основной метод классификатора — GetClassificationSpans() — для заданного фрагмента текста функция должна возвратить список фрагментов с указанием типа фрагмента — класса, реализующего интерфейс IClassificationType.

    class Z80EditorClassifier : IClassifier
    {

      public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
      {
        List<ClassificationSpan> result = new List<ClassificationSpan>();

        ...

        return result;
      }

    }

* This source code was highlighted with Source Code Highlighter.


При этом из GetClassificationSpans() конечно можно получить доступ к разбираемому тексту — через свойство span.Snapshot. В моём случае меня интересовал простой синтаксис, сводящийся к разбору одной строки.

    public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
    {
      List<ClassificationSpan> result = new List<ClassificationSpan>();
      if (span.Length == 0) return result;
      
      ITextSnapshot snapshot = span.Snapshot;
      ITextSnapshotLine line = snapshot.GetLineFromPosition(span.Start.Position);
      ITextSnapshotLine endLine = snapshot.GetLineFromPosition(span.End.Position);

      while (true)
      {
        // Process current line
        ProcessLine(line, result);

        if (line.LineNumber == endLine.LineNumber)
          break;
        // Next line
        line = snapshot.GetLineFromPosition(line.EndIncludingLineBreak + 1);
      }

      return result;
    }

* This source code was highlighted with Source Code Highlighter.


Внутри ProcessLine остаётся идентифицировать отдельные фрагменты строки и добавить их в список, указав для каждого тип фрагмента. Например, вот так добавляется подсветка для комментария:

        IClassificationType commentClassifType = registry.GetClassificationType("z80comment");
        result.Add(new ClassificationSpan(
          new SnapshotSpan(line.Snapshot, new Span(line.Start + commentPos, commentLength)),
          commentClassifType));

* This source code was highlighted with Source Code Highlighter.


Типы фрагментов можно описать все сразу в одном классе:

  internal static class Z80EditorClassifierClassificationDefinition
  {
    /// <summary>
    /// Defines the "z80operator" classification type - Z80 Assembly Operator.
    /// </summary>
    [Export(typeof(ClassificationTypeDefinition))]
    [Name("z80operator")]
    internal static ClassificationTypeDefinition Z80OperatorDefinition = null;

    /// <summary>
    /// Defines the "z80comment" classification type - Z80 Assembly Comment.
    /// </summary>
    [Export(typeof(ClassificationTypeDefinition))]
    [Name("z80comment")]
    internal static ClassificationTypeDefinition Z80CommentDefinition = null;
  }

* This source code was highlighted with Source Code Highlighter.


Классификатору может быть сопоставлен форматтер — класс, порождённый от ClassificationFormatDefinition и определяющий способ подсветки текста.

  [Export(typeof(EditorFormatDefinition))]
  [ClassificationType(ClassificationTypeNames = "z80operator")]
  [Name("z80operator")]
  [UserVisible(true)]
  [Order(Before = Priority.Default)]
  internal sealed class Z80EditorOperatorFormat : ClassificationFormatDefinition
  {
    public Z80EditorOperatorFormat()
    {
      this.DisplayName = "Z80 Assembly Operator";
      this.BackgroundColor = Color.FromRgb(230,255,230);
      this.ForegroundColor = Colors.Blue;
    }
  }

* This source code was highlighted with Source Code Highlighter.


В общем-то, для такого небольшого результата — это практически всё что нужно знать. Несколько более подробно можно посмотреть в статье (осторожно, для текущей версии студии есть отличия в нюансах):
VS 2010 Editor — Text Coloring Sample Deep Dive
dotneteers.net/blogs/divedeeper/archive/2008/11/04/LearnVSXNowPart38.aspx

UPD: Описываемый пример выложен тут: narod.ru/disk/18104996000/Z80Editor.zip.html
+31
20 февраля 2010, 19:08
26

комментарии (20)

+1
Aecktann #
Можно дополнить скриншот?
0
nzeemin #
Сорри — были какие-то проблемы с превью статьи, из-за этого черновая версия ушла в опубликованное.
0
Kalifriki #
И все?
0
nzeemin #
Сорри — были какие-то проблемы с превью статьи, из-за этого черновая версия ушла в опубликованное.
0
Kalifriki #
Извиняюсь, сразу не понял :)
+3
Ordos #
Спасибо, очень интересно, хотя хотелось бы увидеть ма-а-аленький, но всё-таки пример с подробными комментариями, так проще разбираться.
0
nzeemin #
Постараюсь выложить этот пример целиком — прикидываю куда.
0
nzeemin #
narod.ru/disk/18104996000/Z80Editor.zip.html

Проверьте если не трудно — загружается ли, всё ли положил.
+1
Ordos #
Скачивается, но сам проект не открывается. Видимо нужен SDK или более поздняя студия (у меня вторая бета).
0
nzeemin #
Ссылки на RC и VS10 SDK можно найти здесь:
habrahabr.ru/blogs/vs/83827/
и здесь:
msdn.microsoft.com/en-us/vstudio/dd582936.aspx
0
XaocCPS #
переместите пожалуйста пост в блог Visual Studio (напомню, что посты из личного блога не выходят на главуную Хабра)
0
SunexDevelopment #
Поправьте меня если ошибаюсь: модель плагинов для MS VS 2010 выбрана MEF?
+1
chaliy #
Угу, тока не модель плагинов, а модель расширения.

[Export(typeof(EditorFormatDefinition))]

Это декларация MEF.
–2
Novikov #
А что там так много места занимает? На чем они программу написали?
+1
Pollux #
Удобно просто и красиво)
0
nzeemin #
Понять бы ещё — за что минусуют. Посмотрел — у всех записей блога Visual Studio такая картина.
0
professor_k #
за то, что Visual Studio. Есть люди (не будем тыкать пальцем) с алергией на слово Microsoft.
0
denis8158 #
Спасибо за статью, остался только один не решенный вопрос, как обрабатывать много-строчные комментарии?
0
nzeemin #
Сорри — не успел порыть в эту сторону. Но ясно что нужно где-то хранить состояние (классификатор) на начало строки.
0
denis8158 #
Except in the simplest circumstances, language classification probably requires you to build some type of model over the language. If the only state you need to remember is, say, the lines that start in comments or multi-line strings or something similar, that's information that you'll want to create and store in your classifier (and update whenever text changes). More complicated scenarios will generally require building a more complicated model on top of the text, which is what most languages end up doing.

Что-то в этом роде, правда, не совсем понятно, как можно хранить такую информацию и потом использовать.
Если хранить только начало комментария, то как определить, что сейчас мы точно находимся на следующей строчки после этого комментария. Ведь как я понимаю, нас могут вызвать в любой момент для любой строки.

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