VSLab provides the foundations for using Visual Studio as an interactive environment, a sort of big environment for Domain Specific Languages in which you can issue commands interactively to control virtually anything. Now that this foundation is in place we are working to provide the set of packages that shows this potential and transform VSLab into something that is usable for people, not only by F# programmers. Of course this is also a way to find the right abstractions you need when developing packages so that we can possibly extend the core to support new functionalities.
One of the coolest packages I'm working on is the VSLab Shell package, a mean to turn VS into next generation interactive shell, in which text and GUI mix together to obtain a new environment in which point-and-click is used in conjunction with text-based scripts to get the best from the two environments. We are currently defining the core operations of a traditional shell, such as process management and piping, and I want to share with you a neat piece of code we wrote this night to express process piping using F# and F# interactive. Here is the code:
let pwd = Directory.GetCurrentDirectory
let createProcess exec args =
let pi = new ProcessStartInfo()
pi.Arguments <- args
pi.CreateNoWindow <- false
pi.RedirectStandardInput <- true
pi.RedirectStandardError <- true
pi.RedirectStandardOutput <- true
pi.UseShellExecute <- false
pi.WorkingDirectory <- pwd()
pi.FileName <- exec
let p = new Process()
p.StartInfo <- pi p
let (^>) (out:Process) (pf: bool -> string -> unit) =
out.ErrorDataReceived.Add(fun d -> pf false d.Data)
out.OutputDataReceived.Add(fun d -> pf true d.Data)
let (^|) (inp:Process) (out:Process) =
inp.EnableRaisingEvents <- true
inp.ErrorDataReceived.Add(fun d -> out.StandardInput.WriteLine d.Data)
inp.OutputDataReceived.Add(fun d -> out.StandardInput.WriteLine d.Data)
inp.Exited.Add(fun _ -> out.StandardInput.Close())
let p = createProcess @"c:\Windows\System32\Robocopy.exe" "/?"
let s = createProcess @"c:\Windows\System32\sort.exe" ""
let q = createProcess @"c:\Windows\System32\sort.exe" "/R"
p ^| q ^| s ^> (fun out d -> printfn "%s: %s" (if out then "out" else "err") d)
This code shows a limit of .NET API, we have to coordinate the piping process explicitly because we cannot specify the handle to the streams in the ProcessStartInfo structure (in Win32 would be possible). We define two overloaded operators using the ^ prefix to have right association. The callbacks provided by the Process class allow to avoid polling of streams. This code has requested an amount of spelunking of System.IO namespace, I was surprised to find that using the general Stream class it is not possible to multiplex IO operations as the select system call would have permitted. Some day I will post considerations about this design that at a first sight may seem weird but after some thoughts I decided that is reasonable though far from elegant.