Подсветка синтаксиса — выделение синтаксических конструкций текста с использованием различных цветов, шрифтов и начертаний. Обычно применяется для облегчения чтения исходного текста компьютерных программ, улучшения визуального восприятия.

Wikipedia

По просьбам моих читателей, которые по счастливой случайности совпали с моими намерениями, я решил убрать поле example.detail, в котором хранился текст в формате RTF. А вместо него добавить текстовое поле example.snippet, в котором будут храниться фрагмент кода с комментариями. Но так как я уже успел накидать в базу примеров, то для переноса текста пришлось сделать кнопку и небольшой скрипт.

Конвертор RTF -> TXT

procedure frmExampleView_Button1_OnClick (Sender: TObject; var Cancel: boolean); var tmpDataSet:TDataSet; begin SQLQuery('select * from example',tmpDataSet); while not tmpDataSet.EOF do begin frmExampleView.redExample.TextRTF := tmpDataSet.FieldByName('detail').asString; SQLExecute('UPDATE example SET snippet = '''+ escape_special_characters( frmExampleView.redExample.Text )+''' WHERE id = '+tmpDataSet.FieldByName('id').asString); tmpDataSet.Next; end; tmpDataSet.Free; frmExampleView.redExample.Clear; ShowMessage('Перенос завершен!'); end;
Code language: Delphi (delphi)

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

Кнопка frmExampleView.Button1 временная, я её уберу в следующей версии программы.

Форма редактирования

Для редактирования примера будет использовано обычное поле многострочного редактирования TdbMemo, которому активируем отображение вертикальной полосы прокрутки (свойство ScrollBars = ssVertical).

Форма просмотра

Форма просмотра осталась прежней, с использованием компонента RichEdit, у которого отключено отображение панелей инструментов и линейки (свойства Toolbar1.Show = False, Toolbar2.Show = False, Toolbar3.Show = False, Ruler = False).

Подсветку кода я решил реализовать через формат HTML. Мне показалось это более простым, прозрачным и универсальным решением, чем привязываться к возможностям TdbRichEdit или формату RTF.

К сожалению, в MVDB у RichEdit не реализован метод добавления текста в формате HTML, это возможно сделать только через загрузку файла и метод LoadHTML(), поэтому в копилку расширений добавился ещё один маленький скрипт:

procedure RichEdit_SetHTMLText(AEdit:TdbRichEdit; AText:string); // загрузка HTML в редактор var tmpFileName: string; tmpMemo: TdbMemo; begin tmpFileName := GetTempFilename; // получить имя временного файла tmpMemo := TdbMemo.Create(nil); // создать компонент для записи текстового файла в форматье UTF-8 tmpMemo.Text := AText; tmpMemo.SaveToFileUTF8(tmpFileName ); // сохранить файл AEdit.Clear; AEdit.LoadHTML(tmpFileName); // загрузить файл как HTML DeleteFile(tmpFileName); // удалить временный файл end;
Code language: Delphi (delphi)

Поменялся и метод загрузки данных UserApp_LoadExample(): в нем используется описанный выше RichEdit_SetHTMLText(), а также функция конвертации текста в формат HTML с подсветкой синтаксиса CodeHL_TextToHTML().

procedure UserApp_LoadExample; // загрузка примера из базы var tmpData:string; tmpTableName: string; tmpTableID: string; tmpText: string; begin frmExampleView.edtCaption.Text := ''; frmExampleView.redExample.Clear; if ActiveGrid <> nil then begin tmpTableName := Grid_GetTableName( ActiveGrid ); if tmpTableName <> '' then begin tmpTableID := IntToStr(ActiveGrid.dbItemID); try // получить существующий ID IDExample := SQLExecute('SELECT id_example FROM '+tmpTableName+' WHERE id = '+tmpTableID ); except // а если ID не существует, то будет -1 IDExample := -1; end; // загрузить форматированный текст tmpText := SQLExecute('SELECT snippet FROM example WHERE id = '+IntToStr(IDExample)); RichEdit_SetHTMLText( frmExampleView.redExample, CodeHL_TextToHTML( tmpText )); // загрузить название frmExampleView.edtCaption.Text := SQLExecute('SELECT caption FROM example WHERE id = '+IntToStr(IDExample)); end; end; end;
Code language: Delphi (delphi)

Конвертор TXT -> HTML

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

Линейный режим

Линейный режим используется при обнаружения комментариев, а также строковых литералов. В линейном режиме отключается анализ разделения текста на слова до момента обнаружения специального символа (или нескольких символов) завершения линейного режима.

В языке ObjectPascal допустимы три вида комментариев и один способ определения строковых литералов.

ОбъектНачальный символКонечный символ
Строковый литерал ‘ (одинарная кавычка) ‘ (одинарная кавычка)
Комментарий // конец строки ($0A)
Комментарий { }
Комментарий (* *)

Ключевые слова

В языке ObjectPascal выделают две группы ключевых слов: ключевые слова языка и стандартные классы. Каждая группа имеет свой цвет и стиль выделения. Кроме ключевых слов обычно выделяются числовые значения. Весь описанный функционал я реализовал в процедуре CodeHL_CheckWord().

procedure CodeHL_CheckWord(var AWord:string ); // проверка слова, добавление тегов var s:string; i: integer; j: integer; begin s := LowerCase(Trim(AWord)); if (s = '') or (s = ',') then exit; // если слово число if ValidInt(s) or ValidFloat(s) then begin AWord := '<font color="#FF00FF"><b>'+AWord+'</b></font>'; exit; end; // если слово входит в список 1 for i:=0 to Length(CHL_KeyWord1) - 1 do begin j := CompareText(s,CHL_KeyWord1[i]); if j = 0 then begin AWord := '<font color="#005F00"><b>'+AWord+'</b></font>'; exit; end else if j = -1 then begin break end; end; // если слово входит в список 2 for i:=0 to Length(CHL_KeyWord2) - 1 do begin j := CompareText(s,CHL_KeyWord2[i]); if j = 0 then begin AWord := '<font color="#000000"><b>'+AWord+'</b></font>'; exit; end else if j = -1 then begin break end; end; end;
Code language: Delphi (delphi)

Основная функция подсветки кода CodeHL_TextToHTML() кроме описанных выше действий производит замену символов, используемых для разметки, на специальные символьные последовательности. А весь текст оборачивается тегами, которые сохраняют пробелы и переносы строк при отображении данных.

function CodeHL_TextToHTML (AText: string):string; // добавление в текст HTML тегов с подсветкой var i: integer; j: integer; tmpWord: string; tmpChar: string; s1, s2: string; tmpIndex: integer; begin tmpIndex := -1; Result := ''; tmpWord := ''; for i := 1 to Length(AText) do begin tmpChar := AText[i]; // замена для вставки в HTML case tmpChar of '<': tmpChar:='&lt;'; '>': tmpChar:='&gt;'; '&': tmpChar:='&amp;'; chr(10): tmpChar:=''; end; // линейный режим - это отключение посимвольного анализа до обнаружения триггерной последовательности if tmpIndex <> -1 then begin s1 := CHL_LM_End[tmpIndex]; // обнаружена завершающая последовательность? if copy(AText,i,Length(s1) ) = s1 then begin tmpWord := tmpWord + S1; // если завершающая последовательность больше одного символа, то делаем коректировку if Length(S1) > 1 then delete( AText, i+1, Length(s1) - 1 ); // if CHL_LM_Italic[tmpIndex] = '1' then begin s1 := '<i>'; s2 := '</i>'; end else begin s1 := ''; s2 := ''; end; Result := Result + '<font color="'+CHL_LM_Color[tmpIndex]+'">'+S1+ tmpWord +S2+ '</font>'; tmpIndex := -1; tmpWord := ''; end else tmpWord := tmpWord + tmpChar; continue; end; // конец слова if AText[i] in [' ','{','}','/','\',')','(',';',':','"','''','.',chr(13)] then begin // проверка начала линейного режима for j:=0 to Length( CHL_LM_Begin )-1 do begin if copy( AText, i, Length(CHL_LM_Begin[j]) ) = CHL_LM_Begin[j] then begin CodeHL_CheckWord(tmpWord); Result := Result + tmpWord; tmpIndex := j; continue; end; end; // проверка слов CodeHL_CheckWord(tmpWord); Result := Result + tmpWord; Result := Result + AText[i]; // символ-разделитель, который был в конце слова tmpWord := ''; end else tmpWord := tmpWord + tmpChar; end; Result := Result + tmpWord; Result := '<pre>'+Result+'</pre>'; end;
Code language: Delphi (delphi)

И хотя местами функция выглядит шероховатой, в целом она справляется со своей задачей и имеет приемлемую производительность: задержка при отображении данных составляет около половины секунды, включая создание временного файла для загрузки HTML.

Данные

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

var CHL_KeyWord1 : array of string; // ключевые слова языка CHL_KeyWord2 : array of string; // стандартные типы // параметры линейного режима CHL_LM_Begin: array of string; // начальный символ CHL_LM_End: array of string; // конечный символ CHL_LM_Color: array of string; // цвет как код CHL_LM_Italic: array of string; // признак наклонности текста procedure CodeHL_Init; // инициализация // слова должны быть упорядочены по алфавиту begin // настраиваем данные для линейного режима CHL_LM_Begin := SplitString('//,{,(*,''',','); CHL_LM_End := SplitString(chr(13)+',},*),''',','); CHL_LM_Color := SplitString('#00009F,#00009F,#00009F,#FF0000',','); CHL_LM_Italic := SplitString('0,0,0,1',','); // данные по ключевым словам CHL_KeyWord1 := SplitString('absolute,abstract,and,array,as,asm,assembler,automated,begin,case,'+ 'cdecl,class,const,constructor,destructor,dispid,dispinterface,div,do,downto,dynamic,'+ 'else,end,except,export,exports,external,far,file,finalization,finally,for,forward,'+ 'function,goto,if,implementation,in,inherited,initialization,inline,interface,is,'+ 'label,library,message,mod,near,nil,not,object,of,or,out,override,packed,pascal,'+ 'private,procedure,program,property,protected,public,published,raise,record,register,'+ 'repeat,resourcestring,safecall,set,shl,shr,stdcall,then,threadvar,to,try,type,unit,'+ 'until,uses,var,virtual,while,with,xor',','); CHL_KeyWord2 := SplitString('ansichar,ansistring,array,boolean,byte,bytebool,cardinal,'+ 'char,currency,double,false,fixedint,fixeduint,int64,integer,longbool,longint,'+ 'longword,nativeint,nativeuint,pansichar,pansistring,pboolean,pbyte,pcardinal,'+ 'pchar,pcurrency,pdouble,pfixedint,pfixeduint,pint64,pinteger,plongbool,plongint,'+ 'plongword,pnativeint,pnativeuint,pointer,ppointer,prawbytestring,pshortint,'+ 'pshortstring,psingle,psmallint,pstring,puint64,punicodestring,pvariant,'+ 'pwidechar,pwidestring,pword,pwordbool,rawbytestring,real,record,shortint,'+ 'shortstring,single,smallint,string,tdoublerec,text,true,tsinglerec,type,'+ 'uint64,unicodestring,variant,widechar,widestring,word,wordbool',',' ); end;
Code language: PHP (php)

Список стандартных типов включает все типы, которые доступны в Delphi, но в My Visual Database их гораздо меньше. Если сократить данный список, оставив только необходимые значения, то производительность модуля подсветки будет ещё выше.

Что дальше?

Внимательный читатель заметил, что на картинке с формой редактирования присутствует кнопка “Форматировать код”, действие которой заслуживает отдельного описания. Эта кнопка позволяет выравнивать отступы, переносить строчки и выполнять другие действия, в результате которых исходный код будет выглядеть лучше. Но об этом я расскажу в следующей статье.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *