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

от автора

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

Итак вопрос:
Есть редактор RichEdit в который мы ввели текст:

Курсор стоит вначале строки перед "9", RichText.SelStart := 12

Как прочить символ на котором стоит курсор?

Если ваш опыт подсказывает конструкцию наподобие:

   with RichEdit do         textChar:=Text [SelStart];  

— то ваш опыт не верен!

И если вам интересно — то правильный ответ можно увидеть под катом…


Итак обещанный правильный ответ:
для того чтобы прочитать символ на котором стоит курсор — нужно читать символ в строке с номером 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:

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

ссылка на оригинал статьи https://habrahabr.ru/post/277223/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *