Еще один способ расширения функциональности студии Caché

в 13:37, , рубрики: cache objectscript, cos, dbms cache, intersystems cache, Блог компании InterSystems, студия, метки: , , , ,

Приходилось встречаться с мнением, что студия 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). Например, нет смысла делать активным пункт “переформатировать выделенный текст”, если ничего не выделено.

Метод OnMenuItem

Method OnMenuItem(MenuName As %StringInternalName As %StringSelectedText As %StringByRef Enabled As %Boolean
ByRef DisplayName As %StringAs %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
 }

После переподключения студией должен появиться новый пункт контекстного меню, но он пока еще ничего не делает:
Еще один способ расширения функциональности студии Caché

Класс с для работы с отступами

Создадим метод UserAction, который будет реагировать на различные действия, в том числе и на выбор пунктов меню:

Метод 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 %IntegerName As %StringInternalName As %StringSelectedText As %String
ByRef  Action As %StringByRef Target As %StringByRef Msg As %StringByRef Reload As %BooleanAs %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
}

Сам класс, который мы используем для форматирования табуляций:

Класс Util.FormatTab

/// Класс для работы с табуляциями в тексте
Class Util.FormatTab
{
/// Добавить табуляции в начало всех строк, которые начинаются
/// с пробела или табуляции.
/// Для текста с пометкой
ClassMethod 
PlusTab(text As %StringinCsp As %Boolean 0As %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 %StringinCsp 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, добавив обработку новых пунктов:

Метод UserAction

Method UserAction(Type As %IntegerName As %StringInternalName As %StringSelectedText As %String
ByRef Action As %StringByRef Target As %StringByRef Msg As %StringByRef Reload As %BooleanAs %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
        
Target=##class(Util.FormatComment).AddComment(SelectedText)
    
}
    
if Name="Format,UnCommentBlock"
    
{
        
set Action=4
        
Target=##class(Util.FormatComment).RemoveComment(SelectedText)
    
}
    
quit 1
}

Используем класс Util.FormatComment для обработки строк:

Класс Util.FormatComment

/// Класс для комментирования/раскомментирования кода
Class Util.FormatComment
{
/// Добавить комментарий к началу каждой строки
/// с пробела или табуляции.
/// Для текста с пометкой
ClassMethod 
AddComment(text As %StringAs %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)
    
}
    
!,!,$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 %StringAs %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 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 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, добавив обработку новых пунктов меню:

Метод UserAction

Method UserAction(Type As %IntegerName As %StringInternalName As %StringSelectedText As %StringByRef Action As %String
ByRef Target As %StringByRef Msg As %StringByRef Reload As %BooleanAs %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
}

Класс обработчик кода в стиль «скобка над скобкой»

Класс Util.CodeFormat

/// Класс-форматировщик кода
Class Util.CodeFormat Extends %RegisteredObject
{
/// Имя глобала входного буфера
Property 
Buffer1 As %String;

/// Имя глобала выходного буфера
Property 
Buffer2 As %String;

/// Текущая строка входного буфера
Property 
CurrentLine1 As %Numeric InitialExpression ];

/// Текущая строка выходного буфера
Property 
CurrentLine2 As %Numeric InitialExpression ];

/// Текущая позиция в строке входного буфера
Property 
CurrentPos1 As %Numeric InitialExpression ];

/// Уровень фигурных скобок
Property 
LevelFigureBrace As %Integer InitialExpression ];

/// Уровень круглых скобок
Property 
LevelRoundBrace As %Integer InitialExpression ];

/// Есть ли код на строке? <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 - перевод только что пропущен, нужно быть внимательным к меткам и прочему
Property 
SkipCR As %Boolean InitialExpression ];

/// Нужен отступ (при появлении кода)
Property 
NeedIndent As %Boolean;

/// В однострочном комментарии?
Property 
InSingleLineComment As %Boolean InitialExpression ];

/// В многострочном комментарии?
Property 
InMultiLineComment As %Boolean InitialExpression ];

/// Обработать перевод строки
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 %StringAs %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 %Stringmethod 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 %StringAs %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 %StringAs %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(.typequit: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.

Класс Util.CodeFormat3

Class Util.CodeFormat3 Extends Util.CodeFormat
{
/// Откатить последний перевод строки
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é целиком:

Класс Util.StudioExtension

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>
}
Method OnMenuItem(MenuName As %StringInternalName As %StringSelectedText As %String
ByRef Enabled As %BooleanByRef DisplayName As %StringAs %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 %IntegerName As %StringInternalName As %StringSelectedText As %String
ByRef  Action As %StringByRef Target As %StringByRef Msg As %StringByRef Reload As %BooleanAs %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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js