diff --git a/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.InterpolatedStringSplitter.cs b/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.InterpolatedStringSplitter.cs index 622cd8eca86f8311451a5aaa072d4e07e8c686c0..be8b4f6bbd249a168108fff312ccf5cded2267aa 100644 --- a/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.InterpolatedStringSplitter.cs +++ b/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.InterpolatedStringSplitter.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using static Microsoft.CodeAnalysis.Formatting.FormattingOptions; namespace Microsoft.CodeAnalysis.Editor.CSharp.SplitStringLiteral { @@ -17,8 +18,9 @@ private class InterpolatedStringSplitter : StringSplitter Document document, int position, SyntaxNode root, SourceText sourceText, InterpolatedStringExpressionSyntax interpolatedStringExpression, - bool useTabs, int tabSize, CancellationToken cancellationToken) - : base(document, position, root, sourceText, useTabs, tabSize, cancellationToken) + bool useTabs, int tabSize, IndentStyle indentStyle, + CancellationToken cancellationToken) + : base(document, position, root, sourceText, useTabs, tabSize, indentStyle, cancellationToken) { _interpolatedStringExpression = interpolatedStringExpression; } @@ -71,7 +73,7 @@ protected override BinaryExpressionSyntax CreateSplitString() return SyntaxFactory.BinaryExpression( SyntaxKind.AddExpression, leftExpression, - GetPlusToken(), + PlusNewLineToken, rightExpression.WithAdditionalAnnotations(RightNodeAnnotation)); } diff --git a/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.SimpleStringSplitter.cs b/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.SimpleStringSplitter.cs index 267a19fce9b1ded43ce073d9a87f89f7bb7075b1..e0d2d0a451dc407dff59c08a270343081fefe255 100644 --- a/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.SimpleStringSplitter.cs +++ b/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.SimpleStringSplitter.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using static Microsoft.CodeAnalysis.Formatting.FormattingOptions; namespace Microsoft.CodeAnalysis.Editor.CSharp.SplitStringLiteral { @@ -12,8 +13,11 @@ private class SimpleStringSplitter : StringSplitter private const char QuoteCharacter = '"'; private readonly SyntaxToken _token; - public SimpleStringSplitter(Document document, int position, SyntaxNode root, SourceText sourceText, SyntaxToken token, bool useTabs, int tabSize, CancellationToken cancellationToken) - : base(document, position, root, sourceText, useTabs, tabSize, cancellationToken) + public SimpleStringSplitter( + Document document, int position, + SyntaxNode root, SourceText sourceText, SyntaxToken token, + bool useTabs, int tabSize, IndentStyle indentStyle, CancellationToken cancellationToken) + : base(document, position, root, sourceText, useTabs, tabSize, indentStyle, cancellationToken) { _token = token; } @@ -50,7 +54,7 @@ protected override BinaryExpressionSyntax CreateSplitString() return SyntaxFactory.BinaryExpression( SyntaxKind.AddExpression, leftExpression, - GetPlusToken(), + PlusNewLineToken, rightExpression.WithAdditionalAnnotations(RightNodeAnnotation)); } diff --git a/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.StringSplitter.cs b/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.StringSplitter.cs index 9519b2b9e760d6e79e322e5f48f04457bfd156d0..f6a9edd5f47905db33afc1d95e1f3c9e167c0f4e 100644 --- a/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.StringSplitter.cs +++ b/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.StringSplitter.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using static Microsoft.CodeAnalysis.Formatting.FormattingOptions; namespace Microsoft.CodeAnalysis.Editor.CSharp.SplitStringLiteral { @@ -15,6 +16,11 @@ private abstract class StringSplitter { protected static readonly SyntaxAnnotation RightNodeAnnotation = new SyntaxAnnotation(); + protected static readonly SyntaxToken PlusNewLineToken = SyntaxFactory.Token( + leading: default, + SyntaxKind.PlusToken, + SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed)); + protected readonly Document Document; protected readonly int CursorPosition; protected readonly SourceText SourceText; @@ -23,7 +29,13 @@ private abstract class StringSplitter protected readonly bool UseTabs; protected readonly CancellationToken CancellationToken; - public StringSplitter(Document document, int position, SyntaxNode root, SourceText sourceText, bool useTabs, int tabSize, CancellationToken cancellationToken) + private readonly IndentStyle _indentStyle; + + public StringSplitter( + Document document, int position, + SyntaxNode root, SourceText sourceText, + bool useTabs, int tabSize, + IndentStyle indentStyle, CancellationToken cancellationToken) { Document = document; CursorPosition = position; @@ -31,13 +43,15 @@ public StringSplitter(Document document, int position, SyntaxNode root, SourceTe SourceText = sourceText; UseTabs = useTabs; TabSize = tabSize; + _indentStyle = indentStyle; CancellationToken = cancellationToken; } public static StringSplitter Create( Document document, int position, SyntaxNode root, SourceText sourceText, - bool useTabs, int tabSize, CancellationToken cancellationToken) + bool useTabs, int tabSize, IndentStyle indentStyle, + CancellationToken cancellationToken) { var token = root.FindToken(position); @@ -46,7 +60,7 @@ public StringSplitter(Document document, int position, SyntaxNode root, SourceTe return new SimpleStringSplitter( document, position, root, sourceText, token, useTabs, tabSize, - cancellationToken); + indentStyle, cancellationToken); } var interpolatedStringExpression = TryGetInterpolatedStringExpression(token, position); @@ -55,7 +69,7 @@ public StringSplitter(Document document, int position, SyntaxNode root, SourceTe return new InterpolatedStringSplitter( document, position, root, sourceText, interpolatedStringExpression, - useTabs, tabSize, cancellationToken); + useTabs, tabSize, indentStyle, cancellationToken); } return null; @@ -103,19 +117,12 @@ private static bool IsInterpolationOpenBrace(SyntaxToken token, int position) return null; } - return TrySplitWorker(); + return SplitWorker(); } - private int? TrySplitWorker() + private int SplitWorker() { - var newDocumentAndCaretPosition = SplitString(); - if (newDocumentAndCaretPosition == null) - { - return null; - } - - var newDocument = newDocumentAndCaretPosition.Item1; - var finalCaretPosition = newDocumentAndCaretPosition.Item2; + var (newDocument, finalCaretPosition) = SplitString(); var workspace = Document.Project.Solution.Workspace; workspace.TryApplyChanges(newDocument.Project.Solution); @@ -123,15 +130,7 @@ private static bool IsInterpolationOpenBrace(SyntaxToken token, int position) return finalCaretPosition; } - protected static SyntaxToken GetPlusToken() - { - return SyntaxFactory.Token( - default(SyntaxTriviaList), - SyntaxKind.PlusToken, - SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed)); - } - - private Tuple SplitString() + private (Document document, int caretPosition) SplitString() { var splitString = CreateSplitString(); @@ -140,37 +139,28 @@ protected static SyntaxToken GetPlusToken() var rightExpression = newRoot.GetAnnotatedNodes(RightNodeAnnotation).Single(); var indentString = GetIndentString(newRoot); - if (indentString == null) - { - return null; - } - var newRightExpression = rightExpression.WithLeadingTrivia(SyntaxFactory.ElasticWhitespace(indentString)); var newRoot2 = newRoot.ReplaceNode(rightExpression, newRightExpression); var newDocument2 = Document.WithSyntaxRoot(newRoot2); - return Tuple.Create(newDocument2, rightExpression.Span.Start + indentString.Length + StringOpenQuoteLength()); + return (newDocument2, rightExpression.Span.Start + indentString.Length + StringOpenQuoteLength()); } private string GetIndentString(SyntaxNode newRoot) { var newDocument = Document.WithSyntaxRoot(newRoot); - var indentationService = newDocument.GetLanguageService(); + var indentationService = (IBlankLineIndentationService)newDocument.GetLanguageService(); var originalLineNumber = SourceText.Lines.GetLineFromPosition(CursorPosition).LineNumber; - var desiredIndentation = indentationService.GetDesiredIndentation( - newDocument, originalLineNumber + 1, CancellationToken); - if (desiredIndentation == null) - { - return null; - } + var desiredIndentation = indentationService.GetBlankLineIndentation( + newDocument, originalLineNumber + 1, _indentStyle, CancellationToken); var newSourceText = newDocument.GetSyntaxRootSynchronously(CancellationToken).SyntaxTree.GetText(CancellationToken); - var baseLine = newSourceText.Lines.GetLineFromPosition(desiredIndentation.Value.BasePosition); - var baseOffsetInLine = desiredIndentation.Value.BasePosition - baseLine.Start; + var baseLine = newSourceText.Lines.GetLineFromPosition(desiredIndentation.BasePosition); + var baseOffsetInLine = desiredIndentation.BasePosition - baseLine.Start; - var indent = baseOffsetInLine + desiredIndentation.Value.Offset; + var indent = baseOffsetInLine + desiredIndentation.Offset; var indentString = indent.CreateIndentationString(UseTabs, TabSize); return indentString; } diff --git a/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.cs b/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.cs index 3af3564a9dab66366fec3eebe9ffeb2b588fa542..58d491980117db584f5ef8cb0f65b22ede36b358 100644 --- a/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.cs +++ b/src/EditorFeatures/CSharp/SplitStringLiteral/SplitStringLiteralCommandHandler.cs @@ -79,8 +79,8 @@ private bool SplitString(ITextView textView, ITextBuffer subjectBuffer, Snapshot if (document != null) { var options = document.GetOptionsAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None); - var enabled = options.GetOption( - SplitStringLiteralOptions.Enabled); + var enabled = options.GetOption(SplitStringLiteralOptions.Enabled); + var indentStyle = options.GetOption(FormattingOptions.SmartIndent, document.Project.Language); if (enabled) { @@ -132,11 +132,14 @@ private bool LineContainsQuote(ITextSnapshotLine line, int caretPosition) { var useTabs = options.GetOption(FormattingOptions.UseTabs); var tabSize = options.GetOption(FormattingOptions.TabSize); + var indentStyle = options.GetOption(FormattingOptions.SmartIndent, LanguageNames.CSharp); var root = document.GetSyntaxRootSynchronously(cancellationToken); var sourceText = root.SyntaxTree.GetText(cancellationToken); - var splitter = StringSplitter.Create(document, position, root, sourceText, useTabs, tabSize, cancellationToken); + var splitter = StringSplitter.Create( + document, position, root, sourceText, + useTabs, tabSize, indentStyle, cancellationToken); if (splitter == null) { return null; diff --git a/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs index 595aba796f67a7cb44a705c7615bbea7d4817d6b..f4f10c776103301969f29cbb8dc9363ff6b8ff29 100644 --- a/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/SplitStringLiteral/SplitStringLiteralCommandHandlerTests.cs @@ -14,6 +14,7 @@ using Microsoft.VisualStudio.Text.Operations; using Roslyn.Test.Utilities; using Xunit; +using static Microsoft.CodeAnalysis.Formatting.FormattingOptions; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SplitStringLiteral { @@ -27,10 +28,16 @@ public class SplitStringLiteralCommandHandlerTests /// failure. /// private void TestWorker( - string inputMarkup, string expectedOutputMarkup, Action callback, bool verifyUndo = true) + string inputMarkup, + string expectedOutputMarkup, + Action callback, + bool verifyUndo = true, + IndentStyle indentStyle = IndentStyle.Smart) { using (var workspace = TestWorkspace.CreateCSharp(inputMarkup)) { + workspace.Options = workspace.Options.WithChangedOption(SmartIndent, LanguageNames.CSharp, indentStyle); + var document = workspace.Documents.Single(); var view = document.GetTextView(); @@ -76,7 +83,9 @@ public class SplitStringLiteralCommandHandlerTests /// this known test infrastructure issure. This bug does not represent a product /// failure. /// - private void TestHandled(string inputMarkup, string expectedOutputMarkup, bool verifyUndo = true) + private void TestHandled( + string inputMarkup, string expectedOutputMarkup, + bool verifyUndo = true, IndentStyle indentStyle = IndentStyle.Smart) { TestWorker( inputMarkup, expectedOutputMarkup, @@ -84,7 +93,7 @@ private void TestHandled(string inputMarkup, string expectedOutputMarkup, bool v { Assert.True(false, "Should not reach here."); }, - verifyUndo); + verifyUndo, indentStyle); } private void TestNotHandled(string inputMarkup) @@ -280,6 +289,56 @@ void M() verifyUndo: false); } + [WpfFact, Trait(Traits.Feature, Traits.Features.SplitStringLiteral)] + public void TestInEmptyString_BlockIndent() + { + // Do not verifyUndo because of https://github.com/dotnet/roslyn/issues/28033 + // When that issue is fixed, we can reenable verifyUndo + TestHandled( +@"class C +{ + void M() + { + var v = ""[||]""; + } +}", +@"class C +{ + void M() + { + var v = """" + + ""[||]""; + } +}", + verifyUndo: false, + IndentStyle.Block); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.SplitStringLiteral)] + public void TestInEmptyString_NoneIndent() + { + // Do not verifyUndo because of https://github.com/dotnet/roslyn/issues/28033 + // When that issue is fixed, we can reenable verifyUndo + TestHandled( +@"class C +{ + void M() + { + var v = ""[||]""; + } +}", +@"class C +{ + void M() + { + var v = """" + +""[||]""; + } +}", + verifyUndo: false, + IndentStyle.None); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.SplitStringLiteral)] public void TestInEmptyInterpolatedString() { @@ -301,6 +360,48 @@ void M() }"); } + [WpfFact, Trait(Traits.Feature, Traits.Features.SplitStringLiteral)] + public void TestInEmptyInterpolatedString_BlockIndent() + { + TestHandled( +@"class C +{ + void M() + { + var v = $""[||]""; + } +}", +@"class C +{ + void M() + { + var v = $"""" + + $""[||]""; + } +}", indentStyle: IndentStyle.Block); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.SplitStringLiteral)] + public void TestInEmptyInterpolatedString_NoneIndent() + { + TestHandled( +@"class C +{ + void M() + { + var v = $""[||]""; + } +}", +@"class C +{ + void M() + { + var v = $"""" + +$""[||]""; + } +}", indentStyle: IndentStyle.None); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.SplitStringLiteral)] public void TestSimpleString1() {