using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using System.Windows.Forms; using IronPython.Runtime.Exceptions; using Microsoft.Scripting.Hosting; namespace NavisPythonShell.NpsRuntime { /// /// A stream to write output to... /// This can be passed into the python interpreter to render all output to. /// Only a minimal subset is actually implemented - this is all we really /// expect to use. /// public class ScriptOutputStream: Stream { private readonly ScriptOutput _gui; private readonly ScriptEngine _engine; private int _bomCharsLeft; // we want to get rid of pesky UTF8-BOM-Chars on write private readonly Queue _completedLines; // one memorystream per line of input private MemoryStream _inputBuffer; public ScriptOutputStream(ScriptOutput gui, ScriptEngine engine) { _gui = gui; _engine = engine; _gui.txtStdOut.KeyPress += KeyPressEventHandler; _gui.txtStdOut.KeyDown += KeyDownEventHandler; //_gui.Closing += ClosingEventHandler; //_gui.Closed += ClosedEventHandler; _gui.txtStdOut.Focus(); _completedLines = new Queue(); _inputBuffer = new MemoryStream(); _bomCharsLeft = 3; //0xef, 0xbb, 0xbf for UTF-8 (see http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding) } void ClosedEventHandler(object sender, EventArgs e) { _engine.Runtime.Shutdown(); _completedLines.Enqueue(new MemoryStream()); } /// /// Terminate reading from STDIN. /// FIXME: this doesn't work! /// private void ClosingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { _engine.Runtime.Shutdown(); _completedLines.Enqueue(new MemoryStream()); } /// /// Complete a line when the enter key is pressed. Also /// try to emulate a nice control window. This is going to be a big gigantic pile /// of ifs, sigh. /// void KeyDownEventHandler(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter || e.KeyCode == Keys.Return) { var line = _inputBuffer; var newLine = new byte[] {/*0x0d,*/ 0x0a}; line.Write(newLine, 0, newLine.Length); // append new-line line.Seek(0, SeekOrigin.Begin); // rewind the line for later reading... _completedLines.Enqueue(line); _inputBuffer = new MemoryStream(); } else if (e.KeyCode == Keys.Back || e.KeyCode == Keys.Left) { // remove last character from input buffer if (_inputBuffer.Position > 0) { var line = new MemoryStream(); line.Write(_inputBuffer.GetBuffer(), 0, (int)(_inputBuffer.Position - 1)); _inputBuffer = line; _gui.txtStdOut.Text = _gui.txtStdOut.Text.Substring(0, _gui.txtStdOut.Text.Length - 1); _gui.txtStdOut.SelectionStart = _gui.txtStdOut.Text.Length; _gui.txtStdOut.ScrollToCaret(); } // do not pass backspace / left on to txtStdOut e.Handled = true; } else if (e.KeyCode == Keys.Right) { // do not move right ever... e.Handled = true; } } /// /// Stash away any printable characters for later... /// void KeyPressEventHandler(object sender, KeyPressEventArgs e) { if (!char.IsControl(e.KeyChar)) { var bytes = Encoding.UTF8.GetBytes(new[] {e.KeyChar}); _inputBuffer.Write(bytes, 0, bytes.Length); _gui.txtStdOut.Focus(); } else { if (e.KeyChar == '\r') { // user pressed enter _gui.txtStdOut.Text += "\r\n"; _gui.txtStdOut.SelectionStart = _gui.txtStdOut.Text.Length; _gui.txtStdOut.Focus(); } // pretend we have handled this key (so using arrows does not confuse the user) e.Handled = true; } } /// /// Append the text in the buffer to gui.txtStdOut /// public override void Write(byte[] buffer, int offset, int count) { lock (this) { if (_gui.IsDisposed) { return; } while (_bomCharsLeft > 0 && count > 0) { _bomCharsLeft--; count--; offset++; } var actualBuffer = new byte[count]; Array.Copy(buffer, offset, actualBuffer, 0, count); var text = Encoding.UTF8.GetString(actualBuffer); Debug.WriteLine(text); _gui.BeginInvoke((Action)delegate() { _gui.txtStdOut.AppendText(text); _gui.txtStdOut.SelectionStart = _gui.txtStdOut.Text.Length; _gui.txtStdOut.ScrollToCaret(); }); Application.DoEvents(); } } public override void Flush() { } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } /// /// Read from the _inputBuffer, block until a new line has been entered... /// public override int Read(byte[] buffer, int offset, int count) { while (_completedLines.Count < 1) { if (_gui.Visible == false) { throw new EndOfStreamException(); } // wait for user to complete a line Application.DoEvents(); Thread.Sleep(10); } var line = _completedLines.Dequeue(); return line.Read(buffer, offset, count); } public override bool CanRead { get { return !_gui.IsDisposed; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return true; } } public override long Length { get { return _gui.txtStdOut.Text.Length; } } public override long Position { get { return 0; } set { } } } }