SynEdit Highlighter/ru

From Lazarus wiki
Jump to navigationJump to search

Template:MenuTranslate

Для получения дополнительной информации о 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 и HL требует 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; // нет уровня сворачивания текста на этой строке

Creating a SynEdit Highlighter

Since 0.9.31 Revision 35115 the fold-highlighter has changed. Implementing basic folding is now easier.

All Sources can be found in the Lazarus installation directory under:

 examples\SynEdit\NewHighlighterTutorial\

The project HighlighterTutorial contains 3 difference example highlighters:

  • SimpleHl: used in Step 1 below
  • ContextHl: used in Step 2 below
  • FoldHl: used in Step 3 below

SimpleHl and ContextHl will work with 0.9.30 too

The folding highlighter in action:

SynEditFoldingHighlighterDemo.png

The Basics: Returning Tokens and Attributes

As indicated, the SimpleHl unit demonstrates this process.

What it does

  • It splits each line into words and spaces (or tabs)
    • The spaces are part of the text, and must be highlighted too
  • This example allows specifying different colors for
- text (defaults to not-highlighted)
- spaces (defaults to silver frame)
- words, separated by spaces, that start with a,e,i,o,u (defaults to bold)
- the word "not" (defaults to red background)

How it works

  • Creation

The Highlighter creates Attributes that it can return the Words and Spaces.

  • SetLine

Is called by SynEdit before a line gets painted (or before highlight info is needed)

  • GetTokenEx, GetTokenAttribute, Next, GetEol

Are used by SynEdit to iterate over the Line. Note that the first Token (Word or Spaces) must be ready after SetLine, without a call to Next.

Important: The tokens returned for each line must represent the original line-text, and be returned in the correct order.

  • GetToken, GetTokenPos, GetTokenKind

SynEdit uses them e.g for finding matching brackets. If tokenKind returns different values per Attribute, then brackets only match, if they are of the same kind (e.g. if there was a string attribute, brackets outside a string would not match brackets inside a string).

Other notes

For readability, the highlighter has no optimization, so it may be very slow on larger texts. Many of the supplied highlighters use hash functions, to find what word (or any group of chars) is.

Step 2: Using Ranges

As indicated, the ContextHl unit demonstrates this process

The next example allows content of a line that influences other lines that follow. An example: a "(*" in Pascal makes all following lines a comment until a "*)" is found.

This example extends the SimpleHl show above: The tokens -- and ++ (must be surrounded by space or line-begin/end to be a token of their own) will toggle words that start with a,e,i,o,u

Multiple ++ and -- can be nested. Then for each -- a ++ must be given, before the words highlight again.

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)

The current amount of "--" is counted in

  FCurRange: Integer;

The amount is decreased by "++"

To store the info we use:

GetRange
Called after a line is completely scanned, to get the value at the end of the line. The value will be stored.
SetRange
Called before a line gets scanned. Sets the value stored from the end of the previous line.
ResetRange
Called before the 1st line is scanned (as there is no previous line).
Light bulb  Примечание: A scan is triggered by *every* change to a line (every keystroke). It scans the current line, and all lines below, until a line returns the same range that it already had. See: http://forum.lazarus.freepascal.org/index.php/topic,21727.msg139420.html#msg139420

Important note on Ranges

The purpose of the range is to allow the HL to start scanning in any line. The HL will not never need to look at a previous line. Any information needed to scan the current line can be derived from the ranges value.

Example:

  writeln; (*
  readln;
  *)

when scanning the "readln" the HL knows from the range that it is in a comment, it does not need to look back at previous lines.

Therefore scanning can start at any line.

This also explains the note in the previous chapter. "until a line returns the same range that it already had". For even if the text of a line was not changed, if the value of the range at the lines start changed, then the scan result will change too.

Step 3: Add Folding

As indicated, the FoldHl unit demonstrates this process

For the example, the highlighter should fold everything between free-standing "-(-", "-)-".

Change inheritance:

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

Change the way range info is stored, since the base class uses it for fold-info:

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;

Now add code to the scanner which tells the highlighter about opening and closing folds:

procedure TSynDemoHl.FindTokenEnd;
begin
   ...

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

Please see the history of this page, if you are using a 0.9.30 version [[1]]

More info on StartCodeFoldBlock / EndCodeFoldBlock

  function StartCodeFoldBlock(ABlockType: Pointer; IncreaseLevel: Boolean = true): TSynCustomCodeFoldBlock; virtual;
ABlockType
Can be used to specify an ID for the block.

The field is not normally used as a pointer (though this is permitted). Normally IDs are a numeric enumeration.
If you have different types of block (e.g. in Pascal: begin/end; repeat/until, ...), and you do not want a block being closed by the wrong keyword ("end" should not close "repeat"), then you can give them IDs:
StartCodeFoldBlock(PtrUInt(1)) // or other numbers

IncreaseLevel
If set to False, then a block will be inserted that can not be folded.
Blocks like that can be used for internal tracking.

NOTE: All folds must be nested, they can not overlap. That is, the last opened fold must be closed first.
This refers to the "EndLvl" as shown in "Folding"(1.3) above
Overlaps (like IFDEF and begin in the IDE) can not be done this way.

  procedure EndCodeFoldBlock(DecreaseLevel: Boolean = True); virtual;
DecreaseLevel
This *must* match IncreaseLevel, as it was given on the StartCodeFoldBlock

True means a fold ends; False means an internal block is ended. If mismatched then folds will either continue, or end early.
TopCodeFoldBlockType can be used to indicate the ID of the innermost open block. One can use different IDs for internal blocks, and use this to set the value.

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

Returns the ID of the innermost block.

DownIndex
can be used to get ID for the other block.

DownIndex=1 means the block that is surrounding the innermost.

Configurable Highlighters (incl. 3rd party)

SynAnySyn

A real simple Highlighter. More a reference implementation, than a real life useable Highlighter.

SynFacilSyn

Flexible fully configurable Highlighter. https://github.com/t-edson/SynFacilSyn

References

Threads on the forum:

  • Folding
    • "SynEdit - improved highlighters to handle Code Folding?" [topic,7879]
    • "SynEdit - Add support code folding for Java" [topic,7338]
    • "CodeFolding" Config [topic,11064]
    • Fold blocks "end" keyword (end on the line before the next keyword) [topic,23411.msg139621]
    • Folding selected text from code (user/application code): [24473.msg147312]
    • Obtaining state of folding (save fold state with session) [topic=26748]