The problem is Scintilla control does the folding automatically, which means the folding doesn't work if you do your own lexer.
I'm using this to configure the control if you want to take a look at the code:
https://github.com/robinrodricks/ScintillaNET.Demo
And this explains how to configure the code folder:
https://github.com/jacobslusser/ScintillaNET/wiki/Automatic-Code-Folding
Said that, I have made my own lexer which means the code folder doesn't work. Does anyone know how to make it work with a custom lexer?
I only want the code to fold if {
}
.
(.NET 7 Winforms C#)
You ask an excellent question, for which there is currently very little information available. Also, the links you provide are exactly where to look for clues how this can be implemented.
I would also suggest the following, which discusses the implementation of folding for a 'Container' Lexer: https://github.com/LuaDist/scintilla/blob/master/doc/Lexer.txt
The other key source is the ScintillaNET source code https://github.com/jacobslusser/ScintillaNET, which provides many of the variable declarations required to implement code folding cleanly.
I would suggest something similar to the following:
public class FoldTextControl : ScintillaNET.Scintilla
{
public FoldTextControl() : base()
{
Lexer = Lexer.Container;
SetupFolding();
}
private void SetupFolding()
{
const int FOLD_LEVEL_BASE = 0x400; // Copied from sealed ScintillaNET.NativeMethods class
const int FOLD_MARGIN = 2;
int[] FolderIndices = new int[]
{
Marker.Folder,
Marker.FolderEnd,
Marker.FolderMidTail,
Marker.FolderOpen,
Marker.FolderOpenMid,
Marker.FolderSub,
Marker.FolderTail,
}
Margins[FOLD_MARGIN].Type = MarginType.Symbol;
Margins[FOLD_MARGIN].Mask = Marker.MaskFolders;
Margins[FOLD_MARGIN].Sensitive = true;
Margins[FOLD_MARGIN].Width = 20;
foreach(int index in FolderIndices)
{
Markers[index].SetForeColor(SystemColors.ControlLightLight);
Markers[index].SetBackColor(SystemColors.ControlDark);
}
Markers[Marker.Folder].Symbol = MarkerSymbol.BoxPlus;
Markers[Marker.FolderOpen].Symbol = MarkerSymbol.BoxMinus;
Markers[Marker.FolderEnd].Symbol = MarkerSymbol.BoxPlusConnected;
Markers[Marker.FolderMidTail].Symbol = MarkerSymbol.TCorner;
Markers[Marker.FolderOpenMid].Symbol = MarkerSymbol.BoxMinusConnected;
Markers[Marker.FolderSub].Symbol = MarkerSymbol.VLine;
Markers[Marker.FolderTail].Symbol = MarkerSymbol.LCorner;
AutomaticFold = AutomaticFold.Show | AutomaticFold.Click | AutomaticFold.Change;
StyleNeeded += (sender, e) =>
{
for(int index = 0; index < Lines.Count; index++)
{
string lineText = Lines[index].Text;
Lines[index].FoldLevel = FOLD_LEVEL_BASE;
/*
Your custom code goes here to implement folding by doing the following:
Lines[index].FoldLevel += [Add a score for where this line fits in the heirachy of line folding]
if(lineText.Trim() == "")
Lines[index].FoldLevelFlags = FoldLevel.White; // Line is whitespace within the fold heirachy
if([Line should be a fold point])
Lines[index].FoldLevelFlags = FoldLevel.Header;
*/
}
};
}
}
There is documentation suggesting that SetProperty("fold", "1") or SetProperty("fold.compact", "1") are required. It seems to work fine without this, so I wonder if this advice is specific to non-'Container' type lexers.
Setting the fold level starting from FOLD_LEVEL_BASE is critical if you need the correct fold markers to display.
For a minimum viable example of ScintillaNET line folding, here is what I use for whitespace based line folding.
StyleNeeded += (sender, e) =>
{
for(int index = 0; index < Lines.Count; index++)
{
string lineText = Lines[index].Text;
Lines[index].FoldLevel = FOLD_LEVEL_BASE;
if(lineText.Trim() == "" && !lineText.Contains(' '))
continue;
int indentLevel = lineText.TakeWhile(char.IsWhiteSpace).Count();
Lines[index].FoldLevel += indentLevel;
if(lineText.Trim() == "")
Lines[index].FoldLevelFlags = FoldLevelFlags.White;
else
Lines[index].FoldLevelFlags = FoldLevelFlags.Header;
}
};
For your requirements, you may want something like the following:
StyleNeeded += (sender, e) =>
{
int indentLevel = 0;
for(int index = 0; index < Lines.Count; index++)
{
Lines[index].FoldLevel = FOLD_LEVEL_BASE + indentLevel;
string lineText = Lines[index].Text;
if(lineText.Trim() == "")
{
Lines[index].FoldLevelFlags = FoldLevelFlags.White;
continue;
}
foreach(char c in line)
{
if(c == '{')
{
indentLevel++;
Lines[index].FoldLevelFlags = FoldLevelFlags.Header;
}
else if(c == '}')
{
indentLevel--;
}
}
}
};