3

I am working on a Code Editor derived from Winforms RichTextBox using C#. I have already implemented autocompletion and syntax hilighting, but code folding is somewhat a different approach. What I want to achieve is:

The code below:

public static SomeFunction(EventArgs e)
{
    //Some code
    //Some code
    //Some code
    //Some code
    //Some code
    //Some code
}

Should become:

public static SomeFunction(EventArgs e)[...]

Where[...] is a shortened code that is displayed in a tooltip when you hover over at [...] Any ideas or suggestions how to do it, either using Regex or procedural code?

Patrick
  • 1,717
  • 7
  • 21
  • 28
devavx
  • 1,035
  • 9
  • 22
  • 7
    It's highly unlikely you'll get this to work with regular expressions. Putting a stray `{` or `}` in a comment or quoted string would give any regex solution fits. You'd probably be better off using [Roslyn](http://msdn.microsoft.com/en-us/vstudio/roslyn.aspx) to lex and parse the source. – Jim Mischel Aug 09 '13 at 22:18
  • 2
    The solution seems fairly obvious: you will need to maintain knowledge of code blocks. What have you tried? Naive implementation is to use a stack that is pushed when you have non-comment/string `{` and popped on `}`. – lukegravitt Aug 09 '13 at 22:28
  • hey lukegravitt,can you show me some hint on how to do it? – devavx Aug 09 '13 at 22:32
  • 1
    http://www.codeproject.com/Articles/42490/Using-AvalonEdit-WPF-Text-Editor – I4V Aug 09 '13 at 23:25
  • It wouldn't answer your question directly, but you could instead draw your own (or extend someone else's custom drawn) text editor. I would break up the text into lines and spans. Where spans are segments of similar text, such as a reserved keyword, defined class, opening brace, collapsed code block and more. Being able to give your spans such direct attributes will make your project easier in the long run. – Kind Contributor Sep 30 '13 at 23:46

1 Answers1

2

I have created a parser that will return the indices of code folding locations.

  • Folding delimiters are defined by regular expressions.
  • You can specify a start and ending index so that you don't have to check the entire code when one area is updated.
  • It will throw exceptions if the code is not properly formatted, feel free to change that behavior. One alternative could be that it keeps moving up the stack until an appropriate end token is found.

Fold Finder

public class FoldFinder
{
    public static FoldFinder Instance { get; private set; }

    static FoldFinder()
    {
        Instance = new FoldFinder();
    }

    public List<SectionPosition> Find(string code, List<SectionDelimiter> delimiters, int start = 0,
        int end = -1)
    {
        List<SectionPosition> positions = new List<SectionPosition>();
        Stack<SectionStackItem> stack = new Stack<SectionStackItem>();

        int regexGroupIndex;
        bool isStartToken;
        SectionDelimiter matchedDelimiter;
        SectionStackItem currentItem;

        Regex scanner = RegexifyDelimiters(delimiters);

        foreach (Match match in scanner.Matches(code, start))
        {
            // the pattern for every group is that 0 corresponds to SectionDelimter, 1 corresponds to Start
            // and 2, corresponds to End.
            regexGroupIndex = 
                match.Groups.Cast<Group>().Select((g, i) => new {
                    Success = g.Success,
                    Index = i
                })
                .Where(r => r.Success && r.Index > 0).First().Index;

            matchedDelimiter = delimiters[(regexGroupIndex - 1) / 3];
            isStartToken = match.Groups[regexGroupIndex + 1].Success;

            if (isStartToken)
            {
                stack.Push(new SectionStackItem()
                {
                    Delimter = matchedDelimiter,
                    Position = new SectionPosition() { Start = match.Index }
                });
            }
            else
            {
                currentItem = stack.Pop();
                if (currentItem.Delimter == matchedDelimiter)
                {
                    currentItem.Position.End = match.Index + match.Length;
                    positions.Add(currentItem.Position);

                    // if searching for an end, and we've passed it, and the stack is empty then quit.
                    if (end > -1 && currentItem.Position.End >= end && stack.Count == 0) break;
                }
                else
                {
                    throw new Exception(string.Format("Invalid Ending Token at {0}", match.Index)); 
                }
            }
        }

        if (stack.Count > 0) throw new Exception("Not enough closing symbols.");

        return positions;
    }

    public Regex RegexifyDelimiters(List<SectionDelimiter> delimiters)
    {
        return new Regex(
            string.Join("|", delimiters.Select(d =>
                string.Format("(({0})|({1}))", d.Start, d.End))));
    }

}

public class SectionStackItem
{
    public SectionPosition Position;
    public SectionDelimiter Delimter;
}

public class SectionPosition
{
    public int Start;
    public int End;
}

public class SectionDelimiter
{
    public string Start;
    public string End;
}

Sample Find

The sample below matches folds delimited by {,}, [,], and right after a symbol until a ;. I don't see too many IDE's that fold for each line, but it might be handy at long pieces of code, like a LINQ query.

var sectionPositions = 
    FoldFinder.Instance.Find("abc { def { qrt; ghi [ abc ] } qrt }", new List<SectionDelimiter>(
        new SectionDelimiter[3] {
            new SectionDelimiter() { Start = "\\{", End = "\\}" },
            new SectionDelimiter() { Start = "\\[", End = "\\]" },
            new SectionDelimiter() { Start = "(?<=\\[|\\{|;|^)[^[{;]*(?=;)", End = ";" },
        }));
Daniel Gimenez
  • 18,530
  • 3
  • 50
  • 70