diff --git a/Python/Product/PythonTools/PythonTools.csproj b/Python/Product/PythonTools/PythonTools.csproj
index 35a366e58f..9bfbd0e924 100644
--- a/Python/Product/PythonTools/PythonTools.csproj
+++ b/Python/Product/PythonTools/PythonTools.csproj
@@ -295,6 +295,8 @@
IEnumerableExtensions.cs
+
+
@@ -310,6 +312,8 @@
+
+
diff --git a/Python/Product/PythonTools/PythonTools/Repl/BasePythonReplEvaluator.cs b/Python/Product/PythonTools/PythonTools/Repl/BasePythonReplEvaluator.cs
index 923c1b7b74..7922f5a702 100644
--- a/Python/Product/PythonTools/PythonTools/Repl/BasePythonReplEvaluator.cs
+++ b/Python/Product/PythonTools/PythonTools/Repl/BasePythonReplEvaluator.cs
@@ -51,6 +51,8 @@
using Microsoft.VisualStudioTools;
using Microsoft.VisualStudioTools.Project;
using SR = Microsoft.PythonTools.Project.SR;
+using System.Windows.Media;
+using System.Windows.Markup;
#if DEV14_OR_LATER
using IReplEvaluator = Microsoft.VisualStudio.InteractiveWindow.IInteractiveEvaluator;
@@ -361,6 +363,7 @@ private void OutputThread() {
case "RDLN": HandleReadLine(); break;
case "DETC": HandleDebuggerDetach(); break;
case "DPNG": HandleDisplayPng(); break;
+ case "DXAM": HandleDisplayXaml(); break;
case "EXIT":
// REPL has exited
_stream.Write(ExitCommandBytes);
@@ -439,6 +442,40 @@ private void HandleDisplayPng() {
DisplayImage(buffer);
}
+ private void HandleDisplayXaml() {
+ Debug.Assert(Monitor.IsEntered(_streamLock));
+
+ int len = _stream.ReadInt32();
+ byte[] buffer = new byte[len];
+ if (len != 0) {
+ int bytesRead = 0;
+ do {
+ bytesRead += _stream.Read(buffer, bytesRead, len - bytesRead);
+ } while (bytesRead != len);
+ }
+
+ using (new StreamUnlock(this)) {
+ ((System.Windows.UIElement)Window.TextView).Dispatcher.BeginInvoke((Action)(() => {
+ DisplayXaml(buffer);
+ }));
+ }
+ }
+
+ private void DisplayXaml(byte[] buffer) {
+ try {
+ var fe = (FrameworkElement)XamlReader.Load(new MemoryStream(buffer));
+ if (fe != null) {
+ WriteFrameworkElement(fe, fe.DesiredSize);
+ }
+ } catch (Exception ex) {
+ if (ex.IsCriticalException()) {
+ throw;
+ }
+ Window.WriteError(ex.ToString());
+ return;
+ }
+ }
+
internal string DoDebugAttach() {
if (_eval._attached) {
return "Cannot attach to debugger when already attached.";
@@ -560,21 +597,40 @@ private void DisplayImage(byte[] bytes) {
using (new StreamUnlock(this)) {
((System.Windows.UIElement)Window.TextView).Dispatcher.BeginInvoke((Action)(() => {
try {
- var imageSrc = new BitmapImage();
- imageSrc.BeginInit();
- imageSrc.StreamSource = new MemoryStream(bytes);
- imageSrc.EndInit();
-#if DEV14_OR_LATER
- Window.Write(new Image() { Source = imageSrc });
-#else
- Window.WriteOutput(new Image() { Source = imageSrc });
-#endif
+ WriteImage(bytes);
} catch (IOException) {
}
}));
}
}
+ private void WriteImage(byte[] bytes) {
+ var imageSrc = new BitmapImage();
+ imageSrc.BeginInit();
+ imageSrc.StreamSource = new MemoryStream(bytes);
+ imageSrc.EndInit();
+
+ var img = new Image {
+ Source = imageSrc,
+ Stretch = Stretch.Uniform,
+ StretchDirection = StretchDirection.Both
+ };
+ var control = new Border {
+ Child = img,
+ Background = Brushes.White
+ };
+
+ WriteFrameworkElement(control, new Size(imageSrc.PixelWidth, imageSrc.PixelHeight));
+ }
+
+ private void WriteFrameworkElement(UIElement control, Size desiredSize) {
+ Window.FlushOutput();
+
+ var caretPos = Window.TextView.Caret.Position.BufferPosition;
+ var manager = InlineReplAdornmentProvider.GetManager(Window.TextView);
+ manager.AddAdornment(new ZoomableInlineAdornment(control, Window.TextView, desiredSize), caretPos);
+ }
+
private void HandleModuleList() {
Debug.Assert(Monitor.IsEntered(_streamLock));
@@ -1986,6 +2042,12 @@ public static void UseInterpreterPrompts(this IReplWindow window) {
public static void SetSmartUpDown(this IReplWindow window, bool setting) {
window.SetOptionValue(ReplOptions.UseSmartUpDown, setting);
}
+
+ public static void FlushOutput(this IReplWindow window) {
+ // hacky way to force flushing of the output buffer as there is no API for it.
+ window.SetOptionValue(ReplOptions.SupportAnsiColors, window.GetOptionValue(ReplOptions.SupportAnsiColors));
+
+ }
#endif
}
diff --git a/Python/Product/PythonTools/PythonTools/Repl/InlineReplAdornment.cs b/Python/Product/PythonTools/PythonTools/Repl/InlineReplAdornment.cs
new file mode 100644
index 0000000000..8a74940e4f
--- /dev/null
+++ b/Python/Product/PythonTools/PythonTools/Repl/InlineReplAdornment.cs
@@ -0,0 +1,55 @@
+/* ****************************************************************************
+ *
+ * Copyright (c) Microsoft Corporation.
+ *
+ * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
+ * copy of the license can be found in the License.html file at the root of this distribution. If
+ * you cannot locate the Apache License, Version 2.0, please send an email to
+ * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
+ * by the terms of the Apache License, Version 2.0.
+ *
+ * You must not remove this notice, or any other, from this software.
+ *
+ * ***************************************************************************/
+
+using System.ComponentModel.Composition;
+using System.Windows;
+#if DEV14_OR_LATER
+using Microsoft.VisualStudio.InteractiveWindow;
+#else
+using Microsoft.VisualStudio.Repl;
+#endif
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Tagging;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.PythonTools.Repl {
+ [Export(typeof(IViewTaggerProvider))]
+ [TagType(typeof(IntraTextAdornmentTag))]
+#if DEV14_OR_LATER
+ [ContentType(PredefinedInteractiveContentTypes.InteractiveContentTypeName)]
+#else
+ [ContentType(ReplConstants.ReplContentTypeName)]
+#endif
+ internal class InlineReplAdornmentProvider : IViewTaggerProvider {
+ public ITagger CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag {
+ if (buffer == null || textView == null || typeof(T) != typeof(IntraTextAdornmentTag)) {
+ return null;
+ }
+
+ return (ITagger)textView.Properties.GetOrCreateSingletonProperty(
+ typeof(InlineReplAdornmentManager),
+ () => new InlineReplAdornmentManager(textView)
+ );
+ }
+
+ internal static InlineReplAdornmentManager GetManager(ITextView view) {
+ InlineReplAdornmentManager result;
+ if (!view.Properties.TryGetProperty(typeof(InlineReplAdornmentManager), out result)) {
+ return null;
+ }
+ return result;
+ }
+ }
+}
diff --git a/Python/Product/PythonTools/PythonTools/Repl/InlineReplAdornmentManager.cs b/Python/Product/PythonTools/PythonTools/Repl/InlineReplAdornmentManager.cs
new file mode 100644
index 0000000000..be48fe098d
--- /dev/null
+++ b/Python/Product/PythonTools/PythonTools/Repl/InlineReplAdornmentManager.cs
@@ -0,0 +1,96 @@
+/* ****************************************************************************
+ *
+ * Copyright (c) Microsoft Corporation.
+ *
+ * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
+ * copy of the license can be found in the License.html file at the root of this distribution. If
+ * you cannot locate the Apache License, Version 2.0, please send an email to
+ * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
+ * by the terms of the Apache License, Version 2.0.
+ *
+ * You must not remove this notice, or any other, from this software.
+ *
+ * ***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Threading;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Tagging;
+
+namespace Microsoft.PythonTools.Repl {
+ class InlineReplAdornmentManager : ITagger {
+ private readonly ITextView _textView;
+ private readonly List> _tags;
+ private readonly Dispatcher _dispatcher;
+
+ internal InlineReplAdornmentManager(ITextView textView) {
+ _textView = textView;
+ _tags = new List>();
+ _dispatcher = Dispatcher.CurrentDispatcher;
+ textView.TextBuffer.Changed += TextBuffer_Changed;
+ }
+
+ void TextBuffer_Changed(object sender, TextContentChangedEventArgs e) {
+ if (e.After.Length == 0) {
+ // screen was cleared...
+ RemoveAll();
+ }
+ }
+
+ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) {
+ var result = new List>();
+ for (int i = 0; i < _tags.Count; i++) {
+ if (_tags[i].Item1.Snapshot != _textView.TextSnapshot) {
+ // update to the latest snapshot
+ _tags[i] = new Tuple(
+ _tags[i].Item1.TranslateTo(_textView.TextSnapshot, PointTrackingMode.Negative),
+ _tags[i].Item2
+ );
+ }
+
+ var span = new SnapshotSpan(_textView.TextSnapshot, _tags[i].Item1, 0);
+ bool intersects = false;
+ foreach (var applicableSpan in spans) {
+ if (applicableSpan.TranslateTo(_textView.TextSnapshot, SpanTrackingMode.EdgeInclusive).IntersectsWith(span)) {
+ intersects = true;
+ break;
+ }
+ }
+ if (!intersects) {
+ continue;
+ }
+ var tag = new IntraTextAdornmentTag(_tags[i].Item2, null);
+ result.Add(new TagSpan(span, tag));
+ }
+ return result;
+ }
+
+ public void AddAdornment(UIElement uiElement, SnapshotPoint targetLoc) {
+ if (Dispatcher.CurrentDispatcher != _dispatcher) {
+ _dispatcher.BeginInvoke(new Action(() => AddAdornment(uiElement, targetLoc)));
+ return;
+ }
+ var targetLine = targetLoc.GetContainingLine();
+ _tags.Add(new Tuple(targetLoc, uiElement));
+ var handler = TagsChanged;
+ if (handler != null) {
+ var span = new SnapshotSpan(_textView.TextSnapshot, targetLine.Start, targetLine.LengthIncludingLineBreak);
+ var args = new SnapshotSpanEventArgs(span);
+ handler(this, args);
+ }
+ }
+
+ public IList> Adornments {
+ get { return _tags; }
+ }
+
+ public void RemoveAll() {
+ _tags.Clear();
+ }
+
+ public event EventHandler TagsChanged;
+ }
+}
diff --git a/Python/Product/PythonTools/PythonTools/Repl/ResizingAdorner.cs b/Python/Product/PythonTools/PythonTools/Repl/ResizingAdorner.cs
new file mode 100644
index 0000000000..8e441855fa
--- /dev/null
+++ b/Python/Product/PythonTools/PythonTools/Repl/ResizingAdorner.cs
@@ -0,0 +1,166 @@
+/* ****************************************************************************
+ *
+ * Copyright (c) Microsoft Corporation.
+ *
+ * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
+ * copy of the license can be found in the License.html file at the root of this distribution. If
+ * you cannot locate the Apache License, Version 2.0, please send an email to
+ * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
+ * by the terms of the Apache License, Version 2.0.
+ *
+ * You must not remove this notice, or any other, from this software.
+ *
+ * ***************************************************************************/
+
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace Microsoft.PythonTools.Repl {
+ internal class ResizingAdorner : Adorner {
+ private readonly VisualCollection _visualChildren;
+ private readonly FrameworkElement _bottomRight;
+
+ private Point _mouseDownPoint;
+ private readonly Size _originalSize;
+ private Size _initialSize;
+ private Size _desiredSize;
+
+ public ResizingAdorner(UIElement adornedElement, Size originalSize)
+ : base(adornedElement) {
+ _visualChildren = new VisualCollection(this);
+ _originalSize = _desiredSize = originalSize;
+ Width = Height = double.NaN;
+ _bottomRight = BuildAdornerCorner(Cursors.SizeNWSE);
+ }
+
+ private FrameworkElement BuildAdornerCorner(Cursor cursor) {
+ var thumb = new Border();
+ // TODO: this thumb should be styled to look like a dotted triangle,
+ // similar to the one you can see on the bottom right corner of
+ // Internet Explorer window
+ thumb.Cursor = cursor;
+ thumb.Height = thumb.Width = 10;
+ thumb.Opacity = 0.40;
+ thumb.Background = new SolidColorBrush(Colors.MediumBlue);
+ thumb.IsHitTestVisible = true;
+ thumb.MouseDown += Thumb_MouseDown;
+ thumb.MouseMove += Thumb_MouseMove;
+ thumb.MouseUp += Thumb_MouseUp;
+ thumb.HorizontalAlignment = HorizontalAlignment.Right;
+ thumb.VerticalAlignment = VerticalAlignment.Bottom;
+ _visualChildren.Add(thumb);
+ return thumb;
+ }
+
+ private void Thumb_MouseDown(object sender, MouseButtonEventArgs e) {
+ if (e.ChangedButton != MouseButton.Left) {
+ return;
+ }
+
+ _initialSize = _desiredSize;
+ _mouseDownPoint = e.GetPosition(AdornedElement);
+ _bottomRight.CaptureMouse();
+
+ var evt = ResizeStarted;
+ if (evt != null) {
+ evt(this, e);
+ }
+
+ e.Handled = true;
+ }
+
+ private void Thumb_MouseMove(object sender, MouseEventArgs e) {
+ if (!_bottomRight.IsMouseCaptured) {
+ return;
+ }
+
+ var pt = e.GetPosition(AdornedElement);
+ var newWidth = _initialSize.Width + pt.X - _mouseDownPoint.X;
+ //var newHeight = _initialSize.Height + pt.Y - _initialPoint.Y;
+ var newHeight = _initialSize.Height / _initialSize.Width * newWidth;
+
+ var desiredSize = new Size(
+ Math.Max(newWidth, 0),
+ Math.Max(newHeight, 0)
+ );
+
+ if (_desiredSize != desiredSize) {
+ _desiredSize = desiredSize;
+ var evt = DesiredSizeChanged;
+ if (evt != null) {
+ evt(this, new DesiredSizeChangedEventArgs(desiredSize));
+ }
+ }
+
+ e.Handled = true;
+ }
+
+ private void Thumb_MouseUp(object sender, MouseButtonEventArgs e) {
+ if (e.ChangedButton != MouseButton.Left) {
+ return;
+ }
+
+ _initialSize = _desiredSize;
+ _bottomRight.ReleaseMouseCapture();
+
+ var evt = ResizeCompleted;
+ if (evt != null) {
+ evt(this, e);
+ }
+
+ e.Handled = true;
+ }
+
+ protected override Size MeasureOverride(Size constraint) {
+ if (_desiredSize.Width < 0) {
+ _desiredSize.Width = 0;
+ }
+ if (_desiredSize.Height < 0) {
+ _desiredSize.Height = 0;
+ }
+
+ var size = new Size(
+ Math.Min(_desiredSize.Width, constraint.Width),
+ Math.Min(_desiredSize.Height, constraint.Height)
+ );
+ _bottomRight.Measure(size);
+ return size;
+ }
+
+ protected override Size ArrangeOverride(Size finalSize) {
+ _bottomRight.Arrange(new Rect(new Point(), finalSize));
+ return finalSize;
+ }
+
+ protected override int VisualChildrenCount {
+ get { return _visualChildren.Count; }
+ }
+
+ protected override Visual GetVisualChild(int index) {
+ return _visualChildren[index];
+ }
+
+ public event RoutedEventHandler ResizeStarted;
+ public event RoutedEventHandler ResizeCompleted;
+ public event EventHandler DesiredSizeChanged;
+ }
+
+ class DesiredSizeChangedEventArgs : EventArgs {
+ private readonly Size _size;
+
+ public DesiredSizeChangedEventArgs(Size size) {
+ _size = size;
+ }
+
+ public Size Size {
+ get {
+ return _size;
+ }
+ }
+ }
+}
diff --git a/Python/Product/PythonTools/PythonTools/Repl/ZoomableInlineAdornment.cs b/Python/Product/PythonTools/PythonTools/Repl/ZoomableInlineAdornment.cs
new file mode 100644
index 0000000000..b233d9549c
--- /dev/null
+++ b/Python/Product/PythonTools/PythonTools/Repl/ZoomableInlineAdornment.cs
@@ -0,0 +1,197 @@
+/* ****************************************************************************
+ *
+ * Copyright (c) Microsoft Corporation.
+ *
+ * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
+ * copy of the license can be found in the License.html file at the root of this distribution. If
+ * you cannot locate the Apache License, Version 2.0, please send an email to
+ * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
+ * by the terms of the Apache License, Version 2.0.
+ *
+ * You must not remove this notice, or any other, from this software.
+ *
+ * ***************************************************************************/
+
+using System;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.PythonTools.Repl {
+ internal class ZoomableInlineAdornment : Grid {
+ private readonly ITextView _parent;
+ private ResizingAdorner _adorner;
+ private readonly Size _originalSize;
+ private Size _desiredSize;
+
+ public ZoomableInlineAdornment(UIElement content, ITextView parent, Size desiredSize) {
+ _parent = parent;
+ Debug.Assert(parent is IInputElement);
+ _originalSize = _desiredSize = new Size(
+ Math.Max(double.IsNaN(desiredSize.Width) ? 100 : desiredSize.Width, 10),
+ Math.Max(double.IsNaN(desiredSize.Height) ? 100 : desiredSize.Height, 10)
+ );
+
+ // First time through, we want to reduce the image to fit within the
+ // viewport.
+ if (_desiredSize.Width > parent.ViewportWidth) {
+ _desiredSize.Width = parent.ViewportWidth;
+ _desiredSize.Height = _originalSize.Height / _originalSize.Width * _desiredSize.Width;
+ }
+ if (_desiredSize.Height > parent.ViewportHeight) {
+ _desiredSize.Height = parent.ViewportHeight;
+ _desiredSize.Width = _originalSize.Width / _originalSize.Height * _desiredSize.Height;
+ }
+
+ ContextMenu = MakeContextMenu();
+
+ Focusable = true;
+ MinWidth = MinHeight = 50;
+
+ Children.Add(content);
+
+ GotFocus += OnGotFocus;
+ LostFocus += OnLostFocus;
+ }
+
+ protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) {
+ base.OnPreviewMouseLeftButtonDown(e);
+
+ Focus();
+ e.Handled = true;
+ }
+
+ protected override void OnPreviewMouseRightButtonUp(MouseButtonEventArgs e) {
+ base.OnPreviewMouseRightButtonUp(e);
+
+ ContextMenu.IsOpen = true;
+ e.Handled = true;
+ }
+
+ private ContextMenu MakeContextMenu() {
+ var result = new ContextMenu();
+ AddMenuItem(result, "Copy", "Ctrl+C", (s, e) => OnCopy());
+ result.Items.Add(new Separator());
+ AddMenuItem(result, "Zoom In", "Ctrl+OemPlus", (s, e) => OnZoomIn());
+ AddMenuItem(result, "Zoom Out", "Ctrl+OemMinus", (s, e) => OnZoomOut());
+ result.Items.Add(new Separator());
+ AddMenuItem(result, "150%", null, (s, e) => Zoom(1.5));
+ AddMenuItem(result, "100%", null, (s, e) => Zoom(1.0));
+ AddMenuItem(result, "75%", null, (s, e) => Zoom(0.75));
+ AddMenuItem(result, "50%", null, (s, e) => Zoom(0.50));
+ AddMenuItem(result, "25%", null, (s, e) => Zoom(0.25));
+ return result;
+ }
+
+ private static void AddMenuItem(ContextMenu menu, string text, string shortcut, RoutedEventHandler handler) {
+ var item = new MenuItem();
+ item.Header = text;
+ item.Click += handler;
+ menu.Items.Add(item);
+ }
+
+ private void OnGotFocus(object sender, RoutedEventArgs args) {
+ _adorner = new ResizingAdorner(this, _desiredSize);
+ _adorner.DesiredSizeChanged += OnDesiredSizeChanged;
+
+ var adornerLayer = AdornerLayer.GetAdornerLayer(this);
+ if (adornerLayer != null) {
+ adornerLayer.Add(_adorner);
+ }
+ }
+
+ private void OnLostFocus(object sender, RoutedEventArgs args) {
+ if (_adorner == null) {
+ Debug.Fail("Lost focus without creating an adorner");
+ return;
+ }
+
+ _adorner.DesiredSizeChanged -= OnDesiredSizeChanged;
+
+ var adornerLayer = AdornerLayer.GetAdornerLayer(this);
+ if (adornerLayer != null) {
+ adornerLayer.Remove(_adorner);
+ _adorner = null;
+ }
+ }
+
+ protected override Size MeasureOverride(Size constraint) {
+ if (_desiredSize.Width < MinWidth) {
+ _desiredSize.Width = MinWidth;
+ }
+ if (_desiredSize.Height < MinHeight) {
+ _desiredSize.Height = MinHeight;
+ }
+
+ var size = new Size(
+ Math.Min(_desiredSize.Width, constraint.Width),
+ Math.Min(_desiredSize.Height, constraint.Height)
+ );
+ foreach (UIElement c in Children) {
+ c.Measure(size);
+ }
+ return size;
+ }
+
+ private void OnDesiredSizeChanged(object sender, DesiredSizeChangedEventArgs e) {
+ _desiredSize = e.Size;
+ InvalidateMeasure();
+ }
+
+ protected override void OnPreviewKeyDown(KeyEventArgs args) {
+ var modifiers = args.KeyboardDevice.Modifiers & ModifierKeys.Control;
+ if (modifiers == ModifierKeys.Control && (args.Key == Key.OemPlus || args.Key == Key.Add)) {
+ OnZoomIn();
+ args.Handled = true;
+ } else if (modifiers == ModifierKeys.Control && (args.Key == Key.OemMinus || args.Key == Key.Subtract)) {
+ OnZoomOut();
+ args.Handled = true;
+ }
+
+ base.OnPreviewKeyDown(args);
+ }
+
+ private void OnCopy() {
+ double width = ActualWidth;
+ double height = ActualHeight;
+ RenderTargetBitmap bmpCopied = new RenderTargetBitmap(
+ (int)Math.Round(width),
+ (int)Math.Round(height),
+ 96,
+ 96,
+ PixelFormats.Default
+ );
+ DrawingVisual dv = new DrawingVisual();
+ using (DrawingContext dc = dv.RenderOpen()) {
+ dc.DrawRectangle(Brushes.White, null, new Rect(new Point(), new Size(width, height)));
+ VisualBrush vb = new VisualBrush(this);
+ dc.DrawRectangle(vb, null, new Rect(new Point(), new Size(width, height)));
+ }
+ bmpCopied.Render(dv);
+ Clipboard.SetImage(bmpCopied);
+ }
+
+ internal void Zoom(double zoomFactor) {
+ _desiredSize = new Size(
+ Math.Max(_originalSize.Width * zoomFactor, 50),
+ Math.Max(_originalSize.Height * zoomFactor, _originalSize.Height / _originalSize.Width * 50)
+ );
+ InvalidateMeasure();
+ }
+
+ private void OnZoomIn() {
+ _desiredSize = new Size(_desiredSize.Width * 1.1, _desiredSize.Height * 1.1);
+ InvalidateMeasure();
+ }
+
+ private void OnZoomOut() {
+ _desiredSize = new Size(_desiredSize.Width * 0.9, _desiredSize.Height * 0.9);
+ InvalidateMeasure();
+ }
+ }
+}
diff --git a/Python/Product/PythonTools/visualstudio_ipython_repl.py b/Python/Product/PythonTools/visualstudio_ipython_repl.py
index 3fa70540ee..e15aafb0d7 100644
--- a/Python/Product/PythonTools/visualstudio_ipython_repl.py
+++ b/Python/Product/PythonTools/visualstudio_ipython_repl.py
@@ -158,10 +158,20 @@ def handle_execute_output(self, content):
self.write_data(output, execution_count)
def write_data(self, data, execution_count = None):
+ output_xaml = data.get('application/xaml+xml', None)
+ if output_xaml is not None:
+ try:
+ if isinstance(output_xaml, str) and sys.version_info[0] >= 3:
+ output_xaml = output_xaml.encode('ascii')
+ self._vs_backend.write_xaml(decodestring(output_xaml))
+ self._vs_backend.write_stdout('\n')
+ return
+ except:
+ pass
output_png = data.get('image/png', None)
if output_png is not None:
- try:
+ try:
if isinstance(output_png, str) and sys.version_info[0] >= 3:
output_png = output_png.encode('ascii')
self._vs_backend.write_png(decodestring(output_png))
diff --git a/Python/Product/PythonTools/visualstudio_py_repl.py b/Python/Product/PythonTools/visualstudio_py_repl.py
index 535a664821..373dd95860 100644
--- a/Python/Product/PythonTools/visualstudio_py_repl.py
+++ b/Python/Product/PythonTools/visualstudio_py_repl.py
@@ -157,6 +157,7 @@ class ReplBackend(object):
_DBGA = to_bytes('DBGA')
_DETC = to_bytes('DETC')
_DPNG = to_bytes('DPNG')
+ _DXAM = to_bytes('DXAM')
_MERR = to_bytes('MERR')
_SERR = to_bytes('SERR')
@@ -395,6 +396,12 @@ def write_png(self, image_bytes):
write_int(self.conn, len(image_bytes))
write_bytes(self.conn, image_bytes)
+ def write_xaml(self, xaml_bytes):
+ with self.send_lock:
+ write_bytes(self.conn, ReplBackend._DXAM)
+ write_int(self.conn, len(xaml_bytes))
+ write_bytes(self.conn, xaml_bytes)
+
def send_prompt(self, ps1, ps2, update_all = True):
"""sends the current prompt to the interactive window"""
with self.send_lock: