private static bool TryUncommentExactlyBlockComment( CommentSelectionInfo info, SnapshotSpan span, ArrayBuilder <TextChange> textChanges, ArrayBuilder <CommentTrackingSpan> spansToSelect ) { var spanText = span.GetText(); var trimmedSpanText = spanText.Trim(); // See if the selection includes just a block comment (plus whitespace) if ( trimmedSpanText.StartsWith(info.BlockCommentStartString, StringComparison.Ordinal) && trimmedSpanText.EndsWith(info.BlockCommentEndString, StringComparison.Ordinal) ) { var positionOfStart = span.Start + spanText.IndexOf(info.BlockCommentStartString, StringComparison.Ordinal); var positionOfEnd = span.Start + spanText.LastIndexOf(info.BlockCommentEndString, StringComparison.Ordinal); UncommentPosition(info, textChanges, spansToSelect, positionOfStart, positionOfEnd); return(true); } return(false); }
private static void UncommentPosition( CommentSelectionInfo info, ArrayBuilder <TextChange> textChanges, ArrayBuilder <CommentTrackingSpan> spansToSelect, int positionOfStart, int positionOfEnd ) { if (positionOfStart < 0 || positionOfEnd < 0) { return; } spansToSelect.Add( new CommentTrackingSpan( TextSpan.FromBounds( positionOfStart, positionOfEnd + info.BlockCommentEndString.Length ) ) ); DeleteText( textChanges, new TextSpan(positionOfStart, info.BlockCommentStartString.Length) ); DeleteText(textChanges, new TextSpan(positionOfEnd, info.BlockCommentEndString.Length)); }
private static void AddSingleLineComments( SnapshotSpan span, ArrayBuilder <TextChange> textChanges, ArrayBuilder <CommentTrackingSpan> trackingSpans, ITextSnapshotLine firstLine, ITextSnapshotLine lastLine, CommentSelectionInfo commentInfo ) { // Select the entirety of the lines, so that another comment operation will add more // comments, not insert block comments. trackingSpans.Add( new CommentTrackingSpan( TextSpan.FromBounds(firstLine.Start.Position, lastLine.End.Position) ) ); var indentToCommentAt = DetermineSmallestIndent(span, firstLine, lastLine); ApplySingleLineCommentToNonBlankLines( commentInfo, textChanges, firstLine, lastLine, indentToCommentAt ); }
private CommentSelectionResult ToggleLineComment(CommentSelectionInfo commentInfo, NormalizedSnapshotSpanCollection selectedSpans) { var textChanges = ArrayBuilder <TextChange> .GetInstance(); var trackingSpans = ArrayBuilder <CommentTrackingSpan> .GetInstance(); var linesInSelections = selectedSpans.ToDictionary( span => span, span => GetLinesFromSelectedSpan(span).ToImmutableArray()); Operation operation; // If any of the lines are uncommented, add comments. if (linesInSelections.Values.Any(lines => SelectionHasUncommentedLines(lines, commentInfo))) { foreach (var selection in linesInSelections) { CommentLines(selection.Key, selection.Value, textChanges, trackingSpans, commentInfo); } operation = Operation.Comment; } else { foreach (var selection in linesInSelections) { UncommentLines(selection.Value, textChanges, trackingSpans, commentInfo); } operation = Operation.Uncomment; } return(new CommentSelectionResult(textChanges, trackingSpans, operation)); }
private void UncommentContainingBlockComment(CommentSelectionInfo info, SnapshotSpan span, ArrayBuilder <TextChange> textChanges, ArrayBuilder <CommentTrackingSpan> spansToSelect) { // See if we are (textually) contained in a block comment. // This could allow a selection that spans multiple block comments to uncomment the beginning of // the first and end of the last. Oh well. var positionOfEnd = -1; var text = span.Snapshot.AsText(); var positionOfStart = text.LastIndexOf(info.BlockCommentStartString, span.Start, caseSensitive: true); // If we found a start comment marker, make sure there isn't an end comment marker after it but before our span. if (positionOfStart >= 0) { var lastEnd = text.LastIndexOf(info.BlockCommentEndString, span.Start, caseSensitive: true); if (lastEnd < positionOfStart) { positionOfEnd = text.IndexOf(info.BlockCommentEndString, span.End, caseSensitive: true); } else if (lastEnd + info.BlockCommentEndString.Length > span.End) { // The end of the span is *inside* the end marker, so searching backwards found it. positionOfEnd = lastEnd; } } UncommentPosition(info, span, textChanges, spansToSelect, positionOfStart, positionOfEnd); }
private bool TryUncommentSingleLineComments(CommentSelectionInfo info, SnapshotSpan span, ArrayBuilder <TextChange> textChanges, ArrayBuilder <CommentTrackingSpan> spansToSelect) { // First see if we're selecting any lines that have the single-line comment prefix. // If so, then we'll just remove the single-line comment prefix from those lines. var(firstLine, lastLine) = DetermineFirstAndLastLine(span); for (var lineNumber = firstLine.LineNumber; lineNumber <= lastLine.LineNumber; ++lineNumber) { var line = span.Snapshot.GetLineFromLineNumber(lineNumber); var lineText = line.GetText(); if (lineText.Trim().StartsWith(info.SingleLineCommentString, StringComparison.Ordinal)) { DeleteText(textChanges, new TextSpan(line.Start.Position + lineText.IndexOf(info.SingleLineCommentString, StringComparison.Ordinal), info.SingleLineCommentString.Length)); } } // If we made any changes, select the entirety of the lines we change, so that subsequent invocations will // affect the same lines. if (textChanges.Count == 0) { return(false); } spansToSelect.Add(new CommentTrackingSpan(TextSpan.FromBounds(firstLine.Start.Position, lastLine.End.Position))); return(true); }
private static void ApplySingleLineCommentToNonBlankLines( CommentSelectionInfo info, ArrayBuilder <TextChange> textChanges, ITextSnapshotLine firstLine, ITextSnapshotLine lastLine, int indentToCommentAt ) { var snapshot = firstLine.Snapshot; for ( var lineNumber = firstLine.LineNumber; lineNumber <= lastLine.LineNumber; ++lineNumber ) { var line = snapshot.GetLineFromLineNumber(lineNumber); if (!line.IsEmptyOrWhitespace()) { InsertText( textChanges, line.Start + indentToCommentAt, info.SingleLineCommentString ); } } }
private static bool IsLineCommentedOrEmpty(ITextSnapshotLine line, CommentSelectionInfo info) { var lineText = line.GetText(); // We don't add / remove anything for empty lines. return(lineText.Trim().StartsWith(info.SingleLineCommentString, StringComparison.Ordinal) || line.IsEmptyOrWhitespace()); }
private static void AddBlockComment( SnapshotSpan span, ArrayBuilder <TextChange> textChanges, ArrayBuilder <CommentTrackingSpan> trackingSpans, CommentSelectionInfo commentInfo ) { trackingSpans.Add(new CommentTrackingSpan(TextSpan.FromBounds(span.Start, span.End))); InsertText(textChanges, span.Start, commentInfo.BlockCommentStartString); InsertText(textChanges, span.End, commentInfo.BlockCommentEndString); }
private void UncommentPosition(CommentSelectionInfo info, SnapshotSpan span, List <TextChange> textChanges, List <ITrackingSpan> spansToSelect, int positionOfStart, int positionOfEnd) { if (positionOfStart < 0 || positionOfEnd < 0) { return; } spansToSelect.Add(span.Snapshot.CreateTrackingSpan(Span.FromBounds(positionOfStart, positionOfEnd + info.BlockCommentEndString.Length), SpanTrackingMode.EdgeExclusive)); DeleteText(textChanges, new TextSpan(positionOfStart, info.BlockCommentStartString.Length)); DeleteText(textChanges, new TextSpan(positionOfEnd, info.BlockCommentEndString.Length)); }
public bool EndsWithAnyBlockCommentMarker(CommentSelectionInfo commentInfo) { return(_trimmedText.EndsWith( commentInfo.BlockCommentStartString, StringComparison.Ordinal ) || _trimmedText.EndsWith( commentInfo.BlockCommentEndString, StringComparison.Ordinal )); }
private void UncommentContainingBlockComment(CommentSelectionInfo info, SnapshotSpan span, List <TextChange> textChanges, List <ITrackingSpan> spansToSelect) { // We didn't make any single line changes. If the language supports block comments, see // if we're inside a containing block comment and uncomment that. var positionOfStart = -1; var positionOfEnd = -1; var spanText = span.GetText(); var trimmedSpanText = spanText.Trim(); // See if the selection includes just a block comment (plus whitespace) if (trimmedSpanText.StartsWith(info.BlockCommentStartString, StringComparison.Ordinal) && trimmedSpanText.EndsWith(info.BlockCommentEndString, StringComparison.Ordinal)) { positionOfStart = span.Start + spanText.IndexOf(info.BlockCommentStartString, StringComparison.Ordinal); positionOfEnd = span.Start + spanText.LastIndexOf(info.BlockCommentEndString, StringComparison.Ordinal); } else { // See if we are (textually) contained in a block comment. // This could allow a selection that spans multiple block comments to uncomment the beginning of // the first and end of the last. Oh well. var text = span.Snapshot.AsText(); positionOfStart = text.LastIndexOf(info.BlockCommentStartString, span.Start, caseSensitive: true); // If we found a start comment marker, make sure there isn't an end comment marker after it but before our span. if (positionOfStart >= 0) { var lastEnd = text.LastIndexOf(info.BlockCommentEndString, span.Start, caseSensitive: true); if (lastEnd < positionOfStart) { positionOfEnd = text.IndexOf(info.BlockCommentEndString, span.End, caseSensitive: true); } else if (lastEnd + info.BlockCommentEndString.Length > span.End) { // The end of the span is *inside* the end marker, so searching backwards found it. positionOfEnd = lastEnd; } } } if (positionOfStart < 0 || positionOfEnd < 0) { return; } spansToSelect.Add(span.Snapshot.CreateTrackingSpan(Span.FromBounds(positionOfStart, positionOfEnd + info.BlockCommentEndString.Length), SpanTrackingMode.EdgeExclusive)); DeleteText(textChanges, new TextSpan(positionOfStart, info.BlockCommentStartString.Length)); DeleteText(textChanges, new TextSpan(positionOfEnd, info.BlockCommentEndString.Length)); }
private static void UncommentLines(ImmutableArray<ITextSnapshotLine> commentedLines, ArrayBuilder<TextChange> textChanges, ArrayBuilder<CommentTrackingSpan> trackingSpans, CommentSelectionInfo commentInfo) { foreach (var line in commentedLines) { if (!line.IsEmptyOrWhitespace()) { var text = line.GetText(); var commentIndex = text.IndexOf(commentInfo.SingleLineCommentString) + line.Start; var spanToRemove = TextSpan.FromBounds(commentIndex, commentIndex + commentInfo.SingleLineCommentString.Length); DeleteText(textChanges, spanToRemove); } } trackingSpans.Add(new CommentTrackingSpan(TextSpan.FromBounds(commentedLines.First().Start, commentedLines.Last().End))); }
protected override Task <ImmutableArray <TextSpan> > GetBlockCommentsInDocumentAsync( Document document, ITextSnapshot snapshot, TextSpan linesContainingSelections, CommentSelectionInfo commentInfo, CancellationToken cancellationToken ) { var allText = snapshot.AsText(); var commentedSpans = ArrayBuilder <TextSpan> .GetInstance(); var openIdx = 0; while ( ( openIdx = allText.IndexOf( commentInfo.BlockCommentStartString, openIdx, caseSensitive: true ) ) >= 0 ) { // Retrieve the first closing marker located after the open index. var closeIdx = allText.IndexOf( commentInfo.BlockCommentEndString, openIdx + commentInfo.BlockCommentStartString.Length, caseSensitive: true ); // If an open marker is found without a close marker, it's an unclosed comment. if (closeIdx < 0) { closeIdx = allText.Length - commentInfo.BlockCommentEndString.Length; } var blockCommentSpan = new TextSpan( openIdx, closeIdx + commentInfo.BlockCommentEndString.Length - openIdx ); commentedSpans.Add(blockCommentSpan); openIdx = closeIdx; } return(Task.FromResult(commentedSpans.ToImmutableAndFree())); }
protected override async Task <ImmutableArray <TextSpan> > GetBlockCommentsInDocumentAsync( Document document, ITextSnapshot snapshot, TextSpan linesContainingSelections, CommentSelectionInfo commentInfo, CancellationToken cancellationToken ) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); // Only search for block comments intersecting the lines in the selections. return(root.DescendantTrivia(linesContainingSelections) .Where( trivia => trivia.IsKind(SyntaxKind.MultiLineCommentTrivia) || trivia.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia) ) .SelectAsArray(blockCommentTrivia => blockCommentTrivia.Span)); }
private bool SelectionHasUncommentedLines(ImmutableArray <ITextSnapshotLine> linesInSelection, CommentSelectionInfo commentInfo) => linesInSelection.Any(l => !IsLineCommentedOrEmpty(l, commentInfo));
private static void CommentLines(SnapshotSpan selectedSpan, ImmutableArray <ITextSnapshotLine> linesInSelection, ArrayBuilder <TextChange> textChanges, ArrayBuilder <CommentTrackingSpan> trackingSpans, CommentSelectionInfo commentInfo) { var indentation = DetermineSmallestIndent(selectedSpan, linesInSelection.First(), linesInSelection.Last()); foreach (var line in linesInSelection) { if (!line.IsEmptyOrWhitespace()) { InsertText(textChanges, line.Start + indentation, commentInfo.SingleLineCommentString); } } trackingSpans.Add(new CommentTrackingSpan( TextSpan.FromBounds(linesInSelection.First().Start, linesInSelection.Last().End))); }
private async Task <CommentSelectionResult> ToggleBlockCommentsAsync(Document document, CommentSelectionInfo commentInfo, ITextStructureNavigator navigator, NormalizedSnapshotSpanCollection selectedSpans, CancellationToken cancellationToken) { var firstLineAroundSelection = selectedSpans.First().Start.GetContainingLine().Start; var lastLineAroundSelection = selectedSpans.Last().End.GetContainingLine().End; var linesContainingSelection = TextSpan.FromBounds(firstLineAroundSelection, lastLineAroundSelection); var blockCommentedSpans = await GetBlockCommentsInDocumentAsync( document, selectedSpans.First().Snapshot, linesContainingSelection, commentInfo, cancellationToken).ConfigureAwait(false); var blockCommentSelections = selectedSpans.SelectAsArray(span => new BlockCommentSelectionHelper(blockCommentedSpans, span)); var returnOperation = Operation.Uncomment; var textChanges = ArrayBuilder <TextChange> .GetInstance(); var trackingSpans = ArrayBuilder <CommentTrackingSpan> .GetInstance(); // Try to uncomment until an already uncommented span is found. foreach (var blockCommentSelection in blockCommentSelections) { // If any selection does not have comments to remove, then the operation should be comment. if (!TryUncommentBlockComment(blockCommentedSpans, blockCommentSelection, textChanges, trackingSpans, commentInfo)) { returnOperation = Operation.Comment; break; } } if (returnOperation == Operation.Comment) { textChanges.Clear(); trackingSpans.Clear(); foreach (var blockCommentSelection in blockCommentSelections) { BlockCommentSpan(blockCommentSelection, navigator, textChanges, trackingSpans, commentInfo); } } return(new CommentSelectionResult(textChanges.ToArrayAndFree(), trackingSpans.ToArrayAndFree(), returnOperation)); }
/// <summary> /// Retrieves data about the commented spans near the selection. /// </summary> /// <param name="document">the current document.</param> /// <param name="snapshot">the current text snapshot.</param> /// <param name="linesContainingSelections"> /// a span that contains text from the first character of the first line in the selection(s) /// until the last character of the last line in the selection(s) /// </param> /// <param name="commentInfo">the comment information for the document.</param> /// <param name="cancellationToken">a cancellation token.</param> /// <returns>any commented spans relevant to the selection in the document.</returns> protected abstract Task <ImmutableArray <TextSpan> > GetBlockCommentsInDocumentAsync(Document document, ITextSnapshot snapshot, TextSpan linesContainingSelections, CommentSelectionInfo commentInfo, CancellationToken cancellationToken);
private static void DeleteBlockComment(BlockCommentSelectionHelper blockCommentSelection, TextSpan spanToRemove, ArrayBuilder <TextChange> textChanges, CommentSelectionInfo commentInfo) { DeleteText(textChanges, new TextSpan(spanToRemove.Start, commentInfo.BlockCommentStartString.Length)); var endMarkerPosition = spanToRemove.End - commentInfo.BlockCommentEndString.Length; // Sometimes the block comment will be missing a close marker. if (Equals(blockCommentSelection.GetSubstringFromText(endMarkerPosition, commentInfo.BlockCommentEndString.Length), commentInfo.BlockCommentEndString)) { DeleteText(textChanges, new TextSpan(endMarkerPosition, commentInfo.BlockCommentEndString.Length)); } }
private static void AddBlockComment(CommentSelectionInfo commentInfo, TextSpan span, ArrayBuilder <TextChange> textChanges) { InsertText(textChanges, span.Start, commentInfo.BlockCommentStartString); InsertText(textChanges, span.End, commentInfo.BlockCommentEndString); }
/// <summary> /// 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 blockCommentSelection, ArrayBuilder <TextChange> textChanges, ArrayBuilder <CommentTrackingSpan> trackingSpans, CommentSelectionInfo commentInfo) { var selectedSpan = blockCommentSelection.SelectedSpan; var amountToAddToStart = 0; var amountToAddToEnd = 0; // Add comments to all uncommented spans in the selection. foreach (var uncommentedSpan in blockCommentSelection.UncommentedSpansInSelection) { AddBlockComment(commentInfo, uncommentedSpan, textChanges); } var startsWithCommentMarker = blockCommentSelection.StartsWithAnyBlockCommentMarker(commentInfo); var endsWithCommentMarker = blockCommentSelection.EndsWithAnyBlockCommentMarker(commentInfo); // If the start is commented (and not a comment marker), close the current comment and open a new one. if (blockCommentSelection.IsLocationCommented(selectedSpan.Start) && !startsWithCommentMarker) { InsertText(textChanges, selectedSpan.Start, commentInfo.BlockCommentEndString); InsertText(textChanges, selectedSpan.Start, commentInfo.BlockCommentStartString); // Shrink the tracking so the previous comment start marker is not included in selection. amountToAddToStart = commentInfo.BlockCommentEndString.Length; } // If the end is commented (and not a comment marker), close the current comment and open a new one. if (blockCommentSelection.IsLocationCommented(selectedSpan.End) && !endsWithCommentMarker) { InsertText(textChanges, selectedSpan.End, commentInfo.BlockCommentEndString); InsertText(textChanges, selectedSpan.End, commentInfo.BlockCommentStartString); // Shrink the tracking span so the next comment start marker is not included in selection. amountToAddToEnd = -commentInfo.BlockCommentStartString.Length; } trackingSpans.Add(new CommentTrackingSpan(selectedSpan, amountToAddToStart, amountToAddToEnd)); }
private static void BlockCommentSpan(BlockCommentSelectionHelper blockCommentSelection, ITextStructureNavigator navigator, ArrayBuilder <TextChange> textChanges, ArrayBuilder <CommentTrackingSpan> trackingSpans, CommentSelectionInfo commentInfo) { // Add sequential block comments if the selection contains any intersecting comments. if (blockCommentSelection.HasIntersectingBlockComments()) { AddBlockCommentWithIntersectingSpans(blockCommentSelection, textChanges, trackingSpans, commentInfo); } else { // Comment the selected span or caret location. var spanToAdd = blockCommentSelection.SelectedSpan; if (spanToAdd.IsEmpty) { var caretLocation = GetCaretLocationAfterToken(navigator, blockCommentSelection); spanToAdd = TextSpan.FromBounds(caretLocation, caretLocation); } trackingSpans.Add(new CommentTrackingSpan(spanToAdd)); AddBlockComment(commentInfo, spanToAdd, textChanges); } }
private void AddBlockComment(SnapshotSpan span, List <TextChange> textChanges, List <ITrackingSpan> trackingSpans, CommentSelectionInfo commentInfo) { trackingSpans.Add(span.Snapshot.CreateTrackingSpan(span, SpanTrackingMode.EdgeInclusive)); InsertText(textChanges, span.Start, commentInfo.BlockCommentStartString); InsertText(textChanges, span.End, commentInfo.BlockCommentEndString); }
private void AddSingleLineComments(SnapshotSpan span, List <TextChange> textChanges, List <ITrackingSpan> trackingSpans, ITextSnapshotLine firstLine, ITextSnapshotLine lastLine, CommentSelectionInfo commentInfo) { // Select the entirety of the lines, so that another comment operation will add more // comments, not insert block comments. trackingSpans.Add(span.Snapshot.CreateTrackingSpan(Span.FromBounds(firstLine.Start.Position, lastLine.End.Position), SpanTrackingMode.EdgeInclusive)); var indentToCommentAt = DetermineSmallestIndent(span, firstLine, lastLine); ApplySingleLineCommentToNonBlankLines(commentInfo, textChanges, firstLine, lastLine, indentToCommentAt); }
internal VSTypeScriptCommentSelectionInfo(CommentSelectionInfo underlyingObject) { UnderlyingObject = underlyingObject; }
/// <summary> /// Retrieves block comments near the selection in the document. /// Uses the CSharp syntax tree to find the commented spans. /// </summary> protected override ImmutableArray <TextSpan> GetBlockCommentsInDocument(Document document, ITextSnapshot snapshot, TextSpan linesContainingSelections, CommentSelectionInfo commentInfo, CancellationToken cancellationToken) { var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken); // Only search for block comments intersecting the lines in the selections. return(root.DescendantTrivia(linesContainingSelections) .Where(trivia => trivia.IsKind(SyntaxKind.MultiLineCommentTrivia) || trivia.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia)) .SelectAsArray(blockCommentTrivia => blockCommentTrivia.Span)); }
private static bool TryUncommentBlockComment(ImmutableArray <TextSpan> blockCommentedSpans, BlockCommentSelectionHelper blockCommentSelection, ArrayBuilder <TextChange> textChanges, ArrayBuilder <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 (blockCommentSelection.SelectedSpan.IsEmpty && blockCommentSelection.TryGetBlockCommentOnSameLine(blockCommentedSpans, out var blockCommentOnSameLine)) { DeleteBlockComment(blockCommentSelection, blockCommentOnSameLine, textChanges, commentInfo); trackingSpans.Add(new CommentTrackingSpan(blockCommentOnSameLine)); return(true); } // If the selection is entirely commented, remove any block comments that intersect. else if (blockCommentSelection.IsEntirelyCommented()) { var intersectingBlockComments = blockCommentSelection.IntersectingBlockComments; foreach (var spanToRemove in intersectingBlockComments) { DeleteBlockComment(blockCommentSelection, spanToRemove, textChanges, commentInfo); } var trackingSpan = TextSpan.FromBounds(intersectingBlockComments.First().Start, intersectingBlockComments.Last().End); trackingSpans.Add(new CommentTrackingSpan(trackingSpan)); return(true); } else { return(false); } }