提交 d81c9c37 编写于 作者: D David Barbet 提交者: David Barbet

Address basic review feedback.

上级 2943a4cd
......@@ -6,6 +6,7 @@
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CommentSelection;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Editor.Implementation.CommentSelection;
......@@ -27,17 +28,19 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.CommentSelection
{
/* TODO - Modify these once the toggle block comment handler is added.
[Export(typeof(VSCommanding.ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[ContentType(ContentTypeNames.CSharpContentType)]
[Name(PredefinedCommandHandlerNames.CommentSelection)]*/
internal class ToggleBlockCommentCommandHandler :
AbstractCommentSelectionCommandHandler/*,
AbstractCommentSelectionBase/*,
VSCommanding.ICommandHandler<CommentSelectionCommandArgs>*/
{
[ImportingConstructor]
internal ToggleBlockCommentCommandHandler(
ITextUndoHistoryRegistry undoHistoryRegistry,
IEditorOperationsFactoryService editorOperationsFactoryService) : base(undoHistoryRegistry, editorOperationsFactoryService)
{ }
IEditorOperationsFactoryService editorOperationsFactoryService)
: base(undoHistoryRegistry, editorOperationsFactoryService)
{
}
/* TODO - modify once the toggle block comment handler is added.
public VSCommanding.CommandState GetCommandState(CommentSelectionCommandArgs args)
......@@ -47,93 +50,89 @@ public VSCommanding.CommandState GetCommandState(CommentSelectionCommandArgs arg
public bool ExecuteCommand(CommentSelectionCommandArgs args, CommandExecutionContext context)
{
return ExecuteCommand(args.TextView, args.SubjectBuffer, Operation.Toggle, context);
return ExecuteCommand(args.TextView, args.SubjectBuffer, Operation.Undefined, context);
}*/
public override string DisplayName => EditorFeaturesResources.Toggle_Block_Comment;
protected override string GetTitle(Operation operation)
{
return EditorFeaturesResources.Toggle_Block_Comment;
}
protected override string GetMessage(Operation operation)
{
return EditorFeaturesResources.Toggling_block_comment_on_selection;
}
protected override string GetTitle(Operation operation) => EditorFeaturesResources.Toggle_Block_Comment;
protected override void SetTrackingSpans(ITextView textView, ITextBuffer buffer, List<CommentTrackingSpan> trackingSpans)
{
var spans = trackingSpans.Select(trackingSpan => trackingSpan.ToSelection(buffer));
textView.GetMultiSelectionBroker().SetSelectionRange(spans, spans.Last());
}
protected override string GetMessage(Operation operation) => EditorFeaturesResources.Toggling_block_comment;
internal override void CollectEdits(Document document, ICommentSelectionService service, NormalizedSnapshotSpanCollection selectedSpans,
List<TextChange> textChanges, List<CommentTrackingSpan> trackingSpans, Operation operation, CancellationToken cancellationToken)
internal async override Task<CommentSelectionResult> CollectEdits(Document document, ICommentSelectionService service,
NormalizedSnapshotSpanCollection selectedSpans, Operation operation, CancellationToken cancellationToken)
{
var emptyResult = new CommentSelectionResult(new List<TextChange>(), new List<CommentTrackingSpan>(), operation);
var experimentationService = document.Project.Solution.Workspace.Services.GetRequiredService<IExperimentationService>();
if (!experimentationService.IsExperimentEnabled(WellKnownExperimentNames.RoslynToggleBlockComment))
{
return;
return emptyResult;
}
if (selectedSpans.IsEmpty())
{
return;
}
var root = document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var commentInfo = await service.GetInfoAsync(document, selectedSpans.First().Span.ToTextSpan(), cancellationToken).ConfigureAwait(false);
var getRoot = document.GetSyntaxRootAsync();
var commentInfo = service.GetInfoAsync(document, selectedSpans.First().Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken);
var root = getRoot.WaitAndGetResult(cancellationToken);
if (commentInfo.SupportsBlockComment)
{
ToggleBlockComments(commentInfo, root, selectedSpans, textChanges, trackingSpans);
return ToggleBlockComments(commentInfo, await root, selectedSpans);
}
return emptyResult;
}
private static void ToggleBlockComments(CommentSelectionInfo commentInfo, SyntaxNode root, NormalizedSnapshotSpanCollection selectedSpans,
List<TextChange> textChanges, List<CommentTrackingSpan> trackingSpans)
private static CommentSelectionResult ToggleBlockComments(CommentSelectionInfo commentInfo, SyntaxNode root,
NormalizedSnapshotSpanCollection selectedSpans)
{
var blockCommentedSpans = GetDescendentBlockCommentSpansFromRoot(root);
var blockCommentSelectionHelpers = selectedSpans.Select(span => new BlockCommentSelectionHelper(blockCommentedSpans, span));
// If there is a multi selection, either uncomment all or comment all.
var onlyAddComment = false;
if (selectedSpans.Count > 1)
{
onlyAddComment = blockCommentSelectionHelpers.Where(helper => !helper.IsEntirelyCommented()).Any();
}
var blockCommentSelectionHelpers = selectedSpans
.Select(span => new BlockCommentSelectionHelper(blockCommentedSpans, span))
.ToList();
var returnOperation = Operation.Uncomment;
var uncommentChanges = new List<TextChange>();
var uncommentTrackingSpans = new List<CommentTrackingSpan>();
// Try to uncomment until an uncommented span is found.
foreach (var blockCommentSelection in blockCommentSelectionHelpers)
{
if (!onlyAddComment && TryUncommentBlockComment(blockCommentedSpans, blockCommentSelection, textChanges, trackingSpans, commentInfo))
{ }
else
var hasCommentsToRemove = TryUncommentBlockComment(
blockCommentedSpans, blockCommentSelection, uncommentChanges, uncommentTrackingSpans, commentInfo);
// If any selection does not have comments to remove, then the operation should be comment.
if (!hasCommentsToRemove)
{
BlockCommentSpan(blockCommentSelection, textChanges, trackingSpans, root, commentInfo);
returnOperation = Operation.Comment;
break;
}
}
if (returnOperation == Operation.Comment)
{
var commentChanges = new List<TextChange>();
var commentTrackingSpans = new List<CommentTrackingSpan>();
blockCommentSelectionHelpers.ForEach(
blockCommentSelection => BlockCommentSpan(blockCommentSelection, root, commentChanges, commentTrackingSpans, commentInfo));
return new CommentSelectionResult(commentChanges, commentTrackingSpans, returnOperation);
}
else
{
return new CommentSelectionResult(uncommentChanges, uncommentTrackingSpans, returnOperation);
}
}
private static bool TryUncommentBlockComment(IEnumerable<Span> blockCommentedSpans, BlockCommentSelectionHelper blockCommentSelectionHelper,
List<TextChange> textChanges, List<CommentTrackingSpan> trackingSpans, CommentSelectionInfo commentInfo)
private static bool TryUncommentBlockComment(IEnumerable<Span> blockCommentedSpans,
BlockCommentSelectionHelper blockCommentSelectionHelper, List<TextChange> textChanges,
List<CommentTrackingSpan> trackingSpans, CommentSelectionInfo commentInfo)
{
// If the selection is just a caret, try and uncomment blocks on the same line with only whitespace on the line.
if (blockCommentSelectionHelper.SelectedSpan.IsEmpty && blockCommentSelectionHelper.TryGetBlockCommentOnSameLine(blockCommentedSpans, out var blockCommentOnSameLine))
if (blockCommentSelectionHelper.SelectedSpan.IsEmpty
&& blockCommentSelectionHelper.TryGetBlockCommentOnSameLine(blockCommentedSpans, out var blockCommentOnSameLine))
{
DeleteBlockComment(blockCommentSelectionHelper, blockCommentOnSameLine, textChanges, commentInfo);
trackingSpans.Add(blockCommentSelectionHelper.GetTrackingSpan(blockCommentOnSameLine, SpanTrackingMode.EdgeExclusive, Operation.Uncomment));
trackingSpans.Add(blockCommentSelectionHelper.GetTrackingSpan(blockCommentOnSameLine, SpanTrackingMode.EdgeExclusive));
return true;
}
// If there are not any block comments intersecting the selection, there is nothing to uncomment.
if (!blockCommentSelectionHelper.HasIntersectingBlockComments())
{
return false;
}
// If the selection is entirely commented, remove any block comments that intersect.
if (blockCommentSelectionHelper.IsEntirelyCommented())
else if (blockCommentSelectionHelper.IsEntirelyCommented())
{
var intersectingBlockComments = blockCommentSelectionHelper.IntersectingBlockComments;
foreach (var spanToRemove in intersectingBlockComments)
......@@ -141,15 +140,17 @@ protected override void SetTrackingSpans(ITextView textView, ITextBuffer buffer,
DeleteBlockComment(blockCommentSelectionHelper, spanToRemove, textChanges, commentInfo);
}
var trackingSpan = Span.FromBounds(intersectingBlockComments.First().Start, intersectingBlockComments.Last().End);
trackingSpans.Add(blockCommentSelectionHelper.GetTrackingSpan(trackingSpan, SpanTrackingMode.EdgeExclusive, Operation.Uncomment));
trackingSpans.Add(blockCommentSelectionHelper.GetTrackingSpan(trackingSpan, SpanTrackingMode.EdgeExclusive));
return true;
}
return false;
else
{
return false;
}
}
private static void BlockCommentSpan(BlockCommentSelectionHelper blockCommentSelectionHelper, List<TextChange> textChanges,
List<CommentTrackingSpan> trackingSpans, SyntaxNode root, CommentSelectionInfo commentInfo)
private static void BlockCommentSpan(BlockCommentSelectionHelper blockCommentSelectionHelper, SyntaxNode root,
List<TextChange> textChanges, List<CommentTrackingSpan> trackingSpans, CommentSelectionInfo commentInfo)
{
if (blockCommentSelectionHelper.HasIntersectingBlockComments())
{
......@@ -165,8 +166,8 @@ protected override void SetTrackingSpans(ITextView textView, ITextBuffer buffer,
spanToAdd = Span.FromBounds(caretLocation, caretLocation);
}
trackingSpans.Add(blockCommentSelectionHelper.GetTrackingSpan(spanToAdd, SpanTrackingMode.EdgeInclusive));
AddBlockComment(commentInfo, spanToAdd, textChanges);
trackingSpans.Add(blockCommentSelectionHelper.GetTrackingSpan(spanToAdd, SpanTrackingMode.EdgeInclusive, Operation.Comment));
}
}
......@@ -174,8 +175,8 @@ protected override void SetTrackingSpans(ITextView textView, ITextBuffer buffer,
/// Adds a block comment when the selection already contains block comment(s).
/// The result will be sequential block comments with the entire selection being commented out.
/// </summary>
private static void AddBlockCommentWithIntersectingSpans(BlockCommentSelectionHelper blockCommentSelectionHelper, List<TextChange> textChanges,
List<CommentTrackingSpan> trackingSpans, CommentSelectionInfo commentInfo)
private static void AddBlockCommentWithIntersectingSpans(BlockCommentSelectionHelper blockCommentSelectionHelper,
List<TextChange> textChanges, List<CommentTrackingSpan> trackingSpans, CommentSelectionInfo commentInfo)
{
var selectedSpan = blockCommentSelectionHelper.SelectedSpan;
var spanTrackingMode = SpanTrackingMode.EdgeInclusive;
......@@ -190,7 +191,8 @@ protected override void SetTrackingSpans(ITextView textView, ITextBuffer buffer,
}
// If the start is commented (and not a comment marker), close the current comment and open a new one.
if (blockCommentSelectionHelper.IsLocationCommented(selectedSpan.Start) && !blockCommentSelectionHelper.DoesBeginWithBlockComment(commentInfo))
if (blockCommentSelectionHelper.IsLocationCommented(selectedSpan.Start)
&& !blockCommentSelectionHelper.DoesBeginWithBlockComment(commentInfo))
{
InsertText(textChanges, selectedSpan.Start, commentInfo.BlockCommentEndString);
InsertText(textChanges, selectedSpan.Start, commentInfo.BlockCommentStartString);
......@@ -199,7 +201,8 @@ protected override void SetTrackingSpans(ITextView textView, ITextBuffer buffer,
}
// If the end is commented (and not a comment marker), close the current comment and open a new one.
if (blockCommentSelectionHelper.IsLocationCommented(selectedSpan.End) && !blockCommentSelectionHelper.DoesEndWithBlockComment(commentInfo))
if (blockCommentSelectionHelper.IsLocationCommented(selectedSpan.End)
&& !blockCommentSelectionHelper.DoesEndWithBlockComment(commentInfo))
{
InsertText(textChanges, selectedSpan.End, commentInfo.BlockCommentEndString);
InsertText(textChanges, selectedSpan.End, commentInfo.BlockCommentStartString);
......@@ -207,7 +210,8 @@ protected override void SetTrackingSpans(ITextView textView, ITextBuffer buffer,
amountToAddToEnd = -commentInfo.BlockCommentStartString.Length;
}
trackingSpans.Add(blockCommentSelectionHelper.GetTrackingSpan(selectedSpan, spanTrackingMode, Operation.Comment, amountToAddToStart, amountToAddToEnd));
var trackingSpan = blockCommentSelectionHelper.GetTrackingSpan(selectedSpan, spanTrackingMode, amountToAddToStart, amountToAddToEnd);
trackingSpans.Add(trackingSpan);
}
private static void AddBlockComment(CommentSelectionInfo commentInfo, Span span, List<TextChange> textChanges)
......@@ -216,14 +220,14 @@ private static void AddBlockComment(CommentSelectionInfo commentInfo, Span span,
InsertText(textChanges, span.End, commentInfo.BlockCommentEndString);
}
private static void DeleteBlockComment(BlockCommentSelectionHelper blockCommentSelectionHelper, Span spanToRemove, List<TextChange> textChanges,
CommentSelectionInfo commentInfo)
private static void DeleteBlockComment(BlockCommentSelectionHelper blockCommentSelectionHelper, Span spanToRemove,
List<TextChange> textChanges, CommentSelectionInfo commentInfo)
{
DeleteText(textChanges, new TextSpan(spanToRemove.Start, commentInfo.BlockCommentStartString.Length));
var blockCommentMarkerPosition = spanToRemove.End - commentInfo.BlockCommentEndString.Length;
// Sometimes the block comment will be missing a close marker.
if (Equals(blockCommentSelectionHelper.GetSubstringFromText(blockCommentMarkerPosition, commentInfo.BlockCommentEndString.Length), commentInfo.BlockCommentEndString))
if (Equals(blockCommentSelectionHelper.GetSubstringFromText(blockCommentMarkerPosition, commentInfo.BlockCommentEndString.Length),
commentInfo.BlockCommentEndString))
{
DeleteText(textChanges, new TextSpan(blockCommentMarkerPosition, commentInfo.BlockCommentEndString.Length));
}
......@@ -317,7 +321,7 @@ public bool DoesEndWithBlockComment(CommentSelectionInfo commentInfo)
/// </summary>
public bool IsEntirelyCommented()
{
return !UncommentedSpansInSelection.Any();
return !UncommentedSpansInSelection.Any() && HasIntersectingBlockComments();
}
/// <summary>
......@@ -336,11 +340,11 @@ public string GetSubstringFromText(int position, int length)
/// <summary>
/// Returns a tracking span associated with the selected span.
/// </summary>
public CommentTrackingSpan GetTrackingSpan(Span span, SpanTrackingMode spanTrackingMode, Operation operation,
public CommentTrackingSpan GetTrackingSpan(Span span, SpanTrackingMode spanTrackingMode,
int addToStart = 0, int addToEnd = 0)
{
var trackingSpan = SelectedSpan.Snapshot.CreateTrackingSpan(Span.FromBounds(span.Start, span.End), spanTrackingMode);
return new CommentTrackingSpan(trackingSpan, operation, addToStart, addToEnd);
return new CommentTrackingSpan(trackingSpan, addToStart, addToEnd);
}
/// <summary>
......@@ -355,7 +359,9 @@ public bool TryGetBlockCommentOnSameLine(IEnumerable<Span> allBlockComments, out
var caretToLineEndIsWhitespace = IsSpanWhitespace(Span.FromBounds(SelectedSpan.Start, selectedLine.End));
foreach (var blockComment in allBlockComments)
{
if (lineStartToCaretIsWhitespace && SelectedSpan.Start < blockComment.Start && SelectedSpan.Snapshot.AreOnSameLine(SelectedSpan.Start, blockComment.Start))
if (lineStartToCaretIsWhitespace
&& SelectedSpan.Start < blockComment.Start
&& SelectedSpan.Snapshot.AreOnSameLine(SelectedSpan.Start, blockComment.Start))
{
if (IsSpanWhitespace(Span.FromBounds(SelectedSpan.Start, blockComment.Start)))
{
......@@ -363,7 +369,9 @@ public bool TryGetBlockCommentOnSameLine(IEnumerable<Span> allBlockComments, out
return true;
}
}
else if (caretToLineEndIsWhitespace && SelectedSpan.Start > blockComment.End && SelectedSpan.Snapshot.AreOnSameLine(SelectedSpan.Start, blockComment.End))
else if (caretToLineEndIsWhitespace
&& SelectedSpan.Start > blockComment.End
&& SelectedSpan.Snapshot.AreOnSameLine(SelectedSpan.Start, blockComment.End))
{
if (IsSpanWhitespace(Span.FromBounds(blockComment.End, SelectedSpan.Start)))
{
......
......@@ -26,22 +26,6 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CommentSelection
[UseExportProvider]
public class ToggleBlockCommentCommandHandlerTests
{
private const string BlockCommentOpenMarker = "/*";
private const string BlockCommentCloseMarker = "*/";
private class MockCommentSelectionService : AbstractCommentSelectionService
{
public MockCommentSelectionService()
{
SupportsBlockComment = true;
}
public override string SingleLineCommentString => "//";
public override bool SupportsBlockComment { get; }
public override string BlockCommentStartString => BlockCommentOpenMarker;
public override string BlockCommentEndString => BlockCommentCloseMarker;
}
[WpfFact, Trait(Traits.Feature, Traits.Features.ToggleBlockComment)]
public void AddComment_EmptyCaret()
{
......@@ -1948,7 +1932,7 @@ private static void ToggleBlockCommentMultiple(string markup, string[] expectedT
for (var i = 0; i < expectedText.Length; i++)
{
commandHandler.ExecuteCommand(textView, textBuffer, Operation.Toggle, TestCommandExecutionContext.Create());
commandHandler.ExecuteCommand(textView, textBuffer, Operation.Undefined, TestCommandExecutionContext.Create());
AssertCommentResult(doc.TextBuffer, textView, expectedText[i], expectedSelections[i]);
}
}
......
......@@ -1900,11 +1900,11 @@ internal class EditorFeaturesResources {
}
/// <summary>
/// Looks up a localized string similar to Toggling block comment on selection....
/// Looks up a localized string similar to Toggling block comment....
/// </summary>
internal static string Toggling_block_comment_on_selection {
internal static string Toggling_block_comment {
get {
return ResourceManager.GetString("Toggling_block_comment_on_selection", resourceCulture);
return ResourceManager.GetString("Toggling_block_comment", resourceCulture);
}
}
......
......@@ -892,7 +892,7 @@ Do you want to proceed?</value>
<data name="Toggle_Block_Comment" xml:space="preserve">
<value>Toggle Block Comment</value>
</data>
<data name="Toggling_block_comment_on_selection" xml:space="preserve">
<value>Toggling block comment on selection...</value>
<data name="Toggling_block_comment" xml:space="preserve">
<value>Toggling block comment...</value>
</data>
</root>
\ No newline at end of file
......@@ -4,6 +4,7 @@
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CommentSelection;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
......@@ -18,14 +19,30 @@
namespace Microsoft.CodeAnalysis.Editor.Implementation.CommentSelection
{
internal enum Operation { Comment, Uncomment, Toggle }
internal enum Operation
{
/// <summary>
/// The operation is a comment action.
/// </summary>
Comment,
/// <summary>
/// The operation is an uncomment action.
/// </summary>
Uncomment,
/// <summary>
/// The operation is not yet determined.
/// </summary>
Undefined
}
abstract class AbstractCommentSelectionCommandHandler
internal abstract partial class AbstractCommentSelectionBase
{
protected readonly ITextUndoHistoryRegistry _undoHistoryRegistry;
protected readonly IEditorOperationsFactoryService _editorOperationsFactoryService;
private readonly ITextUndoHistoryRegistry _undoHistoryRegistry;
private readonly IEditorOperationsFactoryService _editorOperationsFactoryService;
internal AbstractCommentSelectionCommandHandler(
internal AbstractCommentSelectionBase(
ITextUndoHistoryRegistry undoHistoryRegistry,
IEditorOperationsFactoryService editorOperationsFactoryService)
{
......@@ -36,14 +53,21 @@ abstract class AbstractCommentSelectionCommandHandler
_editorOperationsFactoryService = editorOperationsFactoryService;
}
public abstract string DisplayName { get; }
protected abstract string GetTitle(Operation operation);
protected abstract string GetMessage(Operation operation);
internal abstract Task<CommentSelectionResult> CollectEdits(
Document document, ICommentSelectionService service, NormalizedSnapshotSpanCollection selectedSpans,
Operation operation, CancellationToken cancellationToken);
protected static VSCommanding.CommandState GetCommandState(ITextBuffer buffer)
{
if (!buffer.CanApplyChangeDocumentToWorkspace())
{
return VSCommanding.CommandState.Unspecified;
}
return VSCommanding.CommandState.Available;
return buffer.CanApplyChangeDocumentToWorkspace()
? VSCommanding.CommandState.Available
: VSCommanding.CommandState.Unspecified;
}
protected static void Format(ICommentSelectionService service, ITextSnapshot snapshot, IEnumerable<CommentTrackingSpan> changes, CancellationToken cancellationToken)
......@@ -54,10 +78,8 @@ protected static void Format(ICommentSelectionService service, ITextSnapshot sna
return;
}
// Only format uncomment actions.
var textSpans = changes
.Where(change => change.Operation == Operation.Uncomment)
.Select(uncommentChange => uncommentChange.ToSnapshotSpan(snapshot).Span.ToTextSpan())
.Select(change => change.ToSnapshotSpan(snapshot).Span.ToTextSpan())
.ToImmutableArray();
var newDocument = service.FormatAsync(document, textSpans, cancellationToken).WaitAndGetResult(cancellationToken);
newDocument.Project.Solution.Workspace.ApplyDocumentChanges(newDocument, cancellationToken);
......@@ -78,8 +100,15 @@ internal bool ExecuteCommand(ITextView textView, ITextBuffer subjectBuffer, Oper
var title = GetTitle(operation);
var message = GetMessage(operation);
using (context.OperationContext.AddScope(allowCancellation: false, message))
using (context.OperationContext.AddScope(allowCancellation: true, message))
{
var cancellationToken = context.OperationContext.UserCancellationToken;
var selectedSpans = textView.Selection.GetSnapshotSpansOnBuffer(subjectBuffer);
if (selectedSpans.IsEmpty())
{
return true;
}
var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
if (document == null)
......@@ -93,27 +122,30 @@ internal bool ExecuteCommand(ITextView textView, ITextBuffer subjectBuffer, Oper
return true;
}
var trackingSpans = new List<CommentTrackingSpan>();
var textChanges = new List<TextChange>();
CollectEdits(
document, service, textView.Selection.GetSnapshotSpansOnBuffer(subjectBuffer),
textChanges, trackingSpans, operation, CancellationToken.None);
var edits = CollectEdits(document, service, selectedSpans, operation, cancellationToken).WaitAndGetResult(cancellationToken);
// Apply the text changes.
using (var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService))
{
document.Project.Solution.Workspace.ApplyTextChanges(document.Id, textChanges.Distinct(), CancellationToken.None);
document.Project.Solution.Workspace.ApplyTextChanges(document.Id, edits.TextChanges.Distinct(), cancellationToken);
transaction.Complete();
}
using (var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService))
if (edits.ResultOperation == Operation.Uncomment)
{
Format(service, subjectBuffer.CurrentSnapshot, trackingSpans, CancellationToken.None);
transaction.Complete();
// Format the document only during uncomment operations. Use second transaction so it can be undone.
using (var transaction = new CaretPreservingEditTransaction(title, textView, _undoHistoryRegistry, _editorOperationsFactoryService))
{
Format(service, subjectBuffer.CurrentSnapshot, edits.TrackingSpans, cancellationToken);
transaction.Complete();
}
}
if (trackingSpans.Any())
// Set the selection after the edits have been applied.
if (edits.TrackingSpans.Any())
{
SetTrackingSpans(textView, subjectBuffer, trackingSpans);
var spans = edits.TrackingSpans.Select(trackingSpan => trackingSpan.ToSelection(subjectBuffer));
textView.GetMultiSelectionBroker().SetSelectionRange(spans, spans.Last());
}
}
......@@ -140,17 +172,5 @@ private static ICommentSelectionService GetService(Document document)
return null;
}
public abstract string DisplayName { get; }
protected abstract string GetTitle(Operation operation);
protected abstract string GetMessage(Operation operation);
protected abstract void SetTrackingSpans(ITextView textView, ITextBuffer buffer, List<CommentTrackingSpan> trackingSpans);
internal abstract void CollectEdits(
Document document, ICommentSelectionService service, NormalizedSnapshotSpanCollection selectedSpans,
List<TextChange> textChanges, List<CommentTrackingSpan> trackingSpans, Operation operation, CancellationToken cancellationToken);
}
}
// 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.Collections.Generic;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Editor.Implementation.CommentSelection
{
internal struct CommentSelectionResult
{
/// <summary>
/// Text changes to make for this operation.
/// </summary>
public IEnumerable<TextChange> TextChanges { get; }
/// <summary>
/// Tracking spans used to format and set the output selection after edits.
/// </summary>
public IEnumerable<CommentTrackingSpan> TrackingSpans { get; }
/// <summary>
/// The type of text changes being made.
/// </summary>
public Operation ResultOperation { get; }
public CommentSelectionResult(IEnumerable<TextChange> textChanges, IEnumerable<CommentTrackingSpan> trackingSpans, Operation resultOperation)
{
TextChanges = textChanges;
TrackingSpans = trackingSpans;
ResultOperation = resultOperation;
}
}
}
......@@ -9,8 +9,6 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.CommentSelection
/// </summary>
internal struct CommentTrackingSpan
{
public Operation Operation { get; }
private readonly ITrackingSpan _trackingSpan;
// In some cases, the tracking span needs to be adjusted by a specific amount after the changes have been applied.
......@@ -18,17 +16,15 @@ internal struct CommentTrackingSpan
private readonly int _amountToAddToStart;
private readonly int _amountToAddToEnd;
public CommentTrackingSpan(ITrackingSpan trackingSpan, Operation operation)
public CommentTrackingSpan(ITrackingSpan trackingSpan)
{
Operation = operation;
_trackingSpan = trackingSpan;
_amountToAddToStart = 0;
_amountToAddToEnd = 0;
}
public CommentTrackingSpan(ITrackingSpan trackingSpan, Operation operation, int amountToAddToStart, int amountToAddToEnd)
public CommentTrackingSpan(ITrackingSpan trackingSpan, int amountToAddToStart, int amountToAddToEnd)
{
Operation = operation;
_trackingSpan = trackingSpan;
_amountToAddToStart = amountToAddToStart;
_amountToAddToEnd = amountToAddToEnd;
......@@ -44,8 +40,12 @@ public SnapshotSpan ToSnapshotSpan(ITextSnapshot snapshot)
var snapshotSpan = _trackingSpan.GetSpan(snapshot);
if (_amountToAddToStart != 0 || _amountToAddToEnd != 0)
{
var spanExpandedByAmount = Span.FromBounds(snapshotSpan.Start.Position + _amountToAddToStart, snapshotSpan.End.Position + _amountToAddToEnd);
snapshotSpan = new SnapshotSpan(snapshot, spanExpandedByAmount);
var updatedStart = snapshotSpan.Start.Position + _amountToAddToStart;
var updatedEnd = snapshotSpan.End.Position + _amountToAddToEnd;
if (updatedStart >= snapshotSpan.Start.Position && updatedEnd <= snapshotSpan.End.Position)
{
snapshotSpan = new SnapshotSpan(snapshot, Span.FromBounds(updatedStart, updatedEnd));
}
}
return snapshotSpan;
......
......@@ -6,6 +6,7 @@
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CommentSelection;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions;
......@@ -22,19 +23,21 @@
namespace Microsoft.CodeAnalysis.Editor.Implementation.CommentSelection
{
[Export(typeof(VSCommanding.ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(PredefinedCommandHandlerNames.CommentSelection)]
//[Export(typeof(VSCommanding.ICommandHandler))]
//[ContentType(ContentTypeNames.RoslynContentType)]
//[Name(PredefinedCommandHandlerNames.CommentSelection)]
internal class CommentUncommentSelectionCommandHandler :
AbstractCommentSelectionCommandHandler,
AbstractCommentSelectionBase,
VSCommanding.ICommandHandler<CommentSelectionCommandArgs>,
VSCommanding.ICommandHandler<UncommentSelectionCommandArgs>
{
[ImportingConstructor]
internal CommentUncommentSelectionCommandHandler(
ITextUndoHistoryRegistry undoHistoryRegistry,
IEditorOperationsFactoryService editorOperationsFactoryService) : base(undoHistoryRegistry, editorOperationsFactoryService)
{ }
IEditorOperationsFactoryService editorOperationsFactoryService)
: base(undoHistoryRegistry, editorOperationsFactoryService)
{
}
public VSCommanding.CommandState GetCommandState(CommentSelectionCommandArgs args)
{
......@@ -66,32 +69,29 @@ public bool ExecuteCommand(UncommentSelectionCommandArgs args, CommandExecutionC
protected override string GetTitle(Operation operation)
{
return operation == Operation.Comment ? EditorFeaturesResources.Comment_Selection
return operation == Operation.Comment
? EditorFeaturesResources.Comment_Selection
: EditorFeaturesResources.Uncomment_Selection;
}
protected override string GetMessage(Operation operation)
{
return operation == Operation.Comment ? EditorFeaturesResources.Commenting_currently_selected_text
return operation == Operation.Comment
? EditorFeaturesResources.Commenting_currently_selected_text
: EditorFeaturesResources.Uncommenting_currently_selected_text;
}
protected override void SetTrackingSpans(ITextView textView, ITextBuffer buffer, List<CommentTrackingSpan> trackingSpans)
{
// TODO, this doesn't currently handle block selection
textView.SetSelection(trackingSpans.First().ToSnapshotSpan(buffer.CurrentSnapshot));
}
/// <summary>
/// Add the necessary edits to the given spans. Also collect tracking spans over each span.
///
/// Internal so that it can be called by unit tests.
/// </summary>
internal override void CollectEdits(
internal override Task<CommentSelectionResult> CollectEdits(
Document document, ICommentSelectionService service, NormalizedSnapshotSpanCollection selectedSpans,
List<TextChange> textChanges, List<CommentTrackingSpan> trackingSpans, Operation operation, CancellationToken cancellationToken)
Operation operation, CancellationToken cancellationToken)
{
var spanTrackingList = new List<ITrackingSpan>();
var textChanges = new List<TextChange>();
foreach (var span in selectedSpans)
{
if (operation == Operation.Comment)
......@@ -103,7 +103,7 @@ protected override void SetTrackingSpans(ITextView textView, ITextBuffer buffer,
UncommentSpan(document, service, span, textChanges, spanTrackingList, cancellationToken);
}
}
trackingSpans.AddRange(spanTrackingList.Select(span => new CommentTrackingSpan(span, operation)));
return Task.FromResult(new CommentSelectionResult(textChanges, spanTrackingList.Select(span => new CommentTrackingSpan(span)), operation));
}
/// <summary>
......
......@@ -1284,9 +1284,9 @@ Chcete pokračovat?</target>
<target state="translated">Inteligentní formátování tokenů</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Möchten Sie fortfahren?</target>
<target state="translated">Intelligente Tokenformatierung</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Do you want to proceed?</source>
<target state="translated">Formateador de token inteligente</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Voulez-vous continuer ?</target>
<target state="translated">Formateur de jeton intelligent</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Continuare?</target>
<target state="translated">Formattatore di token intelligente</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Do you want to proceed?</source>
<target state="translated">スマート トークン フォーマッタ</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Do you want to proceed?</source>
<target state="translated">스마트 토큰 포맷터</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Czy chcesz kontynuować?</target>
<target state="translated">Inteligentny element formatujący tokeny</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Deseja continuar?</target>
<target state="translated">Formatador de Token Inteligente</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Do you want to proceed?</source>
<target state="translated">Средство форматирования смарт-токена</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Devam etmek istiyor musunuz?</target>
<target state="translated">Akıllı Belirteç Biçimlendirici</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Do you want to proceed?</source>
<target state="translated">智能令牌格式化程序</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -1284,9 +1284,9 @@ Do you want to proceed?</source>
<target state="translated">智慧權杖格式器</target>
<note />
</trans-unit>
<trans-unit id="Toggling_block_comment_on_selection">
<source>Toggling block comment on selection...</source>
<target state="new">Toggling block comment on selection...</target>
<trans-unit id="Toggling_block_comment">
<source>Toggling block comment...</source>
<target state="new">Toggling block comment...</target>
<note />
</trans-unit>
<trans-unit id="Toggle_Block_Comment">
......
......@@ -767,26 +767,22 @@ private static void CommentSelection(ITextView textView, IEnumerable<TextChange>
var commandHandler = new CommentUncommentSelectionCommandHandler(textUndoHistoryRegistry, editorOperationsFactory);
var service = new MockCommentSelectionService(supportBlockComments);
var trackingSpans = new List<CommentTrackingSpan>();
var textChanges = new List<TextChange>();
var edits = commandHandler.CollectEdits(
null, service, textView.Selection.GetSnapshotSpansOnBuffer(textView.TextBuffer), operation, CancellationToken.None).WaitAndGetResult(CancellationToken.None);
commandHandler.CollectEdits(
null, service, textView.Selection.GetSnapshotSpansOnBuffer(textView.TextBuffer),
textChanges, trackingSpans, operation, CancellationToken.None);
Roslyn.Test.Utilities.AssertEx.SetEqual(expectedChanges, textChanges);
Roslyn.Test.Utilities.AssertEx.SetEqual(expectedChanges, edits.TextChanges);
// Actually apply the edit to let the tracking spans adjust.
using (var edit = textView.TextBuffer.CreateEdit())
{
textChanges.Do(tc => edit.Replace(tc.Span.ToSpan(), tc.NewText));
edits.TextChanges.Do(tc => edit.Replace(tc.Span.ToSpan(), tc.NewText));
edit.Apply();
}
if (trackingSpans.Any())
if (edits.TrackingSpans.Any())
{
textView.SetSelection(trackingSpans.First().ToSnapshotSpan(textView.TextBuffer.CurrentSnapshot));
textView.SetSelection(edits.TrackingSpans.First().ToSnapshotSpan(textView.TextBuffer.CurrentSnapshot));
}
if (expectedSelectedSpans != null)
......
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
......@@ -14,4 +15,13 @@ internal interface ICommentSelectionService : ILanguageService
Task<Document> FormatAsync(Document document, ImmutableArray<TextSpan> changes, CancellationToken cancellationToken);
}
internal interface ICommentSelectionLanguageService : ILanguageService
{
Task<CommentSelectionInfo> GetInfoAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken);
Task<Document> FormatAsync(Document document, ImmutableArray<TextSpan> changes, CancellationToken cancellationToken);
Task<Document> ToggleBlockComment(Document document, IEnumerable<TextSpan> selectedSpans, CancellationToken cancellationToken);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册