diff --git a/src/AddIns/BackendBindings/VBNetBinding/Project/Src/FormattingStrategy/VBNetFormattingStrategy.cs b/src/AddIns/BackendBindings/VBNetBinding/Project/Src/FormattingStrategy/VBNetFormattingStrategy.cs index 6074e9d3e9c..b3af366ebec 100644 --- a/src/AddIns/BackendBindings/VBNetBinding/Project/Src/FormattingStrategy/VBNetFormattingStrategy.cs +++ b/src/AddIns/BackendBindings/VBNetBinding/Project/Src/FormattingStrategy/VBNetFormattingStrategy.cs @@ -408,7 +408,6 @@ bool IsEndStatementNeeded(ITextEditor editor, ref VBStatement statement, int lin while ((currentToken = lexer.NextToken()).Kind != Tokens.EOF) { if (prevToken == null) prevToken = currentToken; - if (IsBlockStart(lexer, currentToken, prevToken)) { if ((tokens.Count > 0 && tokens.Peek().Kind != Tokens.Interface) || IsDeclaration(currentToken.Kind)) tokens.Push(currentToken); @@ -514,10 +513,9 @@ static int SmartIndentInternal(ITextEditor editor, int begin, int end) ILexer lexer = ParserFactory.CreateLexer(SupportedLanguage.VBNet, new StringReader(editor.Document.Text)); Stack indentation = new Stack(); - indentation.Push(string.Empty); - int oldLine = 1; + List eols = new List(); bool inInterface = false; bool isMustOverride = false; @@ -527,6 +525,8 @@ static int SmartIndentInternal(ITextEditor editor, int begin, int end) Token currentToken = null; Token prevToken = null; + int blockStart = 1; + while ((currentToken = lexer.NextToken()).Kind != Tokens.EOF) { if (prevToken == null) prevToken = currentToken; @@ -540,11 +540,16 @@ static int SmartIndentInternal(ITextEditor editor, int begin, int end) if (currentToken.Kind == Tokens.Declare) isDeclare = true; - if (currentToken.Kind == Tokens.EOL) + if (currentToken.Kind == Tokens.EOL) { isDelegate = isDeclare = isMustOverride = false; + eols.Add(currentToken.Location.Line); + } if (IsBlockEnd(currentToken, prevToken)) { - ApplyToRange(editor, indentation, oldLine, currentToken.Location.Line, begin, end); + // indent the lines inside the block + // this is an End-statement + // hence we indent from blockStart to the previous line + ApplyToRange(editor, indentation, eols, blockStart, currentToken.Location.Line - 1, begin, end); if (currentToken.Kind == Tokens.Interface) inInterface = false; @@ -556,12 +561,17 @@ static int SmartIndentInternal(ITextEditor editor, int begin, int end) Unindent(indentation); } - oldLine = currentToken.Location.Line; + // block start is this line (for the lines between two blocks) + blockStart = currentToken.Location.Line; } if (IsBlockStart(lexer, currentToken, prevToken)) { - int line = GetLastVisualLine(currentToken.Location.Line, editor); - ApplyToRange(editor, indentation, oldLine, line, begin, end); + // indent the lines between the last and this block + // this is a Begin-statement + // hence we indent from blockStart to the this line + int lastVisualLine = FindNextEol(lexer); + eols.Add(lastVisualLine); + ApplyToRange(editor, indentation, eols, blockStart, lastVisualLine, begin, end); if (!inInterface && !isMustOverride && !isDeclare && !isDelegate) { Indent(editor, indentation); @@ -573,31 +583,78 @@ static int SmartIndentInternal(ITextEditor editor, int begin, int end) if (currentToken.Kind == Tokens.Interface) inInterface = true; - oldLine = line + 1; + // block start is the following line (for the lines inside a block) + blockStart = lastVisualLine + 1; } prevToken = currentToken; } - // do last indent step - int newLine = prevToken.Location.Line; - - if (oldLine > newLine) - newLine = oldLine; - - ApplyToRange(editor, indentation, oldLine, newLine, begin, end); + ApplyToRange(editor, indentation, eols, blockStart, editor.Document.TotalNumberOfLines, begin, end); return (indentation.PeekOrDefault() ?? string.Empty).Length; } - static int GetLastVisualLine(int line, ITextEditor area) + static int FindNextEol(ILexer lexer) { - string text = area.Document.GetLine(line).Text.TrimComments(); - while (text.EndsWith("_", StringComparison.Ordinal)) { - line++; - text = area.Document.GetLine(line).Text.TrimComments(); + lexer.StartPeek(); + + Token t = lexer.Peek(); + + while (t.Kind != Tokens.EOL) + t = lexer.Peek(); + + return t.Location.Line; + } + + static void ApplyToRange(ITextEditor editor, Stack indentation, List eols, int blockStart, int blockEnd, int selectionStart, int selectionEnd) { + LoggingService.InfoFormatted("indenting line {0} to {1} with {2}", blockStart, blockEnd, (indentation.PeekOrDefault() ?? "").Length); + + int nextEol = -1; + bool wasMultiLine = false; + + for (int i = blockStart; i <= blockEnd; i++) { + IDocumentLine curLine = editor.Document.GetLine(i); + string lineText = curLine.Text.TrimStart(); + string noComments = lineText.TrimComments().TrimEnd(); + + // adjust indentation if the current line is not selected + // lines between the selection will be aligned to the selected level + if (i < selectionStart || i > selectionEnd) { + indentation.PopOrDefault(); + indentation.Push(DocumentUtilitites.GetWhitespaceAfter(editor.Document, curLine.Offset)); + } + + // look for next eol if line is not empty + // (the lexer does not produce eols for empty lines) + if (!string.IsNullOrEmpty(noComments) && i >= nextEol) { + int search = eols.BinarySearch(i); + if (search < 0) + search = ~search; + nextEol = search < eols.Count ? eols[search] : i; + } + + // remove indentation in last line of multiline array(, collection, object) initializers + if (i == nextEol && wasMultiLine && noComments == "}") { + wasMultiLine = false; + Unindent(indentation); + } + + // apply the indentation + editor.Document.SmartReplaceLine(curLine, (indentation.PeekOrDefault() ?? "") + lineText); + + // indent line if it is ended by (implicit) line continuation + if (i < nextEol && !wasMultiLine) { + wasMultiLine = true; + Indent(editor, indentation); + } + + // unindent if this is the last line of a multiline statement + if (i == nextEol && wasMultiLine) { + wasMultiLine = false; + Unindent(indentation); + } } - return line; } static void Unindent(Stack indentation) @@ -718,41 +775,6 @@ static bool IsSpecialCase(Token current, Token prev) return false; } - static void ApplyToRange(ITextEditor editor, Stack indentation, int begin, int end, int selBegin, int selEnd) - { - bool multiLine = false; - - for (int i = begin; i <= end; i++) { - IDocumentLine curLine = editor.Document.GetLine(i); - string lineText = curLine.Text.TrimStart(' ', '\t', '\r', '\n'); - string noComments = lineText.TrimComments().TrimEnd(' ', '\t', '\r', '\n'); - - if (i < selBegin || i > selEnd) { - indentation.PopOrDefault(); - indentation.Push(DocumentUtilitites.GetWhitespaceAfter(editor.Document, curLine.Offset)); - } - - // change indentation before (indent this line) - if (multiLine && noComments.EndsWith("}", StringComparison.OrdinalIgnoreCase)) { - Unindent(indentation); - multiLine = false; - } - - editor.Document.SmartReplaceLine(curLine, (indentation.PeekOrDefault() ?? string.Empty) + lineText); - - // change indentation afterwards (indent next line) - if (!multiLine && noComments.EndsWith("_", StringComparison.OrdinalIgnoreCase)) { - Indent(editor, indentation); - multiLine = true; - } - - if (multiLine && !noComments.EndsWith("_", StringComparison.OrdinalIgnoreCase)) { - multiLine = false; - Unindent(indentation); - } - } - } - /// /// Gets the next member after the specified caret position. /// diff --git a/src/AddIns/BackendBindings/VBNetBinding/Test/FormattingStrategy/IndentationTests.cs b/src/AddIns/BackendBindings/VBNetBinding/Test/FormattingStrategy/IndentationTests.cs index 3e17e459371..1ab60917482 100644 --- a/src/AddIns/BackendBindings/VBNetBinding/Test/FormattingStrategy/IndentationTests.cs +++ b/src/AddIns/BackendBindings/VBNetBinding/Test/FormattingStrategy/IndentationTests.cs @@ -9,7 +9,7 @@ namespace ICSharpCode.VBNetBinding.Tests { - [TestFixture, Ignore] + [TestFixture] public class IndentationTests { [Test] @@ -63,22 +63,93 @@ Sub Test2() } [Test] - public void ArrayInitializerTest() + public void SD1775_Fix() { string expected = @"Public Class Test Private Sub Tester() - test(asdf, _ - asdf, _ - asdf, _ - asdf) - - Dim test As Integer() = { _ - 2,2,3,34,4,5 _ - } + B = New Burger With {Cheese = True, _ + Lettuce = True, _ + Tomato = True, _ + CookLevel = MeatCookLevel.WellDone} End Sub End Class"; string code = @"Public Class Test + Private Sub Tester() + B = New Burger With {Cheese = True, _ + Lettuce = True, _ + Tomato = True, _ + CookLevel = MeatCookLevel.WellDone} + End Sub +End Class"; + + RunFormatTest(code, expected); + } + + [Test] + public void Simple() + { + string expected = @"' +' Created by SharpDevelop. +' User: Siegfried +' Date: 25.02.2011 +' Time: 14:41 +' +' To change this template use Tools | Options | Coding | Edit Standard Headers. +' +Imports Microsoft.VisualBasic.ApplicationServices + +Namespace My + ' This file controls the behaviour of the application. + Partial Class MyApplication + Public Sub New() + MyBase.New(AuthenticationMode.Windows) + Me.IsSingleInstance = False + Me.EnableVisualStyles = True + Me.SaveMySettingsOnExit = True + Me.ShutDownStyle = ShutdownMode.AfterMainFormCloses + End Sub + + Protected Overrides Sub OnCreateMainForm() + Me.MainForm = My.Forms.MainForm + End Sub + End Class +End Namespace"; + + string code = @"' +' Created by SharpDevelop. +' User: Siegfried +' Date: 25.02.2011 +' Time: 14:41 +' +' To change this template use Tools | Options | Coding | Edit Standard Headers. +' +Imports Microsoft.VisualBasic.ApplicationServices + +Namespace My +' This file controls the behaviour of the application. +Partial Class MyApplication +Public Sub New() +MyBase.New(AuthenticationMode.Windows) +Me.IsSingleInstance = False +Me.EnableVisualStyles = True +Me.SaveMySettingsOnExit = True +Me.ShutDownStyle = ShutdownMode.AfterMainFormCloses +End Sub + +Protected Overrides Sub OnCreateMainForm() +Me.MainForm = My.Forms.MainForm +End Sub +End Class +End Namespace"; + + RunFormatTest(code, expected); + } + + [Test] + public void ArrayInitializerTest() + { + string expected = @"Public Class Test Private Sub Tester() test(asdf, _ asdf, _ @@ -86,11 +157,24 @@ Private Sub Tester() asdf) Dim test As Integer() = { _ - 2,2,3,34,4,5 _ + 2,2,3,34,4,5 _ } End Sub End Class"; + string code = @"Public Class Test +Private Sub Tester() +test(asdf, _ +asdf, _ +asdf, _ +asdf) + +Dim test As Integer() = { _ +2,2,3,34,4,5 _ +} +End Sub +End Class"; + RunFormatTest(code, expected); } @@ -177,7 +261,7 @@ End Sub [Test] public void SelectCaseTest() { - string expected = @"Public Class Test + string expected = @"Public Class Test Private Sub Tester() Select Case a Case 0 @@ -215,7 +299,7 @@ End Sub [Test] public void SelectCaseTest2() { - string expected = @"Public Class Test + string expected = @"Public Class Test Private Sub Tester() Select Case a Case 0 @@ -247,7 +331,7 @@ End Sub [Test] public void WithTest() { - string expected = @"Public Class Test + string expected = @"Public Class Test Private Sub Tester() With a If True Then @@ -281,7 +365,7 @@ End Sub [Test] public void WithTest2() { - string expected = @"Public Class Test + string expected = @"Public Class Test Private Sub Tester() With a If True Then