SynEdit/ru
│
Deutsch (de) │
English (en) │
español (es) │
français (fr) │
日本語 (ja) │
русский (ru) │
中文(中国大陆) (zh_CN) │
SynEdit - пакет подсветки синтаксиса для edit/memo, доступный на вкладке SynEdit с поддержкой многих языков/синтаксиса.
SynEdit, содержащийся в Lazarus, был ответветлен от SynEdit 1.0.3, адаптирован и довольно сильно расширен. Изменения перечислены ниже.
Пакет Lazarus содержит компонент редактора исходного кода под названием [TSynEdit/ru|[TSynEdit]], несколько подсветок синтаксиса и другие компоненты, используемые для редактирования исходного кода.
Лицензирован на тех же условиях, что и исходный SynEdit (MPL или GPL).
Оригинальная версия против версии Lazarus
Версия Lazarus поддерживается в основном [[[User:Martin|Martin Friebe]]. Martin написал на форуме, что было добавлено в версию Lazarus с момента появления fork:
Большие изменения, добавленные в версию Lazarus:
- сворачивание блоков кода
- настраиваемые боковое поле / части бокового поля
- общий текст между несколькими редакторами
- поддержка utf-8
- плагин для синхронизации
- базовая поддержка RTL/LTR
- настройка мыши через MouseActions
- переписаны различные модули подсветки/разметки
Кодовые базы версий Delphi / Lazarus были независимо переработаны. Осталось очень мало совпадений.
SynEdit 2.0 port
Существует альтернативный порт оригинальной версии SynEdit версии 2.0.x. Активность не поддерживается, последний коммит (сейчас июнь 2014) был в 2011 году.
SynEdit в IDE
SynEdit в Lazarus - это встроенный пакет, потому что среда IDE использует его сама. Поэтому пакет не может быть удален из списка установки. Чтобы удалить записи из палитры компонентов, пакет SynEditDsgn можно удалить из установки.
Использование SynEdit
Подсветка
- Есть несколько стандартных маркеров подсветки (см. вкладку SynEdit в палитре компонентов)
- Существуют сценарии подсветки, которые можно адаптировать ко многим другим форматам файлов:
- TSynAnySyn (стандартный, на палитре компонентов по умолчанию)
- TSynPositionHighlighter (стандартный, не на палитре компонентов)
- TSynUniHighlighter (стандартный, не на палитре компонентов)
- SynFacilSyn (Github)
- Существуют и другие сторонние маркеры подсветки: SynCacheSyn, SynGeneralSyn, SynRCSyn, SynRubySyn, SynSDDSyn, SynSMLSyn, SynSTSyn, SynTclTkSyn, SynUnrealSyn, SynURISyn, SynVBScriptSyn, SynVrml97Syn, см. здесь.
- Вы можете написать новый маркер подсветки, см. информацию в SynEdit Highlighter.
Изменение существующего маркера подсветки
Иногда у вас может появиться желание отредактировать существующие маркеры подсветки (как этого хотел я несколько дней назад), которые уже существуют. В этом примере мы собираемся отредактировать маркер подсветку для паскаль-подобного кода (classname: TSynPasSyn; package: SynEdit V1.0; unit: SynHighlighterPas.pas).
Скажем, мы хотим достичь того, чтобы наше приложение (в данном случае Lazarus) различало три типа комментариев, которые существуют в Pascal:
(* ansi *)
{ bor }
// Slash
Это может быть полезно, если вы хотите различать различные типы ваших комментариев (например, "Description", "Note", "Reference" и т.д.) и хотите, чтобы они были, например, окрашивались по-разному.
Примечание: На случай, если вы что-то сломаете, я предлагаю сделать несколько "NEW" и "/NEW"-комментариев, но вам не нужно
- Сначала откройте модуль "SynHighlighterPas", который должен находиться в вашем SynEdit-каталоге.
- Поскольку мы не хотим создавать несовместимости, мы создаем новый перечислимый тип, который поможет нам позже идентифицировать наш комментарий:
Напр., под объявлением "tkTokenKind" напишите это:
{NEW}
TtckCommentKind = (tckAnsi, tckBor, tckSlash);
{/NEW}
- В объявлении "TSynPasSyn" найдите "FTokenID" и добавьте следующее между "FTokenID" и следующим полем
{NEW}
FCommentID: TtckCommentKind;
{/NEW}
//Это создает новое поле, где мы можем хранить информацию, какой у нас комментарий
- В объявлении "TSynPasSyn" найдите "fCommentAttri" и добавьте следующее между "fCommentAttri" и следующим полем
{NEW}
fCommentAttri_Ansi: TSynHighlighterAttributes;
fCommentAttri_Bor: TSynHighlighterAttributes;
fCommentAttri_Slash: TSynHighlighterAttributes;
{/NEW}
//Это позволяет нам возвращать различные атрибуты для каждого типа комментариев.
- Затем найдите определение конструктора "TSynPasSyn", которое должно быть "constructor TSynPasSyn.Create(AOwner: TComponent);"
- Нам нужно создать наши новые атрибуты, таким образом, мы добавляем наши атрибуты где-нибудь в конструкторе (я предлагаю после значения по умолчанию "fCommentAttri")
(...)
AddAttribute(fCommentAttri);
{NEW}
fCommentAttri_Ansi := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Ansi', SYNS_XML_AttrComment+'_Ansi'); // Последние две строки - это заголовок и сохраненное имя
//Если вы хотите иметь настройки по умолчанию для вашего атрибута, вы можете, например, добавить это:
//fCommentAttri_Ansi.Background := clBlack; //Установит "Background" в "clBlack" по умолчанию
AddAttribute(fCommentAttri_Ansi);
fCommentAttri_Bor := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Bor', SYNS_XML_AttrComment+'_Bor');
AddAttribute(fCommentAttri_Bor);
fCommentAttri_Slash := TSynHighlighterAttributes.Create(SYNS_AttrComment+'_Slash', SYNS_XML_AttrComment+'_Slash');
AddAttribute(fCommentAttri_Slash);
{/NEW}
(...)
- «Сложная» часть теперь состоит в том, чтобы найти места в коде, где "FTokenID" установлен в "tkComment", и установить наш «подтип» одинаково (конечно, я их уже поискал :)
procedure TSynPasSyn.BorProc;
(...)
fTokenID := tkComment;
{NEW}
FCommentID:=tckBor;
{/NEW}
if rsIDEDirective in fRange then
(...)
procedure TSynPasSyn.AnsiProc;
begin
fTokenID := tkComment;
{NEW}
FCommentID:=tckAnsi;
{/NEW}
(...)
procedure TSynPasSyn.RoundOpenProc;
(...)
fTokenID := tkComment;
{NEW}
FCommentID:=tckAnsi;
{/NEW}
fStringLen := 2; // length of "(*"
(...)
procedure TSynPasSyn.SlashProc;
begin
if fLine[Run+1] = '/' then begin
fTokenID := tkComment;
{NEW}
FCommentID:=tckSlash;
{/NEW}
if FAtLineStart then begin
(...)
procedure TSynPasSyn.SlashContinueProc;
(...)
fTokenID := tkComment;
{NEW}
FCommentID:=tckSlash;
{/NEW}
while not(fLine[Run] in [#0, #10, #13]) do
(...)
- Теперь нам просто нужно извлечь информацию при вызове "GetTokenAttribute" и вернуть правильный атрибут, поэтому мы отредактируем "GetTokenAttribute" следующим образом:
function TSynPasSyn.GetTokenAttribute: TSynHighlighterAttributes;
begin
case GetTokenID of
tkAsm: Result := fAsmAttri;
{OLD
tkComment: Result := fCommentAttri; //Это закомментировано и на всякий пожарный сохранено, поэтому оно будет игнорироваться
/OLD}
{NEW}
tkComment: begin
if (FCommentID=tckAnsi) then Result:=fCommentAttri_Ansi //Тип - AnsiComment
else
if (FCommentID=tckBor) then Result:=fCommentAttri_Bor //Тип - BorComment
else
if (FCommentID=tckSlash) then Result:=fCommentAttri_Slash //Тип - SlashComment
else
Result:=fCommentAttri //Если наш код каким-то образом упал, возврат к умолчанию
end;
{/NEW}
tkIDEDirective: begin
(...)
Если вы используете lazarus, просто переустановите SynEdit-Package, если нет, перекомпилируйте ваш проект/пакет/<аналог>.
ГОТОВО! Нет, серьезно, теперь вы готовы отличать различные типы комментариев.
Lazarus-IDE автоматически определяет, какие атрибуты существуют, и показывает их в опциях, например сохраняет их, если вы их изменяете. Если ваше приложение/IDE не делает этого, вам придется установить Color/Font/ и т.д. новых атрибутов где-то вручную (например, в конструкторе TSynPasSyn)
Плагины для автозавершения кода
Для SynEdit есть 3 подключаемых плагина автозавершения кода:
- Предлагает список слов в раскрывающемся списке с помощью сочетания клавиш (по умолчанию: Ctrl+space).
- Используется в IDE для автозавершения идентификатора.
- Включен в примеры.
- Доступен в палитре компонентов (начиная с 0.9.3x).
Пример кода, чтобы вызвать автозавершение, всплывает программно (т.е. без нажатия сочетания клавиш):
YourSynEdit.CommandProcessor(YourSynCompletion.ExecCommandID, '', nil)
- Заменяет текущий токен фрагментом текста. Не интерактивный. Не выпадающий.
- Включен в примеры.
- Доступен в палитре компонентов.
- Основной шаблон модуля. Не выпадающий.
- Используется IDE для шаблонов кода. IDE содержит дополнительный код, расширяющий эту функцию (IDE добавляет макросы выпадающего списка и синхронизации).
- Не включен в примеры.
Todo: Различия между 2-м и 3-м должны быть задокументированы. Может быть, они могут быть объединены.
Логическая/Физическая позиция каретки
SynEdit предлагает положение каретки (текстовый мигающий курсор) в 2 различных формах:
- Физический X/Y: соответствует визуальной (холст) позиции
- Логический X/Y: соответствует байтовому смещению текста
Оба основаны на 1 (отсчет начинается с 1). В настоящее время координаты Y всегда одинаковы. Это может измениться в будущем.
- Физическая координата
- это позиция в сетке дисплея (без учета прокрутки). То есть:
- обе буквы "a" и "â" занимают ОДНУ ячейку в сетке, увеличивая физический x на 1. Несмотря на то, что в кодировке utf8 "a" занимает один байт, а "â" занимает несколько байтов.
- однако символ табуляции (#9), помимо одного байта и одного символа, может занимать несколько ячеек в сетке, увеличивая физический x более, чем на одну позицию. Есть также некоторые символы в китайском и восточном языках, которые занимают 2 позиции в сетке (гуглите отличие full-width(полной ширины) от half-width (полуширины) символа).
- Логическая координата
- смещение байта в строчке, содержащей строку.
- буква «а» имеет 1 байт и увеличивается на 1
- буква "â" имеет 2 (или 3) байта и увеличивается на эту величину
- символ tab имеет 1 байт и увеличивается на эту величину.
Ни один из 2 не дает позицию в символах/кодовых точках UTF8 (например, для Utf8Copy или Utf8Length).
Физический X всегда отсчитывается слева от текста, даже если он прокручивается. Чтобы получить grid-x текущего прокручиваемого элемента управления, выполните:
- grid-X-in-visible-part-of-synedit := PhysicalX - SynEdit.LeftChar + 1
- grid-y-in-visible-part-of-synedit := SynEdit.RowToScreenRow(PhysicalY); // включает в себя складывание
- используйте ScreenRowToRow для обратного процесса
Change text from code
Text can be accessed via SynEdit.Lines. This is a TStrings based property offering read/write access to each line. It is 0 based.
SynEdit.Lines[0] := 'Text'; // first line
SynEdit.Lines can be used to set the initial version of the text (e.g. loaded from file). Note that SynEdit.Lines.Add/SynEdit.Lines.Append does not support line breaks inside the added strings. You should add lines one by one.
To modify the content of a SynEdit, and allow the user to undo the action use the following methods:
procedure InsertTextAtCaret(aText: String; aCaretMode: TSynCaretAdjustMode = scamEnd);
property TextBetweenPoints[aStartPoint, aEndPoint: TPoint]: String // Logical Points
read GetTextBetweenPoints write SetTextBetweenPointsSimple;
property TextBetweenPointsEx[aStartPoint, aEndPoint: TPoint; CaretMode: TSynCaretAdjustMode]: String
write SetTextBetweenPointsEx;
procedure SetTextBetweenPoints(aStartPoint, aEndPoint: TPoint;
const AValue: String;
aFlags: TSynEditTextFlags = [];
aCaretMode: TSynCaretAdjustMode = scamIgnore;
aMarksMode: TSynMarksAdjustMode = smaMoveUp;
aSelectionMode: TSynSelectionMode = smNormal );
Examples:
// Insert text at caret
SynEdit.InsertTextAtCaret('Text');
// Replace text from (x=2,y=10) to (x=4,y=20) with Str
SynEdit.TextBetweenPoints[Point(2,10), Point(4,20)] := Str;
// Delete/replace single char at caret pos
var p1, p2: TPoint;
begin
p1 := SynEdit.LogicalCaretXY;
p2 := p1;
// Calculate the byte pos of the next char
p2.x := p2.x + UTF8CharacterLength(@SynEdit.LineText[p2.x]);
// p1 points to the first byte of char to be replaced
// p2 points to the first byte of the char after the last replaceable char
// Replace with "Text" (or use empty string to delete)
SynEdit.TextBetweenPoints[p1, p2] := 'Text';
Fold/Unfold from code
- This is still under construction.
- This only works if current highlighter supports folding (details at SynEdit_Highlighter).
- Also note that some highlighters support several independent fold-trees. E.g. in Pascal you have folding on keywords (begin, end, class, procedure, etc) which is the primary fold, and folding on $ifdef or $region which is secondary.
- Folding of current selection is also different from folding on keywords.
Methods for folding:
1) TSynEdit.CodeFoldAction
Folds at the given Line. If there are more than one, folds the inner most (right most). Note: This does not work with selection, nor with Folds that hide entirely / Need testing for 2ndary folds.
2) TSynEdit.FindNextUnfoldedLine
3) TSynEdit.FoldAll / TSynEdit.UnfoldAll
Bookmarks
More info
Discussions on the forum, which contain info about SynEdit:
- Search/replace; caret; position logical/physical
- Diff between SynEdit/SynMemo
- Mouse actions; disable paste on middle click
- TSynEditMarkup info
Example apps
Example applications can be found in the folder "lazarus/examples/synedit".
Adding hotkeys for Cut/Copy/Paste/etc
Hotkeys can be implemented by using SynEdit commands.
uses
SynEdit, SynEditKeyCmds;
procedure TForm1.SynEdit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Shift = [ssCtrl]) then
begin
case Key of
VK_C: SynEdit1.CommandProcessor(TSynEditorCommand(ecCopy), ' ', nil);
VK_V: SynEdit1.CommandProcessor(TSynEditorCommand(ecPaste), ' ', nil);
VK_X: SynEdit1.CommandProcessor(TSynEditorCommand(ecCut), ' ', nil);
end;
end;
end;
Further development, discussions
- RTL (right-to-left): started by Mazen (partly implemented on Windows)
- SynEdit only uses UTF8; an ASCII/ANSI version no longer exists. A font is pre-selected depending on the system. The user can choose another font, but must then take care to choose a monospaced font.
- automatic monospace font selection: At the moment SynEdit starts with a font 'courier'. At the moment the LCL TFont does not provide a property to filter monospaced fonts.
- automatic UTF-8 font selection: Same as above monospace, but also with an UTF-8 font, so that for example umlauts are shown correctly.
- Dead keys. Most keyboards support typing two or more keys to create one special character (like accented or umlaut characters). (This is handled by LCL widgedset)
- Redesign of the SynEdit component. The primary goal is more reliable display and navigation in the text. A more modular approach also allows for better integration of extensions, and for specialized controls, for use outside of Lazarus.
- Word Wrapping. This is an experimental implementation following the idea of the TextTrimmer/TabExpansion classes. The linked bugtraker issue has the class and the explanation of changes required in other files for it to work.
- Hooks in SynEdit key/command processing. On the forum: http://forum.lazarus-ide.org/index.php/topic,35592.msg243316.html#msg243316