AvalonEdit FoldingStrategy by Indent (Python)
AvalonEdit FoldingStrategy by Indent (Python)
有人试过按缩进级别创建 FoldingStrategy 吗?喜欢编程语言 python.
BraceFoldingStrategy 没有问题,因为您有固定的开始和结束标记。有人想为制表符缩进创建这个吗?
这是一个功能最全的解决方案。我没有时间测试所有的可能性,但是我手边的一些 python 脚本(无可否认,它们的格式很好)表现良好。我在制作它时做了一些注释,以便它在折叠时显示行内容。基本上只是缓冲起始行,然后将其与元组或其他内容中的起始索引一起放在堆栈上。
我确信此代码无法正确处理许多带有注释的情况。这是你必须测试和调整的东西。该代码相对幼稚,因此您可能还想添加对关键字的检查,而不仅仅是 "if it has a colon it is a new folding start" 逻辑。此外,由于我之前折叠的经验,它是通过更复杂的字符迭代方式完成的。我尝试了其他 "easier" 方法,例如对行进行 运行 正则表达式检查,这可能非常非常慢。
最后,您可能想要实际使用错误偏移量,而不是像我所做的那样只是放弃。这应该很容易添加。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
using System.Text.RegularExpressions;
namespace Foo.languages
{
public class TabFoldingStrategy : AbstractFoldingStrategy
{
// How many spaces == one tab
private const int SpacesInTab = 4;
/// <summary>
/// Creates a new TabFoldingStrategy.
/// </summary>
public TabFoldingStrategy() {
}
/// <summary>
/// Create <see cref="NewFolding"/>s for the specified document.
/// </summary>
public override IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset)
{
firstErrorOffset = -1;
return CreateNewFoldingsByLine(document);
}
/// <summary>
/// Create <see cref="NewFolding"/>s for the specified document.
/// </summary>
public IEnumerable<NewFolding> CreateNewFoldingsByLine(ITextSource document)
{
List<NewFolding> newFoldings = new List<NewFolding>();
if (document == null || (document as TextDocument).LineCount <= 1)
{
return newFoldings;
}
//Can keep track of offset ourself and from testing it seems to be accurate
int offsetTracker = 0;
// Keep track of start points since things nest
Stack<int> startOffsets = new Stack<int>();
StringBuilder lineBuffer = new StringBuilder();
foreach (DocumentLine line in (document as TextDocument).Lines)
{
if (offsetTracker >= document.TextLength)
{
break;
}
lineBuffer.Clear();
// First task is to get the line and figure out the spacing in front of it
int spaceCounter = 0;
bool foundText = false;
bool foundColon = false;
//for (int i = 0; i < line.Length; i++)
int i = 0;
//TODO buffer the characters so you can have the line contents on the stack too for the folding name (display text)
while (i < line.Length && !(foundText && foundColon))
{
char c = document.GetCharAt(offsetTracker + i);
switch (c)
{
case ' ': // spaces count as one
if (!foundText) {
spaceCounter++;
}
break;
case '\t': // Tabs count as N
if (!foundText) {
spaceCounter += SpacesInTab;
}
break;
case ':': // Tabs count as N
foundColon = true;
break;
default: // anything else means we encountered not spaces or tabs, so keep making the line but stop counting
foundText = true;
break;
}
i++;
}
// before we continue, we need to make sure its a correct multiple
int remainder = spaceCounter % SpacesInTab;
if (remainder > 0)
{
// Some tabbing isn't correct. ignore this line for folding purposes.
// This may break all foldings below that, but it's a complex problem to address.
continue;
}
// Now we need to figure out if this line is a new folding by checking its tabing
// relative to the current stack count. Convert into virtual tabs and compare to stack level
int numTabs = spaceCounter / SpacesInTab; // we know this will be an int because of the above check
if (numTabs >= startOffsets.Count && foundText && foundColon)
{
// we are starting a new folding
startOffsets.Push(offsetTracker);
}
else // numtabs < offsets
{
// we know that this is the end of a folding. It could be the end of multiple foldings. So pop until it matches.
while (numTabs < startOffsets.Count)
{
int foldingStart = startOffsets.Pop();
NewFolding tempFolding = new NewFolding();
//tempFolding.Name = < could add logic here, possibly by tracking key words when starting the folding, to control what is shown when it's folded >
tempFolding.StartOffset = foldingStart;
tempFolding.EndOffset = offsetTracker - 2;
newFoldings.Add(tempFolding);
}
}
// Increment tracker. Much faster than getting it from the line
offsetTracker += line.TotalLength;
}
// Complete last foldings
while (startOffsets.Count > 0)
{
int foldingStart = startOffsets.Pop();
NewFolding tempFolding = new NewFolding();
//tempFolding.Name = < could add logic here, possibly by tracking key words when starting the folding, to control what is shown when it's folded >
tempFolding.StartOffset = foldingStart;
tempFolding.EndOffset = offsetTracker;
newFoldings.Add(tempFolding);
}
newFoldings.Sort((a, b) => (a.StartOffset.CompareTo(b.StartOffset)));
return newFoldings;
}
}
}
我认识到 AvalonEdit.TextDocument 也可以使用基于行的。所以我能够创建自己的解决方案:
using System;
using System.Collections.Generic;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Folding
{
/// <summary>
/// Allows producing tab based foldings
/// </summary>
public class TabFoldingStrategy : AbstractFoldingStrategy
{
internal class TabIndent
{
public int IndentSize;
public int LineStart;
public int LineEnd;
public int StartOffset => LineStart + IndentSize - 1;
public int TextLength => LineEnd - StartOffset;
public TabIndent(int i_indentSize, int i_lineStart, int i_lineEnd)
{
IndentSize = i_indentSize;
LineStart = i_lineStart;
LineEnd = i_lineEnd;
}
}
/// <summary>
/// Creates a new TabFoldingStrategy.
/// </summary>
public TabFoldingStrategy()
{
}
/// <summary>
/// Create <see cref="NewFolding"/>s for the specified document.
/// </summary>
public override IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset)
{
firstErrorOffset = -1;
return CreateNewFoldings(document);
}
/// <summary>
/// Create <see cref="NewFolding"/>s for the specified document.
/// </summary>
public IEnumerable<NewFolding> CreateNewFoldings(TextDocument document)
{
List<NewFolding> newFoldings = new List<NewFolding>();
int documentIndent = 0;
List<TabIndent> tabIndents = new List<TabIndent>();
foreach (DocumentLine line in document.Lines) {
int lineIndent = 0;
for (int i = line.Offset; i < line.EndOffset; i++) {
char c = document.GetCharAt(i);
if (c == '\t') {
lineIndent++;
} else {
break;
}
}
if (lineIndent > documentIndent) {
tabIndents.Add(new TabIndent(lineIndent, line.PreviousLine.Offset, line.PreviousLine.EndOffset));
} else if (lineIndent < documentIndent) {
List<TabIndent> closedIndents = tabIndents.FindAll(x => x.IndentSize > lineIndent);
closedIndents.ForEach(x => {
newFoldings.Add(new NewFolding(x.StartOffset, line.PreviousLine.EndOffset) {
Name = document.GetText(x.StartOffset, x.TextLength)
});
tabIndents.Remove(x);
});
}
documentIndent = lineIndent;
}
tabIndents.ForEach(x => {
newFoldings.Add(new NewFolding(x.StartOffset, document.TextLength));
});
newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset));
return newFoldings;
}
}
}
更新:
我认为这在有人将 python 与 AvalonEdit 一起使用时也非常有用。深色主题的语法高亮 xshd:
<?xml version="1.0"?>
<SyntaxDefinition name ="Python" extensions = ".py" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Comment" foreground="#808080" />
<Color name="String" foreground="#6A8759" />
<Color name="Keywords" foreground="#c75454" fontWeight="bold" />
<Color name="NumAndTypes" foreground="#21b0b0" />
<Color name="FunctionCall" foreground="#38a1d4" />
<Color name="Words" foreground="#8fb1ba" />
<RuleSet>
<Span color="Comment">
<Begin>\#</Begin>
</Span>
<Span color="String" multiline="true">
<Begin>'</Begin>
<End>'</End>
</Span>
<Span color="String" multiline="true">
<Begin>"</Begin>
<End>"</End>
</Span>
<!-- Digits -->
<Rule color="NumAndTypes">
\b0[xX][0-9a-fA-F]+ # hex number
|
\b0[0-9]+ # octal number
|
( \b\d+(\.[0-9]+)? #number with optional floating point
| \.[0-9]+ #or just starting with floating point
)
([eE][+-]?[0-9]+)? # optional exponent
</Rule>
<Keywords color="NumAndTypes">
<Word>False</Word>
<Word>True</Word>
<Word>None</Word>
</Keywords>
<Keywords color="Keywords">
<Word>class</Word>
<Word>finally</Word>
<Word>is</Word>
<Word>return</Word>
<Word>continue</Word>
<Word>for</Word>
<Word>lambda</Word>
<Word>try</Word>
<Word>def</Word>
<Word>from</Word>
<Word>nonlocal</Word>
<Word>while</Word>
<Word>and</Word>
<Word>del</Word>
<Word>global</Word>
<Word>not</Word>
<Word>with</Word>
<Word>as</Word>
<Word>elif</Word>
<Word>if</Word>
<Word>or</Word>
<Word>yield</Word>
<Word>assert</Word>
<Word>else</Word>
<Word>import</Word>
<Word>pass</Word>
<Word>break</Word>
<Word>except</Word>
<Word>in</Word>
<Word>raise</Word>
</Keywords>
<Rule color="FunctionCall">
\b
[\d\w_]+ # an identifier
(?=\s*\() # followed by (
</Rule>
<Rule color="Words">\w+</Rule>
</RuleSet>
</SyntaxDefinition>
有人试过按缩进级别创建 FoldingStrategy 吗?喜欢编程语言 python.
BraceFoldingStrategy 没有问题,因为您有固定的开始和结束标记。有人想为制表符缩进创建这个吗?
这是一个功能最全的解决方案。我没有时间测试所有的可能性,但是我手边的一些 python 脚本(无可否认,它们的格式很好)表现良好。我在制作它时做了一些注释,以便它在折叠时显示行内容。基本上只是缓冲起始行,然后将其与元组或其他内容中的起始索引一起放在堆栈上。
我确信此代码无法正确处理许多带有注释的情况。这是你必须测试和调整的东西。该代码相对幼稚,因此您可能还想添加对关键字的检查,而不仅仅是 "if it has a colon it is a new folding start" 逻辑。此外,由于我之前折叠的经验,它是通过更复杂的字符迭代方式完成的。我尝试了其他 "easier" 方法,例如对行进行 运行 正则表达式检查,这可能非常非常慢。
最后,您可能想要实际使用错误偏移量,而不是像我所做的那样只是放弃。这应该很容易添加。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
using System.Text.RegularExpressions;
namespace Foo.languages
{
public class TabFoldingStrategy : AbstractFoldingStrategy
{
// How many spaces == one tab
private const int SpacesInTab = 4;
/// <summary>
/// Creates a new TabFoldingStrategy.
/// </summary>
public TabFoldingStrategy() {
}
/// <summary>
/// Create <see cref="NewFolding"/>s for the specified document.
/// </summary>
public override IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset)
{
firstErrorOffset = -1;
return CreateNewFoldingsByLine(document);
}
/// <summary>
/// Create <see cref="NewFolding"/>s for the specified document.
/// </summary>
public IEnumerable<NewFolding> CreateNewFoldingsByLine(ITextSource document)
{
List<NewFolding> newFoldings = new List<NewFolding>();
if (document == null || (document as TextDocument).LineCount <= 1)
{
return newFoldings;
}
//Can keep track of offset ourself and from testing it seems to be accurate
int offsetTracker = 0;
// Keep track of start points since things nest
Stack<int> startOffsets = new Stack<int>();
StringBuilder lineBuffer = new StringBuilder();
foreach (DocumentLine line in (document as TextDocument).Lines)
{
if (offsetTracker >= document.TextLength)
{
break;
}
lineBuffer.Clear();
// First task is to get the line and figure out the spacing in front of it
int spaceCounter = 0;
bool foundText = false;
bool foundColon = false;
//for (int i = 0; i < line.Length; i++)
int i = 0;
//TODO buffer the characters so you can have the line contents on the stack too for the folding name (display text)
while (i < line.Length && !(foundText && foundColon))
{
char c = document.GetCharAt(offsetTracker + i);
switch (c)
{
case ' ': // spaces count as one
if (!foundText) {
spaceCounter++;
}
break;
case '\t': // Tabs count as N
if (!foundText) {
spaceCounter += SpacesInTab;
}
break;
case ':': // Tabs count as N
foundColon = true;
break;
default: // anything else means we encountered not spaces or tabs, so keep making the line but stop counting
foundText = true;
break;
}
i++;
}
// before we continue, we need to make sure its a correct multiple
int remainder = spaceCounter % SpacesInTab;
if (remainder > 0)
{
// Some tabbing isn't correct. ignore this line for folding purposes.
// This may break all foldings below that, but it's a complex problem to address.
continue;
}
// Now we need to figure out if this line is a new folding by checking its tabing
// relative to the current stack count. Convert into virtual tabs and compare to stack level
int numTabs = spaceCounter / SpacesInTab; // we know this will be an int because of the above check
if (numTabs >= startOffsets.Count && foundText && foundColon)
{
// we are starting a new folding
startOffsets.Push(offsetTracker);
}
else // numtabs < offsets
{
// we know that this is the end of a folding. It could be the end of multiple foldings. So pop until it matches.
while (numTabs < startOffsets.Count)
{
int foldingStart = startOffsets.Pop();
NewFolding tempFolding = new NewFolding();
//tempFolding.Name = < could add logic here, possibly by tracking key words when starting the folding, to control what is shown when it's folded >
tempFolding.StartOffset = foldingStart;
tempFolding.EndOffset = offsetTracker - 2;
newFoldings.Add(tempFolding);
}
}
// Increment tracker. Much faster than getting it from the line
offsetTracker += line.TotalLength;
}
// Complete last foldings
while (startOffsets.Count > 0)
{
int foldingStart = startOffsets.Pop();
NewFolding tempFolding = new NewFolding();
//tempFolding.Name = < could add logic here, possibly by tracking key words when starting the folding, to control what is shown when it's folded >
tempFolding.StartOffset = foldingStart;
tempFolding.EndOffset = offsetTracker;
newFoldings.Add(tempFolding);
}
newFoldings.Sort((a, b) => (a.StartOffset.CompareTo(b.StartOffset)));
return newFoldings;
}
}
}
我认识到 AvalonEdit.TextDocument 也可以使用基于行的。所以我能够创建自己的解决方案:
using System;
using System.Collections.Generic;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Folding
{
/// <summary>
/// Allows producing tab based foldings
/// </summary>
public class TabFoldingStrategy : AbstractFoldingStrategy
{
internal class TabIndent
{
public int IndentSize;
public int LineStart;
public int LineEnd;
public int StartOffset => LineStart + IndentSize - 1;
public int TextLength => LineEnd - StartOffset;
public TabIndent(int i_indentSize, int i_lineStart, int i_lineEnd)
{
IndentSize = i_indentSize;
LineStart = i_lineStart;
LineEnd = i_lineEnd;
}
}
/// <summary>
/// Creates a new TabFoldingStrategy.
/// </summary>
public TabFoldingStrategy()
{
}
/// <summary>
/// Create <see cref="NewFolding"/>s for the specified document.
/// </summary>
public override IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset)
{
firstErrorOffset = -1;
return CreateNewFoldings(document);
}
/// <summary>
/// Create <see cref="NewFolding"/>s for the specified document.
/// </summary>
public IEnumerable<NewFolding> CreateNewFoldings(TextDocument document)
{
List<NewFolding> newFoldings = new List<NewFolding>();
int documentIndent = 0;
List<TabIndent> tabIndents = new List<TabIndent>();
foreach (DocumentLine line in document.Lines) {
int lineIndent = 0;
for (int i = line.Offset; i < line.EndOffset; i++) {
char c = document.GetCharAt(i);
if (c == '\t') {
lineIndent++;
} else {
break;
}
}
if (lineIndent > documentIndent) {
tabIndents.Add(new TabIndent(lineIndent, line.PreviousLine.Offset, line.PreviousLine.EndOffset));
} else if (lineIndent < documentIndent) {
List<TabIndent> closedIndents = tabIndents.FindAll(x => x.IndentSize > lineIndent);
closedIndents.ForEach(x => {
newFoldings.Add(new NewFolding(x.StartOffset, line.PreviousLine.EndOffset) {
Name = document.GetText(x.StartOffset, x.TextLength)
});
tabIndents.Remove(x);
});
}
documentIndent = lineIndent;
}
tabIndents.ForEach(x => {
newFoldings.Add(new NewFolding(x.StartOffset, document.TextLength));
});
newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset));
return newFoldings;
}
}
}
更新:
我认为这在有人将 python 与 AvalonEdit 一起使用时也非常有用。深色主题的语法高亮 xshd:
<?xml version="1.0"?>
<SyntaxDefinition name ="Python" extensions = ".py" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Comment" foreground="#808080" />
<Color name="String" foreground="#6A8759" />
<Color name="Keywords" foreground="#c75454" fontWeight="bold" />
<Color name="NumAndTypes" foreground="#21b0b0" />
<Color name="FunctionCall" foreground="#38a1d4" />
<Color name="Words" foreground="#8fb1ba" />
<RuleSet>
<Span color="Comment">
<Begin>\#</Begin>
</Span>
<Span color="String" multiline="true">
<Begin>'</Begin>
<End>'</End>
</Span>
<Span color="String" multiline="true">
<Begin>"</Begin>
<End>"</End>
</Span>
<!-- Digits -->
<Rule color="NumAndTypes">
\b0[xX][0-9a-fA-F]+ # hex number
|
\b0[0-9]+ # octal number
|
( \b\d+(\.[0-9]+)? #number with optional floating point
| \.[0-9]+ #or just starting with floating point
)
([eE][+-]?[0-9]+)? # optional exponent
</Rule>
<Keywords color="NumAndTypes">
<Word>False</Word>
<Word>True</Word>
<Word>None</Word>
</Keywords>
<Keywords color="Keywords">
<Word>class</Word>
<Word>finally</Word>
<Word>is</Word>
<Word>return</Word>
<Word>continue</Word>
<Word>for</Word>
<Word>lambda</Word>
<Word>try</Word>
<Word>def</Word>
<Word>from</Word>
<Word>nonlocal</Word>
<Word>while</Word>
<Word>and</Word>
<Word>del</Word>
<Word>global</Word>
<Word>not</Word>
<Word>with</Word>
<Word>as</Word>
<Word>elif</Word>
<Word>if</Word>
<Word>or</Word>
<Word>yield</Word>
<Word>assert</Word>
<Word>else</Word>
<Word>import</Word>
<Word>pass</Word>
<Word>break</Word>
<Word>except</Word>
<Word>in</Word>
<Word>raise</Word>
</Keywords>
<Rule color="FunctionCall">
\b
[\d\w_]+ # an identifier
(?=\s*\() # followed by (
</Rule>
<Rule color="Words">\w+</Rule>
</RuleSet>
</SyntaxDefinition>