diff --git a/src/EditorFeatures/CSharp/BlockCommentEditing/BlockCommentEditingCommandHandler.cs b/src/EditorFeatures/CSharp/BlockCommentEditing/BlockCommentEditingCommandHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..f2968755a7667933c215e69e380c7828300a4b1e --- /dev/null +++ b/src/EditorFeatures/CSharp/BlockCommentEditing/BlockCommentEditingCommandHandler.cs @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Editor.Implementation.BlockCommentEditing; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Operations; +using Microsoft.VisualStudio.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.BlockCommentEditing +{ + [ExportCommandHandler(nameof(BlockCommentEditingCommandHandler), ContentTypeNames.CSharpContentType)] + [Order(After = PredefinedCommandHandlerNames.Completion)] + internal class BlockCommentEditingCommandHandler : AbstractBlockCommentEditingCommandHandler + { + [ImportingConstructor] + public BlockCommentEditingCommandHandler( + ITextUndoHistoryRegistry undoHistoryRegistry, + IEditorOperationsFactoryService editorOperationsFactoryService) : base(undoHistoryRegistry, editorOperationsFactoryService) + { + } + + protected override string GetExteriorTextForNextLine(SnapshotPoint caretPosition) + { + var currentLine = caretPosition.GetContainingLine(); + + var firstNonWhitespacePosition = currentLine.GetFirstNonWhitespacePosition() ?? -1; + if (firstNonWhitespacePosition == -1) + { + return null; + } + + var currentLineStartsWithBlockCommentStartString = currentLine.StartsWith(firstNonWhitespacePosition, "/*", ignoreCase: false); + var currentLineStartsWithBlockCommentEndString = currentLine.StartsWith(firstNonWhitespacePosition, "*/", ignoreCase: false); + var currentLineStartsWithBlockCommentMiddleString = currentLine.StartsWith(firstNonWhitespacePosition, "*", ignoreCase: false); + + if (!currentLineStartsWithBlockCommentStartString && !currentLineStartsWithBlockCommentMiddleString) + { + return null; + } + + if (!IsCaretInsideBlockCommentSyntax(caretPosition)) + { + return null; + } + + if (currentLineStartsWithBlockCommentStartString) + { + if (BlockCommentEndsRightAfterCaret(caretPosition)) + { + // /*|*/ + return " "; + } + else if (caretPosition == firstNonWhitespacePosition + 1) + { + // /|* + return null; // The newline inserted could break the syntax in a way that this handler cannot fix, let's leave it. + } + else + { + // /*| + return " *" + GetPaddingOrIndentation(currentLine, caretPosition, firstNonWhitespacePosition, "/*"); + } + } + + if (currentLineStartsWithBlockCommentEndString) + { + if (BlockCommentEndsRightAfterCaret(caretPosition)) + { + // /* + // |*/ + return " "; + } + else if (caretPosition == firstNonWhitespacePosition + 1) + { + // *|/ + return "*"; + } + else + { + // /* + // | */ + return " * "; + } + } + + if (currentLineStartsWithBlockCommentMiddleString) + { + if (BlockCommentEndsRightAfterCaret(caretPosition)) + { + // *|*/ + return ""; + } + else if (caretPosition > firstNonWhitespacePosition) + { + // *| + return "*" + GetPaddingOrIndentation(currentLine, caretPosition, firstNonWhitespacePosition, "*"); + } + else + { + // /* + // | * + return " * "; + } + } + + return null; + } + + private static bool BlockCommentEndsRightAfterCaret(SnapshotPoint caretPosition) + { + var snapshot = caretPosition.Snapshot; + return ((int)caretPosition + 2 <= snapshot.Length) ? snapshot.GetText(caretPosition, 2) == "*/" : false; + } + + private static string GetPaddingOrIndentation(ITextSnapshotLine currentLine, int caretPosition, int firstNonWhitespacePosition, string exteriorText) + { + Debug.Assert(caretPosition >= firstNonWhitespacePosition + exteriorText.Length); + + var firstNonWhitespaceOffset = firstNonWhitespacePosition - currentLine.Start; + Debug.Assert(firstNonWhitespaceOffset > -1); + + var lineText = currentLine.GetText(); + if ((lineText.Length == firstNonWhitespaceOffset + exteriorText.Length)) + { + // *| + return " "; + } + + var interiorText = lineText.Substring(firstNonWhitespaceOffset + exteriorText.Length); + var interiorFirstNonWhitespaceOffset = interiorText.GetFirstNonWhitespaceOffset() ?? -1; + + if (interiorFirstNonWhitespaceOffset == 0) + { + // /****| + return " "; + } + + var interiorFirstWhitespacePosition = firstNonWhitespacePosition + exteriorText.Length; + if (interiorFirstNonWhitespaceOffset == -1 || caretPosition <= interiorFirstWhitespacePosition + interiorFirstNonWhitespaceOffset) + { + // * | + // or + // * | 1. + // ^^ + return currentLine.Snapshot.GetText(interiorFirstWhitespacePosition, caretPosition - interiorFirstWhitespacePosition); + } + else + { + // * 1. | + // ^^^ + return currentLine.Snapshot.GetText(interiorFirstWhitespacePosition, interiorFirstNonWhitespaceOffset); + } + } + + private static bool IsCaretInsideBlockCommentSyntax(SnapshotPoint caretPosition) + { + var document = caretPosition.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) + { + return false; + } + + var syntaxTree = document.GetSyntaxTreeAsync().WaitAndGetResult(CancellationToken.None); + var trivia = syntaxTree.FindTriviaAndAdjustForEndOfFile(caretPosition, CancellationToken.None); + + return trivia.IsKind(SyntaxKind.MultiLineCommentTrivia) || trivia.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia); + } + } +} diff --git a/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj b/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj index 52bd73c4a06604782d75a7a9022537e18e43ac16..3619e913339897bab93738b035eccf26d82760ae 100644 --- a/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj +++ b/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj @@ -79,6 +79,7 @@ + diff --git a/src/EditorFeatures/CSharpTest/BlockCommentEditing/BlockCommentEditingTests.cs b/src/EditorFeatures/CSharpTest/BlockCommentEditing/BlockCommentEditingTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..d195ace88037c5127c38f7b85f94e0b21d43ddc7 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/BlockCommentEditing/BlockCommentEditingTests.cs @@ -0,0 +1,595 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Commands; +using Microsoft.CodeAnalysis.Editor.CSharp.BlockCommentEditing; +using Microsoft.CodeAnalysis.Editor.UnitTests.BlockCommentEditing; +using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.VisualStudio.Text.Operations; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.BlockCommentEditing +{ + public class BlockCommentEditingTests : AbstractBlockCommentEditingTests + { + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine0() + { + var code = @" + /*$$ +"; + var expected = @" + /* + * $$ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine1() + { + var code = @" + /*$$*/ +"; + var expected = @" + /* + $$*/ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine2() + { + var code = @" + /*$$ */ +"; + var expected = @" + /* + *$$ */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine3() + { + var code = @" + /* $$ 1. + */ +"; + var expected = @" + /* + * $$ 1. + */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine4() + { + var code = @" + /* 1.$$ + */ +"; + var expected = @" + /* 1. + * $$ + */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine5() + { + var code = @" + /********$$ +"; + var expected = @" + /******** + * $$ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine6() + { + var code = @" + /**$$ +"; + var expected = @" + /** + * $$ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine7() + { + var code = @" + /* $$ +"; + var expected = @" + /* + * $$ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task NotInsertOnStartLine0() + { + var code = @" + /$$* + */ +"; + var expected = @" + / +$$* + */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine0() + { + var code = @" + /* + *$$ +"; + var expected = @" + /* + * + * $$ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine1() + { + var code = @" + /* + *$$*/ +"; + var expected = @" + /* + * + $$*/ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine2() + { + var code = @" + /* + *$$ */ +"; + var expected = @" + /* + * + *$$ */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine3() + { + var code = @" + /* + * $$ 1. + */ +"; + var expected = @" + /* + * + * $$ 1. + */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine4() + { + var code = @" + /* + * 1.$$ + */ +"; + var expected = @" + /* + * 1. + * $$ + */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine5() + { + var code = @" + /* + * 1. + * $$ + */ +"; + var expected = @" + /* + * 1. + * + * $$ + */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine6() + { + var code = @" + /* + $$ * + */ +"; + var expected = @" + /* + + * $$ * + */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine7() + { + var code = @" + /* + *************$$ + */ +"; + var expected = @" + /* + ************* + * $$ + */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine8() + { + var code = @" + /** + *$$ + */ +"; + var expected = @" + /** + * + * $$ + */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine9() + { + var code = @" + /** + *$$ +"; + var expected = @" + /** + * + * $$ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnEndLine0() + { + var code = @" + /* + *$$/ +"; + var expected = @" + /* + * + *$$/ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnEndLine1() + { + var code = @" + /** + *$$/ +"; + var expected = @" + /** + * + *$$/ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnEndLine2() + { + var code = @" + /** + * + *$$/ +"; + var expected = @" + /** + * + * + *$$/ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnEndLine3() + { + var code = @" + /* + $$ */ +"; + var expected = @" + /* + + * $$ */ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnEndLine4() + { + var code = @" + /* + $$*/ +"; + var expected = @" + /* + + $$*/ +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task NotInsertInVerbatimString0() + { + var code = @" +var code = @"" +/*$$ +""; +"; + var expected = @" +var code = @"" +/* +$$ +""; +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task NotInsertInVerbatimString1() + { + var code = @" +var code = @"" +/* + *$$ +""; +"; + var expected = @" +var code = @"" +/* + * +$$ +""; +"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task BoundCheckInsertOnStartLine0() + { + var code = @" + /$$*"; + var expected = @" + / +$$*"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task BoundCheckInsertOnStartLine1() + { + var code = @" + /*$$ "; + var expected = @" + /* + *$$ "; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task BoundCheckInsertOnMiddleLine() + { + var code = @" + /* + *$$ "; + var expected = @" + /* + * + *$$ "; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task BoundCheckInsertOnEndLine() + { + var code = @" + /* + *$$/"; + var expected = @" + /* + * + *$$/"; + await VerifyAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine2_Tab() + { + var code = @" + /*$$*/ +"; + var expected = @" + /* + *$$*/ +"; + await VerifyTabsAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine3_Tab() + { + var code = @" + /*$$1. + */ +"; + var expected = @" + /* + *$$1. + */ +"; + await VerifyTabsAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine4_Tab() + { + var code = @" + /* 1.$$ + */ +"; + var expected = @" + /* 1. + * $$ + */ +"; + await VerifyTabsAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnStartLine6_Tab() + { + var code = @" + /*$$ +"; + var expected = @" + /* + *$$ +"; + await VerifyTabsAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine2_Tab() + { + var code = @" + /* + *$$*/ +"; + var expected = @" + /* + * + *$$*/ +"; + await VerifyTabsAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine3_Tab() + { + var code = @" + /* + * $$1. + */ +"; + var expected = @" + /* + * + * $$1. + */ +"; + await VerifyTabsAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine4_Tab() + { + var code = @" + /* + * 1.$$ + */ +"; + var expected = @" + /* + * 1. + * $$ + */ +"; + await VerifyTabsAsync(code, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.BlockCommentEditing)] + public async Task InsertOnMiddleLine5_Tab() + { + var code = @" + /* + * 1. + * $$ + */ +"; + var expected = @" + /* + * 1. + * + * $$ + */ +"; + await VerifyTabsAsync(code, expected); + } + + protected override Task CreateTestWorkspaceAsync(string initialMarkup) => TestWorkspace.CreateCSharpAsync(initialMarkup); + + internal override ICommandHandler CreateCommandHandler(ITextUndoHistoryRegistry undoHistoryRegistry, IEditorOperationsFactoryService editorOperationsFactoryService) + => new BlockCommentEditingCommandHandler(undoHistoryRegistry, editorOperationsFactoryService); + } +} diff --git a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj index ce6a54fb2111a632b238fc879203a11d30904858..8d1226269cd425f26a16631c77fec6d006547242 100644 --- a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj +++ b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj @@ -150,6 +150,7 @@ + diff --git a/src/EditorFeatures/Core/EditorFeatures.csproj b/src/EditorFeatures/Core/EditorFeatures.csproj index dcb9a28f5748c33fa582f6a16d30867224933c49..4cbf8dc59eeb190a77683ca46938c0632334ca79 100644 --- a/src/EditorFeatures/Core/EditorFeatures.csproj +++ b/src/EditorFeatures/Core/EditorFeatures.csproj @@ -321,6 +321,7 @@ + diff --git a/src/EditorFeatures/Core/Implementation/BlockCommentEditing/AbstractBlockCommentEditingCommandHandler.cs b/src/EditorFeatures/Core/Implementation/BlockCommentEditing/AbstractBlockCommentEditingCommandHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..760547edfe4fd1443368e7fb7fb925dfc54ca24a --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/BlockCommentEditing/AbstractBlockCommentEditingCommandHandler.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.Editor.Commands; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Operations; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.BlockCommentEditing +{ + internal abstract class AbstractBlockCommentEditingCommandHandler : ICommandHandler + { + private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; + private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; + + protected AbstractBlockCommentEditingCommandHandler( + ITextUndoHistoryRegistry undoHistoryRegistry, + IEditorOperationsFactoryService editorOperationsFactoryService) + { + Contract.ThrowIfNull(undoHistoryRegistry); + Contract.ThrowIfNull(editorOperationsFactoryService); + + _undoHistoryRegistry = undoHistoryRegistry; + _editorOperationsFactoryService = editorOperationsFactoryService; + } + + public CommandState GetCommandState(ReturnKeyCommandArgs args, Func nextHandler) => nextHandler(); + + public void ExecuteCommand(ReturnKeyCommandArgs args, Action nextHandler) + { + if (TryHandleReturnKey(args)) + { + return; + } + + nextHandler(); + } + + private bool TryHandleReturnKey(ReturnKeyCommandArgs args) + { + var subjectBuffer = args.SubjectBuffer; + var textView = args.TextView; + + if (!subjectBuffer.GetOption(FeatureOnOffOptions.AutoInsertBlockCommentStartString)) + { + return false; + } + + var caretPosition = textView.GetCaretPoint(subjectBuffer); + if (caretPosition == null) + { + return false; + } + + var exteriorText = GetExteriorTextForNextLine(caretPosition.Value); + if (exteriorText == null) + { + return false; + } + + using (var transaction = _undoHistoryRegistry.GetHistory(args.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.InsertNewLine)) + { + var editorOperations = _editorOperationsFactoryService.GetEditorOperations(args.TextView); + + editorOperations.InsertNewLine(); + editorOperations.InsertText(exteriorText); + + transaction.Complete(); + return true; + } + } + + protected abstract string GetExteriorTextForNextLine(SnapshotPoint caretPosition); + } +} diff --git a/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs b/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs index b58b60d3affdbc77d4a58f3328d3c7d31146523c..2053be4f625aedf48bf262b4bcef5310a95224ea 100644 --- a/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs +++ b/src/EditorFeatures/Core/Shared/Options/FeatureOnOffOptions.cs @@ -32,6 +32,9 @@ internal static class FeatureOnOffOptions [ExportOption] public static readonly PerLanguageOption AutoXmlDocCommentGeneration = new PerLanguageOption(OptionName, "Automatic XML Doc Comment Generation", defaultValue: true); + [ExportOption] + public static readonly PerLanguageOption AutoInsertBlockCommentStartString = new PerLanguageOption(OptionName, "Auto Insert Block Comment Start String", defaultValue: true); + [ExportOption] public static readonly PerLanguageOption PrettyListing = new PerLanguageOption(OptionName, "Pretty List On Line Commit", defaultValue: true); diff --git a/src/EditorFeatures/Test/BlockCommentEditing/AbstractBlockCommentEditingTests.cs b/src/EditorFeatures/Test/BlockCommentEditing/AbstractBlockCommentEditingTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..63e9663e76d035c577b1bb1bd890fdce4ae858ce --- /dev/null +++ b/src/EditorFeatures/Test/BlockCommentEditing/AbstractBlockCommentEditingTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Commands; +using Microsoft.CodeAnalysis.Editor.Host; +using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Operations; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.BlockCommentEditing +{ + public abstract class AbstractBlockCommentEditingTests + { + internal abstract ICommandHandler CreateCommandHandler( + ITextUndoHistoryRegistry undoHistoryRegistry, + IEditorOperationsFactoryService editorOperationsFactoryService); + + protected abstract Task CreateTestWorkspaceAsync(string initialMarkup); + + protected async Task VerifyAsync(string initialMarkup, string expectedMarkup) + { + using (var workspace = await CreateTestWorkspaceAsync(initialMarkup)) + { + var testDocument = workspace.Documents.Single(); + var view = testDocument.GetTextView(); + view.Caret.MoveTo(new SnapshotPoint(view.TextSnapshot, testDocument.CursorPosition.Value)); + + var commandHandler = CreateCommandHandler(workspace.GetService(), workspace.GetService()); + + var args = new ReturnKeyCommandArgs(view, view.TextBuffer); + var nextHandler = CreateInsertTextHandler(view, "\r\n"); + + commandHandler.ExecuteCommand(args, nextHandler); + + string expectedCode; + int expectedPosition; + MarkupTestFile.GetPosition(expectedMarkup, out expectedCode, out expectedPosition); + + Assert.Equal(expectedCode, view.TextSnapshot.GetText()); + + var caretPosition = view.Caret.Position.BufferPosition.Position; + Assert.True(expectedPosition == caretPosition, + string.Format("Caret positioned incorrectly. Should have been {0}, but was {1}.", expectedPosition, caretPosition)); + } + } + + protected Task VerifyTabsAsync(string initialMarkup, string expectedMarkup) => VerifyAsync(ReplaceTabTags(initialMarkup), ReplaceTabTags(expectedMarkup)); + + private string ReplaceTabTags(string markup) => markup.Replace("", "\t"); + + private Action CreateInsertTextHandler(ITextView textView, string text) + { + return () => + { + var caretPosition = textView.Caret.Position.BufferPosition; + var newSpanshot = textView.TextBuffer.Insert(caretPosition, text); + textView.Caret.MoveTo(new SnapshotPoint(newSpanshot, (int)caretPosition + text.Length)); + }; + } + } +} diff --git a/src/EditorFeatures/Test/EditorServicesTest.csproj b/src/EditorFeatures/Test/EditorServicesTest.csproj index 5bd13774359871c68ebdafa4e74152d381e6ca94..16230f641ae8ab254dae68822c6b67e14586c001 100644 --- a/src/EditorFeatures/Test/EditorServicesTest.csproj +++ b/src/EditorFeatures/Test/EditorServicesTest.csproj @@ -191,6 +191,7 @@ + diff --git a/src/EditorFeatures/TestUtilities/Traits.cs b/src/EditorFeatures/TestUtilities/Traits.cs index e88d274c516393403bfb25515569b1eb187cc2ba..7b2ac19ce62833ff9af76345f34205075d6ef3a7 100644 --- a/src/EditorFeatures/TestUtilities/Traits.cs +++ b/src/EditorFeatures/TestUtilities/Traits.cs @@ -19,6 +19,7 @@ public static class Features public const string AsyncLazy = nameof(AsyncLazy); public const string AutomaticEndConstructCorrection = nameof(AutomaticEndConstructCorrection); public const string AutomaticCompletion = nameof(AutomaticCompletion); + public const string BlockCommentEditing = nameof(BlockCommentEditing); public const string BraceHighlighting = nameof(BraceHighlighting); public const string BraceMatching = nameof(BraceMatching); public const string CallHierarchy = nameof(CallHierarchy); diff --git a/src/EditorFeatures/Text/Shared/Extensions/ITextSnapshotLineExtensions.cs b/src/EditorFeatures/Text/Shared/Extensions/ITextSnapshotLineExtensions.cs index d35af334614af0d03a01aa95e1c214934b422848..b37b2c9310c56ebbd5f86c54302e1ca0069b6097 100644 --- a/src/EditorFeatures/Text/Shared/Extensions/ITextSnapshotLineExtensions.cs +++ b/src/EditorFeatures/Text/Shared/Extensions/ITextSnapshotLineExtensions.cs @@ -148,7 +148,7 @@ public static int GetLineOffsetFromColumn(this ITextSnapshotLine line, int colum public static bool StartsWith(this ITextSnapshotLine line, int index, string value, bool ignoreCase) { var snapshot = line.Snapshot; - if (index + value.Length >= snapshot.Length) + if (index + value.Length > snapshot.Length) { return false; } diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs b/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs index 60e5fb63cd49eca06f082638bbfee665a5f60732..114fcf28a9130a03091ba3e3996d88f0f7c313ed 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs @@ -483,6 +483,15 @@ internal class CSharpVSResources { } } + /// + /// Looks up a localized string similar to _Insert * at the start of new lines when writing /* */ comments. + /// + internal static string Option_InsertAsteriskAtTheStartOfNewLinesWhenWritingBlockComments { + get { + return ResourceManager.GetString("Option_InsertAsteriskAtTheStartOfNewLinesWhenWritingBlockComments", resourceCulture); + } + } + /// /// Looks up a localized string similar to _Add new line on enter after end of fully typed word. /// diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx index 08cace935e75952194144f1b0d614c675403e073..ff977606cf486d3d50f61d7ac4576e58a3a90348 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx @@ -354,6 +354,9 @@ Highlighting + + _Insert * at the start of new lines when writing /* */ comments + Optimize for solution size diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml index dce6551ce1b2e35ea41b92a6e6c46136edc62e5c..ac8abb93bccf2ddb231c37116103474d3c45223e 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml @@ -34,6 +34,8 @@ +