using System.Text.RegularExpressions; using Microsoft.VisualStudio.TextManager.Interop; namespace BooLangService { /// /// Checks the current users line and indents or dedents the next one. /// 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 ]?(?(?: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' : ' '; } /// /// Sets the indentation for the next lineNumber. /// 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; } /// /// Determines whether the current line should have the newline indented. /// /// Current line /// Whether newline line should be indented 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; } } }