LiveRun - a VS plugin to see the output of your program immediately
Say you're demonstrating a compiler at a conference. What's the best way to do it?
Should you just type in code in the code window? Doing this, you're relying on the audience's imagination -- that they form a mental picture of how the program will behave. You're also relying on their trust that your code really does what you say it does.
Or should you execute your code every minute or so, so the program's output window pops up and the audience can see that the code really works? Here it's risky because each time you switch it breaks the flow. And you're relying on the audience to remember what the code each time they look at the output.
I think this is one of those problems that can be solved by technology! I wrote small plugin for Visual Studio 2008. It looks at what the current text buffer contains, compiles it in the background, and displays the output in a topmost window. It does this every two seconds or so. You don't even need to save or recompile to see the output. It only makes sense for standalone console programs that don't take input. Here's a screenshot:
- Download plugin and source code: https://www.wischik.com/lu/programmer/LiveRun2008.zip
The source code is small and straightforward, and available for download at the link above.
There were two "gotcha" moments. The first was to do with multi-threading. I wanted the source code to be compiled in a background thread so it wouldn't interfere with the Visual Studio UI. But to grab the text of the current buffer you have to be in the UI thread, and also to display the output you have to be in the UI thread. I used a System.Timers.Timer, which fires its events in the background thread, and called form.Invoke(...) for any tasks that needed the UI thread.
I also used a "non-AutoReset" timer. I wanted it to get the source code and compile+run+display it, then pause for two seconds, then get the source code and compile+run+display it, then pause for two seconds, and so on. In other words the timer interval has to be two seconds after the end of handling the previous timer event.
''' <summary>
''' OnTimer handles the non-autoreset timer signal. It runs in a background thread. It gets the source
''' code from the current buffer, and compiles it, and displays the output.
''' </summary>
''' <remarks></remarks>
Sub OnTimer() Handles t.Elapsed
Try
Dim oldsrc = src
' We're in a background thread. But the source can only be obtained from the UI thread...
' This delegate will get the source and store it in the "src" field
f.Invoke(New Action(AddressOf GetSource))
If src <> oldsrc Then
Dim oldoutput = output
' We want to compile-and-run in the background thread
output = CompileAndRun(src)
If output <> "" OrElse oldoutput = "" Then
' Displaying the output on-screen must be done in the UI thread.
' This delegate gets the content of the "output" field and displays it
f.Invoke(New Action(AddressOf ShowOutput))
End If
End If
Finally
t.Start()
End Try
End Sub
The other "gotcha" moment had to do with how to execute the code and capture its output. VB has very nice helper functions surrounding this, in the "My" namespace. My main concern was to recover from exceptions gracefully without leaving any mess. (Note: the code for getting a temporary filename isn't quite correct: the mere fact that you got a temporary unused filename one statement ago does not mean that the filename will still be unused; nor does it mean that the filename with ".vb" appended to it will be unused. But doing it more correctly didn't seem worth the bother; in any case, the exception handling means we'll recover okay from problems.)
Function CompileAndRun(ByVal src As String) As String
Dim fn_exe = ""
Dim fn_src = ""
Dim vbc As System.Diagnostics.Process = Nothing
Dim exe As System.Diagnostics.Process = Nothing
Try
' Prepare for compilation
fn_src = My.Computer.FileSystem.GetTempFileName() & ".vb"
My.Computer.FileSystem.WriteAllText(fn_src, src, False)
fn_exe = My.Computer.FileSystem.GetTempFileName() & ".exe"
Dim framework = Environment.ExpandEnvironmentVariables("%windir%\Microsoft.Net\Framework")
Dim latest_framework = (From d In My.Computer.FileSystem.GetDirectories(framework) Where d Like "*\v*" Select d).Last
' Compile it
vbc = System.Diagnostics.Process.Start(New ProcessStartInfo _
With {.CreateNoWindow = True, _
.UseShellExecute = False, _
.FileName = latest_framework & "\vbc.exe", _
.Arguments = String.Format("/out:""{0}"" /target:exe ""{1}""", fn_exe, fn_src)})
Dim vbc_done = vbc.WaitForExit(3000)
If Not vbc_done Then Return ""
If vbc.ExitCode <> 0 Then Return ""
' Execute it
Dim pinfo = New ProcessStartInfo With {.CreateNoWindow = True, _
.UseShellExecute = False, _
.FileName = fn_exe, _
.RedirectStandardOutput = True}
exe = New System.Diagnostics.Process With {.StartInfo = pinfo}
exe.Start()
Dim output = exe.StandardOutput.ReadToEnd
Dim exe_done = exe.WaitForExit(3000)
If Not exe_done Then Return ""
Return output
Finally
' Close the VBC process as neatly as we can
If vbc IsNot Nothing Then
If Not vbc.HasExited Then
Try : vbc.Kill() : Catch ex As Exception : End Try
Try : vbc.WaitForExit() : Catch ex As Exception : End Try
End If
Try : vbc.Close() : Catch ex As Exception : End Try
vbc = Nothing
End If
' Close the EXE as neatly as we can
If exe IsNot Nothing Then
If Not exe.HasExited Then
Try : exe.Kill() : Catch ex As Exception : End Try
Try : exe.WaitForExit() : Catch ex As Exception : End Try
End If
Try : exe.Close() : Catch ex As Exception : End Try
exe = Nothing
End If
' Delete leftover files
Try : My.Computer.FileSystem.DeleteFile(fn_exe) : Catch ex As Exception : End Try
Try : My.Computer.FileSystem.DeleteFile(fn_src) : Catch ex As Exception : End Try
End Try
End Function
As always, I love to hear suggestions and bugfixes and code improvements and comments!
Comments
- Anonymous
October 22, 2008
Nice idea, but it would be even cooler if you used a MSUnit project type and displayed the test results (and Console if you had to). It would encourage folks to write unit tests. I prefer using MSUnit (or any xUnit framework) over console projects to demo code.