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>