Сегодня я хотел бы поделиться с Вами ещё одним алгоритмом — подсветкой синтаксиса (или другого текста).
Как Вы, наверное, могли заметить в своем блоге я стараюсь связать теорию с практикой. Например, алгоритм облака тегов на Delphi я сейчас использую в программе «Блевантон» и т.д. Алгоритм подсветки синтаксиса тоже не исключение — он используется в новой версии моего детища.
Итак, пусть перед нами стоит задача — сделать простенький редактор с подсветкой синтаксиса на Delphi. Для примера возьмем язык SQL. Самым подходящим для этой цели компонентом является TRichEdit — прост в использовании и имеет все возможности для работы с текстом.
Размещаем RichEdit на форме и начинаем заниматься «кодвством». Для того, чтобы наш алгоритм был полезен всем, я разместил его в отдельном модуле (uSyntax).
Наша программа должна уметь:
- Определять слово, которое необходимо подсветить
- Автоматически подсвечивать синтаксис при вставке текста из буфера обмена
- и конечно же — быстро работать :)
Пойдем по пунктам. Функцию определения слова для подсветки я написал следующим образом:
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 готов.
А Вы не слышали о таком понятии как стиль оформления кода. Без обид, но читать код, который написан в один столбец не очень удобно. Сложно что ли сделать отступы?!
А так, по самому алгоритму, проде бы нет замечаний. Молоток! ;-)
Не поверите, но слышал :) Вот только плагин, который я использую в WP видимо об этом не слышал никогда
Ну тогда нужно сменить плагин, а то как-то не очень хорошо получается. Вы рассказываете про программирование, а листинг в таком не красивом виде. Думаю, что Вам самим не очень приятно читать такой листинг. Попробуйте один из следующих: SyntaxHighlighter (один из лучших!!!), CodeColorer, WP-SynHighlight… есть конечно ещё плагины, но эти самый нормальные, особенно первый! ;)
Спасибо за совет. В ближайшее время посмотрю плагины и выберу.
И ещё один совет. Это уже относительно блога. Сделайте подписку на комментарии. Для этого воспользуйтесь плагиномSubscribe to Comments . А то не удобно следить за новыми комментариями.
Плагин поставил, с подсветкой синтаксиса вроде разобрался — теперь отступы есть. Спасибо за советы по улучшению блога ;)
Привет. Сначала о хорощем: спасибо что размещаете такую полезную информацию. :) Ну а теперь о плохом: CodeGear RAD Studio 2009 Architect Edition (во как) ругается на функцию SearchFor. В MSDN я ее так же не нашел. Что подскажете?
Да собственно — не за что :) А по поводу SearchFor Вы абсолютно правы — это функция не из стандартных просто не размещал её в блоге, т.к. ничего особенно нового в себе она не несет. Представлю её в комментарии. Извините, что без подсветки :)
function SearchFor(WorkSpace, Search: string; Start: integer): integer;
var Temp: string;
begin
Temp := copy(WorkSpace, Start, length(WorkSpace));
Result := pos(Search, Temp);
end;
Вот и всё. Пользуйтесь на здоровье ;)
Эмм, спасибо. А что мне делать с BuildStops.
BuildStops — это переменная типа TStringList в которой содержатся зарезервированные слова, которые требуется подсветить. Например для Delphi в BuildStops будут находиться элементы: begin, end, function, var и т.д. Соответственно, если делаете подсветку для другого языка — наполняете BuildStops другими словами. В принципе это я так обозвал переменную — можете изменить на что угодно, лишь бы это был TStringList
Это вопрос вообще то :)
Спасибо за инфо, но в случае записей, вида: max(x), слово max останется не подсвеченным
Да, есть такое дело. Скажем так — это издержки производства :) Т.к. если проводить анализ подстрок, то можно наломать дров. Как вариант, можно отрезать подстроки которые содержаться в скобках — тогда можно подсвечивать и max
В принципе, да, но тогда следующая тема: а как подсвечивать комменты? :)
Кстати, если отрезать подстроку в скобках, а делается, как я понял, это в функции CheckList, то подсвечиваться будет и max, и скобки, и то, что в скобках
точно :) тогда видимо нужны изменения в алгоритме подсветки. Видимо ещё 1 метод нужен будет.
Да, уж, видимо придется парсить, а так хотелось откосить ))
Ну парсинг оч интересная тема :)) Надеюсь у Вас всё получится. Я этот алгоритм как-то запостил в блоге и особо нигде не применял в чистом виде, но в случае чего — пишите, попробуем разобраться вместе
Значит так )) После доработки все стало боль-мень работать 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,… Подробнее »
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, ‘+’,… Подробнее »
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 =… Подробнее »
Можно еще повылизывать, но принцип оптимизации уже понятен :)
Теперь комментарии по объему равны самому посту :) Что весьма радует — сразу два решения. Спасибо за то, что поделились решением в моем блоге.
Да всегда пожалуйста. Непонятно, только откуда вообще изначальный код и чему так радовались первые комментаторы. :)
Изначальный код был взят с какого-то сайта по Delphi, который благополучно исчез из Сети + немного подправленный под мои нужды. А комментаторы видимо были рады, что и под их нужды код оказался вполне подходящим :)
За статью большооое спасибо, но не могли бы вы выкладывать исходники на менее геморойные файлообменники, хотя бы на народ…
Компилятор в Delphi 7 возмущается, мол отсутствует модуль Common.dcu. И действительно, среди исходников среды его нет. что там находится и каким образом подключается файл с ключевыми словами?, а где же директива {$ Include} ? где искать модуль Common.pas ?
Ничего не компилируется, где модуль Common.pas ?
Помнится давно писал такую вещь для своих нужд. Еще на пятой дельфе.
В итоге оформил все компонентом. Кидаешь на форму, определяешь списки слов и символов, пристегиваешь рич и все дела.
Если интересно могу поделиться исходниками. Проблем с подсветкой вроде не было.
Сам сейчас в основном на мелкомягкой студии пишу. Но Дельфу вспоминаю с большой теплотой. До сих пор еще пользую 2006. А в 7-ке приходится подтачивать старые проекты…
А как сохранять позиции скролл баров вы разбирались ?
дело в том что ваш код хорош при подсветке текста при загрузке (пробежались по тексту и выделили)..
а вот при динамической подсветке, особенно построчной, могут появиться скачки например по горизонтали, когда scrollbars=Both, и строка для выделения не умещается на экране — выделение произойдет за экраном и позиция скролбаров измениться…
я пытаюсь написать IDE для разработки программ на ассемблере..
и одним из «пунктов» у меня лексический разбор написанного текста (а не просто подсветка)…
если кто имеет опыт (или желание) поучаствовать — то с удовольствием бы объединил свои усилия..