diff --git a/src/EditorFeatures/CSharp/Formatting/CSharpEditorFormattingService.cs b/src/EditorFeatures/CSharp/Formatting/CSharpEditorFormattingService.cs index 1887ee71c238b3750db244d09ecb87c285cdae8b..a3fa97cced0a0b77e126f9fb23fac2e29cde55b1 100644 --- a/src/EditorFeatures/CSharp/Formatting/CSharpEditorFormattingService.cs +++ b/src/EditorFeatures/CSharp/Formatting/CSharpEditorFormattingService.cs @@ -39,7 +39,7 @@ public CSharpEditorFormattingService() public bool SupportsFormatDocument => true; public bool SupportsFormatOnPaste => true; public bool SupportsFormatSelection => true; - public bool SupportsFormatOnReturn => true; + public bool SupportsFormatOnReturn => false; public bool SupportsFormattingOnTypedCharacter(Document document, char ch) { @@ -125,53 +125,11 @@ private IEnumerable GetFormattingRules(Document document { var workspace = document.Project.Solution.Workspace; var formattingRuleFactory = workspace.Services.GetService(); - return formattingRuleFactory.CreateRule(document, position).Concat(GetTypingRules(document, tokenBeforeCaret)).Concat(Formatter.GetDefaultFormattingRules(document)); + return formattingRuleFactory.CreateRule(document, position).Concat(GetTypingRules(tokenBeforeCaret)).Concat(Formatter.GetDefaultFormattingRules(document)); } - public async Task> GetFormattingChangesOnReturnAsync(Document document, int caretPosition, CancellationToken cancellationToken) - { - var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); - if (!options.GetOption(FeatureOnOffOptions.AutoFormattingOnReturn)) - { - return null; - } - - // first, find the token user just typed. - SyntaxToken token = await GetTokenBeforeTheCaretAsync(document, caretPosition, cancellationToken).ConfigureAwait(false); - if (token.IsMissing) - { - return null; - } - - var formattingRules = this.GetFormattingRules(document, caretPosition, token); - - string text = null; - if (IsInvalidToken(token, ref text)) - { - return null; - } - - // Check to see if the token is ')' and also the parent is a using statement. If not, bail - if (TokenShouldNotFormatOnReturn(token)) - { - return null; - } - - // if formatting range fails, do format token one at least - var changes = await FormatRangeAsync(document, token, formattingRules, cancellationToken).ConfigureAwait(false); - if (changes.Count > 0) - { - return changes; - } - - // if we can't, do normal smart indentation - return await FormatTokenAsync(document, token, formattingRules, cancellationToken).ConfigureAwait(false); - } - - private static bool TokenShouldNotFormatOnReturn(SyntaxToken token) - { - return !token.IsKind(SyntaxKind.CloseParenToken) || !token.Parent.IsKind(SyntaxKind.UsingStatement); - } + Task> IEditorFormattingService.GetFormattingChangesOnReturnAsync(Document document, int caretPosition, CancellationToken cancellationToken) + => SpecializedTasks.Default>(); private static async Task TokenShouldNotFormatOnTypeCharAsync( SyntaxToken token, CancellationToken cancellationToken) @@ -354,7 +312,7 @@ private ISmartTokenFormatter CreateSmartTokenFormatter(OptionSet optionSet, IEnu return changes; } - private IEnumerable GetTypingRules(Document document, SyntaxToken tokenBeforeCaret) + private IEnumerable GetTypingRules(SyntaxToken tokenBeforeCaret) { // Typing introduces several challenges around formatting. // Historically we've shipped several triggers that cause formatting to happen directly while typing. @@ -464,33 +422,6 @@ private bool ValidSingleOrMultiCharactersTokenKind(char typedChar, SyntaxKind ki } } - private bool IsInvalidToken(char typedChar, SyntaxToken token) - { - string text = null; - if (IsInvalidToken(token, ref text)) - { - return true; - } - - return text[0] != typedChar; - } - - private bool IsInvalidToken(SyntaxToken token, ref string text) - { - if (IsInvalidTokenKind(token)) - { - return true; - } - - text = token.ToString(); - if (text.Length != 1) - { - return true; - } - - return false; - } - private bool IsInvalidTokenKind(SyntaxToken token) { // invalid token to be formatted diff --git a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs index 1f0f3ed7b75613847f96b2edff4ee7303d16b6ca..4a1823e69b4c344977efa73013989277d55068c4 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs @@ -119,7 +119,7 @@ static void Main(string[] args) [WpfFact] [WorkItem(912965, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/912965")] [Trait(Traits.Feature, Traits.Features.Formatting)] - public void FormatUsingStatementOnReturn() + public void DoNotFormatUsingStatementOnReturn() { var code = @"class Program { @@ -136,7 +136,7 @@ static void Main(string[] args) static void Main(string[] args) { using (null) - using (null)$$ + using (null)$$ } } "; @@ -144,6 +144,34 @@ static void Main(string[] args) AssertFormatWithPasteOrReturn(expected, code, allowDocumentChanges: true, isPaste: false); } + [WpfFact] + [WorkItem(912965, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/912965")] + [Trait(Traits.Feature, Traits.Features.Formatting)] + public void FormatUsingStatementWhenTypingCloseParen() + { + var code = @"class Program +{ + static void Main(string[] args) + { + using (null) + using (null)$$ + } +} +"; + + var expected = @"class Program +{ + static void Main(string[] args) + { + using (null) + using (null) + } +} +"; + + AssertFormatAfterTypeChar(code, expected); + } + [WpfFact] [WorkItem(912965, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/912965")] [Trait(Traits.Feature, Traits.Features.Formatting)] diff --git a/src/EditorFeatures/Test2/Formatting/FormattingCommandHandlerTests.vb b/src/EditorFeatures/Test2/Formatting/FormattingCommandHandlerTests.vb new file mode 100644 index 0000000000000000000000000000000000000000..85821b209b0a6b10a3405193be23970e9798d394 --- /dev/null +++ b/src/EditorFeatures/Test2/Formatting/FormattingCommandHandlerTests.vb @@ -0,0 +1,68 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense + ''' + ''' Tests that want to exercise as much of the real command handling stack as possible. + ''' + <[UseExportProvider]> + Public Class FormattingCommandHandlerTests + Public Shared ReadOnly Property AllCompletionImplementations() As IEnumerable(Of Object()) = + TestStateFactory.GetAllCompletionImplementations() + + + + + Public Sub TypingUsingStatementsProperlyAligns1(completionImplementation As CompletionImplementation) + Using state = TestStateFactory.CreateCSharpTestState(completionImplementation, + +using System; +class TestClass +{ + void TestMethod(IDisposable obj) + { + $$ + } +} + , includeFormatCommandHandler:=True) + state.SendTypeChars("using (obj)") + state.SendReturn() + + ' we should be indented one level from the using + AssertVirtualCaretColumn(state, 12) + + ' We should continue being indentded one level in + state.SendTypeChars("u") + Assert.Equal(" u", state.GetLineTextFromCaretPosition()) + + ' Until close paren is typed. Then, we should align the usings + state.SendTypeChars("sing (obj") + Assert.Equal(" using (obj", state.GetLineTextFromCaretPosition()) + + state.SendTypeChars(")") + Assert.Equal(" using (obj)", state.GetLineTextFromCaretPosition()) + + state.SendReturn() + ' we should be indented one level from the using + AssertVirtualCaretColumn(state, 12) + + ' typing open brace should align with using. + state.SendTypeChars("{") + Assert.Equal(" {", state.GetLineTextFromCaretPosition()) + + state.SendReturn() + ' we should be indented one level from the { + AssertVirtualCaretColumn(state, 12) + + ' typing close brace should align with open brace. + state.SendTypeChars("}") + Assert.Equal(" }", state.GetLineTextFromCaretPosition()) + End Using + End Sub + + Private Shared Sub AssertVirtualCaretColumn(state As TestStateBase, expectedCol As Integer) + Dim caretLine = state.GetLineFromCurrentCaretPosition() + Dim caret = state.GetCaretPoint() + Assert.Equal(expectedCol, caret.VirtualBufferPosition.VirtualSpaces) + End Sub + End Class +End Namespace diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index a30fb9d25257bb6b75cace09491e1dae90cd95fe..6f3a85b703be78fce588440f676c9fad11a7003c 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -636,7 +636,12 @@ class Variable state.SendTypeChars("a") Await state.AssertSelectedCompletionItem(displayText:="as", isSoftSelected:=True) state.SendReturn() - Assert.Contains("(var a, var a", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) + + Dim caretLine = state.GetLineFromCurrentCaretPosition() + Assert.Contains(" )", caretLine.GetText(), StringComparison.Ordinal) + + Dim previousLine = caretLine.Snapshot.Lines(caretLine.LineNumber - 1) + Assert.Contains("(var a, var a", previousLine.GetText(), StringComparison.Ordinal) End Using End Function @@ -698,7 +703,12 @@ class Variable state.SendReturn() Await state.AssertNoCompletionSession() - Assert.Contains("(var as, var a", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) + + Dim caretLine = state.GetLineFromCurrentCaretPosition() + Assert.Contains(" )", caretLine.GetText(), StringComparison.Ordinal) + + Dim previousLine = caretLine.Snapshot.Lines(caretLine.LineNumber - 1) + Assert.Contains("(var as, var a", previousLine.GetText(), StringComparison.Ordinal) End Using End Function diff --git a/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs b/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs index 444f4717416aff0fb051914558abdd446f8f003d..a52c3ff86b4a8e7a0390ade142201cd4dc8c26cd 100644 --- a/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs +++ b/src/EditorFeatures/TestUtilities/AbstractCommandHandlerTestState.cs @@ -273,8 +273,8 @@ public ITextSnapshotLine GetLineFromCurrentCaretPosition() public string GetLineTextFromCaretPosition() { - var caretPosition = Workspace.Documents.Single(d => d.CursorPosition.HasValue).CursorPosition.Value; - return SubjectBuffer.CurrentSnapshot.GetLineFromPosition(caretPosition).GetText(); + var caretPosition = GetCaretPoint(); + return caretPosition.BufferPosition.GetContainingLine().GetText(); } public (string TextBeforeCaret, string TextAfterCaret) GetLineTextAroundCaretPosition() diff --git a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs index a8416263d3aea2a6544bedfdd8e1a2beafcfcbe6..14bbf20b081d287c0774b3027c2a4403c56060df 100644 --- a/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs +++ b/src/Workspaces/CSharpTest/Formatting/FormattingTests.cs @@ -5797,6 +5797,74 @@ void M() await AssertFormatAsync(expected, code); } + [Fact] + [WorkItem(530580, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/530580")] + [Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task NoIndentForNestedUsingWithoutBraces2() + { + var code = @"class C +{ + void M() + { + using (null) + using (null) + using (null) + { + } + } +} +"; + + var expected = @"class C +{ + void M() + { + using (null) + using (null) + using (null) + { + } + } +} +"; + + await AssertFormatAsync(expected, code); + } + + [Fact] + [WorkItem(530580, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/530580")] + [Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task NoIndentForNestedUsingWithoutBraces3() + { + var code = @"class C +{ + void M() + { + using (null) + using (null) + using (null) + { + } + } +} +"; + + var expected = @"class C +{ + void M() + { + using (null) + using (null) + using (null) + { + } + } +} +"; + + await AssertFormatAsync(expected, code); + } + [Fact] [WorkItem(546678, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546678")] [Trait(Traits.Feature, Traits.Features.Formatting)]