Skip to content

Commit 586d9ac

Browse files
committed
Merge pull request #555 from zooba/issue-406
Issue 406
2 parents 00a8d07 + 9228a84 commit 586d9ac

File tree

8 files changed

+607
-10
lines changed

8 files changed

+607
-10
lines changed

Python/Product/PythonTools/PythonTools.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@
295295
<Link>IEnumerableExtensions.cs</Link>
296296
</Compile>
297297
<Compile Include="PythonTools\Repl\IMultipleScopeEvaluator.cs" />
298+
<Compile Include="PythonTools\Repl\InlineReplAdornment.cs" />
299+
<Compile Include="PythonTools\Repl\InlineReplAdornmentManager.cs" />
298300
<Compile Include="PythonTools\Repl\InteractiveWindowCommands.cs" />
299301
<Compile Include="PythonTools\Repl\InteractiveWindowProvider.cs" />
300302
<Compile Include="IPythonToolsToolWindowService.cs" />
@@ -310,6 +312,8 @@
310312
<Compile Include="PythonTools\Intellisense\PythonSuggestedImportAction.cs" />
311313
<Compile Include="PythonTools\Project\IPythonProjectLaunchProperties.cs" />
312314
<Compile Include="PythonTools\Project\PythonProjectLaunchProperties.cs" />
315+
<Compile Include="PythonTools\Repl\ResizingAdorner.cs" />
316+
<Compile Include="PythonTools\Repl\ZoomableInlineAdornment.cs" />
313317
<Compile Include="PythonTools\SurveyNewsService.cs" />
314318
<Compile Include="PythonTools\Options\GlobalInterpreterOptions.cs" />
315319
<Compile Include="PythonTools\Options\GeneralOptions.cs" />

Python/Product/PythonTools/PythonTools/Repl/BasePythonReplEvaluator.cs

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
using Microsoft.VisualStudioTools;
5252
using Microsoft.VisualStudioTools.Project;
5353
using SR = Microsoft.PythonTools.Project.SR;
54+
using System.Windows.Media;
55+
using System.Windows.Markup;
5456

5557
#if DEV14_OR_LATER
5658
using IReplEvaluator = Microsoft.VisualStudio.InteractiveWindow.IInteractiveEvaluator;
@@ -361,6 +363,7 @@ private void OutputThread() {
361363
case "RDLN": HandleReadLine(); break;
362364
case "DETC": HandleDebuggerDetach(); break;
363365
case "DPNG": HandleDisplayPng(); break;
366+
case "DXAM": HandleDisplayXaml(); break;
364367
case "EXIT":
365368
// REPL has exited
366369
_stream.Write(ExitCommandBytes);
@@ -439,6 +442,40 @@ private void HandleDisplayPng() {
439442
DisplayImage(buffer);
440443
}
441444

445+
private void HandleDisplayXaml() {
446+
Debug.Assert(Monitor.IsEntered(_streamLock));
447+
448+
int len = _stream.ReadInt32();
449+
byte[] buffer = new byte[len];
450+
if (len != 0) {
451+
int bytesRead = 0;
452+
do {
453+
bytesRead += _stream.Read(buffer, bytesRead, len - bytesRead);
454+
} while (bytesRead != len);
455+
}
456+
457+
using (new StreamUnlock(this)) {
458+
((System.Windows.UIElement)Window.TextView).Dispatcher.BeginInvoke((Action)(() => {
459+
DisplayXaml(buffer);
460+
}));
461+
}
462+
}
463+
464+
private void DisplayXaml(byte[] buffer) {
465+
try {
466+
var fe = (FrameworkElement)XamlReader.Load(new MemoryStream(buffer));
467+
if (fe != null) {
468+
WriteFrameworkElement(fe, fe.DesiredSize);
469+
}
470+
} catch (Exception ex) {
471+
if (ex.IsCriticalException()) {
472+
throw;
473+
}
474+
Window.WriteError(ex.ToString());
475+
return;
476+
}
477+
}
478+
442479
internal string DoDebugAttach() {
443480
if (_eval._attached) {
444481
return "Cannot attach to debugger when already attached.";
@@ -560,21 +597,40 @@ private void DisplayImage(byte[] bytes) {
560597
using (new StreamUnlock(this)) {
561598
((System.Windows.UIElement)Window.TextView).Dispatcher.BeginInvoke((Action)(() => {
562599
try {
563-
var imageSrc = new BitmapImage();
564-
imageSrc.BeginInit();
565-
imageSrc.StreamSource = new MemoryStream(bytes);
566-
imageSrc.EndInit();
567-
#if DEV14_OR_LATER
568-
Window.Write(new Image() { Source = imageSrc });
569-
#else
570-
Window.WriteOutput(new Image() { Source = imageSrc });
571-
#endif
600+
WriteImage(bytes);
572601
} catch (IOException) {
573602
}
574603
}));
575604
}
576605
}
577606

607+
private void WriteImage(byte[] bytes) {
608+
var imageSrc = new BitmapImage();
609+
imageSrc.BeginInit();
610+
imageSrc.StreamSource = new MemoryStream(bytes);
611+
imageSrc.EndInit();
612+
613+
var img = new Image {
614+
Source = imageSrc,
615+
Stretch = Stretch.Uniform,
616+
StretchDirection = StretchDirection.Both
617+
};
618+
var control = new Border {
619+
Child = img,
620+
Background = Brushes.White
621+
};
622+
623+
WriteFrameworkElement(control, new Size(imageSrc.PixelWidth, imageSrc.PixelHeight));
624+
}
625+
626+
private void WriteFrameworkElement(UIElement control, Size desiredSize) {
627+
Window.FlushOutput();
628+
629+
var caretPos = Window.TextView.Caret.Position.BufferPosition;
630+
var manager = InlineReplAdornmentProvider.GetManager(Window.TextView);
631+
manager.AddAdornment(new ZoomableInlineAdornment(control, Window.TextView, desiredSize), caretPos);
632+
}
633+
578634
private void HandleModuleList() {
579635
Debug.Assert(Monitor.IsEntered(_streamLock));
580636

@@ -1992,6 +2048,12 @@ public static void UseInterpreterPrompts(this IReplWindow window) {
19922048
public static void SetSmartUpDown(this IReplWindow window, bool setting) {
19932049
window.SetOptionValue(ReplOptions.UseSmartUpDown, setting);
19942050
}
2051+
2052+
public static void FlushOutput(this IReplWindow window) {
2053+
// hacky way to force flushing of the output buffer as there is no API for it.
2054+
window.SetOptionValue(ReplOptions.SupportAnsiColors, window.GetOptionValue(ReplOptions.SupportAnsiColors));
2055+
2056+
}
19952057
#endif
19962058

19972059
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/* ****************************************************************************
2+
*
3+
* Copyright (c) Microsoft Corporation.
4+
*
5+
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
6+
* copy of the license can be found in the License.html file at the root of this distribution. If
7+
* you cannot locate the Apache License, Version 2.0, please send an email to
8+
* [email protected]. By using this source code in any fashion, you are agreeing to be bound
9+
* by the terms of the Apache License, Version 2.0.
10+
*
11+
* You must not remove this notice, or any other, from this software.
12+
*
13+
* ***************************************************************************/
14+
15+
using System.ComponentModel.Composition;
16+
using System.Windows;
17+
#if DEV14_OR_LATER
18+
using Microsoft.VisualStudio.InteractiveWindow;
19+
#else
20+
using Microsoft.VisualStudio.Repl;
21+
#endif
22+
using Microsoft.VisualStudio.Text;
23+
using Microsoft.VisualStudio.Text.Editor;
24+
using Microsoft.VisualStudio.Text.Tagging;
25+
using Microsoft.VisualStudio.Utilities;
26+
27+
namespace Microsoft.PythonTools.Repl {
28+
[Export(typeof(IViewTaggerProvider))]
29+
[TagType(typeof(IntraTextAdornmentTag))]
30+
#if DEV14_OR_LATER
31+
[ContentType(PredefinedInteractiveContentTypes.InteractiveContentTypeName)]
32+
#else
33+
[ContentType(ReplConstants.ReplContentTypeName)]
34+
#endif
35+
internal class InlineReplAdornmentProvider : IViewTaggerProvider {
36+
public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag {
37+
if (buffer == null || textView == null || typeof(T) != typeof(IntraTextAdornmentTag)) {
38+
return null;
39+
}
40+
41+
return (ITagger<T>)textView.Properties.GetOrCreateSingletonProperty<InlineReplAdornmentManager>(
42+
typeof(InlineReplAdornmentManager),
43+
() => new InlineReplAdornmentManager(textView)
44+
);
45+
}
46+
47+
internal static InlineReplAdornmentManager GetManager(ITextView view) {
48+
InlineReplAdornmentManager result;
49+
if (!view.Properties.TryGetProperty<InlineReplAdornmentManager>(typeof(InlineReplAdornmentManager), out result)) {
50+
return null;
51+
}
52+
return result;
53+
}
54+
}
55+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/* ****************************************************************************
2+
*
3+
* Copyright (c) Microsoft Corporation.
4+
*
5+
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
6+
* copy of the license can be found in the License.html file at the root of this distribution. If
7+
* you cannot locate the Apache License, Version 2.0, please send an email to
8+
* [email protected]. By using this source code in any fashion, you are agreeing to be bound
9+
* by the terms of the Apache License, Version 2.0.
10+
*
11+
* You must not remove this notice, or any other, from this software.
12+
*
13+
* ***************************************************************************/
14+
15+
using System;
16+
using System.Collections.Generic;
17+
using System.Windows;
18+
using System.Windows.Threading;
19+
using Microsoft.VisualStudio.Text;
20+
using Microsoft.VisualStudio.Text.Editor;
21+
using Microsoft.VisualStudio.Text.Tagging;
22+
23+
namespace Microsoft.PythonTools.Repl {
24+
class InlineReplAdornmentManager : ITagger<IntraTextAdornmentTag> {
25+
private readonly ITextView _textView;
26+
private readonly List<Tuple<SnapshotPoint, UIElement>> _tags;
27+
private readonly Dispatcher _dispatcher;
28+
29+
internal InlineReplAdornmentManager(ITextView textView) {
30+
_textView = textView;
31+
_tags = new List<Tuple<SnapshotPoint, UIElement>>();
32+
_dispatcher = Dispatcher.CurrentDispatcher;
33+
textView.TextBuffer.Changed += TextBuffer_Changed;
34+
}
35+
36+
void TextBuffer_Changed(object sender, TextContentChangedEventArgs e) {
37+
if (e.After.Length == 0) {
38+
// screen was cleared...
39+
RemoveAll();
40+
}
41+
}
42+
43+
public IEnumerable<ITagSpan<IntraTextAdornmentTag>> GetTags(NormalizedSnapshotSpanCollection spans) {
44+
var result = new List<TagSpan<IntraTextAdornmentTag>>();
45+
for (int i = 0; i < _tags.Count; i++) {
46+
if (_tags[i].Item1.Snapshot != _textView.TextSnapshot) {
47+
// update to the latest snapshot
48+
_tags[i] = new Tuple<SnapshotPoint, UIElement>(
49+
_tags[i].Item1.TranslateTo(_textView.TextSnapshot, PointTrackingMode.Negative),
50+
_tags[i].Item2
51+
);
52+
}
53+
54+
var span = new SnapshotSpan(_textView.TextSnapshot, _tags[i].Item1, 0);
55+
bool intersects = false;
56+
foreach (var applicableSpan in spans) {
57+
if (applicableSpan.TranslateTo(_textView.TextSnapshot, SpanTrackingMode.EdgeInclusive).IntersectsWith(span)) {
58+
intersects = true;
59+
break;
60+
}
61+
}
62+
if (!intersects) {
63+
continue;
64+
}
65+
var tag = new IntraTextAdornmentTag(_tags[i].Item2, null);
66+
result.Add(new TagSpan<IntraTextAdornmentTag>(span, tag));
67+
}
68+
return result;
69+
}
70+
71+
public void AddAdornment(UIElement uiElement, SnapshotPoint targetLoc) {
72+
if (Dispatcher.CurrentDispatcher != _dispatcher) {
73+
_dispatcher.BeginInvoke(new Action(() => AddAdornment(uiElement, targetLoc)));
74+
return;
75+
}
76+
var targetLine = targetLoc.GetContainingLine();
77+
_tags.Add(new Tuple<SnapshotPoint, UIElement>(targetLoc, uiElement));
78+
var handler = TagsChanged;
79+
if (handler != null) {
80+
var span = new SnapshotSpan(_textView.TextSnapshot, targetLine.Start, targetLine.LengthIncludingLineBreak);
81+
var args = new SnapshotSpanEventArgs(span);
82+
handler(this, args);
83+
}
84+
}
85+
86+
public IList<Tuple<SnapshotPoint, UIElement>> Adornments {
87+
get { return _tags; }
88+
}
89+
90+
public void RemoveAll() {
91+
_tags.Clear();
92+
}
93+
94+
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
95+
}
96+
}

0 commit comments

Comments
 (0)