Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > JavaScript: Общие вопросы > 4 пробела для Python-а (продолжение) +баги Opera8+


Автор: 12345c 2.12.2006, 20:55
Заведённая довольно давно http://forum.vingrad.ru/topic-113416.html ("вставка/удаление 4-х пробелов при нажатии "Ctrl+]"/"Ctrl+[" " 
) по вставке пробелов-отступов для программ богата на раскрывание особенностей работы с браузерами в textarea. Она такой ожидалась. Уже тема "сборной солянки" (BB-коды, текст над курсором) говорила об этом. И здесь, доработка скрипта, основные усилия в котором легли на обход багов IE, показала незначительные, но баги (один) в Опере 8.01. Конкретно - ну никак не хотела она ставить selectionEnd на место selectionStart, несмотря на то, что обратное работало. Пришлось даже применить TextRange, чтобы превратить выделение в курсор.

Потом, у Оперы есть свойство, что не может установить selectionEnd  со значением, меньшим selectionStart. Если такое происходит, смена выделения самостоятельно отменяется сразу после выполнения скрипта (но не во время выполнения).

Итак, скрипт, который http://forum.vingrad.ru/topic-115734.html удалось заставить корректно работать в IE, дополнен до поддержки остальных браузеров. Алгоритм гибридизирован, выполняется одной веткой.  Включает в себя функцию lenTR(,) корректного определения длины выделения в IE и комментарии алгоритма.

Код

lenTR=function(oTR,oTa){var ltr; //длина объекта TextRange oTR в области Textarea oTa (IE only)
//Правильно обрабатывает 2 особенности (бага) работы IE
//  - с последними переносами строк в выделении текста и 
//  с выделением последних переносов в конце области textarea
  var tR=document.body.createTextRange();  tR.moveToElementText(oTa);
  var tR1=tR.duplicate();  tR1.setEndPoint('StartToEnd',oTR);
  var tR2=tR.duplicate();  tR2.setEndPoint('StartToStart',oTR);
  var lenSel=tR2.text.length-tR1.text.length;
  if(tR1.text.length>0||oTa.value.length<=tR.text.length)return lenSel;
  else{
    var lenCorr=0;
    var lenCorr2=0;
    var tR3=oTR.duplicate();
    var i,i0;  i=i0=tR3.text.length;
    while(i>=i0&&tR1.text.length==0){
      if(i==0){tR3.moveStart('character',-1);lenCorr--;lenCorr2=1;}
      tR3.moveEnd('character',-1);
      i=tR3.text.length;
      tR1.setEndPoint('StartToEnd',tR3);
      lenCorr++; //чис. переносов строки в конце textarea
  };return lenSel+(lenCorr+lenCorr2-1)*2;}
}

TextAreaSelectionHelper=function(){;} //"класс" приспособлен для демонстрации, поэтому он пустой

TextAreaSelectionHelper.prototype.move4p=function(i){
//====Вставка/удаление 4 пробелов в начале каждой строки выделенной области поля ввода====
//Текст поля ввода разделяется на 3 области:
//1) от начала поля ввода до символа после переноса строки на строке,
//  где начинается выделение или стоит курсор (помещается в строку "а");
//2) выделение (строка "b");
//3) после выделения (строка "с").
//Но при этом концом выделения считается:
//  a) если выделение захватило последний перенос строки,
//    он не включается в выделение;
//  б) если при удалении пробелов (сдвиг текста влево) конец выделения
//    или курсор находится среди начальных пробелов строки, он устанавливается
//    в такое место, чтобы после удаления оказался в начале строки (начальных
//    пробелов может быть сколько угодно, но удаляются до 4 
//    (до 1-го непробельного символа или 4)).
//Далее, в области "b" добавляется по 4 пробела в начале её и после каждого
//  переноса строки или удаляется до 4 пробелов в начале и после каждого переноса строки.
//Используется функция lenTR(oTR,oTa), корректно определяющая длину области TextRange в textarea.
  var d=document;
  var isIE=d.all&&!self.opera;
  if(isIE)this.iesel=d.selection.createRange(); // ***строка для отладки, в рабочем коде находится в другом месте***
  if(isIE&&this.iesel&&this.iesel.parentElement()==this.target    ||this.target.selectionStart>=0&&this.target.selectionEnd>=this.target.selectionStart){
    var RN=(d.all?'\r':'')+'\n';
    if(isIE){var tR1=this.iesel.duplicate();
      tR1.moveToElementText(this.target);
    tR1.setEndPoint('EndToStart',this.iesel);}
    this.ieStart=isIE?lenTR(tR1,this.target):this.target.selectionStart;
    this.ieEnd=isIE?this.ieStart+lenTR(this.iesel,this.target):this.target.selectionEnd;
  var ttv=this.target.value;
    var posLastStr=ttv.substr(0,this.ieStart).lastIndexOf(RN); //алгоритмическая часть
    if(posLastStr<0)posLastStr++;else posLastStr+=RN.length;
    var wasEndRn=0; //был ли конец выделения в конце строки
    if(ttv.charAt(this.ieEnd-RN.length)==RN.charAt(0)&&this.ieStart<this.ieEnd){this.ieEnd-=RN.length;wasEndRn=1;}
    var j=this.ieEnd; //Для удаления пробелов в последней строке - поиск группы, которая будет удалена
    var k=0;
    while(i==0&&ttv.charAt(j)==' '){
      j--;
      if(j==-1||ttv.charAt(j)=='\n'){
        for(k=1;k<=4;k++)if(ttv.charAt(j+k)!=' ')break;
        j+=k;
        break;
    }};if(k>0)this.ieEnd=j;
    var a=ttv.substring(0,posLastStr);
    var b=ttv.substring(posLastStr,this.ieEnd);
    var c=ttv.substr(this.ieEnd);
    var a1,s2,a2;
    var RNg=new RegExp(RN,'g');
    var nRn1=(a1=ttv.substring(0,this.ieStart).match(RNg))?a1.length:0; //для коррекции длины символов переносов в IE
    var nRn2=(a1=b.match(RNg))?a1.length:0;
    var nnp=b.match(/^ {0,4}/)[0].length; //число начальных пробелов
    this.target.value=a+(s2=(i?"    "+b.replace(RNg,RN+"    ")
        :b.replace(new RegExp(RN+'( {1,4}|\t)','g'),RN).replace(/^ {1,4}/,"")))+c;
    a1=this.ieStart+(i?(ttv.substring(posLastStr,this.ieStart+1).replace(/ /g,"")!=""?4:0)
      :-Math.min(Math.max(this.ieStart-posLastStr,0),nnp));
    a2=this.ieEnd+(i?4*nRn2+4:-(b.length-s2.length))+wasEndRn;
    if(isIE){ //установка нового выделения. Сохранить прежнее не представляется возможным по причине багов
      //  в некоторых случаях, а для установки нового нужна небольшая символическая задержка.
      setTimeout("var t=document.getElementById('Post_area').createTextRange();t.collapse();t.moveEnd('character',"
      +(a2-nRn1-nRn2)+");t.moveStart('character',"+(a1-nRn1)+");t.select();",1);
    }else{  this.start=this.target.selectionStart=a1;
      if(this.ieStart==this.ieEnd&&i==0&&self.opera){ //обход бага Оперы с установкой курсора
        var t=document.selection.createRange();t.collapse();t.select();}
      else this.end=this.target.selectionEnd=a2;   
    }
}
if(!isIE){
if(this.scroll&&this.scroll>=0)this.target.scrollTop=this.scroll;
this.target.focus();}
}

Демонстрирующий код (так как он написан к функции, в нём есть избыточность вызовов по событиям) :
Код

<script>d=document //пример для демонстрации вставки-удаления 4 пробелов в начале строк
f1=new TextAreaSelectionHelper();
onload=function(){f1.target=d.getElementById('Post_area');}
</script>
<input type=button value="Ctrl+[" onmousedown=if(d.all)f1.move4p(1) onmouseup=if(!d.all)f1.move4p(1)> <input type=button value="Ctrl+]" onmousedown=if(d.all)f1.move4p(0) onmouseup=if(!d.all)f1.move4p(0)> <br>
<!-- 4 вызова по событиям - для приспособления демонстрационного примера к функции move4p()-->
<textarea cols=70 rows=20 id=Post_area>fdsF
  dsf
  
  f
  sf
f

f
dsf
ds
fd
sg
f
dg

ffff пример заполненного поля




g
f
g
fd
g
f
g
f
h
gfh
gf
h

</textarea>
Для тестирования имеют значение выделения текста с переносами строк в конце. Обычно баг IE не позволяет их обрабатывать. Выделите текст так, чтобы в конце выделения были переносы строк и проверяйте. В других случаях тестирования, я, например, нашёл 2 алгоритмических недочёта smile. Но если никто их не найдёт, значит, всех устроит.

Если всё устроит, сделанную функцию можно внедрять в форум. Пример внедрённой функции лежит в архиве http://js2.ru/files/vingradScripts.rar (30К). Там же, кстати, и приведённый выше код в 1 файле.

Длины функций кода.Как видно, для реализации корректной работы надо необычно много кода. Кроме того, что сама алгоритмическая часть занимает 50-60% функции move4p, есть ещё функция специально для исправления багов IE, других способов обхода которого практически нет. Суммарная длина кода без комментариев и демо-примера - 2700 байт.

Проверялся только под Win (IE6, 7, FF1.07, 2, Opera8.01), поэтому настоятельная просьба проверить под Юниксом и в Сафари - скорее всего, придётся что-то дорабатывать (для Сафари).

Дополнение по теме багов: По чудесам выделения текстов в IE есть такой пример:http://js2.ru/example/JsPrimeryCode-97.htm - "IE-ошибки определения длины областей TextRange в Textarea".

Дополнение по клавишному управлению на Винграде: посмотрел работу скриптов клавишного управления на Винграде и в примере, который я из него вырезал  - оказывается, кнопочное управление для IE на нём работает очень плохо - в чистом IE6 срабатывают немногие кнопки. Должны работать все, которые описаны в HotKeyHandler.keys и HotKeyHandler.alt_keys (ToolMenuData.js), но работают в чистом IE6 только Ctrl-U, Q, остальные, видимо, перебиваются браузерными функциями. В остальных браузерах клавиши работают нормально, в том числе и внедрённые мной коды Ctrl+[, Ctrl+] .

Автор: Sardar 4.12.2006, 01:36
Цитата(12345c @  2.12.2006,  19:55 Найти цитируемый пост)
посмотрел работу скриптов клавишного управления на Винграде и в примере...

Ух-ты... Это я писал и проверял под всеми популярными браузерами (FireFox1.4+ , IE5.5+, Opera6+, Konqueror), работало всё корректно. Не надо думаю объяснять, что тестя все четыре браузера автоматом тестим все подобные на таких же движках (Seamonkey, MyIE, Safari etc). Покопаюсь в последних переделках...

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

P.S. млин не читабельно пишешь...

Автор: 12345c 4.12.2006, 02:12
Цитата(Sardar @  4.12.2006,  01:36 Найти цитируемый пост)
Не надо думаю объяснять, что тестя все четыре браузера автоматом тестим все подобные на таких же движках (Seamonkey, MyIE, Safari etc). 
Насчёт оболочек - не совсем так. В MyIE и других добавляются новые клавишные функции, которые могут перебивать события чистой оболочки. Haпример, Сtrl+I в Макстоне - открывание боковой панели (Explorer Bar). В IE это Favorities -панель, но неизвестно, кто перехватывает раньше.

Править TextAreaSelectionHelper.prototype.setSelectedText для всех остальных функций - это заново переписать, добавив примерно 1-1.5 К кода. Тут стоит вопрос, что 2.5 К на "4 пробела" много... Скажут админы: "Поставим" - сделаю по аналогии, мне будет проще, чем тебе вдаваться во все детали.

Добавлено @ 02:14 
Что код готов, должны сказать тестеры. Мне - так 2 момента не нравится в алгоритме, и в Сафари никто не проверял и там будут ошибки (если переносы - это \r).

Автор: 12345c 11.1.2007, 14:43


Тем временем обнаружил код с недокументированной функцией вставки-убирания табов в начале каждой строки выделенной области - http://www.artlebedev.ru/tools/technogrette/js/allow_tab_key/ . Если выделить область начала строки, одну или несколько, то нажатие Tab приведёт к вставке табов, а нажатие Shift-Tab - к удалению табов (по одному) в начало / из начала каждой строки. (Захват пустых переносов в конце выделения, как обычно, крупно пролетает в IE, так что значимости написанного кода не умаляет, но интересен принцип реализации - достаточно простая функция вставки-удаления. Конечно, тут не надо ловить пробелы, не кратные 4, что упрощает выполнение, но и нет заморочек с принудительным выделением строки, если нажато "Ctrl-[" .)

Автор: Sardar 11.1.2007, 22:29


У нас задача была несколько иная, с любой позиции в строке сдвинуть строку, что и изменило/усложнило реализацию smile

Автор: Girder 13.1.2007, 12:33


Цитата(Sardar @  4.12.2006,  01:36 Найти цитируемый пост)
Ух-ты... Это я писал и проверял под всеми популярными браузерами (FireFox1.4+ , IE5.5+, Opera6+, Konqueror), работало всё корректно.
Естественно - но только когда было повешенно на Body smile , а по просьбе трудящийся теперь только на тектовом поле.

Автор: pythonwin 15.1.2007, 07:42
12345c, Sardar, Girder, спасибо, что работаете над проблемой и делаете наш форум лучше smile

Автор: V.A.KeRneL 3.3.2007, 15:37
По http://forum.vingrad.ru/topic-121716.html#entry1038555 pythonwin тоже говорю здесь всем спасибо! smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)