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

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 не будет опубликован. Обязательные поля помечены *