AutoHotKey for quick Git access

I really like Git’s GUI tools: Git Gui for reviewing diffs and committing, and Gitk for dealing with branches. They’re powerful tools — powerful enough that I’m skeptical of any IDE that claims to include all that functionality in a little tool window.

So that means I do a lot of task switching between my IDE, Git Gui, and Gitk. And since I hate repetitive Alt+Tabbing, I’ve written an AutoHotKey script to make it easier.

AutoHotKey is an open-source tool that lets you bind scripts to various keys or key combinations on your keyboard. I’ve chosen these keybindings:

  • Numpad Minus: bring up the Git Gui window (or start it if it’s not already running), send F5 to refresh it, and put focus in the “description” box.
  • Numpad Plus: bring up the Gitk window (or start it if it’s not already running) and send F5 to refresh it. (Mnemonic: Gitk shows a tree, hence my choice of “+”)

I wrote a little AutoHotKey script and put it in version control at work, and several of the other devs started using it too. It’s almost disorienting when I’m at someone else’s computer and they don’t have it running.

Here’s the script. I suggest saving it as a GitHotKeys.ahk file somewhere inside your Git working copy; that way, if it needs to launch Git Gui or Gitk, they’ll automatically come up in the right repository.

; Copyright (c) 2011 Joe White
;
; 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.

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
SetTitleMatchMode, 1

NumpadSub::
IfWinNotExist, Git Gui
  Run, git gui,, Hide
WinActivate, Git Gui
IfWinActive, Git Gui
{
  ControlGetPos, control_x, control_y,,, TkChild18, Git Gui
  CoordMode, Mouse, Screen
  MouseGetPos, mouse_x, mouse_y
  click_x := control_x + 5
  click_y := control_y + 5
  CoordMode, Mouse, Relative
  Click %click_x%, %click_y%
  CoordMode, Mouse, Screen
  MouseMove, %mouse_x%, %mouse_y%, 0

  Send, {F5}
}
return

NumpadAdd::
IfWinNotExist, gitk
  Run, gitk
WinActivate, gitk
IfWinActive, gitk
  Send, {F5}
return

CoffeeScript’s for loops don’t work on strings (despite appearances to the contrary)

I’ve been playing around with CoffeeScript, and found myself needing to loop over the characters (and indexes) in a string. So I tried a simple CoffeeScript for loop:

for char, index in line
    # ...

I ran it, and it worked, even in IE, so I assumed that CoffeeScript was doing whatever magical syntax was required to make it work everywhere.

Later I found out I was wrong. CoffeeScript was generating the syntax that works with arrays — list[index] — and that syntax only happens to sometimes work with strings. It works consistently in Chrome, and FireFox, and Opera. It even works in IE… but only if you have an HTML5 doctype.

<!DOCTYPE html>
<script>
document.write('abc'[1]);
</script>

The above HTML page works fine in IE9, outputting b as expected. But if you remove the <!DOCTYPE html> line, IE outputs undefined instead. That’s right, it loads a completely different version of JavaScript depending on what version of HTML you’re using! And if you load the above page into a WPF Frame, even the doctype won’t save you — it always outputs undefined.

When I went back and looked through the CoffeeScript documentation, sure enough, nowhere do they actually say that for loops can be used to iterate through the characters in a string. The fact that it ever works is apparently an accident, so don’t rely on it.

If you need to loop over the characters in a string, the right thing is to loop over the indexes and use charAt: See update below

for index in [0...line.length]
    char = line.charAt index
    # ...

Edit: Here’s a better way, suggested by the kind CoffeeScript folks:

for char, index in str.split ''

Contributing to open source: uhttpsharp

I’ve been using open-source since at least my senior honors project in college, 15 years ago. I’m well overdue to give something back.*

Last night I made my very first commit to an open-source project: uhttpsharp, “a very lightweight & simple embedded http server for c#”. And I’ve added quite a bit of stuff in the day since that first commit — file serving, customizable index and error pages, keep-alive support. I’m thinking about adding NuGet support at some point.

If you need a simple HTTP server for .NET, and you don’t want to use HttpListener (e.g. because you don’t want to require admin permissions to register the URI prefix), go check it out.

* Actually, I’ve done plenty of open-source work before, if you count stuff that I wrote myself. And some of that stuff does count; I think a few people have found DGrok helpful. But I have a strong tendency toward not-invented-here syndrome… and this time I offered to contribute to an existing open-source project, that I heard about through posts on StackOverflow. So it’s not just me writing something by myself, which is cool.

Well, okay, I’m contributing to this project so I can use it in another project that I am writing by myself…

.NET and CoffeeScript: comparing Jurassic, Jint, and IronJS

Recently I went looking for ways to write a .NET desktop app that could compile CoffeeScript to JavaScript. There are already several NuGet packages for exactly this, but most of them look like they’re tightly bound to ASP.NET. So I struck out on my own, following the general steps in “CoffeeDemo – A Simple Demo of IronJS, using CoffeeScript”. The main CoffeeScript compiler is written in CoffeeScript, but they also provide one written in JavaScript, so my basic outline was:

  1. Instantiate a JavaScript engine.
  2. Tell the JavaScript engine to run coffee-script.js. This creates the CoffeeScript object and its compile method.
  3. Tell the JavaScript engine to call CoffeeScript.compile, and pass a string containing the CoffeeScript code to compile.

But my first attempt ran slower than I’d hoped for. (Well, coffee-script.js is 163 KB, and that’s the minified version! So I guess it does have a lot to do.)

I decided to find out whether I could do better: I tried several different JavaScript-in-.NET implementations, to see which one would perform the best. I tested Jurassic, Jint, and IronJS. My results are below, along with the C# code in case anyone is interested in seeing the minor differences between the APIs.

In all three cases, the coffeeCompiler parameter contains the 1.1.2 version of coffee-script.js, as downloaded from GitHub; and the input parameter contains a one-line CoffeeScript script:

alert "Hello world!"

Jurassic

Jurassic dynamically compiles JavaScript to CLR code at runtime, so you take a performance hit the first time you run some JS, but it should be pretty fast after that. Jurassic is available via NuGet.

private void CompileCoffeeScriptUsingJurassic(
    string coffeeCompiler, string input)
{
    Console.WriteLine("Jurassic");
    var stopwatch = Stopwatch.StartNew();
    Console.WriteLine(stopwatch.Elapsed + ": Creating engine");
    var engine = new ScriptEngine();
    Console.WriteLine(stopwatch.Elapsed + ": Parsing coffee-script.js");
    engine.Execute(coffeeCompiler);
    Console.WriteLine(stopwatch.Elapsed + ": Adding compile wrapper");
    engine.Execute("var compile = function (src) " +
        "{ return CoffeeScript.compile(src, { bare: true }); };");
    Console.WriteLine(stopwatch.Elapsed + ": Compiling CoffeeScript input");
    var output = engine.CallGlobalFunction("compile", input);
    Console.WriteLine(stopwatch.Elapsed + ": Done");
    Console.WriteLine("Output:");
    Console.WriteLine(output);
    Console.WriteLine();
}

Jint

Jint is a JavaScript interpreter. It’s not available through NuGet yet, but it’s a single DLL.

private void CompileCoffeeScriptUsingJint(
    string coffeeCompiler, string input)
{
    Console.WriteLine("Jint");
    var stopwatch = Stopwatch.StartNew();
    Console.WriteLine(stopwatch.Elapsed + ": Creating engine");
    var engine = new JintEngine();
    Console.WriteLine(stopwatch.Elapsed + ": Parsing coffee-script.js");
    engine.Run(coffeeCompiler);
    Console.WriteLine(stopwatch.Elapsed + ": Adding compile wrapper");
    engine.Run("var compile = function (src) " +
        "{ return CoffeeScript.compile(src, { bare: true }); };");
    Console.WriteLine(stopwatch.Elapsed + ": Compiling CoffeeScript input");
    object output = null;
    try
    {
        output = engine.CallFunction("compile", input);
    }
    catch (JsException ex)
    {
        Console.WriteLine("ERROR: " + ex.Value);
    }
    Console.WriteLine(stopwatch.Elapsed + ": Done");
    Console.WriteLine("Output:");
    Console.WriteLine(output);
    Console.WriteLine();
}

IronJS

IronJS is based on the DLR, so it seemed like it might strike a great balance between upfront compile time and runtime — after all, that’s what the DLR is all about.

IronJS is available through NuGet — there’s both an IronJS.Core (standalone) and an IronJS (depends on IronJS.Core), with nothing to explain the difference between the two; but at least for this code, you only need IronJS.Core.

private void CompileCoffeeScriptUsingIronJs(
    string coffeeCompiler, string input)
{
    Console.WriteLine("IronJS");
    var stopwatch = Stopwatch.StartNew();
    Console.WriteLine(stopwatch.Elapsed + ": Creating engine");
    var engine = new CSharp.Context();
    Console.WriteLine(stopwatch.Elapsed + ": Parsing coffee-script.js");
    engine.Execute(coffeeCompiler);
    Console.WriteLine(stopwatch.Elapsed + ": Adding compile wrapper");
    engine.Execute("var compile = function (src) " +
        "{ return CoffeeScript.compile(src, { bare: true }); };");
    Console.WriteLine(stopwatch.Elapsed + ": Fetching compile wrapper");
    var compile = engine.GetGlobalAs<FunctionObject>("compile");
    Console.WriteLine(stopwatch.Elapsed + ": Compiling CoffeeScript input");
    var result = compile.Call(engine.Globals, input);
    var output = IronJS.TypeConverter.ToString(result);
    Console.WriteLine(stopwatch.Elapsed + ": Done");
    Console.WriteLine("Output:");
    Console.WriteLine(output);
    Console.WriteLine();
}

The results

Jurassic
00:00:00.0000063: Creating engine
00:00:00.1540545: Parsing coffee-script.js
00:00:03.4346969: Adding compile wrapper
00:00:03.4408860: Compiling CoffeeScript input
00:00:05.3466983: Done
Output:
alert("Hello world!");

Jint
00:00:00.0000019: Creating engine
00:00:00.2617049: Parsing coffee-script.js
00:00:02.5270733: Adding compile wrapper
00:00:02.5295317: Compiling CoffeeScript input
ERROR: Parse error on line 2: Unexpected 'STRING'
00:00:02.5832895: Done
Output:


IronJS
00:00:00.0000019: Creating engine
00:00:00.2282421: Parsing coffee-script.js
00:00:55.5590620: Adding compile wrapper
00:00:55.5629230: Fetching compile wrapper
00:00:55.5642908: Compiling CoffeeScript input
00:01:17.8580574: Done
Output:
alert("Hello world!");

Jint wasn’t up to the task — it got a weird error when trying to call CoffeeScript.compile. I played with this a bit, and found that it would work if I passed an empty string, but give errors with non-blank CoffeeScript to compile; sometimes a string error like above, sometimes a weird error about multiline comments. It’s too bad, because Jint shows a lot of promise, speed-wise. I don’t know what the problem is; the error didn’t give me much to go on, and I’m not terribly motivated to pursue the problem when the other libraries work. (I did write this up in their bugtracker, though — it’s issue #6928.)

I was surprised that IronJS was so much slower than the others — about 20x slower than Jint at running coffee-script.js, and about 10x slower than Jurassic. This is especially puzzling since the article I based my code on mentions a “compilation lag”. To me, 55 seconds is hardly “lag”!

The winner here (and coincidentally the first one I tried) is Jurassic — so the performance that disappointed me is also the best I’m likely to get. On my laptop, you take about a 3.5-second penalty to compile coffee-script.js, and then another two seconds to run CoffeeScript.compile on a one-line script.

I did find that subsequent calls to CoffeeScript.compile were nearly instantaneous with all three libraries. So Jurassic’s 2 seconds is probably due to the JIT compiler running for the first time on that runtime-generated code. Not sure what to make of the 20 seconds for IronJS; is the DLR just that big?

Aptana, GPL, and the definition of “intact”

I’m looking at Python IDEs and decided to try Aptana, but their installer isn’t giving the best first impressions. First it did nothing but peg my CPU for half a minute or so, without so much as a splash screen. Then, when it finally showed some GUI, the very first screenful of text was downright incomprehensible:

Aptana License Agreement

No, there’s no horizontal scrollbar to explain why the beginnings of words are cut off.

What I love most is how that last line insists that this victim of random hack-and-slash copy/paste must be kept… “intact”.

If Reflector needed money so badly, why didn’t they ask?

Your copy of Reflector will self-destruct at the end of February… unless you pay the ransom.

This is depressing, not because of the money — I could easily pay $35 for a kick-ass tool like Reflector — but because of the betrayal of trust.

It’s not unlike the way Borland repeatedly betrayed their users’ trust with crap like “Inprise” and “Application Lifecycle Management”. (What’s left of Borland just got bought out. Good riddance.) Or the way Embarcadero priced me out of the Delphi market a couple of years ago. (They’ve since decided that was a bad move and started selling a starter edition. Some people learn from their mistakes, though sometimes too late.)

When RedGate bought Reflector, they said that they would continue to offer a free version. Now they admit that they lied. Well, actually, they don’t admit anything; they just repeatedly say that they never “promised” a free version. I guess that interview, and the “Red Gate will continue to offer the tool for free to the community” soundbite, were imaginary.

But wait! You can buy a version that will continue to work forever! Honest! They promise! Well, no, actually. If you search their open letter for the word “promise”, you’ll find it conspicuously absent.

I’ve been reading the reactions on StackOverflow, and even finally got a Twitter account so I could follow the news there. Some people say “suck it up, it’s worth it”. More people say “that’s not the point, RedGate has proven they can’t be trusted”. I lean toward the latter camp.

Then, across the Twitter feed comes a link to a YouTube interview with Simon Galbraith, one of the co-founders of RedGate, about the decision to charge for Reflector. Apart from again going on about the word “promise”, he explains something that should have been forefront of their announcement: keeping up with new frameworks and new platforms is a big deal. They want to make Reflector an even more awesome tool, and people haven’t been paying for Reflector Pro (our department actually did buy it, BTW) so they can’t bankroll what they want to do.

But instead of actually talking to the community about this, they kept it quiet. They “agonized” over it for about six months, and then decided that the right move was to break their word, antagonize the community, and try to extort the money, at the cost of their professional reputation.

They forgot two things that should have made this easy.

One, they forgot to ask. Wikipedia isn’t afraid to ask for donations, and they get them. NaNoWriMo isn’t afraid to ask for donations, and they get them. Granted, there are plenty of open-source projects with “Donate” buttons that probably never see a dime. But for a tool like Reflector, if they had said, “Hey, we want to do X and Y and Z to make this great tool even better, but we can’t do it without your help. We need to raise this many dollars to make it happen. Who’s with us?” I think people would have responded.

And two, empathy matters. Putting up a cold, faceless, impersonal warning icon that says “Screw you, we know we said we wouldn’t do this but we’re sticking it to you anyway” is not going to earn you many friends.

Compare that to: “We need your help. We want to keep up with new platforms and features, and we want to do more than keep up: we want to make an awesome tool even more awesome. But we can’t do it without you. We know you’ve always had Reflector for free — and we promise that won’t change — but if this tool is going to survive the changes Microsoft has in store and still get even better than it’s ever been, if this tool that’s satisfied your curiosity, and taught you loads, and, yes, saved your butt time and time again, is going to stay relevant, we need you. Think back over the questions you’ve answered with Reflector, the number of people you’ve recommended Reflector to, the times you’ve sworn you couldn’t do your job without Reflector, and then just tell us this: Can we count on your help?”

Sigh.

Okay, that’s enough of that. Off to check out Monoflector.