提交 48b4e0cb 编写于 作者: C Charles Stoner

Copy current line if no selection

上级 ab05f362
......@@ -746,7 +746,26 @@ private void CutOrDelete(IEnumerable<SnapshotSpan> projectionSpans, bool isCut)
/// </summary>
private void CopySelection()
{
var spans = _textView.Selection.SelectedSpans;
var spans = GetSelectionSpans(_textView);
var data = Copy(spans);
Clipboard.SetDataObject(data, true);
}
private static NormalizedSnapshotSpanCollection GetSelectionSpans(ITextView textView)
{
var selection = textView.Selection;
if (selection.IsEmpty)
{
// Use the current line as the selection.
var snapshotLine = textView.Caret.Position.VirtualBufferPosition.Position.GetContainingLine();
var span = new SnapshotSpan(snapshotLine.Start, snapshotLine.LengthIncludingLineBreak);
return new NormalizedSnapshotSpanCollection(span);
}
return selection.SelectedSpans;
}
private DataObject Copy(NormalizedSnapshotSpanCollection spans)
{
var text = spans.Aggregate(new StringBuilder(), GetTextWithoutPrompts, b => b.ToString());
var rtf = _rtfBuilderService.GenerateRtf(spans, _textView);
var data = new DataObject();
......@@ -754,26 +773,36 @@ private void CopySelection()
data.SetData(DataFormats.Text, text);
data.SetData(DataFormats.UnicodeText, text);
data.SetData(DataFormats.Rtf, rtf);
Clipboard.SetDataObject(data, true);
return data;
}
private StringBuilder GetTextWithoutPrompts(StringBuilder builder, SnapshotSpan span)
{
// Find the range of source spans that cover the span.
var sourceSpans = GetSourceSpans(span.Snapshot);
int startIndex = GetSourceSpanIndex(sourceSpans, span.Start);
int endIndex = GetSourceSpanIndex(sourceSpans, span.End);
Debug.Assert(startIndex >= 0);
Debug.Assert(endIndex >= startIndex);
int n = sourceSpans.Count;
int index = GetSourceSpanIndex(sourceSpans, span.Start);
if (index == n)
{
index--;
}
// Add the text for all non-prompt spans within that range.
for (int i = startIndex; i <= endIndex; i++)
// Add the text for all non-prompt spans within the range.
for (; index < n; index++)
{
var sourceSpan = sourceSpans[i];
var sourceSpan = sourceSpans[index];
if (sourceSpan.IsEmpty)
{
continue;
}
var sourceSnapshot = sourceSpan.Snapshot;
var mappedSpans = _textView.BufferGraph.MapDownToBuffer(span, SpanTrackingMode.EdgeExclusive, sourceSnapshot.TextBuffer);
if (mappedSpans.Count == 0)
{
break;
}
if (!IsPrompt(sourceSnapshot))
{
var mappedSpans = _textView.BufferGraph.MapDownToBuffer(span, SpanTrackingMode.EdgeExclusive, sourceSnapshot.TextBuffer);
foreach (var mappedSpan in mappedSpans)
{
var intersection = sourceSpan.Span.Intersection(mappedSpan);
......@@ -1570,6 +1599,10 @@ private static ReadOnlyCollection<SnapshotSpan> GetSourceSpans(ITextSnapshot sna
private int GetPromptIndexForPoint(ReadOnlyCollection<SnapshotSpan> sourceSpans, SnapshotPoint point)
{
int index = GetSourceSpanIndex(sourceSpans, point);
if (index == sourceSpans.Count)
{
index--;
}
// Find the nearest preceding prompt.
while (!IsPrompt(sourceSpans[index].Snapshot))
{
......@@ -1578,68 +1611,82 @@ private int GetPromptIndexForPoint(ReadOnlyCollection<SnapshotSpan> sourceSpans,
return index;
}
/// <summary>
/// Return the index of the span containing the point. Returns the
/// length of the collection if the point is at the end of the last span.
/// </summary>
private int GetSourceSpanIndex(ReadOnlyCollection<SnapshotSpan> sourceSpans, SnapshotPoint point)
{
int index = BinarySearch(
sourceSpans,
sourceSpan =>
{
var start = _textView.BufferGraph.MapUpToBuffer(sourceSpan.Start, PointTrackingMode.Positive, PositionAffinity.Successor, _projectionBuffer);
Debug.Assert(start != null);
if (point < start.Value)
{
return -1;
}
var end = _textView.BufferGraph.MapUpToBuffer(sourceSpan.End, PointTrackingMode.Positive, PositionAffinity.Successor, _projectionBuffer);
Debug.Assert(end != null);
return (point < end.Value) ? 0 : 1;
});
if (index < 0)
{
Debug.Assert(~index == sourceSpans.Count);
Debug.Assert(point.Position == point.Snapshot.Length);
index = ~index - 1;
}
Debug.Assert(index >= 0);
Debug.Assert(index < sourceSpans.Count);
return index;
}
private static int BinarySearch<T>(ReadOnlyCollection<T> collection, Func<T, int> compare)
{
if (collection.Count == 0)
{
return ~0;
}
int low = 0;
int high = collection.Count - 1;
while (true)
int high = sourceSpans.Count;
while (low < high)
{
int mid = low + (high - low) / 2;
int value = compare(collection[mid]);
int value = CompareToSpan(_textView, sourceSpans, mid, point);
if (value == 0)
{
return mid;
}
else if (value < 0)
{
if (low == mid)
{
return ~mid;
}
high = mid - 1;
}
else
{
if (mid == high)
{
return ~(mid + 1);
}
low = mid + 1;
}
}
Debug.Assert(low >= 0);
Debug.Assert(low <= sourceSpans.Count);
return low;
}
/// <summary>
/// Returns negative value if the point is less than the span start,
/// positive if greater than or equal to the span end, and 0 otherwise.
/// </summary>
private static int CompareToSpan(ITextView textView, ReadOnlyCollection<SnapshotSpan> sourceSpans, int index, SnapshotPoint point)
{
// If this span is zero-width and there are multiple projections of the
// containing snapshot in the projection buffer, MapUpToBuffer will return
// multiple (ambiguous) projection spans. To avoid that, we compare the
// point to the end point of the nearest non-zero width span instead.
int indexToCompare = index;
while (sourceSpans[indexToCompare].IsEmpty)
{
if (indexToCompare == 0)
{
// Empty span at start of buffer. Point
// must be to the right of span.
return 1;
}
indexToCompare--;
}
var sourceSpan = sourceSpans[indexToCompare];
Debug.Assert(sourceSpan.Length > 0);
var mappedSpans = textView.BufferGraph.MapUpToBuffer(sourceSpan, SpanTrackingMode.EdgeInclusive, textView.TextBuffer);
Debug.Assert(mappedSpans.Count == 1);
var mappedSpan = mappedSpans[0];
Debug.Assert(mappedSpan.Length == sourceSpan.Length);
if (indexToCompare < index)
{
var result = point.CompareTo(mappedSpan.End);
return (result == 0) ? 1 : result;
}
else
{
var result = point.CompareTo(mappedSpan.Start);
if (result <= 0)
{
return result;
}
result = point.CompareTo(mappedSpan.End);
return (result < 0) ? 0 : 1;
}
}
/// <summary>
......@@ -1810,6 +1857,8 @@ private void ProjectionBufferChanged(object sender, TextContentChangedEventArgs
{
ReplaceProjectionSpans(oldProjectionSpans, spanEdits);
}
CheckProjectionSpans();
}
/// <remarks>
......@@ -1897,6 +1946,53 @@ private void AppendProjectionSpans(ITrackingSpan span1, ITrackingSpan span2)
_projectionBuffer.ReplaceSpans(index, 0, new[] { span1, span2 }, EditOptions.None, editTag: s_suppressPromptInjectionTag);
}
// Verify spans and GetSourceSpanIndex.
[Conditional("DEBUG")]
private void CheckProjectionSpans()
{
var snapshot = _projectionBuffer.CurrentSnapshot;
var sourceSpans = snapshot.GetSourceSpans();
int n = sourceSpans.Count;
// Spans should be contiguous and span the entire buffer.
int offset = 0;
for (int i = 0; i < n; i++)
{
// Determine the index of the first non-zero width
// span starting at the same point as current span.
int expectedIndex = i;
while (sourceSpans[expectedIndex].IsEmpty)
{
expectedIndex++;
if (expectedIndex == n)
{
break;
}
}
// Verify GetSourceSpanIndex returns the expected
// index for the start of the span.
int index = GetSourceSpanIndex(sourceSpans, new SnapshotPoint(snapshot, offset));
Debug.Assert(index == expectedIndex);
// If this is a non-empty span, verify GetSourceSpanIndex
// returns the index for the midpoint of the span.
int length = sourceSpans[i].Length;
if (length > 0)
{
index = GetSourceSpanIndex(sourceSpans, new SnapshotPoint(snapshot, offset + length / 2));
Debug.Assert(index == i);
}
offset += length;
}
Debug.Assert(offset == snapshot.Length);
if (n > 0)
{
int index = GetSourceSpanIndex(sourceSpans, new SnapshotPoint(snapshot, snapshot.Length));
Debug.Assert(index == n);
}
}
#endregion
#region Editor Helpers
......
......@@ -434,8 +434,8 @@ private void AppendInput(string text)
var snapshot = _window._projectionBuffer.CurrentSnapshot;
var spanCount = snapshot.SpanCount;
var inputSpan = snapshot.GetSourceSpan(spanCount - 1);
var kind = _window.GetSpanKind(inputSpan.Snapshot);
Debug.Assert(kind == ReplSpanKind.Language || kind == ReplSpanKind.StandardInput);
Debug.Assert(_window.GetSpanKind(inputSpan.Snapshot) == ReplSpanKind.Language ||
_window.GetSpanKind(inputSpan.Snapshot) == ReplSpanKind.StandardInput);
var buffer = inputSpan.Snapshot.TextBuffer;
var span = inputSpan.Span;
......
// 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.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.VisualStudio.InteractiveWindow.Commands;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Projection;
using Moq;
using Roslyn.Test.Utilities;
using Xunit;
......@@ -660,6 +661,41 @@ public void CutInputAndOutput()
VerifyClipboardData(null);
}
/// <summary>
/// When there is no selection, copy
/// should copy the current line.
/// </summary>
[Fact]
public void CopyNoSelection()
{
Submit(
@"s +
t",
@" 1
2 ");
CopyNoSelectionAndVerify(0, 7, "s +\r\n", @"> s +\par ");
CopyNoSelectionAndVerify(7, 11, "\r\n", @"> \par ");
CopyNoSelectionAndVerify(11, 17, " t\r\n", @"> t\par ");
CopyNoSelectionAndVerify(17, 21, " 1\r\n", @" 1\par ");
CopyNoSelectionAndVerify(21, 23, "\r\n", @"\par ");
CopyNoSelectionAndVerify(23, 28, "2 ", "2 > ");
}
private void CopyNoSelectionAndVerify(int start, int end, string expectedText, string expectedRtf)
{
var caret = Window.TextView.Caret;
var snapshot = Window.TextView.TextBuffer.CurrentSnapshot;
for (int i = start; i < end; i++)
{
Clipboard.Clear();
caret.MoveTo(new SnapshotPoint(snapshot, i));
Window.Operations.Copy();
VerifyClipboardData(expectedText, expectedRtf);
}
}
private void Submit(string submission, string output)
{
Task.Run(() => Window.SubmitAsync(new[] { submission })).PumpingWait();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册