jagregory / boolangstudio forked from olsonjeffery/boolangstudio

Boo language integration for Visual Studio 2008

This URL has Read+Write access

boolangstudio / Source / BooLangService / LineIndenter.cs
100644 118 lines (95 sloc) 4.055 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TextManager.Interop;
 
namespace BooLangService
{
    /// <summary>
    /// Checks the current users line and indents or dedents the next one.
    /// </summary>
    public class LineIndenter : ILineIndenter
    {
        // matches comments on the end of a line
        private readonly Regex removeCommentsRegExp = new Regex(@".*((?:#|\/\/|\/\*).*)$", RegexOptions.Compiled);
        // matches a dedenting code line (pass, return, return xxx, return if x = x, return xxx unless x = x, etc...)
        private readonly Regex dedentRegExp = new Regex(@"(?:return|pass)(?:[\t ]?[\w]*[\t ]?(?<exp>(?:if|unless)))?", RegexOptions.Compiled);
        private readonly ISource source;
        private readonly IVsTextView view;
        private readonly char IndentChar;
 
        public LineIndenter(ISource source, IVsTextView view)
        {
            this.source = source;
            this.view = view;
 
            IndentChar = source.UseTabs ? '\t' : ' ';
        }
 
        /// <summary>
        /// Sets the indentation for the next lineNumber.
        /// </summary>
        public void SetIndentationForNextLine(int lineNumber)
        {
            string previousLine = GetPreviousNonWhitespaceLine(lineNumber);
            int indentLevel = GetIndentLevel(previousLine);
 
            if (RequiresIndent(previousLine))
                indentLevel++;
            else if (RequiresDedent(previousLine) && indentLevel > 0)
                indentLevel--;
 
            var nextLine = "".PadRight(indentLevel, IndentChar);
            var firstChar = source.ScanToNonWhitespaceChar(lineNumber);
 
            source.SetText(lineNumber, firstChar, nextLine);
            view.PositionCaretForEditing(lineNumber, indentLevel);
        }
 
        private int GetIndentLevel(string line)
        {
            var count = 0;
 
            foreach (var c in line)
            {
                if (c != IndentChar)
                    break; // got to the text on the line
 
                count++;
            }
 
            return count;
        }
 
        private string GetPreviousNonWhitespaceLine(int startLine)
        {
            int prevLineIndex = startLine - 1;
            var lineText = source.GetLine(prevLineIndex);
 
            //searchig for not blank line
            while (prevLineIndex > 0 && string.IsNullOrEmpty(lineText))
            {
                prevLineIndex--;
                lineText = source.GetLine(prevLineIndex);
            }
 
            return lineText;
        }
 
        /// <summary>
        /// Determines whether the current line should have the newline indented.
        /// </summary>
        /// <param name="line">Current line</param>
        /// <returns>Whether newline line should be indented</returns>
        private bool RequiresIndent(string line)
        {
            line = PrepareLine(line); // cheat and make parsing easier
 
            return line.EndsWith(":");
        }
 
        private bool RequiresDedent(string line)
        {
            line = PrepareLine(line); // cheat and make parsing easier
            Match dedentMatch = dedentRegExp.Match(line);
 
            // my regex foo is not strong enough to do this in one, so match
            // a dedent line (i.e. return) but then check if it ends with an
            // expression (i.e. return "value" if x = 10). Only indent if
            // there isn't an expression
            if (dedentMatch.Success && !dedentMatch.Groups["exp"].Success)
                return true;
 
            return false;
        }
 
        private string PrepareLine(string line)
        {
            // remove comments, makes parsing easier
            Match commentMatch = removeCommentsRegExp.Match(line);
 
            if (commentMatch != null && commentMatch.Groups.Count >= 2)
                line = line.Remove(line.IndexOf(commentMatch.Groups[1].Value));
 
            // get rid of trailing whitespace
            line = line.TrimEnd();
 
            return line;
        }
    }
}