- PVSM.RU - https://www.pvsm.ru -

Delphi: Простая задачка RichEdit которую нужно решить до того как вы решите его использовать

Потихоньку начал писать собственный редактор для программирование ARM на языке ассемблера [1], и решил начать с самого простого: сделать разбор текста при редактировании.
И тут я нашел небольшие такие грабельки :-)

Итак вопрос:
Есть редактор RichEdit в который мы ввели текст:
Delphi: Простая задачка RichEdit которую нужно решить до того как вы решите его использовать - 1
Курсор стоит вначале строки перед "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.
Ниже описание данное этим символам в Википедии [2]:

Возврат каретки (англ. carriage return, CR) — управляющий символ ASCII (0x0D, 1310, 'r'), при выводе которого курсор перемещается к левому краю поля, не переходя на другую строку. Этот управляющий символ вводится клавишей «Enter». Будучи записан в файле, в отдельности рассматривается как перевод строки только в системах Macintosh.

Подача строки (от англ. line feed, LF — «подача [бумаги] на строку») — управляющий символ ASCII (0x0A, 10 в десятичной системе счисления, 'n'), при выводе которого курсор перемещается на следующую строку. В случае принтера это означает сдвиг бумаги вверх, в случае дисплея — сдвиг курсора вниз, если ещё осталось место, и прокрутку текста вверх, если курсор находился на нижней строке. Возвращается ли при этом курсор к левому краю или нет, зависит от реализации.

Так вот SelStart считает эти два байта (возврат каретки — 0х0D, перевод строки — 0x0A ) — одним символом!

Таким образом, символ "4" в нашем редакторе будет иметь номер SelStart = 5!
Delphi: Простая задачка RichEdit которую нужно решить до того как вы решите его использовать - 2

А вот в свойстве RichEdit.Text у него будет номер 7!!!

Для того чтобы все стало совсем понятным я нарисовал табличку (первый столбец — индекс, второй SelStart — соответствие индекса SelStart символу, третий CharPos — соответствие Text[индекс] символу):
Delphi: Простая задачка RichEdit которую нужно решить до того как вы решите его использовать - 3

Такие вот особенности адресации символов у редактора 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:

  1. Процедуры которые я привел — несомненно нуждаются в оптимизации, но я уверен — с этим вы справитесь :-)
  2. Если вы считаете, что лучше использовать другой компонент вместо RichEdit — то я не буду с вами спорить, я всего лишь люблю разобраться до конца в том, что работает не так, как я ожидаю
  3. Программист на Delphi для написания редактора еще ищется, пока, к сожалению, я ушел недалеко [3], в настоящий момент сделано: распознавание меток к занесением их в общий список (с контролем повторности объявления), распознавание комментариев (есть один известный баг), распознавание имени директив (на очереди контроль параметров). Так что если вы чувствуете в себе силы и желание — присоединяйтесь! (мой емайл прежний: gorbukov @ тот_кто_знает_все точка ру, ну или пишите здесь в личку)

Автор: VitGo

Источник [4]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/delphi/112283

Ссылки в тексте:

[1] программирование ARM на языке ассемблера: https://habrahabr.ru/post/276121/

[2] Википедии: https://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%D0%B4_%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8

[3] недалеко: https://yadi.sk/d/5cKk9y7por9qM

[4] Источник: https://habrahabr.ru/post/277223/