AvalonEdit C#代码折叠策略类

背景

AvalonEdit是一个基于WPF的开源、可扩展文本编辑器,用于代码的编辑显示,还可实现语法高亮、代码折叠、自动补全等功能,在需要显示或编辑代码的应用程序中确是一个不错的选择。然美中不足的是这些功能多数还需要用户自己扩展。以代码折叠为例,类库中仅提供了Xml文档的折叠功能,其它语言的代码折叠尚需用户自己实现。所幸的是在其示例代码中提供了一个BraceFoldingStrategy类,实现了代码基于花括号{、}的折叠,为我们扩展其它内容的折叠提供了有益的借鉴。本文根据BraceFoldingStrategy的思路,增加了C#代码的using块、region块和注释块的折叠功能。

相关类

要编写自己的 Folding strategy类(这里名为CSharpFoldingStrategy)主要用到AvalonEdit类库的两个类:AbstractFoldingStrategy和NewFolding,这里先简要说明一下:

1. AbstractFoldingStrategy类

AbstractFoldingStrategy是所要编写的CSharpFoldingStrategy类的基类。它声明了两个公共方法:

public void UpdateFoldings(FoldingManager manager, TextDocument document)public abstract IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset)

在其子类中,第一个方法继承即可,关键是第二个方法,需要用户具体实现。

2. NewFolding类

NewFolding对象表示一个折叠,该类定义了以下属性:

  • int StartOffset //表示折叠的开始位置
  • int EndOffset //表示折叠的结束位置
  • string Name //折叠的名称,在折叠时显示
  • bool DefaultClosed //缺省状态是否折叠

有两个构造函数:

public NewFolding();
public NewFolding(int start,int end);

可见,编写CSharpFoldingStrategy类,关键是要找出所有需要折叠的代码块开始位置和结束位置,构造NewFolding对象。

基本思路

BraceFoldingStrategy的思路是逐字符扫描文档字符,遇到“{”,将其位置压入堆栈(starts),遇到“}”,如果starts不为空,弹出一个作为NewFolding的开始位置,将“}”所在位置的下一个字符位置作为结束位置,构造NewFolding对象。
鉴于我们所要增加的折叠,其开始、结束标志并非单个字符,而是一个字符串,所以我们还是用正则表达式匹配的办法来寻找开始、结束位置。我们的思路是将文档文本分割成文本行(自定义类TextLine对象,TextLine类代码详见完整代码),再逐行匹配开始、结束标志,开始标志匹配成功,将开始位置压入堆栈(starts),结束标志匹配成功,如果starts不为空,弹出一个作为开始位置,再将结束标志最后一个字符的下一个字符位置作为结束位置,构造NewFolding对象,添加到NewFloding列表之中。

代码示例

1. using块的折叠

private NewFolding UsingFolding(List<TextLine> lines)
{	
	int start = -1;
    for (int i = 0; i < lines.Count; i++)
    {
        TextLine line = lines[i];
        if (Regex.Match(line.Text, "using").Success)
        {
            if (start < 0) start = line.Offset;
        }
        else
        {
            if (start >= 0)
            {
                NewFolding fold = new NewFolding(start, line.Offset);
                fold.Name = "using";
                fold.DefaultClosed = true;
                return fold;
            }
        }
    }
    return null;
}

C#代码的using块一般都集中在一起,所以这里只返回一个NewFolding对象,是将找到的第一个“using”字符串的首字母位置作为开始位置,直到找到某一行中没有“using’字符串时,将这一行的开始位置作为折叠的结束位置,构造NewFolding对象并返回。当然这里隐含的一个条件是using块中没有空行,如果有空行,空行以后的using语句将被排除在外。如要避免这种情况,还得稍加处理。

2. 注释块的折叠

单行注释没有折叠的必要,所以这里针对的是多行注释(注释块)。

private List<NewFolding> NoteFoldings(List<TextLine> lines)
{
    List<NewFolding> folds = new List<NewFolding>();
    Stack<int> starts = new Stack<int>();
    Stack<string> names = new Stack<string>();
    int startLine = -1;
    for (int i = 0; i < lines.Count; i++)
    {
        TextLine line = lines[i];
        Match m = Regex.Match(line.Text, "/{2,}(\\s.+)$", RegexOptions.Multiline);
        if (m.Success)
        {
            if (startLine < 0)
            {
                starts.Push(line.Offset + m.Index);
                names.Push(m.Groups[1].Value);
                startLine = i;
            }
        }
        else if(starts.Count>0)
        {
            int start = starts.Pop();
            string name = names.Pop();
            if (i - startLine > 1)
            {
                m = Regex.Match(line.Text, "^\\s*", RegexOptions.Multiline);
                NewFolding fold = new NewFolding(start, line.Offset + (m.Success ? m.Length : 4));
                fold.Name =  "…";
                fold.DefaultClosed = true;
                folds.Add(fold);
                startLine = -1;
            }
        }
    }
    return folds;
}

注释块的开始标志是2个及以上的斜杠“/”,代码中的变量startLine(表示注释开始行),有两个作用,一是标志注释块有没有开始,当startLine<0时匹配到开始标志,将当前行号赋值给startLine,意味着注释块开始,随后再匹配到开始标志,则意味着在注释块内部,忽略之。二是判断是单行注释还是多行注释。当开始标志在某行中匹配失败时,意味着当前行不是注释行,将当前行号与startLine比较,看其差是否大于1,若是说明是多行注释,需要折叠,构造NewFolding对象,否则忽略。

3. region块的折叠

private List<NewFolding> RegionFoldings(List<TextLine> lines)
{
    List<NewFolding> folds = new List<NewFolding>();
    Stack<int> starts = new Stack<int>();
    Stack<string> names = new Stack<string>();            
    for (int i = 0; i < lines.Count; i++)
    {
        TextLine line = lines[i];
        Match m=Regex.Match(line.Text, "#region(\\s.+)$", RegexOptions.Multiline);
        if (m.Success)
        {
            starts.Push(line.Offset + m.Index);
            names.Push(m.Groups[1].Value);
        }
        m = Regex.Match(line.Text, "^\\s*#endregion", RegexOptions.Multiline);
        if (m.Success && starts.Count > 0)
        {
            int start=starts.Pop();
            NewFolding fold = new NewFolding(start, line.Offset + m.Length);
            fold.Name = names.Pop();
            fold.DefaultClosed = true;
            folds.Add(fold);
        }
    }
    return folds;
}

这里针对每一行,进行了两次匹配,第一次匹配开始标志"#region",匹配模式中一直匹配到行尾,主要是为了获取该块的描述以作为NewFolding对象的名称。第二次匹配结束标志“#endregion”,匹配模式中包含行首空格的匹配,是为了方便获取折叠的结束位置。

有了这些函数后,即可重写CreateNewFoldings()了。方法是将以上函数找到的NewFolding对象添加到一个List中,再按开始位置进行排序后即可返回,此略。

结束语

这里对每一类折叠对象分别构建,意味着要多次遍历文档文本中的所有行,可能会影响效率。一个改进的办法是将上节中的三个函数整合到一个函数中,在一次遍历中构造所有的折叠对象,但在这样的函数中须设置多个开始位置的堆栈,显然是以增加内存占用为代价的。

代码运行环境:Windows 7.0, VisualStudio 2010, AvalonEdit v4.0.30319
完整代码请从这里下载

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值