Приходилось встречаться с мнением, что студия Caché не вполне совершенна, в частности тем, что ее невозможно расширить. Тем не менее, это не совсем так. Существует штатный механизм шаблонов и расширений, позволяющий достраивать студию интерактивными дополнениями.
В этой статье мы коснемся еще одного способа расширения, который может помочь более эффективно управлять создаваемым кодом. В этом поможет класс интеграции студии с системой контроля версий.
Что можно сделать с помощью класса контроля версий? Во-первых, отслеживать некоторые события, происходящие в студии, такие как подключение, создание, удаление, открытие документов, а во-вторых, возможность добавлять пункты в меню, в том числе в контекстное. Через эти пункты меню можно не только выполнять код на сервере, но и создавать некоторые простые диалоги.
Попробуем сделать расширение меню студии для того, чтобы уменьшить/увеличить отступ, комментировать/раскомментировать блок кода (эти возможности в последних версиях Caché есть, доступны по сочетаниям клавиш), а также добавим возможность переформатирования отступов и скобок, которого часто не хватает при редактировании больших программ.
Создание класса контроля версий
Создадим класс Util.StudioExtension как наследника %Studio.Extension.Base и пропишем его в конфигурации сервера для области, в которой собираемся использовать это расширение:
Администрирование системы > Конфигурация > Дополнительные настройки -> Система контроля версий
Class Util.StudioExtension Extends %Studio.Extension.Base
{
XData Menu
{
<MenuBase>
<Menu Name="Format" Type="1">
<MenuItem Name="PlusTab"/>
<MenuItem Name="MinusTab"/>
<MenuItem Separator="1" />
<MenuItem Name="ReformatSelection"/>
<MenuItem Name="ReformatDocument" Save="100"/>
<MenuItem Separator="1" />
<MenuItem Name="CommentBlock"/>
<MenuItem Name="UnCommentBlock"/>
</Menu>
</MenuBase>
}
}
В этом блоке теги MenuBase, Menu, MenuItem являются отображением классов %Studio.Extension.Base, %Studio.Extension.Menu, %Studio.Extension.MenuItem, смысл атрибутов можно понять по их документации. В нашем случае Name — внутреннее имя пункта, Type=1 означает, что меню контекстное, Separator=1, что пункт является разделителем, Save=100, что при нажатии пункта меню документ должен сохраниться.
Создадим метод, обрабатывающий отображение самого меню. Добавим читаемые русские названия пунктам меню, в случаях, когда считаем нужным, делаем их неактивными (Enabled=0). Например, нет смысла делать активным пункт “переформатировать выделенный текст”, если ничего не выделено.
ByRef DisplayName As %String) As %Status
{
set:MenuName="Format" DisplayName="Формат"
set:MenuName="Format,PlusTab" DisplayName="->| увеличить отступ"
set:MenuName="Format,MinusTab" DisplayName="|<- уменьшить отступ"
set:MenuName="Format,ReformatDocument" DisplayName="Переформатировать документ"
set:MenuName="Format,ReformatSelection" DisplayName="Переформатировать выделенное"
set:MenuName="Format,CommentBlock" DisplayName="Комментировать блок"
set:MenuName="Format,UnCommentBlock" DisplayName="Раскомментировать блок"
set Enabled=1
set ext=$piece(InternalName,".",$length(InternalName,"."))
set:(MenuName="Format,ReformatDocument")&&(ext'="MAC")&&(ext'="CLS") Enabled=0
set:(MenuName="Format,ReformatSelection")&&(SelectedText="") Enabled=0
set:(MenuName="Format,MinusTab")&&(SelectedText="") Enabled=0
set:(MenuName="Format,UnCommentBlock")&&(SelectedText="") Enabled=0
quit 1
}
После переподключения студией должен появиться новый пункт контекстного меню, но он пока еще ничего не делает:
Класс с для работы с отступами
Создадим метод UserAction, который будет реагировать на различные действия, в том числе и на выбор пунктов меню:
Type = 0 — нажат пункт меню,
При этом InternalName — имя пункта меню, при этом
InternalName — имя документа,
SelectedText — выделенные текст (если он выделен)
Type = 1 — событие создано автоматом, при этом
Name = 0 — пользователь изменил документ
Name = 1 — создан и сохранен новый документ
Name = 2 — пользователь удалил документ
Name = 3 — пользователь открыл документ
Name = 4 — пользователь закрыл документ
Name = 5 — пользователь подключился
InternalName — имя документа, с которым произошло событие
Action используется для указания действия студии
0 — Не делать ничего
1 — Показать стандартный диалог да/нет/отмена. Текст диалога указывается в переменной Target.
2 — Запустить csp страницу. Target указывает имя страницы
3 — Запустить EXE — файл на клиенте. Target указывает путь к файлу
4 — Вставить текст, содержащийся в переменной Target в текущую позицию в документе
5 — Открыть документы, содержащийся в переменной Target. Список документов разделяется запятой. Если имя документа содержит 'test.mac:label+10', то откроется документ test.mac в позиции 'label+10'.
6 — Отобразить окно с сообщением, содаржащейся в переменной Target
7 — Отобразит диалог с вариантами Yes/No/Cancel. Текст диалога содержится в переменной Target. Тест сообщения передается в переменной Msg.
Если агрумент Reload равен истине, текущий документ будет перезагружен.
*/
Method UserAction(Type As %Integer, Name As %String, InternalName As %String, SelectedText As %String,
ByRef Action As %String, ByRef Target As %String, ByRef Msg As %String, ByRef Reload As %Boolean) As %Status
{
set ext=$zconvert($piece(InternalName,".",$length(InternalName,".")),"U")
if Name="Format,PlusTab"
{
set Action=4 ; вставка текста
set Target=##class(Util.FormatTab).PlusTab(SelectedText,ext="CSP")
}
if Name="Format,MinusTab"
{
set Action=4 ; вставка текста
set Target=##class(Util.FormatTab).MinusTab(SelectedText,ext="CSP")
}
quit 1
}
Сам класс, который мы используем для форматирования табуляций:
Class Util.FormatTab
{
/// Добавить табуляции в начало всех строк, которые начинаются
/// с пробела или табуляции.
/// Для текста с пометкой
ClassMethod PlusTab(text As %String, inCsp As %Boolean = 0) As %String
{
set cr=$char(13,10) ; символы перевода строки
set tab=$char(9) ; символы табуляции
set stringsIn=$listfromstring(text,cr)
set stringsOut=""
for i=1:1:$listlength(stringsIn)
{
set line=$listget(stringsIn,i)
set firstSymbol=$extract(line,1,1)
if (inCsp=1)||(firstSymbol=" ")||(firstSymbol=tab)
{
for pos=1:1:$length(line) ; ищем позицию первого непробельного символа
{
set symbol=$extract(line,pos,pos)
quit:(symbol'=" ")&&(symbol'=tab)
}
set line=$extract(line,1,pos-1)_tab_$extract(line,pos,*)
}
set stringsOut=stringsOut_$listbuild(line)
}
quit $listtostring(stringsOut,cr)
}
/// Удалить табуляции в начале строки
/// text - текст, который обрабатываем
/// inCsp - признак, что работаем с csp страницей,
/// при этом не пытаемся принудительно оставить пробел в начале строки
ClassMethod MinusTab(text As %String, inCsp As %Boolean = 0)
{
set cr=$char(13,10) ; символы перевода строки
set tab=$char(9) ; символы табуляции
set tabSize=4
set stringsIn=$listfromstring(text,cr)
set stringsOut=""
for i=1:1:$listlength(stringsIn)
{
set line=$listget(stringsIn,i)
set firstSymbol=$extract(line,1,1)
for j=1:1:tabSize
{
set symbol=$extract(line,1,1)
if symbol=" " ; убираем первые 4 пробела
{
set line=$extract(line,2,*)
}
elseif symbol=tab ; табуляцию убираем, и на этом заканчиваем
{
set line=$extract(line,2,*)
quit
}
else ; не пробел и не табуляция, заканчивам удалять пробелы
{
quit
}
}
; а вдруг удалили все пробелы, и строка стала ошибочной с точки зрения синтаксиса
; это точно произойдет, если это не csp документ
if (inCsp=0) &&((firstSymbol=" ")||(firstSymbol=tab))&&($extract(line,1,1)'=" ") &&($extract(line,1,1)'=tab)
{
set line=" "_line
}
set stringsOut=stringsOut_$listbuild(line)
}
quit $listtostring(stringsOut,cr)
}
}
Теперь, если переподключиться студией (это всегда нужно делать после изменения класса контроля версий), пункты меню “добавить отступ” и “убрать отступ” должны заработать.
Класс для комментирования/раскомментирования кода
Модифицируем метод UserAction класса Util.StudioExtension, добавив обработку новых пунктов:
ByRef Action As %String, ByRef Target As %String, ByRef Msg As %String, ByRef Reload As %Boolean) As %Status
{
set ext=$zconvert($piece(InternalName,".",$length(InternalName,".")),"U")
set docname=$extract(InternalName,1,*-4)
if Name="Format,PlusTab"
{
set Action=4 ; вставка текста
set Target=##class(Util.FormatTab).PlusTab(SelectedText,ext="CSP")
}
if Name="Format,MinusTab"
{
set Action=4 ; вставка текста
set Target=##class(Util.FormatTab).MinusTab(SelectedText,ext="CSP")
}
if Name="Format,CommentBlock"
{
set Action=4
s Target=##class(Util.FormatComment).AddComment(SelectedText)
}
if Name="Format,UnCommentBlock"
{
set Action=4
s Target=##class(Util.FormatComment).RemoveComment(SelectedText)
}
quit 1
}
Используем класс Util.FormatComment для обработки строк:
Class Util.FormatComment
{
/// Добавить комментарий к началу каждой строки
/// с пробела или табуляции.
/// Для текста с пометкой
ClassMethod AddComment(text As %String) As %String
{
set cr=$char(13,10) ; символы перевода строки
set stringsIn=$listfromstring(text,cr)
set stringsOut=""
set stringsCount=$listlength(stringsIn)
for i=1:1:$listlength(stringsIn)
{
set line=$listget(stringsIn,i)
s:(i<stringsCount)||(line'="") line="//"_line ; при копировании блока текста часто попадает лишний перевод строки, его комментировать не надо
set stringsOut=stringsOut_$listbuild(line)
}
w !,!,$listtostring(stringsOut,cr)
quit $listtostring(stringsOut,cr)
}
/// Удалить комментарии с начала строки
ClassMethod RemoveComment(text As %String)
{
set cr=$char(13,10) ; символ перевода строки
set tab=$char(9) ; символ табуляции
set stringsIn=$listfromstring(text,cr)
set stringsOut=""
for i=1:1:$listlength(stringsIn)
{
set line=$listget(stringsIn,i)
; удалим ведущие пробелы и табуляции для определения того, есть ли в этой строке комментарий
set line0=$translate(line," "_tab,"")
if $extract(line0,1,1)=";"
{
set pos=$find(line,";")
set line=$extract(line,1,pos-2)_$extract(line,pos,*)
}
elseif $extract(line0,1,3)="///"
{
set pos=$find(line,"///")
set line=$extract(line,1,pos-4)_$extract(line,pos,*)
}
elseif $extract(line0,1,2)="//"
{
set pos=$find(line,"//")
set line=$extract(line,1,pos-3)_$extract(line,pos,*)
}
set stringsOut=stringsOut_$listbuild(line)
}
quit $listtostring(stringsOut,cr)
}
/// Возвращает информацию о том, закомментирована ли строка.
/// понимает только однострочные комментарии
ClassMethod CheckForComment(text As %String) As %Boolean
{
set cr=$char(13,10) ; символ перевода строки
set tab=$char(9) ; символ табуляции
set stringsIn=$listfromstring(text,cr)
set stringsOut=""
for i=1:1:$listlength(stringsIn)
{
set line=$listget(stringsIn,i)
set line=$translate(line," "_tab,"")
return:$e(line,1,2)="//" 1
return:$e(line,1,1)=";" 1
}
quit 0
}
}
Форматирование текста
Подключим класс для переформатирования кода. Может быть полезно, если ваш проект писался разными командами или программистами, которые не придерживались единого стиля. Код типа
for i = TheNum:1:TheNum+2 {
set StartTime = 12 * 3600 + (300 * $R(6))
while StartTime < (3600 * 22) {
set show=##class(Cinema.Show).%New()
set show.Film=TheFilm
set show.Theater=the(i)
set show.StartTime=StartTime
do show.%Save()
set StartTime = StartTime+(TheFilm.Length * 60) + 1200 300 * 300
}
}
можно превратить в
for i = TheNum:1:TheNum+2
{
set StartTime = 12 * 3600 + (300 * $R(6))
while StartTime < (3600 * 22)
{
set show=##class(Cinema.Show).%New()
set show.Film=TheFilm
set show.Theater=the(i)
set show.StartTime=StartTime
do show.%Save()
set StartTime = StartTime+(TheFilm.Length * 60) + 1200 300 * 300
}
}
или наоборот, как кому нравится. Класс для стиля “скобка над скобкой” — Util.CodeFormat, класс для стиля “скобка в конце строки” — Util.CodeFormat3
Модифицируем метод UserAction, добавив обработку новых пунктов меню:
ByRef Target As %String, ByRef Msg As %String, ByRef Reload As %Boolean) As %Status
{
set ext=$zconvert($piece(InternalName,".",$length(InternalName,".")),"U")
set docname=$extract(InternalName,1,*-4)
if Name="Format,PlusTab"
{
set Action=4 ; вставка текста
set Target=##class(Util.FormatTab).PlusTab(SelectedText,ext="CSP")
}
if Name="Format,MinusTab"
{
set Action=4 ; вставка текста
set Target=##class(Util.FormatTab).MinusTab(SelectedText,ext="CSP")
}
if Name="Format,CommentBlock"
{
set Action=4
set Target=##class(Util.FormatComment).AddComment(SelectedText)
}
if Name="Format,UnCommentBlock"
{
set Action=4
set Target=##class(Util.FormatComment).RemoveComment(SelectedText)
}
if Name="Format,ReformatSelection"
{
set Action=4
set Target=##class(Util.CodeFormat).FormatString(SelectedText)
}
if Name="Format,ReformatDocument"
{
set Action=0
do:ext="MAC" ##class(Util.CodeFormat).FormatMAC(docname)
do:ext="CLS" ##class(Util.CodeFormat).FormatClass(docname)
}
quit 1
}
Класс обработчик кода в стиль «скобка над скобкой»
Class Util.CodeFormat Extends %RegisteredObject
{
/// Имя глобала входного буфера
Property Buffer1 As %String;
/// Имя глобала выходного буфера
Property Buffer2 As %String;
/// Текущая строка входного буфера
Property CurrentLine1 As %Numeric [ InitialExpression = 1 ];
/// Текущая строка выходного буфера
Property CurrentLine2 As %Numeric [ InitialExpression = 1 ];
/// Текущая позиция в строке входного буфера
Property CurrentPos1 As %Numeric [ InitialExpression = 1 ];
/// Уровень фигурных скобок
Property LevelFigureBrace As %Integer [ InitialExpression = 0 ];
/// Уровень круглых скобок
Property LevelRoundBrace As %Integer [ InitialExpression = 0 ];
/// Есть ли код на строке? <br>
/// Сочетания:
/// LineHasCode=0 && SkipSpace=1 -> начало строки, ожидаем метку или код
/// LineHasCode=0 && SkipSpace=0 -> не должно такого быть
/// LineHasCode=1 && SkipSpace=0 -> уже есть код, просто пишем все как есть
/// LineHasCode=1 && SkipSpace=1 -> уже есть код, но пробелы пропускаем, например, после {
Property LineHasCode As %Boolean;
/// Пропускать пробелы
Property SkipSpace As %Boolean [ InitialExpression = 1 ];
/// Пропускать переводы строк
/// 1 - пропустить один
/// -1 - перевод только что пропущен, нужно быть внимательным к меткам и прочему
Property SkipCR As %Boolean [ InitialExpression = 0 ];
/// Нужен отступ (при появлении кода)
Property NeedIndent As %Boolean;
/// В однострочном комментарии?
Property InSingleLineComment As %Boolean [ InitialExpression = 0 ];
/// В многострочном комментарии?
Property InMultiLineComment As %Boolean [ InitialExpression = 0 ];
/// Обработать перевод строки
Method ProcessCR(text As %String)
{
if ..SkipCR=1
{
set ..SkipCR=-1
quit 1
}
do:..NeedIndent ..Write(..MakeIndent())
set ..LineHasCode=0
set ..SkipSpace=1
set ..NeedIndent=0
set ..InSingleLineComment=0
do ..WriteCR()
quit 1
}
/// Обработать пробельные символы
Method ProcessSpace(text As %String)
{
quit:(..InMultiLineComment) ..ProcessCommonCode(text)
; был подавлен перевод строки, нормальный символ за ним - пробельный.
set:..SkipCR=-1 ..SkipCR=0,..NeedIndent=0,..SkipSpace=1
do:'..SkipSpace ..Write(text)
set:'..LineHasCode ..NeedIndent=1
quit 1
}
/// Обработать открытие фигурной скобки
Method ProcessFigureBraceOpen(text As %String)
{
quit:(..LevelRoundBrace) ..ProcessCommonCode(text)
quit:(..InMultiLineComment) ..ProcessCommonCode(text)
quit:(..InSingleLineComment) ..ProcessCommonCode(text)
do:..LineHasCode ..WriteCR()
do ..Write(..MakeIndent()_text)
do ..WriteCR()
set ..LevelFigureBrace=..LevelFigureBrace+1
set ..LineHasCode=0,..SkipSpace=1,..NeedIndent=1,..SkipCR=1
quit 1
}
/// Обработать закрытие фигурной скобки
Method ProcessFigureBraceClose(text As %String)
{
quit:(..LevelRoundBrace) ..ProcessCommonCode(text)
quit:(..InMultiLineComment) ..ProcessCommonCode(text)
quit:(..InSingleLineComment) ..ProcessCommonCode(text)
quit:(..LevelFigureBrace=0) ..ProcessCommonCode(text)
do:..LineHasCode ..WriteCR()
set ..LevelFigureBrace=..LevelFigureBrace-1
do ..Write(..MakeIndent()_text)
do ..WriteCR()
set ..SkipCR=1,..NeedIndent=1,..SkipSpace=1,..LineHasCode=0
quit 1
}
/// Обработать открытие круглой скобки
Method ProcessRoundBraceOpen(text As %String)
{
set:(..InMultiLineComment=0)&&(..InSingleLineComment=0) ..LevelRoundBrace=..LevelRoundBrace+1
do ..Write(text)
quit 1
}
/// Обработать закрытие круглой скобки
Method ProcessRoundBraceClose(text As %String)
{
set:(..InMultiLineComment=0)&&(..InSingleLineComment=0)&&(..LevelRoundBrace>0) ..LevelRoundBrace=..LevelRoundBrace-1
do ..Write(text)
quit 1
}
/// Обработать открытие многострочного комментария
Method ProcessMultilineCommentOpen(text As %String)
{
set ..InMultiLineComment=1
do ..ProcessCommonCode(text)
quit 1
}
/// Обработать закрытие многострочного комментария
Method ProcessMultilineCommentClose(text As %String)
{
set ..InMultiLineComment=0
do ..ProcessCommonCode(text)
quit 1
}
/// Однострочный комментарий
Method ProcessOnelineCommentOpen(text As %String)
{
do ..ProcessCommonCode(text)
set ..InSingleLineComment=1
quit 1
}
/// Обработать код без каких-то особенностей
Method ProcessCommonCode(text As %String)
{
// перевели строчку, а на новой строке сразу текст - это метка видимо.
// Нужно перевести все - таки строку, не смотря на то, что перевод был подавлен
if ..SkipCR=-1
{
do ..WriteCR()
set ..NeedIndent=0
}
set ..SkipCR=0
set:..NeedIndent text=..MakeIndent()_text
do ..Write(text)
set ..SkipSpace=0
set ..LineHasCode=1
set ..NeedIndent=0
quit 1
}
/// Переформатируем мак-программу
/// d ##class(Util.CodeFormat).FormatMAC("Untitled1")
ClassMethod FormatMAC(name As %String) As %Integer
{
quit:name=""
quit:'$data(^rMAC(name))
set parser=..%New()
set parser.Buffer1="^||buffer1"
set parser.Buffer2="^||buffer2"
kill @parser.Buffer1
kill @parser.Buffer2
set key=0,line=0
for //запишем в буфер данные из глобала программы
{
set key=$order(^rMAC(name,0,key)) quit:+key=0
set @parser.Buffer1@($increment(line))=^rMAC(name,0,key)
}
set braces=parser.FormatBuffer()
write !,name,":",braces
set key=0
for ; удаляем старые строки программы
{
set key=$order(^rMAC(name,0,key)) quit:+key=0
kill ^rMAC(name,0,key)
}
merge ^rMAC(name,0)=@parser.Buffer2
set ^rMAC(name,0,0)=$order(@parser.Buffer2@(""),-1)
set ^rMAC(name,0)=$horolog
}
/// Форматировать все методы класса <br>
/// d ##class(Util.CodeFormat).FormatClass("Import.NIAdvs")
ClassMethod FormatClass(class As %String)
{
set method=""
for
{
set method=$order(^oddDEF(class,"m",method)) quit:method=""
do ..FormatMethod(class,method)
}
}
/// Форматировать метод класса. Не очень красиво написано - смесь глобалов и объектов <br>
/// d ##class(Util.CodeFormat).FormatMethod("Import.NIAdvs","%OnValidateObject5")
ClassMethod FormatMethod(class As %String, method As %String)
{
set mdef=##class(%Dictionary.MethodDefinition).%OpenId(class_"||"_method)
quit:mdef=""
set gl1="^oddDEF("""_class_""",""m"","""_method_""",30)" ; тут хранится код метода
set parser=..%New()
set parser.Buffer1="^||buffer1"
set parser.Buffer2="^||buffer2"
kill @parser.Buffer1
kill @parser.Buffer2
set key=0
for //запишем в буфер данные из глобала метода
{
set key=$order(@gl1@(key)) quit:+key=0
set @parser.Buffer1@(key)=@gl1@(key)
}
set braces=parser.FormatBuffer()
write !,class,".",method,":",braces
do mdef.Implementation.Clear()
set key=0
for
{
set key=$order(@parser.Buffer2@(key)) quit:+key=0
do mdef.Implementation.WriteLine(@parser.Buffer2@(key))
}
do mdef.%Save()
}
/// Форматировать строку, которую потом возвращает
/// w ##class(Util.CodeFormat).FormatString(" if x=1 {s x=1}")
ClassMethod FormatString(str As %String) As %String
{
set parser=..%New()
set parser.Buffer1="^||buffer1"
set parser.Buffer2="^||buffer2"
kill @parser.Buffer1
kill @parser.Buffer2
set rowend=$char(13,10)
set rows=$listfromstring(str,rowend)
set key=0
for i=1:1:$listlength(rows) //запишем в буфер данные из глобала метода
{
set @parser.Buffer1@(i)=$listget(rows,i)
}
do parser.FormatBuffer()
set str=""
set key=""
for
{
set key=$order(@parser.Buffer2@(key)) quit:key=""
set str=str_$listbuild(@parser.Buffer2@(key))
}
set:$listget(rows,$listlength(rows))="" str=str_$listbuild("")
quit $listtostring(str,rowend)
}
/// Прочитать текст из входного буффера <br>
/// Возвращает список из текста и его типа <br>
/// Типы текста <br>
/// p - пробельный символ <br>
/// s - строка <br>
/// e - конец строки <br>
/// E - конец текста вообще <br>
/// { - { <br>
/// } - } <br>
/// ( - ( <br>
/// ) - ) <br>
/// ; - начало однострочного комментария <br>
/// /* - начало многострочного комментария <br>
/// */ - конец многострочного комментария
Method ReadTerm() As %List
{
set:$data(@..Buffer1@(..CurrentLine1))=0 ..CurrentLine1=..CurrentLine1+1,..CurrentPos1=1
quit:$data(@..Buffer1@(..CurrentLine1))=0 $listbuild("","E")
set str=@..Buffer1@(..CurrentLine1)
set char1=$extract(str,..CurrentPos1,..CurrentPos1)
set char2=$extract(str,..CurrentPos1+1,..CurrentPos1+1)
set:char1="" ..CurrentLine1=..CurrentLine1+1,..CurrentPos1=1
quit:char1="" $listbuild("","e")
set ..CurrentPos1=..CurrentPos1+1
quit:char1=" " $listbuild(char1,"p")
quit:char1=$char(9) $listbuild(char1,"p")
quit:char1="{" $listbuild(char1,"{")
quit:char1="}" $listbuild(char1,"}")
quit:char1="(" $listbuild(char1,"(")
quit:char1=")" $listbuild(char1,")")
quit:char1=";" $listbuild(char1,";")
if (char1="/")&&(char2="/") set ..CurrentPos1=..CurrentPos1+1 quit $listbuild(char1_char2,";")
if (char1="/")&&(char2="*") set ..CurrentPos1=..CurrentPos1+1 quit $listbuild(char1_char2,"/*")
if (char1="*")&&(char2="/") set ..CurrentPos1=..CurrentPos1+1 quit $listbuild(char1_char2,"*/")
if (char1="""")
{
set pos2=$find(str,"""",..CurrentPos1)
set text=$extract(str,..CurrentPos1-1,pos2-1)
set ..CurrentPos1=pos2
quit $listbuild(text,"s")
}
quit $listbuild(char1,"s")
}
/// То же самое ReadTerm, но по другому подаются и получаются аргументы
Method ReadTerm1(ByRef type As %String) As %String
{
set res=..ReadTerm()
set type=$listget(res,2)
quit $listget(res,1)
}
/// Вывести текст в выходной буфер
Method Write(text As %String = "")
{
set @..Buffer2@(..CurrentLine2)=$get(@..Buffer2@(..CurrentLine2))_text
}
/// перевести строку в выходном буфере
Method WriteCR()
{
set ..CurrentLine2=..CurrentLine2+1
}
/// Сделать текстовую строку с отступом
Method MakeIndent() As %String
{
set indent=$char(9)
for i=1:1:..LevelFigureBrace set indent=indent_$char(9)
for i=1:1:..LevelRoundBrace set indent=indent_" "
quit indent
}
/// Форматировать текст во внутреннем "буфере"
Method FormatBuffer()
{
set level=0
for
{
set text=..ReadTerm1(.type) quit:type="E"
do:type="e" ..ProcessCR(text)
do:type="p" ..ProcessSpace(text)
do:type=";" ..ProcessOnelineCommentOpen(text)
do:type="/*" ..ProcessMultilineCommentOpen(text)
do:type="*/" ..ProcessMultilineCommentClose(text)
do:type="(" ..ProcessRoundBraceOpen(text)
do:type=")" ..ProcessRoundBraceClose(text)
do:type="{" ..ProcessFigureBraceOpen(text)
do:type="}" ..ProcessFigureBraceClose(text)
do:type="s" ..ProcessCommonCode(text)
set:..LevelFigureBrace>level level=..LevelFigureBrace
}
quit level
}
}
Класс-обработчик кода в стиль «скобка в конце строки». В случае, если такой стиль предпочтительней, нужно заменить обращения к классу UtilCodeFormat из метода UserAction на UtilCodeFormat3.
{
/// Откатить последний перевод строки
Method ReturnCR()
{
set ..CurrentLine2=..CurrentLine2-1
}
/// Откатить последние пробелы
Method ReturnSpace()
{
set str=@..Buffer2@(..CurrentLine2)
set len=$length(str)
for
{
set lastchar=$extract(str,len,len)
quit:(lastchar'=" ")&&(lastchar'=$char(9))
set str=$extract(str,1,len-1)
set len=len-1
set @..Buffer2@(..CurrentLine2)=str
}
}
/// Обработать открытие фигурной скобки
Method ProcessFigureBraceOpen(text As %String)
{
quit:(..LevelRoundBrace) ..ProcessCommonCode(text)
quit:(..InMultiLineComment) ..ProcessCommonCode(text)
quit:(..InSingleLineComment) ..ProcessCommonCode(text)
do:'..LineHasCode ..ReturnCR()
do ..ReturnSpace()
do ..Write(" ")
do ..Write(text)
set ..LevelFigureBrace=..LevelFigureBrace+1
do ..WriteCR()
set ..LineHasCode=0,..SkipSpace=1,..NeedIndent=1,..SkipCR=1
quit 1
}
}
Класс-расширение студии Caché целиком:
{
XData Menu
{
<MenuBase>
<Menu Name="Format" Type="1">
<MenuItem Name="PlusTab"/>
<MenuItem Name="MinusTab"/>
<MenuItem Separator="1" />
<MenuItem Name="ReformatSelection"/>
<MenuItem Name="ReformatDocument" Save="100"/>
<MenuItem Separator="1" />
<MenuItem Name="CommentBlock"/>
<MenuItem Name="UnCommentBlock"/>
</Menu>
</MenuBase>
}
Method OnMenuItem(MenuName As %String, InternalName As %String, SelectedText As %String,
ByRef Enabled As %Boolean, ByRef DisplayName As %String) As %Status
{
set:MenuName="Format" DisplayName="Формат"
set:MenuName="Format,PlusTab" DisplayName="->| увеличить отступ"
set:MenuName="Format,MinusTab" DisplayName="|<- уменьшить отступ"
set:MenuName="Format,ReformatDocument" DisplayName="Переформатировать документ"
set:MenuName="Format,ReformatSelection" DisplayName="Переформатировать выделенное"
set:MenuName="Format,CommentBlock" DisplayName="Комментировать блок"
set:MenuName="Format,UnCommentBlock" DisplayName="Раскомментировать блок"
set Enabled=1
set ext=$piece(InternalName,".",$length(InternalName,"."))
set:(MenuName="Format,ReformatDocument")&&(ext'="MAC")&&(ext'="CLS") Enabled=0
set:(MenuName="Format,ReformatSelection")&&(SelectedText="") Enabled=0
set:(MenuName="Format,MinusTab")&&(SelectedText="") Enabled=0
set:(MenuName="Format,UnCommentBlock")&&(##class(Util.FormatComment).CheckForComment(SelectedText)=0) Enabled=0
quit 1
}
/*Случилось какое-то событие
Type = 0 - нажат пункт меню,
При этом InternalName - имя пункта меню, при этом
InternalName - имя документа,
SelectedText - выделенные текст (если он выделен)
Type = 1 - событие создано автоматом, при этом
Name = 0 - пользователь изменил документ
Name = 1 - создан и сохранен новый документ
Name = 2 - пользователь удалил документ
Name = 3 - пользователь открыл документ
Name = 4 - пользователь закрыл документ
Name = 5 - пользователь подключился
InternalName - имя документа, с которым произошло событие
Action используется для указания действия студии
0 - Не делать ничего
1 - Показать стандартный диалог да/нет/отмена. Текст диалога указывается в переменной Target.
2 - Запустить csp страницу. Target указывает имя страницы
3 - Запустить EXE - файл на клиенте. Target указывает путь к файлу
4 - Вставить текст, содержащийся в переменной Target в текущую позицию в документе
5 - Открыть документы, содержащийся в переменной Target. Список документов разделяется запятой. Если имя документа содержит 'test.mac:label+10', то откроется документ test.mac в позиции 'label+10'.
6 - Отобразить окно с сообщением, содаржащейся в переменной Target
7 - Отобразит диалог с вариантами Yes/No/Cancel. Текст диалога содержится в переменной Target. Тест сообщения передается в переменной Msg.
Если агрумент Reload равен истине, текущий документ будет перезагружен.
*/
Method UserAction(Type As %Integer, Name As %String, InternalName As %String, SelectedText As %String,
ByRef Action As %String, ByRef Target As %String, ByRef Msg As %String, ByRef Reload As %Boolean) As %Status
{
set ext=$zconvert($piece(InternalName,".",$length(InternalName,".")),"U")
set docname=$extract(InternalName,1,*-4)
if Name="Format,PlusTab"
{
set Action=4 ; вставка текста
set Target=##class(Util.FormatTab).PlusTab(SelectedText,ext="CSP")
}
if Name="Format,MinusTab"
{
set Action=4 ; вставка текста
set Target=##class(Util.FormatTab).MinusTab(SelectedText,ext="CSP")
}
if Name="Format,CommentBlock"
{
set Action=4
set Target=##class(Util.FormatComment).AddComment(SelectedText)
}
if Name="Format,UnCommentBlock"
{
set Action=4
set Target=##class(Util.FormatComment).RemoveComment(SelectedText)
}
if Name="Format,ReformatSelection"
{
set Action=4
set Target=##class(Util.CodeFormat).FormatString(SelectedText)
}
if Name="Format,ReformatDocument"
{
set Action=0
do:ext="MAC" ##class(Util.CodeFormat).FormatMAC(docname)
do:ext="CLS" ##class(Util.CodeFormat).FormatClass(docname)
}
quit 1
}
}
Готово!
Автор: BlockAN