SynEdit Highlighter/ru

From Free Pascal wiki

English (en) русский (ru)

Для получения дополнительной информации о SynEdit перейдите к статье: SynEdit


Прим.перев.: для облегчения читаемости статьи далее под термином Highlighter будет подразумеваться механизм(маркер) подсветки синтаксиса.



Понимание SynEdit Highlighter

Взаимоотношения SynEdit - Highlighter

SynEdit - Highlighter имеют взаимоотношение N к 1.

  • Один экземпляр Highlighter может обслуживать N (много) экземпляров SynEdits
  • Каждый SynEdit имеет только один Highlighter
  • Но: один текст (текстовый буфер) может иметь много маркеров подсветки синтаксиса, если он используется несколькими SynEdit (каждый SynEdit будет иметь один HL, но все HL будут работать с одним и тем же документом)

В результате:

  • Экземпляр Highlighter не имеет (фиксированной) ссылки на SynEdit.
(Однако Highlighter'ы хранят список SynEditTextBuffers, к которому они прикреплены)
  • Все данные для Highlighter (должны быть) сохранены в SynEdit (фактически в TextBuffer SynEdit (называемом «Линии»).

Однако перед каждым вызовом Highlighter SynEdit гарантирует, что для Highlighter.CurrentLines будут установлены текущие строки SynEdits. Таким образом, маркер может получить доступ к данным в любое время. Формат хранения данных определяется маркером (TSynCustomHighlighter.AttachToLines).

Сканирование и возврат атрибутов подсветки

Ожидается, что Highlighter будет работать на основе каждой строки.

Если какой-либо текст был изменен, SynEdit будет вызывать (TSynCustomHighlighter.ScanFrom / в настоящее время вызывается из TSynEdit.ScanFrom) с диапазоном строк. Highlighter должен знать состояние предыдущей строки.

Если требуются атрибуты подсветки, SynEdit будет запрашивать их также для каждой строки. SynEdit будет проходить через отдельные токены на линии. В настоящее время это происходит из вложенных процедур PaintLines в SynEdit.PaintTextLines. Он вызывает TSynCustomHighlighter.StartAtLineIndex, за которым следует HL.GetTokenEx/HL.GetTokenAttribute до тех пор, пока HL.GetEol имеет значение false.

Кроме того, базовый класс для данных Highlighter'а (см. AttachToLines) основан на хранении данных на каждой строке, а TextBuffer (строки) SynEdit выполняет обслуживание этих данных для их синхронизации. То есть: всякий раз, когда строки текста вставляются или удаляются, записи также вставляются или удаляются из данных highlighter'ов (следовательно, в каждой строке должна быть одна запись).

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

Folding (сворачивание текста)

Событие фолдинга (схлопывания/сворачивания текста) SynEdit обрабатывается модулями SynEditFoldedView и SynGutterCodeFolding. Highlighter'ы, которые реализуют сворачивание, должны основываться на TSynCustomFoldHighlighter.

Базовая информация для связи между SynEditFoldedView и Highlighter'ом требует 2 значения, сохраненных для каждой строчки (конечно, сам highlighter может хранить больше информации):

  • FoldLevel в конце строки
  • Минимальный FoldLevel, встречающийся где-либо в строке

Foldlevel указывает, сколько (вложенных) уровней схлопывания/сворачивания текста существует. Он повышается всякий раз, когда уровень сворачивания начинается, и понижается, когда уровень сворачивания заканчивается:

                            EndLvl   MinLvl
  Procedure a;               1 -      0
  Begin                      2 --     1 -
    b:= 1;                   2 --     2 --
    if c > b then begin      3 ---    2 --
      c:=b;                  3 ---    3 ---
    end else begin           3 ---    2 --
      b:=c;                  3 ---    3 ---
    end;                     2 --     2 --
  end;                       0        0  // Оператор end закрывает оба уровня сворачивания текста: операторов                                                       
                                         //begin и procedure

На строке

Procedure a;               1 -      0

MinLvl равен 0, потому что строка началась с уровня 0 (и она никогда не спускалась / не закрывалась). Аналогично во всех строках, где есть только ключевое слово, открывающее уровень сворачивания текста ("begin").


А строка

    end else begin           3 ---    2 --

начинается с уровня 3, а также заканчивается им (один закрытый, один открытый). Но так как она спускается первой, минимальный уровень, встречающийся где-либо в строке, равен 2.


Без MinLvl было бы невозможно сказать, что уровень сворачивания текста заканчивается на этой строке.

MaxLvl не существует, потому что уровни сворачивания текста, начинающиеся и заканчивающиеся на одной и той же линии, не могут быть свернуты в любом случае. Не нужно их обнаруживать.

  if a then begin b:=1; c:=2; end; // нет уровня сворачивания текста на этой строке

Создание SynEdit Highlighter

Начиная с версии 0.9.31 rev.35115, сворачивание текста в highlighter'е изменилось. Реализовать базовое свертывание текста теперь проще.

Все исходники можно найти в каталоге установки Lazarus в: examples\SynEdit\NewHighlighterTutorial\

Проект HighlighterTutorial содержит 3 различных примера highlighter'ов:

  • SimpleHl: используется в шаге 1 ниже
  • ContextHl: используется в шаге 2 ниже
  • FoldHl: используется в шаге 3 ниже

SimpleHl и ContextHl тоже будут работать с v.0.9.30 Лазаруса

Сворачивание текста в highlighter'е в действии

SynEditFoldingHighlighterDemo.png

Основы: возврат токенов и атрибутов

Как указано, модуль SimpleHl демонстрирует этот процесс.

Что он делает

  • Он разбивает каждую строку на слова и пробелы (или табуляции)
    • Пробелы являются частью текста и также должны быть подсвечены
  • Этот пример позволяет указать разные цвета для
- текста (по умолчанию не подсвечен)
- пробелов (по умолчанию в серебристой рамке)
- слов, разделенных пробелами, начинающихся с a,e,i,o,u (по умолчанию выделены жирным шрифтом)
- "не" слов (по умолчанию на красном фоне)

Как это работает

  • Creation

Highlighter создает Атрибуты, которые могут вернуть Words(слова) и Spaces(пробелы).

  • SetLine

Событие вызывается SynEdit до того, как будет прорисована строка (или до того, как потребуется информация о выделении)

  • GetTokenEx, GetTokenAttribute, Next, GetEol

События используются SynEdit для перебора строки. Обратите внимание, что первый токен (слово или пробел) должен быть готов после SetLine, без вызова Next.

Note-icon.png

Примечание: токены, возвращаемые для каждой строки, должны представлять исходный текст строки и возвращаться в правильном порядке.

  • GetToken, GetTokenPos, GetTokenKind

SynEdit использует их, например, для поиска подходящих скобок. Если tokenKind возвращает разные значения для каждого атрибута, то скобки совпадают только в том случае, если они имеют одинаковый вид (например, если был строковый атрибут, скобки вне строки не будут совпадать с скобками внутри строки).

Другие заметки

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

Шаг 2: Использование диапазонов

Как указано, модуль ContextHl демонстрирует этот процесс.

Следующий пример допускает влияние содержимого строки на последующие строки. Например: "(*" в Паскале делает все последующие строки комментарием, пока не будет найден "*)".

Этот пример расширяет показанный выше SimpleHl: Токены -- и ++ (должны быть обрамлены пробелом или началом/концом строки, чтобы стать их собственным токеном) будут переключать слова, начинающиеся с a,e,i,o,u

Несколько ++ и -- могут быть вложенными. Тогда для каждого слова должны быть заданы -- и ++, прежде чем оно снова будет подсвечено.

Then we extend the scanner. The pre-scan to store the information calls the same functions as the highlighter. It is automatically called, if anything changes. (It is called for all lines below the changed line, until a line returns the same Range-value as it already had) Затем мы расширяем сканер. Предварительное сканирование для хранения информации вызывает те же функции, что и highlighter. Он автоматически вызывается, если что-то меняется (он вызывается для всех строк ниже измененной строки, пока строка не вернет то же значение Range, что и раньше).

Текущее количество "--" рассчитывается в

  FCurRange: Integer;

Количество уменьшается посредством "++"


Для хранения информации мы используем:

GetRange
Вызывается после полного сканирования строки, чтобы получить значение в конце строки. Значение будет сохранено.
SetRange
Вызывается до сканирования линии. Устанавливает значение, хранящееся в конце предыдущей строки.
ResetRange
Вызывается до сканирования 1-й строки (так как предыдущей строки нет).
Note-icon.png

Примечание: Сканирование запускается при *каждом* изменении строки (при каждом нажатии клавиши). Он сканирует текущую строку и все строки ниже, пока строка не вернет тот же диапазон, который у нее уже был. См.: http://forum.lazarus.freepascal.org/index.php/topic,21727.msg139420.html#msg139420

Важное замечание о диапазонах

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

Пример:

  writeln; (*
  readln;
  *)

при сканировании "readln" Highlighter знает из диапазона, который находится в комментарии, ему не нужно осматривать предыдущие строки.

Поэтому сканирование может начинаться с любой строки.

Это также объясняет примечание в предыдущей главе: "пока строка не вернет тот же диапазон, который у нее уже был". Ибо, даже если текст строки не был изменен, если значение диапазона в начале строк изменилось, то результат сканирования также изменится.

Шаг 3: Добавление маркеров сворачивания текста

Как указано выше, модуль FoldHl демонстрирует этот процесс.

Например, highlighter должен сворачивать весь текст между отдельно стоящими "-(-", "-)-".

Изменим наследование:

  uses SynEditHighlighterFoldBase;
  ...
  TSynDemoHl = class(TSynCustomFoldHighlighter)

Изменим способ хранения информации о диапазоне, поскольку базовый класс использует ее для информации о схлопывании текста:

procedure TSynDemoHl.SetRange(Value: Pointer);
begin
  inherited;
  FCurRange := PtrInt(CodeFoldRange.RangeType);
end;

procedure TSynDemoHl.ResetRange;
begin
  inherited;
  FCurRange := 0;
end;

function TSynDemoHl.GetRange: Pointer;
begin
  CodeFoldRange.RangeType := Pointer(PtrInt(FCurRange));
  inherited;
end;

Теперь добавим код в сканер, который сообщает highlighter'у об открытии и закрытии маркеров схлопывания текста:

procedure TSynDemoHl.FindTokenEnd;
begin
   ...

  if (FTokenEnd = FTokenPos+1) and (FLineText[FTokenPos] = '[') then
    StartCodeFoldBlock(nil);
  if (FTokenEnd = FTokenPos+1) and (FLineText[FTokenPos] = ']') then
    EndCodeFoldBlock();
end;
  • Для 0.9.30

Пожалуйста, смотрите историю этой страницы, если вы используете версию 0.9.30.

Подробнее о StartCodeFoldBlock / EndCodeFoldBlock

  function StartCodeFoldBlock(ABlockType: Pointer; IncreaseLevel: Boolean = true): TSynCustomCodeFoldBlock; virtual;
ABlockType
может использоваться для указания идентификатора блока.

Поле обычно не используется в качестве указателя (хотя это и не запрещено). Обычно идентификаторы - это числовое перечисление.
Если у вас есть блоки разных типов (например, в Паскале: begin/end; repeat/until, ...), и вы не хотите, чтобы блок закрывался по неправильному ключевому слову ("end" не должен закрывать "repeat"), тогда вы можете дать им идентификаторы:

StartCodeFoldBlock(PtrUInt(1)) // или другие числа
IncreaseLevel
Если установлено значение False, будет вставлен блок, который нельзя свернуть.
Подобные блоки можно использовать для внутреннего отслеживания.

ПРИМЕЧАНИЕ: все уровни сворачивания текста должны быть вложенными, они не могут перекрываться. То есть последний открытый уровень сворачивания текста должен быть закрыт первым.
Это относится к EndLvl, как показано в разделе Folding (сворачивание текста) выше.
Перекрытия (как IFDEF и begin в IDE) не могут быть выполнены таким образом.

  procedure EndCodeFoldBlock(DecreaseLevel: Boolean = True); virtual;
DecreaseLevel
Параметр *должен* соответствовать IncreaseLevel, как это было указано в StartCodeFoldBlock

True означает, что уровень сворачивания текста заканчивается; False означает, что внутренний блок окончен. Если есть несоответствие, уровни сворачивания текста будут либо продолжаться, либо заканчиваться раньше.
TopCodeFoldBlockType может использоваться для указания идентификатора самого внутреннего открытого блока. Можно использовать разные идентификаторы для внутренних блоков, и используя их, чтобы установить значение.

  function TopCodeFoldBlockType(DownIndex: Integer = 0): Pointer;

Возвращает идентификатор самого внутреннего блока.

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

DownIndex=1 обозначает блок, который окружает самый внутренний.

Конфигурируемые Highlighter'ы (включая сторонние)

SynAnySyn

Настоящий простой Highlighter. Более эталонная реализация, чем реальный пригодный для использования Highlighter.

SynFacilSyn

Гибкий полностью настраиваемый Highlighter. См. SynFacilSyn.

Ссылки

Темы на форуме:

  • "Создание Highlighter'а для синтаксиса С" [topic,10260]
  • "Создание Syntax Highlighter" [topic,10959.msg54714]
  • Получение подсветки для печати [11384.msg57160]
  • Некоторые подробности о диапазонах и использовании объектов для хранения, подробнее [topic,21727.msg139354] и [topic,21727.msg139419]
  • Подробнее о диапазонах, рабочей копии vs immutable, foldblocks...[http://forum.lazarus.freepascal.org/index.php/topic,30685]
  • Разметка (подсветка) всех вхождений токена (или регулярного выражения)[topic=26833]
  • Разметка и сворачивание информации:: [[1]]
  • Как сделать отмену в TSynEditMarkup [[2]]
  • Сворачивание текста
    • "SynEdit - улучшенные highlighter'ы для обработки Code Folding?" [topic,7879]
    • "SynEdit - Добавление поддержки свертывания кода для Java" [topic,7338]
    • "CodeFolding" конфигурация [topic,11064]
    • Fold блокирует ключевое слово "end" (конец строки перед следующим ключевым словом) [topic,23411.msg139621]
    • Сворачивание выделенного текста из кода (код пользователя / приложения):: [24473.msg147312]
    • Получение состояния уровня сворачивания текста (сохранение состояния уровня сворачивания в сеансе работы) [topic=26748]