It was necessary to meet with the opinion that the studio Caché is not completely perfect, in particular the fact that it cannot be expanded. However, this is not the case. There is a regular mechanism of
templates and extensions , which allows the studio to be completed with interactive additions.
In this article we will touch on another extension method that can help more effectively manage the generated code. This will help the studio integration class with the version control system.
What can be done with the version control class? First, to track some events occurring in the studio, such as connecting, creating, deleting, opening documents, and secondly, the ability to add items to the menu, including the shortcut. Through these menu items you can not only execute code on the server, but also create some simple dialogs.
Let's try to expand the studio menu in order to reduce / increase the indent, comment / uncomment the code block (these features in recent versions of Caché are available by keyboard shortcuts), and also add the ability to reformat indents and brackets, which is often not enough when editing large programs.
Creating a version control class
Create a class Util.StudioExtension as a descendant of% Studio.Extension.Base and assign it in the server configuration for the area in which we are going to use this extension:
System Administration> Configuration> Advanced Settings -> Version Control System
')
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 >
}
}In this block, the tags MenuBase, Menu, MenuItem are the display of the classes% Studio.Extension.Base,% Studio.Extension.Menu,% Studio.Extension.MenuItem, the meaning of the attributes can be understood from their documentation. In our case, Name is the internal name of the item, Type = 1 means that the context menu, Separator = 1, that the item is a separator, Save = 100, that when you click the menu item, the document should be saved.
Create a method that handles the display of the menu itself. Add readable Russian names to menu items; in cases when we consider it necessary, we make them inactive (Enabled = 0). For example, it makes no sense to make active the item “reformat the selected text” if nothing is selected.
OnMenuItem methodMethod OnMenuItem ( MenuName As% String , InternalName As% String , SelectedText As% String , ByRef Enabled As% Boolean ,
ByRef DisplayName As% String ) As% Status
{
set : MenuName = "Format" DisplayName = "Format"
set : MenuName = "Format, PlusTab" DisplayName = "-> | increase indent"
set : MenuName = "Format, MinusTab" DisplayName = "| <- decrease indent"
set : MenuName = "Format, ReformatDocument" DisplayName = "Reformat Document"
set : MenuName = "Format, ReformatSelection" DisplayName = "Reformat selected"
set : MenuName = "Format, CommentBlock" DisplayName = "Comment block"
set : MenuName = "Format, UnCommentBlock" DisplayName = "Uncomment the block"
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
}
After reconnection by the studio, a new context menu item should appear, but it still does nothing:

Indenting Class
Create a UserAction method that will respond to various actions, including the selection of menu items:
UserAction method/ * Some event happened
Type = 0 - the menu item is pressed,
In this case, InternalName is the name of the menu item;
InternalName - the name of the document
SelectedText - selected text (if selected)
Type = 1 - the event is created automatically, while
Name = 0 - the user has changed the document.
Name = 1 - a new document has been created and saved.
Name = 2 - user deleted the document
Name = 3 - user opened the document
Name = 4 - the user closed the document
Name = 5 - the user is connected
InternalName - the name of the document with which the event occurred
Action is used to specify a studio action.
0 - Do nothing
1 - Show standard yes / no / cancel dialog. The dialogue text is specified in the variable Target.
2 - Launch csp page. Target specifies the page name
3 - Run the EXE file on the client. Target specifies the path to the file
4 - Insert the text contained in the variable Target at the current position in the document
5 - Open documents contained in the variable Target. The list of documents is separated by a comma. If the document name contains 'test.mac: label + 10', then the document test.mac will open at the position 'label + 10'.
6 - Display a window with a message displayed in the variable Target
7 - Displays a dialog with Yes / No / Cancel options. The text of the dialog is contained in the variable Target. The test message is passed in the variable Msg.
If the Reload argument is true, the current document will be reloaded.
* /
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 ; insert text
set Target = ## class ( Util.FormatTab ). PlusTab ( SelectedText , ext = "CSP" )
}
if Name = "Format, MinusTab"
{
set Action = 4 ; insert text
set Target = ## class ( Util.FormatTab ). MinusTab ( SelectedText , ext = "CSP" )
}
quit 1
}
The class itself that we use to format tabs:
Class Util.FormatTab/// Class for working with tabs in the text
Class Util.FormatTab
{
/// Add tabs to the beginning of all lines that begin
/// with a space or tab.
/// For text with a mark
ClassMethod PlusTab ( text As% String , inCsp As% Boolean = 0 ) As% String
{
set cr = $ char (13,10) ; linefeed characters
set tab = $ char (9) ; tab characters
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 ) ; look for the position of the first non-whitespace character
{
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 )
}
/// Remove tabs at the beginning of a line
/// text - the text that is processed
/// inCsp - sign that we work with the csp page,
/// while not trying to forcibly leave a space at the beginning of the line
ClassMethod MinusTab ( text As% String , inCsp As% Boolean = 0 )
{
set cr = $ char (13,10) ; linefeed characters
set tab = $ char (9) ; tab characters
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 = ""; remove the first 4 spaces
{
set line = $ extract ( line , 2, *)
}
elseif symbol = tab ; we remove tabulation, and on it we finish
{
set line = $ extract ( line , 2, *)
quit
}
else ; no space or tab, end spaces delete spaces
{
quit
}
}
; and suddenly all spaces were removed, and the string was erroneous in terms of syntax
; it will definitely happen if it is not a csp document
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 )
}
}
Now, if you reconnect with the studio (this should always be done after changing the version control class), the menu items “add indent” and “remove indent” should work.
Class for commenting / uncommenting code
We modify the UserAction method of the Util.StudioExtension class by adding the processing of new items:
UserAction methodMethod 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 ; insert text
set Target = ## class ( Util.FormatTab ). PlusTab ( SelectedText , ext = "CSP" )
}
if Name = "Format, MinusTab"
{
set Action = 4 ; insert text
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
}
We use the Util.FormatComment class to process strings:
Class Util.FormatComment/// Class for commenting / uncommenting code
Class Util.FormatComment
{
/// Add a comment to the beginning of each line
/// with a space or tab.
/// For text with a mark
ClassMethod AddComment ( text As% String ) As% String
{
set cr = $ char (13,10) ; linefeed characters
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 ; when copying a block of text, an extra line feed often ends up, no need to comment on it
set stringsOut = stringsOut _ $ listbuild ( line )
}
w !!! $ listtostring ( stringsOut , cr )
quit $ listtostring ( stringsOut , cr )
}
/// Remove comments from the beginning of the line.
ClassMethod RemoveComment ( text As% String )
{
set cr = $ char (13,10) ; newline character
set tab = $ char (9) ; tab character
set stringsIn = $ listfromstring ( text , cr )
set stringsOut = ""
for i = 1: 1: $ listlength ( stringsIn )
{
set line = $ listget ( stringsIn , i )
; remove leading spaces and tabs to determine if there is a comment in this line
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 )
}
/// Returns whether the line is commented out.
/// understands only single-line comments.
ClassMethod CheckForComment ( text As% String ) As% Boolean
{
set cr = $ char (13,10) ; newline character
set tab = $ char (9) ; tab character
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) = ";" one
}
quit 0
}
}
Text formatting
Let's connect a class to reformat code. It can be useful if your project was written by different teams or programmers who did not adhere to the same style. Type Code
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
}
}can be turned into
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
}
}or vice versa, as someone like. The class for the style “bracket above the bracket” - Util.CodeFormat, the class for the style “bracket at the end of the line” - Util.CodeFormat3
Modify the UserAction method by adding processing new menu items:
UserAction methodMethod 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 ; insert text
set Target = ## class ( Util.FormatTab ). PlusTab ( SelectedText , ext = "CSP" )
}
if Name = "Format, MinusTab"
{
set Action = 4 ; insert text
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 handler code in the style of "bracket above the bracket"
Class Util.CodeFormat/// Code formatter class
Class Util.CodeFormat Extends% RegisteredObject
{
/// name of the input buffer global
Property Buffer1 As% String ;
/// name of the output buffer global
Property Buffer2 As% String ;
/// Current line input buffer
Property CurrentLine1 As% Numeric [ InitialExpression = 1 ];
/// Current line of the output buffer
Property CurrentLine2 As% Numeric [ InitialExpression = 1 ];
/// Current position in the input buffer line
Property CurrentPos1 As% Numeric [ InitialExpression = 1 ];
/// Level of curly braces
Property LevelFigureBrace As% Integer [ InitialExpression = 0 ];
/// Level of parentheses
Property LevelRoundBrace As% Integer [ InitialExpression = 0 ];
/// Is there any code on the line? <br>
/// Combinations:
/// LineHasCode = 0 && SkipSpace = 1 -> the beginning of the line, we expect a label or code
/// LineHasCode = 0 && SkipSpace = 0 -> should not be such
/// LineHasCode = 1 && SkipSpace = 0 -> already have the code, just write it as it is
/// LineHasCode = 1 && SkipSpace = 1 -> already have the code, but skip spaces, for example, after {
Property LineHasCode As% Boolean ;
/// Skip spaces
Property SkipSpace As% Boolean [ InitialExpression = 1 ];
/// Skip line feeds
/// 1 - skip one
/// -1 - translation just missed, you need to be attentive to the labels and other
Property SkipCR As% Boolean [ InitialExpression = 0 ];
/// Need indent (when code appears)
Property NeedIndent As% Boolean ;
/// In a single line comment?
Property InSingleLineComment As% Boolean [ InitialExpression = 0 ];
/// In multiline comments?
Property InMultiLineComment As% Boolean [ InitialExpression = 0 ];
/// Process the line feed
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
}
/// Handle whitespace
Method ProcessSpace ( text As% String )
{
quit : (.. InMultiLineComment ) .. ProcessCommonCode ( text )
; the line break was suppressed, the normal character behind it was whitespace.
set : .. SkipCR = -1 .. SkipCR = 0, .. NeedIndent = 0, .. SkipSpace = 1
do : '.. SkipSpace .. Write ( text )
set : '.. LineHasCode .. NeedIndent = 1
quit 1
}
/// Process the opening of the brace
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
}
/// Handle curly braces
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
}
/// Process the opening of the parenthesis
Method ProcessRoundBraceOpen ( text As% String )
{
set :( .. InMultiLineComment = 0) && (.. InSingleLineComment = 0) .. LevelRoundBrace = .. LevelRoundBrace +1
do .. Write ( text )
quit 1
}
/// Process the closing of the parenthesis
Method ProcessRoundBraceClose ( text As% String )
{
set :( .. InMultiLineComment = 0) && (.. InSingleLineComment = 0) && (.. LevelRoundBrace > 0) .. LevelRoundBrace = .. LevelRoundBrace -1
do .. Write ( text )
quit 1
}
/// Handle the opening of a multi-line comment
Method ProcessMultilineCommentOpen ( text As% String )
{
set .. InMultiLineComment = 1
do .. ProcessCommonCode ( text )
quit 1
}
/// Handle closing a multi-line comment
Method ProcessMultilineCommentClose ( text As% String )
{
set .. InMultiLineComment = 0
do .. ProcessCommonCode ( text )
quit 1
}
/// Single line comment
Method ProcessOnelineCommentOpen ( text As% String )
{
do .. ProcessCommonCode ( text )
set .. InSingleLineComment = 1
quit 1
}
/// Process the code without any special features
Method ProcessCommonCode ( text As% String )
{
// translated the line, and on the new line immediately the text is visible label.
// It is necessary to translate all the same string, despite the fact that the translation was suppressed
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
}
/// Reformat the mac program
/// 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 // write to the buffer data from the global program
{
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 ; delete old program lines
{
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
}
/// Format all class methods <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 )
}
}
/// Format the class method. Not very nicely written - a mixture of globals and objects <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) "; method code is stored here
set parser = .. % New ()
set parser . Buffer1 = "^ || buffer1"
set parser . Buffer2 = "^ || buffer2"
kill @ parser . Buffer1
kill @ parser . Buffer2
set key = 0
for // write the data from the global method to the buffer
{
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 ()
}
/// Format the string, which then returns
/// 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 ) // write to the buffer data from the global method
{
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 )
}
/// Read the text from the input buffer <br>
/// Returns a list of text and its type <br>
/// Types of text <br>
/// p - whitespace character <br>
/// s - string <br>
/// e - end of line <br>
/// E - the end of the text in general <br>
/// {- {<br>
///} -} <br>
/// (- (<br>
///) -) <br>
///; - start of single line comment <br>
/// / * - the beginning of a multi-line comment <br>
/// * / - end of multi-line comment
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" )
}
/// Same as ReadTerm, but arguments are given and obtained differently
Method ReadTerm1 ( ByRef type As% String ) As% String
{
set res = .. ReadTerm ()
set type = $ listget ( res , 2)
quit $ listget ( res , 1)
}
/// Print text to output buffer
Method Write ( text As% String = "" )
{
set @ .. Buffer2 @ (.. CurrentLine2 ) = $ get (@ .. Buffer2 @ (.. CurrentLine2 )) _ text
}
/// translate the string in the output buffer
Method WriteCR ()
{
set .. CurrentLine2 = .. CurrentLine2 +1
}
/// Indent text line
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
}
/// Format the text in the internal "buffer"
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
}
}
Class handler code in the style of "bracket at the end of the line." If this style is preferable, you need to replace the calls to the UtilCodeFormat class from the UserAction method with UtilCodeFormat3.
Class Util.CodeFormat3Class Util.CodeFormat3 Extends Util.CodeFormat
{
/// Undo the last line feed
Method ReturnCR ()
{
set .. CurrentLine2 = .. CurrentLine2 -1
}
/// Roll back the last spaces
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
}
}
/// Process the opening of the brace
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
}
}
Class expansion studio Caché entirely:
Class Util.StudioExtensionClass 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% String , InternalName As% String , SelectedText As% String ,
ByRef Enabled As% Boolean , ByRef DisplayName As% String ) As% Status
{
set : MenuName = "Format" DisplayName = "Format"
set : MenuName = "Format, PlusTab" DisplayName = "-> | increase indent"
set : MenuName = "Format, MinusTab" DisplayName = "| <- decrease indent"
set : MenuName = "Format, ReformatDocument" DisplayName = "Reformat Document"
set : MenuName = "Format, ReformatSelection" DisplayName = "Reformat selected"
set : MenuName = "Format, CommentBlock" DisplayName = "Comment block"
set : MenuName = "Format, UnCommentBlock" DisplayName = "Uncomment the block"
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
}
/ * Some event happened
Type = 0 - the menu item is pressed,
In this case, InternalName is the name of the menu item;
InternalName - the name of the document
SelectedText - selected text (if selected)
Type = 1 - the event is created automatically, while
Name = 0 - the user has changed the document.
Name = 1 - a new document has been created and saved.
Name = 2 - user deleted the document
Name = 3 - user opened the document
Name = 4 - the user closed the document
Name = 5 - the user is connected
InternalName - the name of the document with which the event occurred
Action is used to specify a studio action
0 - Do nothing
1 - Show standard yes / no / cancel dialog. The dialogue text is specified in the variable Target.
2 - Launch csp page. Target specifies the page name
3 - Run the EXE file on the client. Target specifies the path to the file
4 - Insert the text contained in the variable Target at the current position in the document
5 - Open documents contained in the variable Target. The list of documents is separated by a comma. If the document name contains 'test.mac: label + 10', then the document test.mac will open at the position 'label + 10'.
6 - Display a window with a message displayed in the variable Target
7 - Displays a dialog with Yes / No / Cancel options. The text of the dialog is contained in the variable Target. The test message is passed in the variable Msg.
If the Reload argument is true, the current document will be reloaded.
* /
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 ; insert text
set Target = ## class ( Util.FormatTab ). PlusTab ( SelectedText , ext = "CSP" )
}
if Name = "Format, MinusTab"
{
set Action = 4 ; insert text
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
}
}
Done!