Потихоньку начал писать собственный редактор для программирование ARM на языке ассемблера, и решил начать с самого простого: сделать разбор текста при редактировании.
И тут я нашел небольшие такие грабельки :-)
Итак вопрос:
Есть редактор RichEdit в который мы ввели текст:
Курсор стоит вначале строки перед "9", RichText.SelStart := 12
Как узнать символ на котором стоит курсор?
Если ваш опыт подсказывает конструкцию наподобие:
with RichEdit do
textChar:=Text [SelStart];
— то ваш опыт не верен!
И если вам интересно — то правильный ответ можно увидеть под катом…
Итак обещанный правильный ответ:
для того чтобы прочитать символ на котором стоит курсор — нужно читать символ в RichEdit.Text с номером 16!
with RichEdit do
textChar:=Text [16];
Число взятое непонятно откуда? — ну не скажите! все имеет логическое объяснение, просто нужно один раз разобраться!
Первое.
Нумерация символов в свойстве RichEdit.SelStart идет с нуля. То есть символ "0" когда перед ним стоит курсор будет иметь индекс RichEdit.SelStart = 0
Согласитесь — это очень просто!
Одновременно, для того чтобы прочитать этот символ нам нужно читать символ в строке в позиции = 1:
textChar := RichEdit.Text[1]
А все потому, что свойство RichEdit.Text имеет тип String, а в типе String — нулевой символ хранит длину строки, и только после этого идет собственно текст: таким образом первый символ в строке (у нас это "0") имеет номер "1".
Запоминайте!
Нумерация символов в свойстве RichEdit.SelStart начинается с нуля (первый символ имеет номер =0), а нумерация символов в строке (свойство RichEdit.Text, тип String) идет с единицы (первый символ имеет номер =1)
Второе.
Документация нам указывает, что свойство SelStart указывает на номер символа в тексте. И тут необходимо вспомнить про символ генерируемый при переводе строки (нажатии на клавишу «Enter») — в текст добавляются 2 байта (символа) идущих подряд: 0x0D и 0x0A.
Ниже описание данное этим символам в Википедии:
Возврат каретки (англ. carriage return, CR) — управляющий символ ASCII (0x0D, 1310, 'r'), при выводе которого курсор перемещается к левому краю поля, не переходя на другую строку. Этот управляющий символ вводится клавишей «Enter». Будучи записан в файле, в отдельности рассматривается как перевод строки только в системах Macintosh.
Подача строки (от англ. line feed, LF — «подача [бумаги] на строку») — управляющий символ ASCII (0x0A, 10 в десятичной системе счисления, 'n'), при выводе которого курсор перемещается на следующую строку. В случае принтера это означает сдвиг бумаги вверх, в случае дисплея — сдвиг курсора вниз, если ещё осталось место, и прокрутку текста вверх, если курсор находился на нижней строке. Возвращается ли при этом курсор к левому краю или нет, зависит от реализации.
Так вот SelStart считает эти два байта (возврат каретки — 0х0D, перевод строки — 0x0A ) — одним символом!
Таким образом, символ "4" в нашем редакторе будет иметь номер SelStart = 5!
А вот в свойстве RichEdit.Text у него будет номер 7!!!
Для того чтобы все стало совсем понятным я нарисовал табличку (первый столбец — индекс, второй SelStart — соответствие индекса SelStart символу, третий CharPos — соответствие Text[индекс] символу):
Такие вот особенности адресации символов у редактора RichEdit.
Возможно именно из-за этого, многие, начиная делать подсветку кода или разбор строк в RichEdit — вскоре забрасывают это занятие и смотрят на различные сторонние компоненты…
С другой стороны, нам, разобрав то, как происходит адресация символов в RichEdit, было бы глупо не перевести эти знания в практическую плоскость!
Буквально за 5 минут я написал две функции которые преобразуют SelStart в CharPos и обратно:
// Определим номер символа в (.Text) по позиции курсора (SelStart)
function SelStartToCPOS(worktext:string; SelStart:integer):integer;
var
i:integer;
begin
result:=1;
i:=SelStart;
while i>0 do
begin
if (worktext[result]>=' ') or (worktext[result]=#$09) then result:=result+1
else result:=result+2;
i:=i-1;
end;
end;
// Определим позицию символа (SelStart) по номеру символа в строке (.Text)
function CPOSToSelStart(worktext:string; cpos:integer):integer;
var
i:integer;
begin
result:=0;
i:=1;
while i<>cpos do
begin
if (i<length(worktext)) and ((worktext[i]<>#$09) and (worktext[i]<' ')) then i:=i+1;
i:=i+1;
result:=result+1;
end;
end;
Теперь, при помощи этих двух процедур, вы сможете узнать на каком символе в тексте редактора RichEdit стоит курсор:
with RichEdit do
Ch:= Text [ SelStartToCPOS (Text, SelStart) ];
И наоборот, зная позицию символа в тексте RichEdit.Text сможете произвести изменение его атрибутов:
// в переменной cpos - индекс символа в строке редактора
with RichEdit do
begin
SelStart := CPOSToSelStart(Text, cpos);
SelLength := 1;
SelAttributes.Color:=clBlue;
end;
PostScriptum's:
- Процедуры которые я привел — несомненно нуждаются в оптимизации, но я уверен — с этим вы справитесь :-)
- Если вы считаете, что лучше использовать другой компонент вместо RichEdit — то я не буду с вами спорить, я всего лишь люблю разобраться до конца в том, что работает не так, как я ожидаю
- Программист на Delphi для написания редактора еще ищется, пока, к сожалению, я ушел недалеко, в настоящий момент сделано: распознавание меток к занесением их в общий список (с контролем повторности объявления), распознавание комментариев (есть один известный баг), распознавание имени директив (на очереди контроль параметров). Так что если вы чувствуете в себе силы и желание — присоединяйтесь! (мой емайл прежний: gorbukov @ тот_кто_знает_все точка ру, ну или пишите здесь в личку)
Автор: VitGo