уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Сегодня я хотел бы поделиться с Вами ещё одним алгоритмом — подсветкой синтаксиса (или другого текста).

Как Вы, наверное, могли заметить в своем блоге я стараюсь связать теорию с практикой. Например, алгоритм облака тегов на Delphi я сейчас использую в программе «Блевантон» и т.д. Алгоритм подсветки синтаксиса тоже не исключение — он используется в новой версии моего детища.

Итак, пусть перед нами стоит задача — сделать простенький редактор с подсветкой синтаксиса на Delphi. Для примера возьмем язык SQL. Самым подходящим для этой цели компонентом является TRichEdit — прост в использовании и имеет все возможности для работы с текстом.

Размещаем RichEdit на форме и начинаем заниматься «кодвством». Для того, чтобы наш алгоритм был полезен всем, я разместил его в отдельном модуле (uSyntax).

Наша программа должна уметь:

  1. Определять слово, которое необходимо подсветить
  2. Автоматически подсвечивать синтаксис при вставке текста из буфера обмена
  3. и конечно же — быстро работать :)

Пойдем по пунктам. Функцию определения слова для подсветки я написал следующим образом:

function CheckList(InString: string): boolean;
var X: integer;
begin
  Result := false;
  X := 0;
  InString := StringReplace(InString, ' ', '',[rfReplaceAll]);
  InString := StringReplace(InString, #$A, '',[rfReplaceAll]);
  InString := StringReplace(InString, #$D, '',[rfReplaceAll]); 
  while X < BuildStops.Count do
    if AnsiLowerCase(BuildStops.Strings[X]) = AnsiLowerCase(InString) then
      begin
       Result:=true;
       X:=BuildStops.Count;
      end
    else inc(X);
end;

Здесь мы: вначале удаляем из строки все пробелы и управляющие символы, а затем проходим в цикле по списку слов BuildStops в поисках заданной строки InString. Если строка обнаружена, то это слово для подсветки. При этом Вы можете записать в список любое количество слов и конструкция языка для подсветки — наш редактор не прихотлив в использовании и сможет подсветить всё, что пожелаете.

Следующий шаг — подсвечиваем слово в RichEdit. Для того, чтобы дать пользователю самомы выбирать цвет подсветки я определил следующий тип данных:

type
TColorer = record
  FontSize: integer;  //размер шрифта для слов с подсветкой
  CurrSize: integer;  //размер основного текста
  FontColor: TColor; //цвет подсветки
  CurrColor: TColor; //цвет основного текста
end;

А вы например можете сделать многоцветную подсветку, но для этого Вам потребуется задать как минимум ещё один список слов для поиска.

Теперь сама процедура. На мой взгляд, наиболее целесообразно вызывать процедуру подсветки в определенный момент, например при отпускании клавиши «Пробел», а не держать её в отдельном потоке.

procedure RichEditKeyUp(REdit:TRichEdit; var Key: Word; Shift: TShiftState);
var WEnd, WStart, BCount: integer;
Mark: string;
begin
{условие при котором начинает работу алгоритм подсветки синтаксиса}
  if (Key = VK_Return) or (Key = VK_Back) or (Key = VK_Space) then
    begin
      if REdit.SelStart > 1 then
        begin
          WStart := 0;
          WEnd := REdit.SelStart;
          BCount := WEnd - 1;
          while BCount <> 0 do
            begin
              Mark := copy(REdit.Text, BCount, 1);
              if (Mark = ' ') or (Mark = #$A) then
                begin
                  WStart := BCount;
                  BCount := 1;
                end;
              dec(BCount);
            end;
          {выделяем слово}
          REdit.SelStart := WEnd - (WEnd - WStart);
          REdit.SelLength := WEnd - WStart;
          {проверяем его в списке и, если необходимо - подсвечиваем}
          if CheckList(REdit.SelText) then
            begin
              REdit.SelAttributes.Size:=Colorer.FontSize;
              REdit.SelAttributes.Color:=Colorer.FontColor;
            end
          else
            begin
              REdit.SelAttributes.Size:=Colorer.CurrSize;
              REdit.SelAttributes.Color:=Colorer.CurrColor;
            end;
         {не забываем поставить каретку на место и установить шрифт для основного текста}
         REdit.SelStart := WEnd;
         REdit.SelAttributes.Size:=Colorer.CurrSize;
         REdit.SelAttributes.Color:=Colorer.CurrColor;
       end;
    end;
end;

Ну, и наконец процедура подсветки синтаксиса при вставлении большого куска текста из буфера обмена:

procedure HighLight(REdit: TRichEdit);
var WStart, WEnd, WEnd2: integer;
    WorkSpace, SWord: string;
begin
  WStart  :=  1;
  WEnd  :=  1;
  with  REdit do
    begin
      WorkSpace  :=  Text + ' ' + #$D#$A;
      while WEnd > 0 do
        begin
          WEnd := SearchFor(WorkSpace, ' ', WStart);
          WEnd2 := SearchFor(WorkSpace, #$A, WStart);
          if WEnd2 < WEnd then WEnd := WEnd2;
            SWord := copy(WorkSpace, WStart, WEnd - 1);
          if (SWord <> ' ') and (SWord <>'') then
            if CheckList(SWord) then
              begin
                SelStart  := WStart - 1;
                SelLength := length(SWord);
                REdit.SelAttributes.Size:=Colorer.FontSize;
                REdit.SelAttributes.Color:=Colorer.FontColor;
                SelStart := WStart + length(SWord) + 1;
                REdit.SelAttributes.Size:=Colorer.CurrSize;
                REdit.SelAttributes.Color:=Colorer.CurrColor;
              end;
      WStart := WStart + WEnd;
    end;
  SelStart:=length(Text);
  SetFocus;
end;
end;

Располагаем процедуру RichEditKeyUp на событие OnKeyUp у Вашего RichEdit, а процедуру HighLight, например на нажатие кнопку Button на форме.

Ну, а для того, чтобы снять подсветку с текста, достаточно написать простенькую процедуру:

procedure RemoveHightLight(REdit: TRichEdit);
var WEnd:integer;
begin
  WEnd:=REdit.SelStart;
  REdit.SelectAll;
  REdit.SelAttributes.Color:=Colorer.CurrColor;
  REdit.SelAttributes.Size:=Colorer.CurrSize;
  REdit.SelStart:=WEnd;
end;

Алгоритм подсветки синтаксиса на Delphi готов.

Скачать исходник: Исходники —> Прочие
5 1 голос
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
31 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Алекcей
Алекcей
20/08/2009 06:06

А Вы не слышали о таком понятии как стиль оформления кода. Без обид, но читать код, который написан в один столбец не очень удобно. Сложно что ли сделать отступы?!
А так, по самому алгоритму, проде бы нет замечаний. Молоток! ;-)

Алекcей
Алекcей
20/08/2009 14:23

Ну тогда нужно сменить плагин, а то как-то не очень хорошо получается. Вы рассказываете про программирование, а листинг в таком не красивом виде. Думаю, что Вам самим не очень приятно читать такой листинг. Попробуйте один из следующих: SyntaxHighlighter (один из лучших!!!), CodeColorer, WP-SynHighlight… есть конечно ещё плагины, но эти самый нормальные, особенно первый! ;)

Алексей
Алексей
21/08/2009 06:54

И ещё один совет. Это уже относительно блога. Сделайте подписку на комментарии. Для этого воспользуйтесь плагином Subscribe to Comments. А то не удобно следить за новыми комментариями.

Лавентий
Лавентий
18/09/2009 12:44

Привет. Сначала о хорощем: спасибо что размещаете такую полезную информацию. :) Ну а теперь о плохом: CodeGear RAD Studio 2009 Architect Edition (во как) ругается на функцию SearchFor. В MSDN я ее так же не нашел. Что подскажете?

Лаврентий
Лаврентий
18/09/2009 13:25

Эмм, спасибо. А что мне делать с BuildStops.

Лаврентий
Лаврентий
18/09/2009 13:45

Это вопрос вообще то :)

GrigAir
GrigAir
18/01/2010 18:09

Спасибо за инфо, но в случае записей, вида:   max(x), слово max останется не подсвеченным

GrigAir
GrigAir
18/01/2010 18:21

В принципе, да, но тогда следующая тема: а как подсвечивать комменты? :)

GrigAir
GrigAir
18/01/2010 18:27

Кстати, если отрезать подстроку в скобках, а делается, как я понял, это в функции CheckList, то подсвечиваться будет и max, и скобки, и то, что в скобках

GrigAir
GrigAir
18/01/2010 18:39

Да, уж, видимо придется парсить, а так хотелось откосить ))

GrigAir
GrigAir
19/01/2010 18:43

Значит так ))  После доработки все стало боль-мень работать function TChildForm.checklist(instring: string): boolean; const thelist: array[1..47] of string = (‘as’, ‘by’, ‘count’, ‘group’, ‘from’, ‘order’, ‘select’, ‘outer’, ‘where’, ‘real’, ’round’, ‘max’, ‘min’, ‘asc’, ‘desc’, ‘inner’,  ‘full’, ‘join’, ‘union’, ‘sum’, ‘avg’, ‘case’, ‘fetch’, ‘first’, ‘when’, ‘then’, ‘or’, ‘and’, ‘end’, ‘if’, ‘rows’, ‘only’, ‘in’, ‘begin’, ‘on’, ‘having’, ‘drop’, ‘table’, ‘insert’, ‘into’, ‘create’, ‘else’, ‘left’, ‘is’, ‘not’, ‘between’, ‘values’); var x: integer; begin result := false; x := 1; instring := stringreplace(instring, ‘ ‘, »,[rfreplaceall]); instring := stringreplace(instring, ‘(‘, »,[rfreplaceall]); instring := stringreplace(instring, ‘)’, »,[rfreplaceall]); instring := stringreplace(instring, ‘=’, »,[rfreplaceall]); instring := stringreplace(instring,… Подробнее »

GrigAir
GrigAir
19/01/2010 18:43

procedure TChildForm.HighLight; var wstart, i        : integer; wend             : array [1..11] of integer; workspace, sword : string; begin wstart  := 1; wend[11]:= 1; with Memo1 do begin workspace := text + ‘ ‘ + ‘(‘ + ‘)’ + ‘=’ + ‘+’ + ‘-‘ + ‘*’ + ‘/’ + #$d#$a; while wend[11] > 0 do begin wend[11] := searchfor(workspace, ‘ ‘, wstart);   wend[1] := searchfor(workspace, #$a, wstart); wend[2] := searchfor(workspace, #$d, wstart); wend[3] := searchfor(workspace, ‘(‘, wstart); wend[4] := searchfor(workspace, ‘)’, wstart); wend[5] := searchfor(workspace, ‘=’, wstart); wend[6] := searchfor(workspace, ‘+’,… Подробнее »

GrigAir
GrigAir
19/01/2010 18:44

procedure TChildForm.Memo1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); var wend, wstart, bcount: integer; mark                : string; begin if key=116 then begin MainForm.ToolButton11Click(Sender); end; if not ((key = 33) or (key = 34) or (key = 35) or (key = 36) or (key = 37) or (key = 38) or (key = 39) or (key = 40) or (key = 16)) then begin if Memo1.selstart > 1 then begin wstart := 0; wend := Memo1.selstart; bcount := wend — 1; while bcount <> 0 do begin mark := copy(Memo1.text, bcount, 1); if (mark =… Подробнее »

GrigAir
GrigAir
19/01/2010 18:45

Можно еще повылизывать, но принцип оптимизации уже понятен :)

GrigAir
GrigAir
20/01/2010 17:28

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

Darked
Darked
29/09/2010 17:46

За статью большооое спасибо, но не могли бы вы выкладывать исходники на менее геморойные файлообменники, хотя бы на народ…

Игорь
Игорь
12/11/2014 16:58

Компилятор в Delphi 7 возмущается, мол отсутствует модуль Common.dcu. И действительно, среди исходников среды его нет. что там находится и каким образом подключается файл с ключевыми словами?, а где же директива {$ Include} ? где искать модуль Common.pas ?

Игорь
Игорь
12/11/2014 17:04

Ничего не компилируется, где модуль Common.pas ?

Babay
Babay
12/11/2015 12:38

Помнится давно писал такую вещь для своих нужд. Еще на пятой дельфе.
В итоге оформил все компонентом. Кидаешь на форму, определяешь списки слов и символов, пристегиваешь рич и все дела.
Если интересно могу поделиться исходниками. Проблем с подсветкой вроде не было.

Сам сейчас в основном на мелкомягкой студии пишу. Но Дельфу вспоминаю с большой теплотой. До сих пор еще пользую 2006. А в 7-ке приходится подтачивать старые проекты…

Vitaly Gorbukov
18/02/2016 12:11

А как сохранять позиции скролл баров вы разбирались ?

дело в том что ваш код хорош при подсветке текста при загрузке (пробежались по тексту и выделили)..

а вот при динамической подсветке, особенно построчной, могут появиться скачки например по горизонтали, когда scrollbars=Both, и строка для выделения не умещается на экране — выделение произойдет за экраном и позиция скролбаров измениться…

Vitaly Gorbukov
18/02/2016 12:13
Ответить на  Vitaly Gorbukov

я пытаюсь написать IDE для разработки программ на ассемблере..
и одним из «пунктов» у меня лексический разбор написанного текста (а не просто подсветка)…

если кто имеет опыт (или желание) поучаствовать — то с удовольствием бы объединил свои усилия..