First of all best regards to the F# team for producing CTP! This is a great event! Thanks a million!
I have implemented a windows application in F# which is a wrapper for fsi.exe (F# REPL or Interactive Console). You can load a fsx or save your working text to a file and it works like F# console in Visual Studio. You select your code and press Alt+Enter and your code will be sent to fsi.exe and the result can be seen in the output window. Of course it is far from being comparable to the Visual Studio Interactive Console (We have intellisense there and much more utilities which are indeed very hard work to accomplish). Yet it is good enough for quick tests. It works well out of visual studio. Here the code and then questions:
(do not forget to add the necessary refrences if you are going to run this in REPL)
#light
open System
open System.Collections.Generic
open System.ComponentModel
open System.Data
open System.Drawing
open System.Linq
open System.Text
open System.Windows.Forms
open System.IO
open System.Diagnostics
open System.Threading
type MainForm =
inherit Form
val mutable components : System.ComponentModel.IContainer
val mutable rtbInput : System.Windows.Forms.RichTextBox
val mutable rtbOutput : System.Windows.Forms.RichTextBox
val mutable ofdOpen : System.Windows.Forms.OpenFileDialog
val mutable sfdSave : System.Windows.Forms.SaveFileDialog
val mutable cmsMenu : System.Windows.Forms.ContextMenuStrip
val mutable saveToolStripMenuItem : System.Windows.Forms.ToolStripMenuItem
val mutable openToolStripMenuItem : System.Windows.Forms.ToolStripMenuItem
val mutable exitToolStripMenuItem : System.Windows.Forms.ToolStripMenuItem
val mutable proc : Process
val mutable appPath : string
val mutable reader : Thread
val mutable errReader : Thread
val mutable stop : ManualResetEvent
override this.Dispose (disposing) =
if disposing && (this.components <> null) then
this.components.Dispose ()
base.Dispose (disposing)
member this.InitializeComponent () =
this.components <- new System.ComponentModel.Container ()
this.rtbInput <- new System.Windows.Forms.RichTextBox ()
this.rtbOutput <- new System.Windows.Forms.RichTextBox ()
this.ofdOpen <- new System.Windows.Forms.OpenFileDialog ()
this.sfdSave <- new System.Windows.Forms.SaveFileDialog ()
this.cmsMenu <- new System.Windows.Forms.ContextMenuStrip (this.components)
this.saveToolStripMenuItem <- new System.Windows.Forms.ToolStripMenuItem ()
this.openToolStripMenuItem <- new System.Windows.Forms.ToolStripMenuItem ()
this.exitToolStripMenuItem <- new System.Windows.Forms.ToolStripMenuItem ()
this.cmsMenu.SuspendLayout ()
this.SuspendLayout ()
//
// rtbInput
//
this.rtbInput.AcceptsTab <- true
this.rtbInput.ContextMenuStrip <- this.cmsMenu
this.rtbInput.Dock <- System.Windows.Forms.DockStyle.Top
this.rtbInput.Font <- new System.Drawing.Font ("Consolas", 11.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) (0)))
this.rtbInput.Location <- new System.Drawing.Point (0, 0)
this.rtbInput.Name <- "rtbInput"
this.rtbInput.Size <- new System.Drawing.Size (757, 312)
this.rtbInput.TabIndex <- 0
this.rtbInput.Text <- ""
this.rtbInput.KeyDown.AddHandler(new System.Windows.Forms.KeyEventHandler (fun s e -> this.rtbInput_KeyDown (s, e)))
this.rtbInput.KeyUp.AddHandler(new System.Windows.Forms.KeyEventHandler (fun s e -> this.rtbInput_KeyUp (s, e)))
//
// rtbOutput
//
this.rtbOutput.Location <- new System.Drawing.Point (0, 318)
this.rtbOutput.Name <- "rtbOutput"
this.rtbOutput.Size <- new System.Drawing.Size (757, 190)
this.rtbOutput.TabIndex <- 1
this.rtbOutput.Text <- ""
this.rtbOutput.TextChanged.AddHandler(new System.EventHandler (fun s e -> this.rtbOutput_TextChanged (s, e)))
//
// ofdOpen
//
this.ofdOpen.Filter <- "F# script files (*.fsx)|*.fsx|All Files (*.*)|*.*"
this.ofdOpen.FileOk.AddHandler(new System.ComponentModel.CancelEventHandler (fun s e -> this.ofdOpen_FileOk (s, e)))
//
// sfdSave
//
this.sfdSave.Filter <- "F# script files (*.fsx)|*.fsx|All Files (*.*)|*.*"
this.sfdSave.FileOk.AddHandler(new System.ComponentModel.CancelEventHandler (fun s e -> this.sfdSave_FileOk (s, e)))
//
// cmsMenu
//
this.cmsMenu.Items.AddRange ([| (this.saveToolStripMenuItem :> System.Windows.Forms.ToolStripItem);
(this.openToolStripMenuItem :> System.Windows.Forms.ToolStripItem);
(this.exitToolStripMenuItem :> System.Windows.Forms.ToolStripItem) |])
this.cmsMenu.Name <- "cmsMenu"
this.cmsMenu.Size <- new System.Drawing.Size (153, 92)
//
// saveToolStripMenuItem
//
this.saveToolStripMenuItem.Name <- "saveToolStripMenuItem"
this.saveToolStripMenuItem.ShortcutKeys <- System.Windows.Forms.Keys.Control ||| System.Windows.Forms.Keys.S
this.saveToolStripMenuItem.Size <- new System.Drawing.Size (152, 22)
this.saveToolStripMenuItem.Text <- "Save"
this.saveToolStripMenuItem.Click.AddHandler(new System.EventHandler (fun s e -> this.saveToolStripMenuItem_Click(s, e)))
//
// openToolStripMenuItem
//
this.openToolStripMenuItem.Name <- "openToolStripMenuItem"
this.openToolStripMenuItem.ShortcutKeys <- System.Windows.Forms.Keys.Control ||| System.Windows.Forms.Keys.O
this.openToolStripMenuItem.Size <- new System.Drawing.Size (152, 22)
this.openToolStripMenuItem.Text <- "Open"
this.openToolStripMenuItem.Click.AddHandler(new System.EventHandler (fun s e -> this.openToolStripMenuItem_Click(s, e)))
//
// exitToolStripMenuItem
//
this.exitToolStripMenuItem.Name <- "exitToolStripMenuItem"
this.exitToolStripMenuItem.ShortcutKeys <- System.Windows.Forms.Keys.Control ||| System.Windows.Forms.Keys.X
this.exitToolStripMenuItem.Size <- new System.Drawing.Size (152, 22)
this.exitToolStripMenuItem.Text <- "Exit"
this.exitToolStripMenuItem.Click.AddHandler(new System.EventHandler (fun s e -> this.exitToolStripMenuItem_Click(s, e)))
//
// MainForm
//
this.AutoScaleDimensions <- new System.Drawing.SizeF (6.0f, 13.0f)
this.AutoScaleMode <- System.Windows.Forms.AutoScaleMode.Font
this.ClientSize <- new System.Drawing.Size (757, 508)
this.ContextMenuStrip <- this.cmsMenu
this.Controls.Add (this.rtbOutput)
this.Controls.Add (this.rtbInput)
this.Name <- "MainForm"
this.Text <- "MainForm"
this.FormClosing.AddHandler(new System.Windows.Forms.FormClosingEventHandler (fun s e -> this.MainForm_FormClosing(s, e)))
this.ResizeEnd.AddHandler(new System.EventHandler (fun s e -> this.MainForm_ResizeEnd(s, e)))
this.cmsMenu.ResumeLayout (false)
this.ResumeLayout (false)
member this.ErrReaderTrd () =
let MAX_BUF = 4096
let mutable buffer = Array.create MAX_BUF '0'
let mutable bread = 0
while not (this.stop.WaitOne (1, true)) do
try
bread <- this.proc.StandardError.Read (buffer, 0, MAX_BUF)
while (bread > 0) do
this.rtbOutput.AppendText (new string (buffer, 0, bread))
bread <- this.proc.StandardError.Read (buffer, 0, MAX_BUF)
with
| _ -> ()
member this.ReaderTrd () =
let MAX_BUF = 4096
let mutable buffer = Array.create MAX_BUF '0'
let mutable bread = 0
while not (this.stop.WaitOne (1, true)) do
try
bread <- this.proc.StandardOutput.Read (buffer, 0, MAX_BUF)
while (bread > 0) do
this.rtbOutput.AppendText (new string (buffer, 0, bread))
bread <- this.proc.StandardOutput.Read (buffer, 0, MAX_BUF)
with
| _ -> ()
member this.InitializeREPL () =
let mutable psi = new ProcessStartInfo ()
psi.FileName <- this.appPath
psi.CreateNoWindow <- true
psi.RedirectStandardInput <- true
psi.RedirectStandardOutput <- true
psi.RedirectStandardError <- true
psi.UseShellExecute <- false
psi.ErrorDialog <- true
this.proc <- new Process ()
this.proc.StartInfo <- psi
this.proc.SynchronizingObject <- this
this.proc.EnableRaisingEvents <- true
this.proc.Start () |> ignore
this.stop <- new ManualResetEvent (false)
this.reader <- new Thread (new ThreadStart (this.ReaderTrd))
this.errReader <- new Thread (new ThreadStart (this.ErrReaderTrd))
this.reader.Start ()
this.errReader.Start ()
member this.MainForm_FormClosing (sender : System.Object, e : FormClosingEventArgs) =
this.stop.Set () |> ignore
Thread.Sleep (200)
if not (this.reader.Join (200)) then
this.reader.Abort ()
if not (this.errReader.Join (200)) then
this.errReader.Abort ()
try
this.proc.Kill ();
with
| _ -> ()
member this.rtbOutput_TextChanged (sender : System.Object, e : EventArgs) =
// still there is no good way to do this
// I mean to make a RichTextBox auto scroll to bottom
this.rtbOutput.SelectionStart <- this.rtbOutput.Text.Length + 10;
this.rtbOutput.SelectionLength <- 0
this.rtbOutput.ScrollToCaret ()
member this.MainForm_ResizeEnd (sender : System.Object, e : EventArgs) =
let src = this.Height - 32
let top = src * 60 / 100
let bottom = src - top
this.rtbInput.Height <- top
this.rtbOutput.Height <- bottom
member this.rtbInput_KeyDown (sender : System.Object, e : KeyEventArgs) =
if (e.KeyData = Keys.Tab) then
e.Handled <- true
this.rtbInput.AppendText (" ")
member this.rtbInput_KeyUp (sender : System.Object, e : KeyEventArgs) =
if (e.KeyData = (Keys.Enter ||| Keys.Alt)) then
this.rtbOutput.AppendText ("\r\n")
this.proc.StandardInput.Write (this.rtbInput.SelectedText + "\r\n;;\r\n")
member this.ofdOpen_FileOk (sender : System.Object, e : CancelEventArgs) =
use sr = new StreamReader (this.ofdOpen.FileName)
this.rtbInput.AppendText (sr.ReadToEnd ())
sr.Close ()
member this.sfdSave_FileOk (sender : System.Object, e : CancelEventArgs) =
use sw = new StreamWriter (this.sfdSave.FileName)
sw.Write (this.rtbInput.Text)
sw.Flush ()
sw.Close ()
member this.saveToolStripMenuItem_Click (sender : System.Object, e : EventArgs) =
this.sfdSave.ShowDialog () |> ignore
member this.openToolStripMenuItem_Click (sender : System.Object, e : EventArgs) =
this.ofdOpen.ShowDialog () |> ignore
member this.exitToolStripMenuItem_Click (sender : System.Object, e : EventArgs) =
this.Close () |> ignore
new () as this =
{ components = null;
rtbInput = null;
rtbOutput = null;
ofdOpen = null;
sfdSave = null;
cmsMenu = null;
saveToolStripMenuItem = null;
openToolStripMenuItem = null;
exitToolStripMenuItem = null;
proc = null;
appPath = null;
reader = null;
errReader = null;
stop = null; }
then
this.appPath <- @"C:\Program Files\FSharp-1.9.6.0\bin\fsi.exe"
this.InitializeComponent()
this.InitializeREPL()
[<STAThread>]
let main() =
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(false)
Thread.Sleep(400)
Application.Run(new MainForm())
[<STAThread>]
main()
Now the questions:
1 - When I am developing in Visual Studio, this code does not work from within VS. Is this something about fsi.exe and if not can anyone spot the problem?
2 - Debugging in reader threads almost does not work. It hits the line and when I press next step it continues to run.
I had some other problems that I don't remember yet I have enjoyed CTP indeed!
(There is another F# try of mine from old days at http://cs.hubfs.net/forums/thread/1495.aspx; that feelings came to life again!)
Best Regards