Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change interactive window behavior when typing inside readonly area #5339

Merged
merged 10 commits into from
Sep 23, 2015
182 changes: 144 additions & 38 deletions src/InteractiveWindow/Editor/InteractiveWindow.UIThreadOnly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,25 @@ private void RestoreUncommittedInput()
/// </summary>
public bool Paste()
{
MoveCaretToClosestEditableBuffer();
if (!TextView.Selection.IsEmpty)
{
if (CutOrDeleteSelection(isCut: false))
{
MoveCaretToClosestEditableBuffer();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Else return false?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

else
{
return false;
}
}
else if (IsInActivePrompt(TextView.Caret.Position.BufferPosition))
{
MoveCaretToClosestEditableBuffer();
}
else if (MapToEditableBuffer(TextView.Caret.Position.BufferPosition) == null)
{
return false;
}

string format = Evaluator.FormatClipboard();
if (format != null)
Expand Down Expand Up @@ -1164,6 +1182,32 @@ private int GetPromptIndexForPoint(ReadOnlyCollection<SnapshotSpan> sourceSpans,
}
return index;
}

private bool IsInActivePrompt(SnapshotPoint point)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could use TextView.BufferGraph.MapDownToFirst to find the subject buffer containing the point and then just check if it's editableBuffer (which I would call activeBuffer for consistency with the method name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even better, see GetPositionInLanguageBuffer and GetPositionInStandardInputBuffer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@genlu Yes, I appear to have misunderstood what the method was trying to do.

var editableBuffer = ReadingStandardInput ? StandardInputBuffer : CurrentLanguageBuffer;
if (editableBuffer == null)
{
return false;
}

var sourceSpans = GetSourceSpans(point.Snapshot);
var index = GetSourceSpanIndex(sourceSpans, point);
if (index == sourceSpans.Count)
{
index--;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return false;?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amcasey Well, GetSourceSpanIndex returns the length of the collection if the point is at the end of the last span. Are you suggesting that the prompt will never be in the last span?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true in practice, but we probably don't want to depend on it. I'm very surprised to learn that GetSourceSpanIndex behaves that way, but it looks like all callers handle it.

}

if (!IsPrompt(sourceSpans[index]))
{
return false;
}

Debug.Assert(index + 1 < sourceSpans.Count);
var followingSpan = sourceSpans[index + 1];
// if the following span is editable, then the prompt is active.
return GetPositionInBuffer(followingSpan.Start, editableBuffer) != null;
}

/// <summary>
/// Return the index of the span containing the point. Returns the
Expand Down Expand Up @@ -2119,17 +2163,37 @@ public bool Delete()
bool handled = false;
if (!TextView.Selection.IsEmpty)
{
if (TextView.Selection.Mode == TextSelectionMode.Stream || ReduceBoxSelectionToEditableBox())
if (CutOrDeleteSelection(isCut: false))
{
CutOrDeleteSelection(isCut: false);
MoveCaretToClosestEditableBuffer();
handled = true;
}
}

else if (IsInActivePrompt(TextView.Caret.Position.BufferPosition))
{
MoveCaretToClosestEditableBuffer();
}
return handled;
}

private bool IsStreamSelectionInEditableBuffer(ITextSelection selection)
{
Debug.Assert(selection.Mode == TextSelectionMode.Stream);

var editableBuffer = (ReadingStandardInput) ? StandardInputBuffer : CurrentLanguageBuffer;
var selectedSpans = selection.SelectedSpans;

foreach (var selectedSpan in selectedSpans)
{
var spans = TextView.BufferGraph.MapDownToBuffer(selectedSpan, SpanTrackingMode.EdgeInclusive, editableBuffer);
if (spans.Count > 0)
{
return true;
}
}
return false;
}

private bool ReduceBoxSelectionToEditableBox(bool isDelete = true)
{
Debug.Assert(TextView.Selection.Mode == TextSelectionMode.Box);
Expand Down Expand Up @@ -2202,8 +2266,7 @@ private bool ReduceBoxSelectionToEditableBox(ref VirtualSnapshotPoint selectionT
if (selectionLeftColumn > maxPromptLength || maxPromptLength == minPromptLength)
{
selectionTopLine = editableLine;
selectionLeftColumn = Math.Max(selectionLeftColumn, maxPromptLength);
result = false;
selectionLeftColumn = Math.Max(selectionLeftColumn, maxPromptLength);
}
}
else
Expand Down Expand Up @@ -2261,16 +2324,24 @@ private void MeasurePrompts(int startLine, int endLine, out int minPromptLength,
/// <summary>Implements <see cref="IInteractiveWindowOperations.Cut"/>.</summary>
public void Cut()
{
if (TextView.Selection.IsEmpty)
if (!TextView.Selection.IsEmpty)
{
CutOrDeleteCurrentLine(isCut: true);
if (CutOrDeleteSelection(isCut: true))
{
MoveCaretToClosestEditableBuffer();
}
return;
}
else

var caretPosition = TextView.Caret.Position.BufferPosition;

// don't cut and move caret if it's in readonly buffer (except in active prompt)
if (MapToEditableBuffer(caretPosition) != null ||
IsInActivePrompt(caretPosition))
{
CutOrDeleteSelection(isCut: true);
CutOrDeleteCurrentLine(isCut: true);
MoveCaretToClosestEditableBuffer();
}

MoveCaretToClosestEditableBuffer();
}

private void CutOrDeleteCurrentLine(bool isCut)
Expand All @@ -2285,15 +2356,26 @@ private void CutOrDeleteCurrentLine(bool isCut)
}

/// <summary>
/// Deletes currently selected text from the language buffer and optionally saves it to the clipboard.
/// </summary>
private void CutOrDeleteSelection(bool isCut)
/// If any of currently selected text is editable, then deletes editable selection, optionally saves it
/// to the clipboard. Otherwise do nothing (and preserve selection).
/// </summary>
private bool CutOrDeleteSelection(bool isCut)
{
CutOrDelete(TextView.Selection.SelectedSpans, isCut);

// if the selection spans over prompts the prompts remain selected, so clear manually:
TextView.Selection.Clear();
}
if (!TextView.Selection.IsEmpty)
{
bool isEditable = TextView.Selection.Mode == TextSelectionMode.Stream ?
IsStreamSelectionInEditableBuffer(TextView.Selection) :
ReduceBoxSelectionToEditableBox();
if (isEditable)
{
CutOrDelete(TextView.Selection.SelectedSpans, isCut);
// if the selection spans over prompts the prompts remain selected, so clear manually:
TextView.Selection.Clear();
return true;
}
}
return false;
}

private void CutOrDelete(IEnumerable<SnapshotSpan> projectionSpans, bool isCut)
{
Expand Down Expand Up @@ -2464,31 +2546,41 @@ public bool Backspace()
{
if (TextView.Selection.Mode == TextSelectionMode.Stream || ReduceBoxSelectionToEditableBox())
{
CutOrDeleteSelection(isCut: false);
MoveCaretToClosestEditableBuffer();
handled = true;
if (CutOrDeleteSelection(isCut: false))
{
MoveCaretToClosestEditableBuffer();
handled = true;
}
}
}
else if (TextView.Caret.Position.VirtualSpaces == 0)
{
DeletePreviousCharacter();
handled = true;
if (IsInActivePrompt(TextView.Caret.Position.BufferPosition))
{
MoveCaretToClosestEditableBuffer();
}
if (DeletePreviousCharacter())
{
handled = true;
}
}

return handled;
}

/// <summary>
/// Deletes characters preceding the current caret position in the current language buffer.
///
/// Returns true if the previous character was deleted
/// </summary>
private void DeletePreviousCharacter()
private bool DeletePreviousCharacter()
{
SnapshotPoint? point = MapToEditableBuffer(TextView.Caret.Position.BufferPosition);

// We are not in an editable buffer, or we are at the start of the buffer, nothing to delete.
if (point == null || point.Value == 0)
{
return;
return false;
}

var line = point.Value.GetContainingLine();
Expand All @@ -2506,6 +2598,7 @@ private void DeletePreviousCharacter()
point.Value.Snapshot.TextBuffer.Delete(new Span(point.Value.Position - characterSize, characterSize));

ScrollToCaret();
return true;
}

/// <summary>
Expand Down Expand Up @@ -2624,8 +2717,25 @@ private bool HandlePostServicesReturn(bool trySubmit)
return false;
}

if (!TextView.Selection.IsEmpty)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments as in Paste.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

{
if (CutOrDeleteSelection(isCut: false))
{
MoveCaretToClosestEditableBuffer();
}
else
{
return false;
}
}
else if (IsInActivePrompt(TextView.Caret.Position.BufferPosition))
{
MoveCaretToClosestEditableBuffer();
}

// handle "RETURN" command that is not handled by either editor or service
var langCaret = GetPositionInLanguageBuffer(TextView.Caret.Position.BufferPosition);

if (langCaret != null)
{
int caretPosition = langCaret.Value.Position;
Expand All @@ -2634,20 +2744,16 @@ private bool HandlePostServicesReturn(bool trySubmit)
if (trySubmit && caretPosition >= CurrentLanguageBuffer.CurrentSnapshot.Length && CanExecuteActiveCode())
{
var dummy = SubmitAsync();
return true;
}

// insert new line (triggers secondary prompt injection in buffer changed event):
CurrentLanguageBuffer.Insert(caretPosition, _lineBreakString);
IndentCurrentLine(TextView.Caret.Position.BufferPosition);
ScrollToCaret();

else
{
// insert new line (triggers secondary prompt injection in buffer changed event):
CurrentLanguageBuffer.Insert(caretPosition, _lineBreakString);
IndentCurrentLine(TextView.Caret.Position.BufferPosition);
ScrollToCaret();
}
return true;
}
else
{
MoveCaretToClosestEditableBuffer();
}

return false;
}
Expand Down
Loading