Align Comments
chozo
NOTE: From Igor 9 onwards this functionality is available as a standard feature (as Align Comments in the Edit menu), which makes this script unnecessary. You may find it useful if you are working with an earlier version.
Aligns all comments in the selection of a procedure file to the same tab position. The distance is set by the comment furthest to the left or right. Use the function ToTabPos to set a fixed alignment distance in number of tabs.
EDIT: Updated the code to properly honor the first line, even if it is selected only partly, and included the ability to left-align the comments or use a fixed tab number.
NOTES:
- The current font name, font size, and default tab width settings for procedures need to be set to the correct values at the beginning of below code.
- This code will be loaded as an independent module. You'll have to show hidden items in the procedure browser to see the code.
#pragma TextEncoding = "UTF-8"
#pragma rtGlobals = 3 // Use modern global access method and strict wave access.
#pragma IndependentModule = AlignComments
#pragma version = 1.1
// Inspired by the Code Prettify snipped by Tony Withers
// https://www.wavemetrics.com/user/chozo
// To align comments in the selected code use:
// Left-aligned: cmd-7 (Mac), ctrl-7 (Windows)
// Right-aligned: cmd-8 (Mac), ctrl-8 (Windows)
// Dial in your procedure window settings here:
// You can find the default tab size under Procedure->Document Settings...
StrConstant FontName = "Courier New"
Constant FontSize = 11
Constant DefaultTab = 20 // in points
Menu "Edit"
"Align Comments in Clipboard Code (Right)", /Q, PutScrapText AlignComments#AlignComments(GetScrapText())
"Align Comments in Selected Code (Left)/7", /Q, AlignCommentsInSelection(1)
"Align Comments in Selected Code (Right)/8", /Q, AlignCommentsInSelection(0)
End
Function AlignCommentsInSelection(Variable LeftOrRight)
String strScrap = GetScrapText()
DoIgorMenu "Edit" "Copy"
PutScrapText AlignComments#AlignComments(GetScrapText(),AligntoLeft=LeftOrRight)
DoIgorMenu "Edit" "Paste"
PutScrapText strScrap // leave clipboard state unchanged
End
// For setting a determined tab position of all selected comments.
// Execute from the commandline as AlignComments#toTabPos(desiredPos).
Function toTabPos(Variable TabPos) // for aligning all comments to a certain tab position
DisplayProcedure/W=$StringFromList(0,WinList("*", ";","WIN:128")) // bring to front
String strScrap = GetScrapText()
DoIgorMenu "Edit" "Copy"
PutScrapText AlignComments#AlignComments(GetScrapText(),TabPos=TabPos)
DoIgorMenu "Edit" "Paste"
PutScrapText strScrap
End
// The optional variable TabPos lets you choose a fixed offset (in tab widths) for all comments.
// For example, TabPos = 30 will position all comments at 30 tabs, if possible.
Function/S AlignComments(String strText, [Variable TabPos, Variable AlignToLeft])
Variable LorR = 0
if(!ParamIsDefault(AlignToLeft))
LorR = AlignToLeft
endif
Variable fsPix = FontSize * ScreenResolution/72 // font size in pix
Variable TabPix = floor(DefaultTab * ScreenResolution/72) // tab width in pix
Wave/T wText = ListToTextWave(strText, "\r")
Variable endsWithReturn = (cmpstr(strText[strlen(strText)-1], "\r") == 0)
Variable entries = numpnts(wText)
String strEverything = "", strStartOfFirst = "", strEndOfFirst = wText[0] // extract the start of the first line from the whole text
strEverything = ProcedureText("",0,StringFromList(0,WinList("*", ";","WIN:128")))
strEverything = ReplaceString(strText,strEverything,"\rCUT\r") // create a break at selected text
strEverything = StringFromList(0, strEverything, "\rCUT\r") // only the stuff before the selection
if (cmpstr(strEverything[strlen(strEverything)-1], "\r") != 0 && strlen(strEverything)>0) // only if it is not a full line
Wave/T wBegin = ListToTextWave(strEverything, "\r")
strStartOfFirst = wBegin[numpnts(wBegin)-1] // the extra part of the first line
endif
if (strlen(strStartOfFirst) > 0) // put the full firt line in here
wText[0] = strStartOfFirst + strEndOfFirst // this is needed to calculate the correct line length
endif
wText += SelectString(p<(entries-1) || endsWithReturn, "", "\r")
Make/D/FREE/N=(entries) CodeSize = 0 // saves the code size in 'tab widths'
Make/T/FREE/N=(entries) CodeOnly, CommentOnly
Variable i,j, bytes = 0, strSize = 0
String strLine = "", strCode = ""
for (i = 0; i < entries; i += 1)
strLine = wText[i]
//bytes = strsearch(strLine, "//",Inf,1)
bytes = startOfComment(strLine) // is there a comment?
bytes = bytes == strlen(strLine) ? -1 : bytes // no comment found
if (i == 0 && strlen(strStartOfFirst) > bytes) // if the full comment in not selected -> skip
bytes = -1
endif
if(bytes > 1)
strCode = strLine[0,bytes-1]
if(GrepString(strCode, "\S")) // line contains non-whitespace
CodeOnly[i] = strCode // just the code
CommentOnly[i] = strLine[bytes,Inf] // just the comment
if (cmpstr(strCode[strlen(strCode)-1], " ") == 0)
CodeOnly[i] = RemoveEnding(CodeOnly[i] , " ") // get rid of a possible space character in front of the comments
CodeOnly[i] += "\t"
endif
// Below code calculates the line length in units of 'tab length'.
// Multiplication by TabPix would give the length in pix.
Wave/T wTabList = ListToTextWave(CodeOnly[i], "\t") // split code line by tabs
for (j = 0; j < DimSize(wTabList,0); j += 1)
strCode = wTabList[j]
if (strlen(strCode) > 0) // tab only or code?
strSize = FontSizeStringWidth(FontName,fsPix,0,strCode,"") // get the length of all characters
if (mod(strSize,TabPix) < 0.05) // does this have roughly the size of a certain no. of tabs?
strSize = ceil(strSize/TabPix + 1) // a full tab length is added
else
strSize = ceil(strSize/TabPix) // this will add a fractional tab length
endif
CodeSize[i] += strSize
else
CodeSize[i] += 1 // if it's just a tab then add one tab length
endif
endfor
if (cmpstr(strCode[strlen(strCode)-1], "\t") == 0) // no tab in last entry -> not aligned to the next tab position
CodeSize[i] -= 1
endif
endif
endif
endfor
wText[0] = strEndOfFirst + "\r" // put only the end line back (in case there is no modification)
if (strlen(strStartOfFirst) > 0) // remove the first part of the first line again
CodeOnly[0] = ReplaceString(strStartOfFirst,CodeOnly[0],"")
endif
Variable AlignDistance = 0 // how many tabs define the new alignment
if(!ParamIsDefault(TabPos))
AlignDistance = TabPos
else
Extract/Free CodeSize,SizeVals,CodeSize!=0
AlignDistance = LorR == 1 ? max(WaveMin(SizeVals),AlignDistance) : max(WaveMax(SizeVals),AlignDistance)
endif
CodeSize = CodeSize[p] > 0 ? AlignDistance - CodeSize[p] : 0 // how many tabs to add or subtract
for (i = 0; i < entries; i += 1)
String newTabs = ""
if(abs(CodeSize[i]) > 0)
for (j = 0; j < abs(CodeSize[i]); j += 1)
if (CodeSize[i] < 0)
CodeOnly[i] = RemoveEnding(CodeOnly[i],"\t")
else
newTabs += "\t"
endif
endfor
wText[i] = CodeOnly[i] + newTabs + CommentOnly[i]
endif
endfor
wfprintf strText, "%s", wText
return strText
End
// comment search function by Tony Withers
Function startOfComment(String strLine)
Variable startByte, commentByte, startQuote, endQuote, lineLength
lineLength=strlen(strLine)
startByte=0
do
if(startByte>=(lineLength-1))
return lineLength
endif
commentByte = strsearch(strLine, "//", startByte)
if(commentByte == -1)
return lineLength
endif
// found "//"
startQuote=strsearch(strLine, "\"", startByte)
if(startQuote==-1 || startQuote>commentByte) // no quotes before //, we're done
return commentByte
endif
// we have a quotation mark before //
do
endQuote=startQuote
do
endQuote=strsearch(strLine, "\"", endQuote+1)
if(endQuote==-1)
return lineLength
endif
// endQuote is possible end of quote
// remove escaped backslashes!
strLine[startQuote,endQuote]=ReplaceString("\\\\", strLine[startQuote,endQuote], " ")
while(cmpstr(strLine[endQuote-1], "\\")==0) // ignore escaped quotes
// found end of quote
if(endQuote>commentByte) // commentByte was within commented text
startByte=endQuote+1
break // look for another comment mark
else
startQuote=strsearch(strLine, "\"", endQuote+1)
if(startQuote==-1 || startQuote>commentByte) // no quotes before //, we're done
return commentByte
endif
endif
// if we get to here we've found another start-of-quoted-text before //
while(1) // next quoted text before //
while(1) // next comment marker
End
#pragma rtGlobals = 3 // Use modern global access method and strict wave access.
#pragma IndependentModule = AlignComments
#pragma version = 1.1
// Inspired by the Code Prettify snipped by Tony Withers
// https://www.wavemetrics.com/user/chozo
// To align comments in the selected code use:
// Left-aligned: cmd-7 (Mac), ctrl-7 (Windows)
// Right-aligned: cmd-8 (Mac), ctrl-8 (Windows)
// Dial in your procedure window settings here:
// You can find the default tab size under Procedure->Document Settings...
StrConstant FontName = "Courier New"
Constant FontSize = 11
Constant DefaultTab = 20 // in points
Menu "Edit"
"Align Comments in Clipboard Code (Right)", /Q, PutScrapText AlignComments#AlignComments(GetScrapText())
"Align Comments in Selected Code (Left)/7", /Q, AlignCommentsInSelection(1)
"Align Comments in Selected Code (Right)/8", /Q, AlignCommentsInSelection(0)
End
Function AlignCommentsInSelection(Variable LeftOrRight)
String strScrap = GetScrapText()
DoIgorMenu "Edit" "Copy"
PutScrapText AlignComments#AlignComments(GetScrapText(),AligntoLeft=LeftOrRight)
DoIgorMenu "Edit" "Paste"
PutScrapText strScrap // leave clipboard state unchanged
End
// For setting a determined tab position of all selected comments.
// Execute from the commandline as AlignComments#toTabPos(desiredPos).
Function toTabPos(Variable TabPos) // for aligning all comments to a certain tab position
DisplayProcedure/W=$StringFromList(0,WinList("*", ";","WIN:128")) // bring to front
String strScrap = GetScrapText()
DoIgorMenu "Edit" "Copy"
PutScrapText AlignComments#AlignComments(GetScrapText(),TabPos=TabPos)
DoIgorMenu "Edit" "Paste"
PutScrapText strScrap
End
// The optional variable TabPos lets you choose a fixed offset (in tab widths) for all comments.
// For example, TabPos = 30 will position all comments at 30 tabs, if possible.
Function/S AlignComments(String strText, [Variable TabPos, Variable AlignToLeft])
Variable LorR = 0
if(!ParamIsDefault(AlignToLeft))
LorR = AlignToLeft
endif
Variable fsPix = FontSize * ScreenResolution/72 // font size in pix
Variable TabPix = floor(DefaultTab * ScreenResolution/72) // tab width in pix
Wave/T wText = ListToTextWave(strText, "\r")
Variable endsWithReturn = (cmpstr(strText[strlen(strText)-1], "\r") == 0)
Variable entries = numpnts(wText)
String strEverything = "", strStartOfFirst = "", strEndOfFirst = wText[0] // extract the start of the first line from the whole text
strEverything = ProcedureText("",0,StringFromList(0,WinList("*", ";","WIN:128")))
strEverything = ReplaceString(strText,strEverything,"\rCUT\r") // create a break at selected text
strEverything = StringFromList(0, strEverything, "\rCUT\r") // only the stuff before the selection
if (cmpstr(strEverything[strlen(strEverything)-1], "\r") != 0 && strlen(strEverything)>0) // only if it is not a full line
Wave/T wBegin = ListToTextWave(strEverything, "\r")
strStartOfFirst = wBegin[numpnts(wBegin)-1] // the extra part of the first line
endif
if (strlen(strStartOfFirst) > 0) // put the full firt line in here
wText[0] = strStartOfFirst + strEndOfFirst // this is needed to calculate the correct line length
endif
wText += SelectString(p<(entries-1) || endsWithReturn, "", "\r")
Make/D/FREE/N=(entries) CodeSize = 0 // saves the code size in 'tab widths'
Make/T/FREE/N=(entries) CodeOnly, CommentOnly
Variable i,j, bytes = 0, strSize = 0
String strLine = "", strCode = ""
for (i = 0; i < entries; i += 1)
strLine = wText[i]
//bytes = strsearch(strLine, "//",Inf,1)
bytes = startOfComment(strLine) // is there a comment?
bytes = bytes == strlen(strLine) ? -1 : bytes // no comment found
if (i == 0 && strlen(strStartOfFirst) > bytes) // if the full comment in not selected -> skip
bytes = -1
endif
if(bytes > 1)
strCode = strLine[0,bytes-1]
if(GrepString(strCode, "\S")) // line contains non-whitespace
CodeOnly[i] = strCode // just the code
CommentOnly[i] = strLine[bytes,Inf] // just the comment
if (cmpstr(strCode[strlen(strCode)-1], " ") == 0)
CodeOnly[i] = RemoveEnding(CodeOnly[i] , " ") // get rid of a possible space character in front of the comments
CodeOnly[i] += "\t"
endif
// Below code calculates the line length in units of 'tab length'.
// Multiplication by TabPix would give the length in pix.
Wave/T wTabList = ListToTextWave(CodeOnly[i], "\t") // split code line by tabs
for (j = 0; j < DimSize(wTabList,0); j += 1)
strCode = wTabList[j]
if (strlen(strCode) > 0) // tab only or code?
strSize = FontSizeStringWidth(FontName,fsPix,0,strCode,"") // get the length of all characters
if (mod(strSize,TabPix) < 0.05) // does this have roughly the size of a certain no. of tabs?
strSize = ceil(strSize/TabPix + 1) // a full tab length is added
else
strSize = ceil(strSize/TabPix) // this will add a fractional tab length
endif
CodeSize[i] += strSize
else
CodeSize[i] += 1 // if it's just a tab then add one tab length
endif
endfor
if (cmpstr(strCode[strlen(strCode)-1], "\t") == 0) // no tab in last entry -> not aligned to the next tab position
CodeSize[i] -= 1
endif
endif
endif
endfor
wText[0] = strEndOfFirst + "\r" // put only the end line back (in case there is no modification)
if (strlen(strStartOfFirst) > 0) // remove the first part of the first line again
CodeOnly[0] = ReplaceString(strStartOfFirst,CodeOnly[0],"")
endif
Variable AlignDistance = 0 // how many tabs define the new alignment
if(!ParamIsDefault(TabPos))
AlignDistance = TabPos
else
Extract/Free CodeSize,SizeVals,CodeSize!=0
AlignDistance = LorR == 1 ? max(WaveMin(SizeVals),AlignDistance) : max(WaveMax(SizeVals),AlignDistance)
endif
CodeSize = CodeSize[p] > 0 ? AlignDistance - CodeSize[p] : 0 // how many tabs to add or subtract
for (i = 0; i < entries; i += 1)
String newTabs = ""
if(abs(CodeSize[i]) > 0)
for (j = 0; j < abs(CodeSize[i]); j += 1)
if (CodeSize[i] < 0)
CodeOnly[i] = RemoveEnding(CodeOnly[i],"\t")
else
newTabs += "\t"
endif
endfor
wText[i] = CodeOnly[i] + newTabs + CommentOnly[i]
endif
endfor
wfprintf strText, "%s", wText
return strText
End
// comment search function by Tony Withers
Function startOfComment(String strLine)
Variable startByte, commentByte, startQuote, endQuote, lineLength
lineLength=strlen(strLine)
startByte=0
do
if(startByte>=(lineLength-1))
return lineLength
endif
commentByte = strsearch(strLine, "//", startByte)
if(commentByte == -1)
return lineLength
endif
// found "//"
startQuote=strsearch(strLine, "\"", startByte)
if(startQuote==-1 || startQuote>commentByte) // no quotes before //, we're done
return commentByte
endif
// we have a quotation mark before //
do
endQuote=startQuote
do
endQuote=strsearch(strLine, "\"", endQuote+1)
if(endQuote==-1)
return lineLength
endif
// endQuote is possible end of quote
// remove escaped backslashes!
strLine[startQuote,endQuote]=ReplaceString("\\\\", strLine[startQuote,endQuote], " ")
while(cmpstr(strLine[endQuote-1], "\\")==0) // ignore escaped quotes
// found end of quote
if(endQuote>commentByte) // commentByte was within commented text
startByte=endQuote+1
break // look for another comment mark
else
startQuote=strsearch(strLine, "\"", endQuote+1)
if(startQuote==-1 || startQuote>commentByte) // no quotes before //, we're done
return commentByte
endif
endif
// if we get to here we've found another start-of-quoted-text before //
while(1) // next quoted text before //
while(1) // next comment marker
End
Forum
Support
Gallery
Igor Pro 9
Learn More
Igor XOP Toolkit
Learn More
Igor NIDAQ Tools MX
Learn More
tricky!
See also the related snippet for comment wrapping. It doesn't help with what you're trying to do, but it does attempt to deal with indentation for long comments.
You will likely want to put your snippet into an independent module for compatibility with auto-compile.
June 23, 2020 at 11:58 pm - Permalink
In reply to tricky! See also the… by tony
Yes, thanks for the comment, I will do that. I am also planning to upgrade the code to optionally align to the rightmost or leftmost comment. Do you have any idea how to improve the following points:
Btw, are you interested / do you plan to wrap all these snippets for procedure windows into a full tool set at some point?
June 24, 2020 at 01:49 am - Permalink
In reply to Yes, thanks for the comment,… by chozo
OK, here is the version as independent module. There are two menu entries now for aligning all selected comments to the leftmost (if possible) or rightmost comment. Also I have added a command-line function to set a fixed tab position. I have added the new version (v0.8) above.
#pragma rtGlobals=3 // Use modern global access method and strict wave access.
#pragma IndependentModule = AlignComments
#pragma version=0.8
// Inspired by the Code Prettify snipped from Tony Withers
// https://www.wavemetrics.com/user/chozo
// To align comments in the selected code use:
// Left-aligned: cmd-7 (Mac), ctrl-7 (Windows)
// Right-aligned: cmd-8 (Mac), ctrl-8 (Windows)
// Dial in your procedure window settings here:
// You can find the default tab size under Procedure->Document Settings...
StrConstant FontName = "Courier New"
Constant FontSize = 11
Constant DefaultTab = 20 // in points
Menu "Edit"
"Align Comments in Clipboard Code (Right)", /Q, PutScrapText AlignComments#AlignComments(GetScrapText())
"Align Comments in Selected Code (Left)/7", /Q, AlignCommentsInSelection(1)
"Align Comments in Selected Code (Right)/8", /Q, AlignCommentsInSelection(0)
End
Function AlignCommentsInSelection(Variable LeftOrRight)
String strScrap = GetScrapText()
DoIgorMenu "Edit" "Copy"
PutScrapText AlignComments#AlignComments(GetScrapText(),AligntoLeft=LeftOrRight)
DoIgorMenu "Edit" "Paste"
PutScrapText strScrap // leave clipboard state unchanged
End
// For setting a determined tab position of all selected comments
// Execute from the commandline as AlignComments#toTabPos(desiredPos)
Function toTabPos(Variable TabPos) // for aligning all comments to a certain tab position
DisplayProcedure/W=$StringFromList(0,WinList("*", ";","WIN:128")) // bring to front
String strScrap = GetScrapText()
DoIgorMenu "Edit" "Copy"
PutScrapText AlignComments#AlignComments(GetScrapText(),TabPos=TabPos)
DoIgorMenu "Edit" "Paste"
PutScrapText strScrap
End
// The optional variable TabPos lets you choose a fixed offset (in tab widths) for all comments.
// For example, TabPos = 30 will position all comments at 30 tabs, if possible
Function/S AlignComments(String strText, [Variable TabPos, Variable AlignToLeft])
Variable LorR = 0
if(!ParamIsDefault(AlignToLeft))
LorR = AlignToLeft
endif
Variable fsPix = FontSize * ScreenResolution/72 // font size in pix
Variable TabPix = floor(DefaultTab * ScreenResolution/72) // tab width in pix
strText = ReplaceString(" //", strText, "//") // get rid of a space character in front of the comments
Wave/T wText = ListToTextWave(strText, "\r")
Variable endsWithReturn = (cmpstr(strText[strlen(strText)-1], "\r") == 0)
Variable entries = numpnts(wText)
wText += SelectString(p<(entries-1) || endsWithReturn, "", "\r")
Make/D/FREE/N=(entries) CodeSize = 0 // saves the code size in 'tab widths'
Make/T/FREE/N=(entries) CodeOnly, CommentOnly
Variable i,j, bytes = 0, strSize = 0
String strLine = "", strCode = ""
for (i = 0; i < entries; i += 1)
strLine = wText[i]
bytes = strsearch(strLine, "//",Inf,1) // is there a comment?
if(bytes > 1)
strCode = strLine[0,bytes-1]
if(GrepString(strCode, "\S")) // line contains non-whitespace
CodeOnly[i] = strCode // just the code
CommentOnly[i] = strLine[bytes,inf] // just the comment
// Below code calculates the line length in units of 'tab length'
// Multiplication by TabPix would give the length in pix.
Wave/T wTabList = ListToTextWave(strCode, "\t") // split code line by tabs
for (j = 0; j < DimSize(wTabList,0); j += 1)
strCode = wTabList[j]
if (strlen(strCode) > 0) // tab only or code?
strSize = FontSizeStringWidth(FontName,fsPix,0,strCode,"") // get the length of all characters
if (mod(strSize,TabPix) < 0.05) // does this have roughly the size of a certain no. of tabs?
strSize = ceil(strSize/TabPix + 1) // a full tab length is added
else
strSize = ceil(strSize/TabPix) // this will add a fractional tab length
endif
CodeSize[i] += strSize
else
CodeSize[i] += 1 // if it's just a tab then add one tab length
endif
endfor
if (cmpstr(strCode[strlen(strCode)-1], "\t") == 0) // no tab in last entry => not aligned to the next tab position
CodeSize[i] -= 1
endif
endif
endif
endfor
Variable AlignDistance = 0 // how many tabs define the new alignment
if(!ParamIsDefault(TabPos))
AlignDistance = TabPos
else
Extract/Free CodeSize,SizeVals,CodeSize!=0
AlignDistance = LorR == 1 ? max(WaveMin(SizeVals),AlignDistance) : max(WaveMax(SizeVals),AlignDistance)
endif
CodeSize = CodeSize[p] > 0 ? AlignDistance - CodeSize[p] : 0 // how many tabs to add or subtract
for (i = 0; i < entries; i += 1)
String newTabs = ""
if(abs(CodeSize[i]) > 0)
for (j = 0; j < abs(CodeSize[i]); j += 1)
if (CodeSize[i] < 0)
CodeOnly[i] = RemoveEnding(CodeOnly[i],"\t")
else
newTabs += "\t"
endif
endfor
wText[i] = CodeOnly[i] + newTabs + CommentOnly[i]
endif
endfor
wfprintf strText, "%s", wText
return strText
End
June 24, 2020 at 06:06 am - Permalink
In reply to Yes, thanks for the comment,… by chozo
Procedure windows cannot, for the most part, be controlled programatically. So as far as I know there is no way to modify the selection.
But you can use the ProcedureText function to get all of the text of a procedure window, and then parse off the first line of the text.
June 24, 2020 at 06:51 am - Permalink
In reply to Yes, thanks for the comment,… by chozo
The sorts of things you're looking for are easy to do with notebooks, but I don't know any way to do that with procedure windows, which makes sense because users were probably never expected to adjust the behaviour of that window. If we could use hook functions with procedure windows that would be great for adding contextual menus, but I can also see that it might make sense to put in requests for new features for Igor 9 (or 10?) rather than trying to 'roll our own'. Something that I would like is an operation to query the current state of ALL of the user-editable settings for Igor.
You could try using ProcedureText() to grab the whole procedure and then looking for a unique match of selected text within the procedure text... I just checked and it looks like the current text of uncompiled and unsaved ipf files is retrieved by ProcedureText(), so there may be some mileage in that.
I do maintain a TextEditTools.ipf procedure file for myself, but it doesn't contain much, just the snippets mentioned above and a transpose paste function. If the collection of user contributions grows then it might make sense to put together a compilation of utility text-editing code.
June 24, 2020 at 09:00 am - Permalink
Tony and Adam,
Thank you very much for the input! I have modified the code again to grab the first line from ProcedureText(). Works great so far. I guess nothing can be done about having to dial in the font settings for now. I have attached the new version above.
#pragma rtGlobals = 3 // Use modern global access method and strict wave access.
#pragma IndependentModule = AlignComments
#pragma version = 1.0
// Inspired by the Code Prettify snipped by Tony Withers
// https://www.wavemetrics.com/user/chozo
// To align comments in the selected code use:
// Left-aligned: cmd-7 (Mac), ctrl-7 (Windows)
// Right-aligned: cmd-8 (Mac), ctrl-8 (Windows)
// Dial in your procedure window settings here:
// You can find the default tab size under Procedure->Document Settings...
StrConstant FontName = "Courier New"
Constant FontSize = 11
Constant DefaultTab = 20 // in points
Menu "Edit"
"Align Comments in Clipboard Code (Right)", /Q, PutScrapText AlignComments#AlignComments(GetScrapText())
"Align Comments in Selected Code (Left)/7", /Q, AlignCommentsInSelection(1)
"Align Comments in Selected Code (Right)/8", /Q, AlignCommentsInSelection(0)
End
Function AlignCommentsInSelection(Variable LeftOrRight)
String strScrap = GetScrapText()
DoIgorMenu "Edit" "Copy"
PutScrapText AlignComments#AlignComments(GetScrapText(),AligntoLeft=LeftOrRight)
DoIgorMenu "Edit" "Paste"
PutScrapText strScrap // leave clipboard state unchanged
End
// For setting a determined tab position of all selected comments.
// Execute from the commandline as AlignComments#toTabPos(desiredPos).
Function toTabPos(Variable TabPos) // for aligning all comments to a certain tab position
DisplayProcedure/W=$StringFromList(0,WinList("*", ";","WIN:128")) // bring to front
String strScrap = GetScrapText()
DoIgorMenu "Edit" "Copy"
PutScrapText AlignComments#AlignComments(GetScrapText(),TabPos=TabPos)
DoIgorMenu "Edit" "Paste"
PutScrapText strScrap
End
// The optional variable TabPos lets you choose a fixed offset (in tab widths) for all comments.
// For example, TabPos = 30 will position all comments at 30 tabs, if possible.
Function/S AlignComments(String strText, [Variable TabPos, Variable AlignToLeft])
Variable LorR = 0
if(!ParamIsDefault(AlignToLeft))
LorR = AlignToLeft
endif
Variable fsPix = FontSize * ScreenResolution/72 // font size in pix
Variable TabPix = floor(DefaultTab * ScreenResolution/72) // tab width in pix
strText = ReplaceString(" //", strText, "//") // get rid of a possible space character in front of the comments
Wave/T wText = ListToTextWave(strText, "\r")
Variable endsWithReturn = (cmpstr(strText[strlen(strText)-1], "\r") == 0)
Variable entries = numpnts(wText)
String strEverything = "" // extract the start of the first line from the whole text
strEverything = ProcedureText("",0,StringFromList(0,WinList("*", ";","WIN:128")))
strEverything = ReplaceString(strText,strEverything,"\rCUT\r") // create a break at selected text
strEverything = StringFromList(0, strEverything, "\rCUT\r") // only the stuff before the selection
Wave/T wBegin = ListToTextWave(strEverything, "\r")
String strStartOfFirst = wBegin[numpnts(wBegin)-1] // the extra part of the first line
String strEndOfFirst = wText[0]
if (strlen(strStartOfFirst) > 0) // put the full firt line in here
wText[0] = strStartOfFirst + strEndOfFirst // this is needed to calculate the correct line length
endif
wText += SelectString(p<(entries-1) || endsWithReturn, "", "\r")
Make/D/FREE/N=(entries) CodeSize = 0 // saves the code size in 'tab widths'
Make/T/FREE/N=(entries) CodeOnly, CommentOnly
Variable i,j, bytes = 0, strSize = 0
String strLine = "", strCode = ""
for (i = 0; i < entries; i += 1)
strLine = wText[i]
bytes = strsearch(strLine, "//",Inf,1) // is there a comment?
if(bytes > 1)
strCode = strLine[0,bytes-1]
if(GrepString(strCode, "\S")) // line contains non-whitespace
CodeOnly[i] = strCode // just the code
CommentOnly[i] = strLine[bytes,inf] // just the comment
// Below code calculates the line length in units of 'tab length'.
// Multiplication by TabPix would give the length in pix.
Wave/T wTabList = ListToTextWave(strCode, "\t") // split code line by tabs
for (j = 0; j < DimSize(wTabList,0); j += 1)
strCode = wTabList[j]
if (strlen(strCode) > 0) // tab only or code?
strSize = FontSizeStringWidth(FontName,fsPix,0,strCode,"") // get the length of all characters
if (mod(strSize,TabPix) < 0.05) // does this have roughly the size of a certain no. of tabs?
strSize = ceil(strSize/TabPix + 1) // a full tab length is added
else
strSize = ceil(strSize/TabPix) // this will add a fractional tab length
endif
CodeSize[i] += strSize
else
CodeSize[i] += 1 // if it's just a tab then add one tab length
endif
endfor
if (cmpstr(strCode[strlen(strCode)-1], "\t") == 0) // no tab in last entry -> not aligned to the next tab position
CodeSize[i] -= 1
endif
endif
endif
endfor
if (strlen(strStartOfFirst) > 0) // remove the first part of the first line again
CodeOnly[0] = ReplaceString(strStartOfFirst,CodeOnly[0],"")
endif
Variable AlignDistance = 0 // how many tabs define the new alignment
if(!ParamIsDefault(TabPos))
AlignDistance = TabPos
else
Extract/Free CodeSize,SizeVals,CodeSize!=0
AlignDistance = LorR == 1 ? max(WaveMin(SizeVals),AlignDistance) : max(WaveMax(SizeVals),AlignDistance)
endif
CodeSize = CodeSize[p] > 0 ? AlignDistance - CodeSize[p] : 0 // how many tabs to add or subtract
for (i = 0; i < entries; i += 1)
String newTabs = ""
if(abs(CodeSize[i]) > 0)
for (j = 0; j < abs(CodeSize[i]); j += 1)
if (CodeSize[i] < 0)
CodeOnly[i] = RemoveEnding(CodeOnly[i],"\t")
else
newTabs += "\t"
endif
endfor
wText[i] = CodeOnly[i] + newTabs + CommentOnly[i]
endif
endfor
wfprintf strText, "%s", wText
return strText
End
June 24, 2020 at 08:43 pm - Permalink
looking at the file AlignComments_v10.ipf:
you need to remove the prepended piece of the first line before pasting the adjusted text over the selection.
Also, you might worry about comment markers buried inside quotation marks, for instance in a line like
bytes = strsearch(strLine, "//",Inf,1)
Here is an (untested) attempt to find the start of comments that doesn't get caught out by lines like
print "this is \"not a // comment\"" // and // this " is
// or strlen(strLine) when no comment found.
function startOfComment(string strLine)
variable startByte, commentByte, startQuote, endQuote, lineLength
lineLength=strlen(strLine)
startByte=0
do
if(startByte>=(lineLength-1))
return lineLength
endif
commentByte = strsearch(strLine, "//", startByte)
if(commentByte == -1)
return lineLength
endif
// found "//"
startQuote=strsearch(strLine, "\"", startByte)
if(startQuote==-1 || startQuote>commentByte) // no quotes before //, we're done
return commentByte
endif
// we have a quotation mark before //
do
endQuote=startQuote
do
endQuote=strsearch(strLine, "\"", endQuote+1)
if(endQuote==-1)
return lineLength
endif
// endQuote is possible end of quote
// remove escaped backslashes!
strLine[startQuote,endQuote]=ReplaceString("\\\\", strLine[startQuote,endQuote], " ")
while(cmpstr(strLine[endQuote-1], "\\")==0) // ignore escaped quotes
// found end of quote
if(endQuote>commentByte) // commentByte was within commented text
startByte=endQuote+1
break // look for another comment mark
else
startQuote=strsearch(strLine, "\"", endQuote+1)
if(startQuote==-1 || startQuote>commentByte) // no quotes before //, we're done
return commentByte
endif
endif
// if we get to here we've found another start-of-quoted-text before //
while(1) // next quoted text before //
while(1) // next comment marker
end
June 25, 2020 at 06:28 am - Permalink
Tony, thank you for your help with this. Indeed, properly selecting whole lines screwed up the code insertion thanks to the added handling of the first line in the last version. Oops. There were other hiccups as well.
I was searching for the start of a comment from the right. But as you have mentioned, two // in a comment or // in code without any comment would have been aligned as well. I have included your comment search function now. I hope that is OK.
I have added a new version (this time in the first post).
June 25, 2020 at 09:47 pm - Permalink
great. let me know if you find any problems with the comment search function. i updated the prettify code to use the more robust comment searching, though it's not critical to get it right for that purpose.
June 26, 2020 at 12:07 am - Permalink
Aligning comments is a nice feature!
But do we really want to align code with tabs even when we are indenting with tabs?I don't think this will work.
Consider the following example
variable a // some double
int i // some integer
End
If the comments ought to be right aligned they should be that for every tab width. As IMHO the tab width is settable by the user and that should not obstruct the code.
If I now align with tabs manually in IP
variable a // some double
int i // some integer
End
which looks different here in the forum as a tab consists of 8 spaces here vs 4 in IP. You need to copy and paste to IP to see that it is correctly aligned with a tab width of 4.
July 7, 2020 at 02:48 pm - Permalink
I am actually not sure I understand. Is this a problem with the snippet? Yes, aligning comments with tabs is futile if you are looking at the code in another software, as tab widths change widely. The above snippet only aligns the comments so that they look 'good' within Igor. Unless we get the feature that comments are aligned/shown in a separate space next to the code, no matter the number of tabs before them, there is really no other solution than to align the stuff for one program and stick with it (or use the align functionality within each of these programs if available).
By the way, an additional complication arises with the forum, since tabs are replaced with a number of space characters. I don't know a way to get the original code with tabs out of a post.
July 9, 2020 at 07:55 pm - Permalink