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: