diff --git a/src/InteractiveWindow/Editor/InteractiveWindow.cs b/src/InteractiveWindow/Editor/InteractiveWindow.cs index 15837cb5d41203adaeaee2b52a3e4b184bdf838a..c17f2da83099936b9bc81b6b91f78e3128b7207d 100644 --- a/src/InteractiveWindow/Editor/InteractiveWindow.cs +++ b/src/InteractiveWindow/Editor/InteractiveWindow.cs @@ -34,6 +34,34 @@ namespace Microsoft.VisualStudio.InteractiveWindow /// internal partial class InteractiveWindow : IInteractiveWindow, IInteractiveWindowOperations2 { + private enum ReplSpanKind + { + /// + /// Primary, secondary, or standard input prompt. + /// + Prompt, + + /// + /// Line break inserted at end of output. + /// + LineBreak, + + /// + /// The span represents output from the program (standard output). + /// + Output, + + /// + /// The span represents code inputted after a prompt or secondary prompt. + /// + Language, + + /// + /// The span represents the input for a standard input (non code input). + /// + StandardInput, + } + private bool _adornmentToMinimize; private readonly IWpfTextView _textView; @@ -61,10 +89,7 @@ internal partial class InteractiveWindow : IInteractiveWindow, IInteractiveWindo private readonly ITextBuffer _outputBuffer; private readonly IProjectionBuffer _projectionBuffer; private readonly ITextBuffer _standardInputBuffer; - private readonly ITextBuffer _promptBuffer; - private readonly ITextBuffer _secondaryPromptBuffer; - private readonly ITextBuffer _standardInputPromptBuffer; - private readonly ITextBuffer _outputLineBreakBuffer; + private readonly IContentType _inertType; private ITextBuffer _currentLanguageBuffer; private string _historySearch; @@ -463,12 +488,13 @@ void IInteractiveWindowOperations.SelectAll() // Grab the span following the prompt (either language or standard input). var projectionSpan = sourceSpans[promptIndex + 1]; var inputSnapshot = projectionSpan.Snapshot; + var kind = GetSpanKind(projectionSpan); - Debug.Assert(GetSpanKind(inputSnapshot) == ReplSpanKind.Language || GetSpanKind(inputSnapshot) == ReplSpanKind.StandardInput); + Debug.Assert(kind == ReplSpanKind.Language || kind == ReplSpanKind.StandardInput); // Language input block is a projection of the entire snapshot; // std input block is a projection of a single span: - SnapshotPoint inputBufferEnd = GetSpanKind(inputSnapshot) == ReplSpanKind.Language ? + SnapshotPoint inputBufferEnd = (kind == ReplSpanKind.Language) ? new SnapshotPoint(inputSnapshot, inputSnapshot.Length) : projectionSpan.End; @@ -504,7 +530,7 @@ void IInteractiveWindowOperations.SelectAll() int nextPromptIndex = -1; for (int i = promptIndex + 1; i < sourceSpans.Count; i++) { - if (IsPrompt(sourceSpans[i].Snapshot)) + if (IsPrompt(sourceSpans[i])) { nextPromptIndex = i; break; @@ -521,7 +547,7 @@ void IInteractiveWindowOperations.SelectAll() } var lastSpanBeforeNextPrompt = sourceSpans[nextPromptIndex - 1]; - Debug.Assert(GetSpanKind(lastSpanBeforeNextPrompt.Snapshot) == ReplSpanKind.Output); + Debug.Assert(GetSpanKind(lastSpanBeforeNextPrompt) == ReplSpanKind.Output); // select all text in between the language buffer and the next prompt: return new SnapshotSpan( @@ -558,7 +584,7 @@ private void IndentCurrentLine(SnapshotPoint caretPosition) var sourceSpans = GetSourceSpans(caretPosition.Snapshot); var promptIndex = GetPromptIndexForPoint(sourceSpans, caretPosition); var promptSpan = sourceSpans[promptIndex]; - Debug.Assert(IsPrompt(promptSpan.Snapshot)); + Debug.Assert(IsPrompt(promptSpan)); int promptLength = promptSpan.Length; Debug.Assert(promptLength == 2 || promptLength == 0); // Not required, just expected. var adjustedIndentationValue = indentation.GetValueOrDefault() - promptLength; @@ -790,19 +816,23 @@ private StringBuilder GetTextWithoutPrompts(StringBuilder builder, SnapshotSpan { continue; } - var sourceSnapshot = sourceSpan.Snapshot; - var mappedSpans = _textView.BufferGraph.MapDownToBuffer(span, SpanTrackingMode.EdgeExclusive, sourceSnapshot.TextBuffer); - if (mappedSpans.Count == 0) - { - break; - } - if (!IsPrompt(sourceSnapshot)) + if (!IsPrompt(sourceSpan)) { + var sourceSnapshot = sourceSpan.Snapshot; + var mappedSpans = _textView.BufferGraph.MapDownToBuffer(span, SpanTrackingMode.EdgeExclusive, sourceSnapshot.TextBuffer); + bool added = false; foreach (var mappedSpan in mappedSpans) { var intersection = sourceSpan.Span.Intersection(mappedSpan); - Debug.Assert(intersection.HasValue); - builder.Append(sourceSnapshot.GetText(intersection.Value)); + if (intersection.HasValue) + { + builder.Append(sourceSnapshot.GetText(intersection.Value)); + added = true; + } + } + if (!added) + { + break; } } } @@ -1288,36 +1318,13 @@ private void SetActiveCodeToHistory(History.Entry entry) private void ClearInput() { - var sourceSpans = _projectionBuffer.CurrentSnapshot.GetSourceSpans(); - Debug.Assert(sourceSpans.Count > 0); - - // Finds the last primary prompt (standard input or code input). - // Removes all spans following the primary prompt from the projection buffer. - int i = sourceSpans.Count - 1; - while (i >= 0) + if (_stdInputStart != null) { - var sourceSnapshot = sourceSpans[i].Snapshot; - if (GetSpanKind(sourceSnapshot) == ReplSpanKind.Prompt || GetSpanKind(sourceSnapshot) == ReplSpanKind.StandardInputPrompt) - { - Debug.Assert(i != sourceSpans.Count - 1); - break; - } - - i--; + _standardInputBuffer.Delete(Span.FromBounds(_stdInputStart.Value, _standardInputBuffer.CurrentSnapshot.Length)); } - - if (i >= 0) + else { - var sourceSnapshot = sourceSpans[i].Snapshot; - if (GetSpanKind(sourceSnapshot) != ReplSpanKind.StandardInputPrompt) - { - _currentLanguageBuffer.Delete(new Span(0, _currentLanguageBuffer.CurrentSnapshot.Length)); - } - else - { - Debug.Assert(_stdInputStart != null); - _standardInputBuffer.Delete(Span.FromBounds(_stdInputStart.Value, _standardInputBuffer.CurrentSnapshot.Length)); - } + _currentLanguageBuffer.Delete(new Span(0, _currentLanguageBuffer.CurrentSnapshot.Length)); } } @@ -1340,7 +1347,7 @@ TextReader IInteractiveWindow.ReadStandardInput() { var snapshot = _projectionBuffer.CurrentSnapshot; var spanCount = snapshot.SpanCount; - if (spanCount > 0 && IsLanguage(snapshot.GetSourceSpan(spanCount - 1).Snapshot)) + if (spanCount > 0 && GetSpanKind(snapshot.GetSourceSpan(spanCount - 1)) == ReplSpanKind.Language) { // we need to remove our input prompt. uiOnly.RemoveLastInputPrompt(); @@ -1420,9 +1427,9 @@ private void SubmitStandardInput() _inputEvent.Set(); } - #endregion +#endregion - #region Output +#region Output Span IInteractiveWindow.Write(string text) { @@ -1475,9 +1482,9 @@ private void OnAdornmentLoaded(object source, EventArgs e) Caret.EnsureVisible(); } - #endregion +#endregion - #region Execution +#region Execution private bool CanExecuteActiveCode() { @@ -1503,35 +1510,23 @@ private bool CanExecuteActiveCode() return _evaluator.CanExecuteCode(input); } - #endregion +#endregion - #region Buffers, Spans and Prompts - private ITrackingSpan CreateStandardInputPrompt() +#region Buffers, Spans and Prompts + private object CreateStandardInputPrompt() { - return CreateTrackingSpan(_standardInputPromptBuffer, string.Empty); + return string.Empty; } - private ITrackingSpan CreatePrimaryPrompt() + private object CreatePrimaryPrompt() { - return CreateTrackingSpan(_promptBuffer, _evaluator.GetPrompt()); + return _evaluator.GetPrompt(); } - private ITrackingSpan CreateSecondaryPrompt() + private object CreateSecondaryPrompt() { // TODO (crwilcox) format prompt used to get a blank here but now gets "> " from get prompt. - return CreateTrackingSpan(_secondaryPromptBuffer, _evaluator.GetPrompt()); - } - - private static ITrackingSpan CreateTrackingSpan(ITextBuffer buffer, string textToAppend) - { - using (var edit = buffer.CreateEdit()) - { - var snapshot = edit.Snapshot; - int offset = snapshot.Length; - edit.Insert(offset, textToAppend); - snapshot = edit.Apply(); - return new CustomTrackingSpan(snapshot, new Span(offset, snapshot.Length - offset), PointTrackingMode.Negative, PointTrackingMode.Negative); - } + return _evaluator.GetPrompt(); } /// @@ -1545,15 +1540,15 @@ private void MeasurePrompts(int startLine, int endLine, out int minPromptLength, var sourceSpans = projectionSnapshot.GetSourceSpans(); var promptSpanIndex = GetProjectionSpanIndexFromEditableBufferPosition(projectionSnapshot, sourceSpans.Count, startLine) - 1; var promptSpan = sourceSpans[promptSpanIndex]; - Debug.Assert(IsPrompt(promptSpan.Snapshot)); + Debug.Assert(IsPrompt(promptSpan)); minPromptLength = maxPromptLength = promptSpan.Length; } - private ReplSpanKind GetSpanKind(ITextSnapshot snapshot) + private ReplSpanKind GetSpanKind(SnapshotSpan span) { - var textBuffer = snapshot.TextBuffer; - if ((textBuffer == _outputBuffer) || (textBuffer == _outputLineBreakBuffer)) + var textBuffer = span.Snapshot.TextBuffer; + if (textBuffer == _outputBuffer) { return ReplSpanKind.Output; } @@ -1561,30 +1556,18 @@ private ReplSpanKind GetSpanKind(ITextSnapshot snapshot) { return ReplSpanKind.StandardInput; } - if (textBuffer == _promptBuffer) - { - return ReplSpanKind.Prompt; - } - if (textBuffer == _secondaryPromptBuffer) - { - return ReplSpanKind.SecondaryPrompt; - } - if (textBuffer == _standardInputPromptBuffer) + if (textBuffer.ContentType == _inertType) { - return ReplSpanKind.StandardInputPrompt; + return (span.Length == _lineBreakString.Length) && string.Equals(span.GetText(), _lineBreakString) ? + ReplSpanKind.LineBreak : + ReplSpanKind.Prompt; } return ReplSpanKind.Language; } - private bool IsPrompt(ITextSnapshot snapshot) - { - var kind = GetSpanKind(snapshot); - return (kind == ReplSpanKind.Prompt) || (kind == ReplSpanKind.SecondaryPrompt) || (kind == ReplSpanKind.StandardInputPrompt); - } - - private bool IsLanguage(ITextSnapshot snapshot) + private bool IsPrompt(SnapshotSpan span) { - return GetSpanKind(snapshot) == ReplSpanKind.Language; + return GetSpanKind(span) == ReplSpanKind.Prompt; } private static ReadOnlyCollection GetSourceSpans(ITextSnapshot snapshot) @@ -1600,7 +1583,7 @@ private int GetPromptIndexForPoint(ReadOnlyCollection sourceSpans, index--; } // Find the nearest preceding prompt. - while (!IsPrompt(sourceSpans[index].Snapshot)) + while (!IsPrompt(sourceSpans[index])) { index--; } @@ -1708,9 +1691,9 @@ private struct SpanRangeEdit { public readonly int Start; public readonly int End; - public readonly ITrackingSpan[] Replacement; + public readonly object[] Replacement; - public SpanRangeEdit(int start, int count, ITrackingSpan[] replacement) + public SpanRangeEdit(int start, int count, object[] replacement) { Start = start; End = start + count; @@ -1818,7 +1801,7 @@ private void ProjectionBufferChanged(object sender, TextContentChangedEventArgs int i = 0; int lineBreakCount = newSubjectEndLineNumber - newSubjectStartLine.LineNumber; - var newSpans = new ITrackingSpan[lineBreakCount * SpansPerLineOfInput + 1]; + var newSpans = new object[lineBreakCount * SpansPerLineOfInput + 1]; var subjectLine = newSubjectStartLine; while (true) @@ -1868,7 +1851,7 @@ private int GetProjectionSpanIndexFromEditableBufferPosition(IProjectionSnapshot // and ending at the end of the projection buffer, each language buffer projection is on a separate line: // [prompt)[language)...[prompt)[language) int result = projectionSpansCount - (surfaceSnapshot.LineCount - surfaceLineNumber) * SpansPerLineOfInput + 1; - Debug.Assert(GetSpanKind(surfaceSnapshot.GetSourceSpan(result).Snapshot) == ReplSpanKind.Language); + Debug.Assert(GetSpanKind(surfaceSnapshot.GetSourceSpan(result)) == ReplSpanKind.Language); return result; } @@ -1911,9 +1894,14 @@ private void ReplaceProjectionSpans(ReadOnlyCollection oldProjecti _projectionBuffer.ReplaceSpans(start, end - start, replacement, EditOptions.None, s_suppressPromptInjectionTag); } - private static ITrackingSpan CreateTrackingSpan(SnapshotSpan snapshotSpan) + private object CreateTrackingSpan(SnapshotSpan snapshotSpan) { - return new CustomTrackingSpan(snapshotSpan.Snapshot, snapshotSpan.Span, PointTrackingMode.Negative, PointTrackingMode.Negative); + var snapshot = snapshotSpan.Snapshot; + if (snapshot.ContentType == _inertType) + { + return snapshotSpan.GetText(); + } + return new CustomTrackingSpan(snapshot, snapshotSpan.Span, PointTrackingMode.Negative, PointTrackingMode.Negative); } private ITrackingSpan CreateLanguageSpanForLine(ITextSnapshotLine languageLine) @@ -1936,7 +1924,7 @@ private void AppendLineNoPromptInjection(ITextBuffer buffer) } } - private void AppendProjectionSpans(ITrackingSpan span1, ITrackingSpan span2) + private void AppendProjectionSpans(object span1, object span2) { int index = _projectionBuffer.CurrentSnapshot.SpanCount; _projectionBuffer.ReplaceSpans(index, 0, new[] { span1, span2 }, EditOptions.None, editTag: s_suppressPromptInjectionTag); diff --git a/src/InteractiveWindow/Editor/InteractiveWindow.csproj b/src/InteractiveWindow/Editor/InteractiveWindow.csproj index 3da91df0753fb7c6182bacf58b8b9f424b674233..6e743acffa89dc3075342e7a2db6d028be1b3842 100644 --- a/src/InteractiveWindow/Editor/InteractiveWindow.csproj +++ b/src/InteractiveWindow/Editor/InteractiveWindow.csproj @@ -108,7 +108,6 @@ - diff --git a/src/InteractiveWindow/Editor/InteractiveWindow_UIThread.cs b/src/InteractiveWindow/Editor/InteractiveWindow_UIThread.cs index c44f8db4c4c6dcb3d2fa5855692a3b5ed36cea0f..9a1c3402a05fc23e764f30b504549537575f5ec8 100644 --- a/src/InteractiveWindow/Editor/InteractiveWindow_UIThread.cs +++ b/src/InteractiveWindow/Editor/InteractiveWindow_UIThread.cs @@ -60,10 +60,7 @@ internal partial class InteractiveWindow _outputBuffer = bufferFactory.CreateTextBuffer(replOutputContentType); _standardInputBuffer = bufferFactory.CreateTextBuffer(); - _promptBuffer = bufferFactory.CreateTextBuffer(); - _secondaryPromptBuffer = bufferFactory.CreateTextBuffer(); - _standardInputPromptBuffer = bufferFactory.CreateTextBuffer(); - _outputLineBreakBuffer = bufferFactory.CreateTextBuffer(); + _inertType = bufferFactory.InertContentType; var projBuffer = projectionBufferFactory.CreateProjectionBuffer( new EditResolver(this), @@ -217,7 +214,7 @@ public Task ResetAsync(bool initialize) { var snapshot = _window._projectionBuffer.CurrentSnapshot; var spanCount = snapshot.SpanCount; - Debug.Assert(_window.IsLanguage(snapshot.GetSourceSpan(spanCount - 1).Snapshot)); + Debug.Assert(_window.GetSpanKind(snapshot.GetSourceSpan(spanCount - 1)) == ReplSpanKind.Language); StoreUncommittedInput(); RemoveProjectionSpans(spanCount - 2, 2); _window._currentLanguageBuffer = null; @@ -434,8 +431,8 @@ private void AppendInput(string text) var snapshot = _window._projectionBuffer.CurrentSnapshot; var spanCount = snapshot.SpanCount; var inputSpan = snapshot.GetSourceSpan(spanCount - 1); - Debug.Assert(_window.GetSpanKind(inputSpan.Snapshot) == ReplSpanKind.Language || - _window.GetSpanKind(inputSpan.Snapshot) == ReplSpanKind.StandardInput); + Debug.Assert(_window.GetSpanKind(inputSpan) == ReplSpanKind.Language || + _window.GetSpanKind(inputSpan) == ReplSpanKind.StandardInput); var buffer = inputSpan.Snapshot.TextBuffer; var span = inputSpan.Span; @@ -618,10 +615,9 @@ public void NewOutputBuffer() { // Stop growing the current output projection span. var sourceSpan = _window._projectionBuffer.CurrentSnapshot.GetSourceSpan(_currentOutputProjectionSpan); - var sourceSnapshot = sourceSpan.Snapshot; - Debug.Assert(_window.GetSpanKind(sourceSnapshot) == ReplSpanKind.Output); + Debug.Assert(_window.GetSpanKind(sourceSpan) == ReplSpanKind.Output); var nonGrowingSpan = new CustomTrackingSpan( - sourceSnapshot, + sourceSpan.Snapshot, sourceSpan.Span, PointTrackingMode.Negative, PointTrackingMode.Negative); @@ -650,7 +646,7 @@ private int AppendProjectionSpan(ITrackingSpan span) return index; } - private void InsertProjectionSpan(int index, ITrackingSpan span) + private void InsertProjectionSpan(int index, object span) { _window._projectionBuffer.ReplaceSpans(index, 0, new[] { span }, EditOptions.None, editTag: s_suppressPromptInjectionTag); } @@ -674,12 +670,11 @@ internal void AppendOutput(IEnumerable output, int outputLength) { Debug.Assert(output.Any()); - // we maintain this invariant so that projections don't split "\r\n" in half - // (the editor isn't happy about it and our line counting also gets simpler): + // we maintain this invariant so that projections don't split "\r\n" in half: Debug.Assert(!_window._outputBuffer.CurrentSnapshot.EndsWith('\r')); var projectionSpans = _window._projectionBuffer.CurrentSnapshot.GetSourceSpans(); - Debug.Assert(_window.GetSpanKind(projectionSpans[_currentOutputProjectionSpan].Snapshot) == ReplSpanKind.Output); + Debug.Assert(_window.GetSpanKind(projectionSpans[_currentOutputProjectionSpan]) == ReplSpanKind.Output); int lineBreakProjectionSpanIndex = _currentOutputProjectionSpan + 1; @@ -688,26 +683,25 @@ internal void AppendOutput(IEnumerable output, int outputLength) if (lineBreakProjectionSpanIndex < projectionSpans.Count) { var oldSpan = projectionSpans[lineBreakProjectionSpanIndex]; - hasLineBreakProjection = _window.GetSpanKind(oldSpan.Snapshot) == ReplSpanKind.Output && string.Equals(oldSpan.GetText(), _window._lineBreakString); + hasLineBreakProjection = _window.GetSpanKind(oldSpan) == ReplSpanKind.LineBreak; } Debug.Assert(output.Last().Last() != '\r'); bool endsWithLineBreak = output.Last().Last() == '\n'; - bool insertLineBreak = !endsWithLineBreak && !hasLineBreakProjection; - bool removeLineBreak = endsWithLineBreak && hasLineBreakProjection; - // insert text to the subject buffer. int oldBufferLength = _window._outputBuffer.CurrentSnapshot.Length; InsertOutput(output, oldBufferLength); - if (removeLineBreak) + if (endsWithLineBreak && hasLineBreakProjection) { + // Remove line break. RemoveProjectionSpans(lineBreakProjectionSpanIndex, 1); } - else if (insertLineBreak) + else if (!endsWithLineBreak && !hasLineBreakProjection) { - InsertProjectionSpan(lineBreakProjectionSpanIndex, CreateTrackingSpan(_window._outputLineBreakBuffer, _window._lineBreakString)); + // Insert line break. + InsertProjectionSpan(lineBreakProjectionSpanIndex, _window._lineBreakString); } // caret didn't move since last time we moved it to track output: @@ -826,13 +820,14 @@ private ITextBuffer GetLanguageBuffer(SnapshotPoint point) // Grab the span following the prompt (either language or standard input). var projectionSpan = sourceSpans[promptIndex + 1]; - var inputSnapshot = projectionSpan.Snapshot; - if (_window.GetSpanKind(inputSnapshot) != ReplSpanKind.Language) + var kind = _window.GetSpanKind(projectionSpan); + if (kind != ReplSpanKind.Language) { - Debug.Assert(_window.GetSpanKind(inputSnapshot) == ReplSpanKind.StandardInput); + Debug.Assert(kind == ReplSpanKind.StandardInput); return null; } + var inputSnapshot = projectionSpan.Snapshot; var inputBuffer = inputSnapshot.TextBuffer; var projectedSpans = _window._textView.BufferGraph.MapUpToBuffer( @@ -914,7 +909,7 @@ public int IndexOfLastStandardInputSpan(ReadOnlyCollection sourceS { for (int i = sourceSpans.Count - 1; i >= 0; i--) { - if (_window.GetSpanKind(sourceSpans[i].Snapshot) == ReplSpanKind.StandardInput) + if (_window.GetSpanKind(sourceSpans[i]) == ReplSpanKind.StandardInput) { return i; } @@ -927,7 +922,7 @@ public void RemoveLastInputPrompt() { var snapshot = _window._projectionBuffer.CurrentSnapshot; var spanCount = snapshot.SpanCount; - Debug.Assert(_window.IsPrompt(snapshot.GetSourceSpan(spanCount - SpansPerLineOfInput).Snapshot)); + Debug.Assert(_window.IsPrompt(snapshot.GetSourceSpan(spanCount - SpansPerLineOfInput))); // projection buffer update must be the last operation as it might trigger event that accesses prompt line mapping: RemoveProjectionSpans(spanCount - SpansPerLineOfInput, SpansPerLineOfInput); diff --git a/src/InteractiveWindow/Editor/ReplSpanKind.cs b/src/InteractiveWindow/Editor/ReplSpanKind.cs deleted file mode 100644 index 4b5e15827c7e3ef22cb892b40986f33756f58618..0000000000000000000000000000000000000000 --- a/src/InteractiveWindow/Editor/ReplSpanKind.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.VisualStudio.InteractiveWindow -{ - internal enum ReplSpanKind - { - None, - - /// - /// The span represents output from the program (standard output) - /// - Output, - - /// - /// The span represents a prompt for input of code. - /// - Prompt, - - /// - /// The span represents a secondary prompt for more code. - /// - SecondaryPrompt, - - /// - /// The span represents code inputted after a prompt or secondary prompt. - /// - Language, - - /// - /// The span represents the prompt for input for standard input (non code input) - /// - StandardInputPrompt, - - /// - /// The span represents the input for a standard input (non code input) - /// - StandardInput, - } -} diff --git a/src/InteractiveWindow/EditorTest/InteractiveWindowTests.cs b/src/InteractiveWindow/EditorTest/InteractiveWindowTests.cs index 40ea7ab48b9ad41d21a9356841e4458579e7d0b0..9df3dd185c6749903f2e03a13631062edbb0c97f 100644 --- a/src/InteractiveWindow/EditorTest/InteractiveWindowTests.cs +++ b/src/InteractiveWindow/EditorTest/InteractiveWindowTests.cs @@ -573,8 +573,23 @@ public void ReformatBraces() new TextChange(5, 1, " "), new TextChange(7, 1, "\r\n")); + // Text from language buffer. var actualText = snapshot.GetText(); Assert.Equal("{\r\n {\r\n }\r\n}", actualText); + + // Text including prompts. + buffer = Window.TextView.TextBuffer; + snapshot = buffer.CurrentSnapshot; + actualText = snapshot.GetText(); + Assert.Equal("> {\r\n> {\r\n> }\r\n> }", actualText); + + // Prompts should be read-only. + var regions = buffer.GetReadOnlyExtents(new Span(0, snapshot.Length)); + AssertEx.SetEqual(regions, + new Span(0, 2), + new Span(5, 2), + new Span(14, 2), + new Span(23, 2)); } [Fact] @@ -721,6 +736,25 @@ private void CopyNoSelectionAndVerify(int start, int end, string expectedText, s } } + [Fact] + public void CancelMultiLineInput() + { + ApplyChanges( + Window.CurrentLanguageBuffer, + new TextChange(0, 0, "{\r\n {\r\n }\r\n}")); + + // Text including prompts. + var buffer = Window.TextView.TextBuffer; + var snapshot = buffer.CurrentSnapshot; + Assert.Equal("> {\r\n> {\r\n> }\r\n> }", snapshot.GetText()); + + Task.Run(() => Window.Operations.Cancel()).PumpingWait(); + + // Text after cancel. + snapshot = buffer.CurrentSnapshot; + Assert.Equal("> ", snapshot.GetText()); + } + private void Submit(string submission, string output) { Task.Run(() => Window.SubmitAsync(new[] { submission })).PumpingWait();