630 строки
16 KiB
C#
630 строки
16 KiB
C#
/***************************************************************************
|
|
* CSharpShell.cs, based on the BooShell.cs from Banshee.
|
|
*
|
|
* Copyright (C) 2006-2008 Novell, Inc.
|
|
* Written by Aaron Bockover <aaron@abock.org>.
|
|
* Miguel de Icaza (miguel@gnome.org).
|
|
*
|
|
* Based on ShellTextView.boo in the MonoDevelop Boo Binding
|
|
* originally authored by Peter Johanson <latexer@gentoo.org>
|
|
****************************************************************************/
|
|
|
|
/* THIS FILE IS LICENSED UNDER THE MIT LICENSE AS OUTLINED IMMEDIATELY BELOW:
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Collections.Generic;
|
|
using System.Collections;
|
|
using System.IO;
|
|
using Gtk;
|
|
using Mono.CSharp;
|
|
using System.ComponentModel;
|
|
using System.Runtime.InteropServices;
|
|
using System.Xml.Linq;
|
|
using System.Linq;
|
|
|
|
namespace Mono.CSharp.Gui
|
|
{
|
|
[ToolboxItem (true)]
|
|
public class Shell : TextView
|
|
{
|
|
TextMark end_of_last_processing;
|
|
string expr;
|
|
Evaluator evaluator;
|
|
CompilerContext context;
|
|
|
|
List<string> history = new List<string> ();
|
|
int history_cursor;
|
|
|
|
MainWindow container;
|
|
public MainWindow Container { get { return container; }}
|
|
|
|
public Shell(MainWindow container) : base()
|
|
{
|
|
this.container = container;
|
|
WrapMode = WrapMode.Word;
|
|
CreateTags ();
|
|
|
|
Pango.FontDescription font_description = new Pango.FontDescription();
|
|
font_description.Family = "Monospace";
|
|
ModifyFont(font_description);
|
|
|
|
TextIter end = Buffer.EndIter;
|
|
Buffer.InsertWithTagsByName (ref end, "Mono C# Shell, type 'help;' for help\n\nEnter statements or expressions below.\n", "Comment");
|
|
ShowPrompt (false);
|
|
|
|
|
|
context = new CompilerContext (new CompilerSettings (), new ConsoleReportPrinter ());
|
|
evaluator = new Evaluator (context);
|
|
evaluator.DescribeTypeExpressions = true;
|
|
|
|
evaluator.InteractiveBaseClass = typeof (InteractiveGraphicsBase);
|
|
evaluator.Run ("LoadAssembly (\"System.Drawing\");");
|
|
evaluator.Run ("using System; using System.Linq; using System.Collections; using System.Collections.Generic; using System.Drawing;");
|
|
|
|
if (!MainClass.Debug){
|
|
GuiStream error_stream = new GuiStream ("Error", (x, y) => Output (x, y));
|
|
StreamWriter gui_output = new StreamWriter (error_stream);
|
|
gui_output.AutoFlush = true;
|
|
Console.SetError (gui_output);
|
|
|
|
GuiStream stdout_stream = new GuiStream ("Stdout", (x, y) => Output (x, y));
|
|
gui_output = new StreamWriter (stdout_stream);
|
|
gui_output.AutoFlush = true;
|
|
Console.SetOut (gui_output);
|
|
}
|
|
}
|
|
|
|
void CreateTags ()
|
|
{
|
|
TextTag freeze_tag = new TextTag("Freezer") {
|
|
Editable = false
|
|
};
|
|
Buffer.TagTable.Add(freeze_tag);
|
|
|
|
TextTag prompt_tag = new TextTag("Prompt") {
|
|
Foreground = "blue",
|
|
//Background = "#f8f8f8",
|
|
Weight = Pango.Weight.Bold
|
|
};
|
|
Buffer.TagTable.Add(prompt_tag);
|
|
|
|
TextTag prompt_continuation_tag = new TextTag("PromptContinuation") {
|
|
Foreground = "orange",
|
|
//Background = "#f8f8f8",
|
|
Weight = Pango.Weight.Bold
|
|
};
|
|
Buffer.TagTable.Add(prompt_continuation_tag);
|
|
|
|
TextTag error_tag = new TextTag("Error") {
|
|
Foreground = "red"
|
|
};
|
|
Buffer.TagTable.Add(error_tag);
|
|
|
|
TextTag stdout_tag = new TextTag("Stdout") {
|
|
Foreground = "#006600"
|
|
};
|
|
Buffer.TagTable.Add(stdout_tag);
|
|
|
|
TextTag comment = new TextTag ("Comment") {
|
|
Foreground = "#3f7f5f"
|
|
};
|
|
Buffer.TagTable.Add (comment);
|
|
}
|
|
|
|
public event EventHandler QuitRequested;
|
|
|
|
//
|
|
// Returns true if the line is complete, so that the line can be entered
|
|
// into the history, false if this was partial
|
|
//
|
|
public bool Evaluate (string s)
|
|
{
|
|
string res = null;
|
|
object result;
|
|
bool result_set;
|
|
StringWriter errorwriter = new StringWriter ();
|
|
|
|
var old_printer = context.Report.SetPrinter (new StreamReportPrinter (errorwriter));
|
|
|
|
try {
|
|
res = evaluator.Evaluate (s, out result, out result_set);
|
|
} catch (Exception e){
|
|
expr = null;
|
|
ShowError (e.ToString ());
|
|
ShowPrompt (true, false);
|
|
return true;
|
|
} finally {
|
|
context.Report.SetPrinter (old_printer);
|
|
}
|
|
|
|
// Partial input
|
|
if (res != null){
|
|
ShowPrompt (false, true);
|
|
return false;
|
|
}
|
|
string error = errorwriter.ToString ();
|
|
if (error.Length > 0){
|
|
ShowError (error);
|
|
ShowPrompt (false, false);
|
|
} else {
|
|
if (result_set){
|
|
ShowResult (result);
|
|
ShowPrompt (true, false);
|
|
} else
|
|
ShowPrompt (false, false);
|
|
}
|
|
expr = null;
|
|
|
|
return true;
|
|
}
|
|
|
|
string GetCurrentExpression ()
|
|
{
|
|
string input = InputLine;
|
|
|
|
return expr == null ? input : expr + "\n" + input;
|
|
}
|
|
|
|
[Conditional("DEBUG_HISTORY")]
|
|
void DumpHistory ()
|
|
{
|
|
for (int i = 0; i < history.Count; i++)
|
|
Console.WriteLine ("{0} {1}: {2}",
|
|
i == history_cursor ? "==>" : " ",
|
|
i, history [i]);
|
|
Console.WriteLine ("--- {0} --- ", history_cursor);
|
|
}
|
|
|
|
protected override bool OnKeyPressEvent(Gdk.EventKey evnt)
|
|
{
|
|
if(Cursor.Compare (InputLineBegin) < 0) {
|
|
Buffer.MoveMark(Buffer.SelectionBound, InputLineEnd);
|
|
Buffer.MoveMark(Buffer.InsertMark, InputLineEnd);
|
|
}
|
|
|
|
switch (evnt.Key){
|
|
case Gdk.Key.Return:
|
|
case Gdk.Key.KP_Enter:
|
|
string input_line = InputLine;
|
|
history [history.Count-1] = input_line;
|
|
history_cursor = history.Count;
|
|
|
|
string expr_copy = expr = GetCurrentExpression ();
|
|
|
|
// Insert a new line before we evaluate.
|
|
TextIter end = Buffer.EndIter;
|
|
Buffer.InsertWithTagsByName (ref end, "\n", "Stdout");
|
|
|
|
if (Evaluate (expr)){
|
|
if (expr_copy != input_line){
|
|
history.Add (expr_copy);
|
|
history_cursor = history.Count;
|
|
}
|
|
}
|
|
history.Add ("");
|
|
DumpHistory ();
|
|
if (InteractiveBase.QuitRequested && QuitRequested != null)
|
|
QuitRequested (this, EventArgs.Empty);
|
|
return true;
|
|
|
|
case Gdk.Key.Up:
|
|
if (history_cursor == 0){
|
|
DumpHistory ();
|
|
return true;
|
|
}
|
|
string input = InputLine;
|
|
if (!String.IsNullOrEmpty (input)){
|
|
DumpHistory ();
|
|
history [history_cursor] = input;
|
|
}
|
|
history_cursor--;
|
|
InputLine = (string) history [history_cursor];
|
|
DumpHistory ();
|
|
return true;
|
|
|
|
case Gdk.Key.Down:
|
|
if (history_cursor+1 >= history.Count){
|
|
DumpHistory ();
|
|
return true;
|
|
}
|
|
history_cursor++;
|
|
InputLine = (string) history [history_cursor];
|
|
DumpHistory ();
|
|
return true;
|
|
|
|
case Gdk.Key.Left:
|
|
if(Cursor.Compare(InputLineBegin) <= 0) {
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case Gdk.Key.Home:
|
|
Buffer.MoveMark(Buffer.InsertMark, InputLineBegin);
|
|
if((evnt.State & Gdk.ModifierType.ShiftMask) != Gdk.ModifierType.ShiftMask) {
|
|
Buffer.MoveMark(Buffer.SelectionBound, InputLineBegin);
|
|
}
|
|
return true;
|
|
|
|
case Gdk.Key.Tab:
|
|
string saved_text = InputLine;
|
|
string prefix;
|
|
string [] completions = evaluator.GetCompletions (LineUntilCursor, out prefix);
|
|
if (completions == null)
|
|
return true;
|
|
|
|
if (completions.Length == 1){
|
|
TextIter cursor = Cursor;
|
|
Buffer.Insert (ref cursor, completions [0]);
|
|
return true;
|
|
}
|
|
|
|
Console.WriteLine ();
|
|
foreach (var s in completions){
|
|
Console.Write (prefix);
|
|
Console.Write (s);
|
|
Console.Write (" ");
|
|
}
|
|
// Insert a new line before we evaluate.
|
|
end = Buffer.EndIter;
|
|
Buffer.InsertWithTagsByName (ref end, "\n", "Stdout");
|
|
ShowPrompt (false);
|
|
InputLine = saved_text;
|
|
#if false
|
|
Gtk.TextIter start = Cursor;
|
|
if (prefix.Length != 0)
|
|
MoveVisually (ref start, -prefix.Length);
|
|
int x, y;
|
|
GdkWindow.GetOrigin (out x, out y);
|
|
var r = GetIterLocation (start);
|
|
x += r.X;
|
|
y += r.Y;
|
|
var w = new Gtk.Window (WindowType.Popup);
|
|
w.SetUposition (x, y);
|
|
w.SetUsize (100, 100);
|
|
foreach (var s in completions){
|
|
Console.WriteLine ("{0}[{1}]", prefix, s);
|
|
}
|
|
w.ShowAll ();
|
|
Console.WriteLine ("Position: x={0} y={1}", x + r.X, y +r.Y);
|
|
#endif
|
|
return true;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return base.OnKeyPressEvent(evnt);
|
|
}
|
|
|
|
public void ShowPrompt(bool newline)
|
|
{
|
|
ShowPrompt (newline, false);
|
|
}
|
|
|
|
private void ShowPrompt (bool newline, bool continuation)
|
|
{
|
|
TextIter end_iter = Buffer.EndIter;
|
|
|
|
if(newline) {
|
|
Buffer.Insert(ref end_iter, "\n");
|
|
}
|
|
|
|
string prompt = continuation ? InteractiveBase.ContinuationPrompt : InteractiveBase.Prompt;
|
|
Buffer.Insert(ref end_iter, prompt);
|
|
|
|
Buffer.PlaceCursor(Buffer.EndIter);
|
|
ScrollMarkOnscreen(Buffer.InsertMark);
|
|
|
|
end_of_last_processing = Buffer.CreateMark(null, Buffer.EndIter, true);
|
|
Buffer.ApplyTag(Buffer.TagTable.Lookup("Freezer"), Buffer.StartIter, InputLineBegin);
|
|
|
|
TextIter prompt_start_iter = InputLineBegin;
|
|
prompt_start_iter.LineIndex -= prompt.Length;
|
|
|
|
TextIter prompt_end_iter = InputLineBegin;
|
|
prompt_end_iter.LineIndex -= 1;
|
|
|
|
Buffer.ApplyTag(Buffer.TagTable.Lookup(continuation ? "PromptContinuation" : "Prompt"),
|
|
prompt_start_iter, prompt_end_iter);
|
|
}
|
|
|
|
public void Output (string kind, string s)
|
|
{
|
|
TextIter end = Buffer.EndIter;
|
|
Buffer.InsertWithTagsByName (ref end, s, kind);
|
|
}
|
|
|
|
public void ShowResult (object res)
|
|
{
|
|
TextIter end = Buffer.EndIter;
|
|
|
|
var handlers = new List<InteractiveGraphicsBase.TransformHandler> (InteractiveGraphicsBase.type_handlers);
|
|
|
|
//object original = res;
|
|
bool retry;
|
|
do {
|
|
retry = false;
|
|
foreach (var render_handler in handlers){
|
|
object transformed = render_handler (res);
|
|
if (transformed == null || transformed == res)
|
|
continue;
|
|
|
|
if (transformed is Gtk.Widget){
|
|
Gtk.Widget w = (Gtk.Widget) transformed;
|
|
TextChildAnchor anchor = Buffer.CreateChildAnchor (ref end);
|
|
w.Show ();
|
|
AddChildAtAnchor (w, anchor);
|
|
return;
|
|
} else {
|
|
res = transformed;
|
|
handlers.Remove (render_handler);
|
|
retry = true;
|
|
break;
|
|
}
|
|
}
|
|
} while (retry && handlers.Count > 0);
|
|
|
|
StringWriter pretty = new StringWriter ();
|
|
try {
|
|
PrettyPrint (pretty, res);
|
|
} catch (Exception e) {
|
|
Console.WriteLine (e);
|
|
}
|
|
Buffer.InsertWithTagsByName (ref end, pretty.ToString (), "Stdout");
|
|
}
|
|
|
|
public void ShowError (string err)
|
|
{
|
|
TextIter end = Buffer.EndIter;
|
|
|
|
Buffer.InsertWithTagsByName (ref end, err, "Error");
|
|
}
|
|
|
|
TextIter InputLineBegin {
|
|
get { return Buffer.GetIterAtMark(end_of_last_processing); }
|
|
}
|
|
|
|
TextIter InputLineEnd {
|
|
get { return Buffer.EndIter; }
|
|
}
|
|
|
|
TextIter Cursor {
|
|
get { return Buffer.GetIterAtMark(Buffer.InsertMark); }
|
|
}
|
|
|
|
public Evaluator Evaluator {
|
|
get {
|
|
return evaluator;
|
|
}
|
|
}
|
|
|
|
string InputLine {
|
|
get { return Buffer.GetText(InputLineBegin, InputLineEnd, false); }
|
|
set {
|
|
TextIter start = InputLineBegin;
|
|
TextIter end = InputLineEnd;
|
|
Buffer.Delete(ref start, ref end);
|
|
start = InputLineBegin;
|
|
Buffer.Insert(ref start, value);
|
|
ScrollMarkOnscreen(Buffer.InsertMark);
|
|
}
|
|
}
|
|
|
|
string LineUntilCursor {
|
|
get {
|
|
return Buffer.GetText (InputLineBegin, Cursor, false);
|
|
}
|
|
}
|
|
|
|
static void p (TextWriter output, string s)
|
|
{
|
|
output.Write (s);
|
|
}
|
|
|
|
static string EscapeString (string s)
|
|
{
|
|
return s.Replace ("\"", "\\\"");
|
|
}
|
|
|
|
static void EscapeChar (TextWriter output, char c)
|
|
{
|
|
if (c == '\''){
|
|
output.Write ("'\\''");
|
|
return;
|
|
}
|
|
if (c > 32){
|
|
output.Write ("'{0}'", c);
|
|
return;
|
|
}
|
|
switch (c){
|
|
case '\a':
|
|
output.Write ("'\\a'");
|
|
break;
|
|
|
|
case '\b':
|
|
output.Write ("'\\b'");
|
|
break;
|
|
|
|
case '\n':
|
|
output.Write ("'\\n'");
|
|
break;
|
|
|
|
case '\v':
|
|
output.Write ("'\\v'");
|
|
break;
|
|
|
|
case '\r':
|
|
output.Write ("'\\r'");
|
|
break;
|
|
|
|
case '\f':
|
|
output.Write ("'\\f'");
|
|
break;
|
|
|
|
case '\t':
|
|
output.Write ("'\\t");
|
|
break;
|
|
|
|
default:
|
|
output.Write ("'\\x{0:x}", (int) c);
|
|
break;
|
|
}
|
|
}
|
|
|
|
internal XElement SaveHistory ()
|
|
{
|
|
var doc = new XElement (
|
|
"history",
|
|
from x in history
|
|
select new XElement ("item", x));
|
|
|
|
return doc;
|
|
}
|
|
|
|
internal void LoadHistory (IEnumerable<XElement> items)
|
|
{
|
|
if (items == null){
|
|
history.Add ("");
|
|
return;
|
|
}
|
|
|
|
foreach (var e in items){
|
|
string s = e.Value;
|
|
if (s == null || s.Length == 0)
|
|
continue;
|
|
|
|
history.Add (s);
|
|
}
|
|
history.Add ("");
|
|
history_cursor = history.Count-1;
|
|
}
|
|
|
|
internal static void PrettyPrint (TextWriter output, object result)
|
|
{
|
|
if (result == null){
|
|
p (output, "null");
|
|
return;
|
|
}
|
|
|
|
if (result is Array){
|
|
Array a = (Array) result;
|
|
|
|
p (output, "{ ");
|
|
int top = a.GetUpperBound (0);
|
|
for (int i = a.GetLowerBound (0); i <= top; i++){
|
|
PrettyPrint (output, a.GetValue (i));
|
|
if (i != top)
|
|
p (output, ", ");
|
|
}
|
|
p (output, " }");
|
|
} else if (result is bool){
|
|
if ((bool) result)
|
|
p (output, "true");
|
|
else
|
|
p (output, "false");
|
|
} else if (result is string){
|
|
p (output, String.Format ("\"{0}\"", EscapeString ((string)result)));
|
|
} else if (result is IDictionary){
|
|
IDictionary dict = (IDictionary) result;
|
|
int top = dict.Count, count = 0;
|
|
|
|
p (output, "{");
|
|
foreach (DictionaryEntry entry in dict){
|
|
count++;
|
|
p (output, "{ ");
|
|
try {
|
|
PrettyPrint (output, entry.Key);
|
|
} catch {
|
|
p (output, "<error>");
|
|
}
|
|
p (output, ", ");
|
|
try {
|
|
PrettyPrint (output, entry.Value);
|
|
} catch {
|
|
p (output, "<error>");
|
|
}
|
|
if (count != top)
|
|
p (output, " }, ");
|
|
else
|
|
p (output, " }");
|
|
}
|
|
p (output, "}");
|
|
} else if (result is IEnumerable) {
|
|
int i = 0;
|
|
p (output, "{ ");
|
|
try {
|
|
foreach (object item in (IEnumerable) result) {
|
|
if (i++ != 0)
|
|
p (output, ", ");
|
|
|
|
PrettyPrint (output, item);
|
|
}
|
|
} catch {
|
|
p (output, "<error>");
|
|
}
|
|
p (output, " }");
|
|
} else if (result is char){
|
|
EscapeChar (output, (char) result);
|
|
} else {
|
|
try {
|
|
p (output, result.ToString ());
|
|
} catch {
|
|
p (output, "<error>");
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public class GuiStream : Stream {
|
|
string kind;
|
|
Action<string,string> callback;
|
|
|
|
public GuiStream (string k, Action<string, string> cb)
|
|
{
|
|
kind = k;
|
|
callback = cb;
|
|
}
|
|
|
|
public override bool CanRead { get { return false; } }
|
|
public override bool CanSeek { get { return false; } }
|
|
public override bool CanWrite { get { return true; } }
|
|
|
|
|
|
public override long Length { get { return 0; } }
|
|
public override long Position { get { return 0; } set {} }
|
|
public override void Flush () { }
|
|
public override int Read ([In,Out] byte[] buffer, int offset, int count) { return -1; }
|
|
|
|
public override long Seek (long offset, SeekOrigin origin) { return 0; }
|
|
|
|
public override void SetLength (long value) { }
|
|
|
|
public override void Write (byte[] buffer, int offset, int count) {
|
|
callback (kind, Encoding.UTF8.GetString (buffer, offset, count));
|
|
}
|
|
}
|
|
}
|