Einar Egilsson

ZenCoding Visual Studio AddIn

Posted: Last updated:

UPDATE 2012-05-02: There have been at least two new implementations of ZenCoding created and added to the VS gallery. You should probably go there instead and download one of them instead. See Version 1, Version 2.

UPDATE 2010-02-04: I'm no longer working on this addin and cannot provide support for failed installations.

The original blog post about the architecture of the addin is now completely out of date as 90% of the addin is now written in IronPython with just a tiny shim layer of C# to instantiate the IronPython classes. I'll probably write a blog post about that architecture seperately (or generally about how to write addins for VS in IronPython). Those interested in the architecture can download the source and look at it.

A nice tutorial with screenshots on how to setup the keyboard mappings and use the addin has just been posted at http://www.netsi.dk/wordpress/index.php/2009/12/02/zen-coding-a-very-fast-way-of-generating-html-elements-in-your-editor/ so go there for your setup instructions.

ZenCoding.VisualStudio v1.1.0.333



Version, 02.12.2009:

This release adds the following features

Also a total rewrite with everything now written in IronPython

Version 1.0, 12.11.2009:

Initial release, features:

Original blog post follows:

Earlier this week, while reading Leon Bambrick's blog I learned about a set of plugins, named "zen-coding", which expand snippets of css-selector like code into full blown html and/or css elements. Leon's article explains it better and there is also some documentation on the official zen-coding page. Anyway, this plugin is available for a number of text editors and IDE's but not for Visual Studio. I use Visual Studio a lot and wanted zen-coding in there, so I decided to try and whip up a small add-in for it.

The original library is available both as JavaScript and Python. I really didn't want to port or alter anything in the original code, since for future versions I want to be able to just drop in a couple of files from the original library and rebuild the add-in. The obvious choice then was to see if the library would work with IronPython. Fortunately the zen-coding library is just basic string manipulation and worked perfectly with IronPython right out of the box.

The next challenge was figuring out how to call into that from the add-in, which I wanted to write in C#. The library contained two functions I needed to be able to call. That turned out to be surprisingly easy. I made a wrapper class, ZenCodingEngine, which has two delegates with the correct method signatures, and then in its constructor I ran some custom python import code, and got references to the functions I needed and assigned them to my delegates. I changed that approach in the latest version and moved almost all of the logic into Python instead. Now the C# AddIn is very thin, it basically just receives events from VS and invokes a single exec_command() method that is defined in a python script. The interesting parts of that code are below:

private void ReloadPython() { ScriptEngine engine = Python.CreateEngine(); ScriptScope scope = engine.CreateScope(); string folder = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); List searchPaths = new List<string>(engine.GetSearchPaths()); searchPaths.Add(folder); engine.SetSearchPaths(searchPaths); scope = engine.ExecuteFile(Path.Combine(folder, "vs_zen_coding.py")); engine.SetVariable(scope, "App", _applicationObject); execCommand = engine.GetVariable<Func<bool>>(scope, "exec_command"); } public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled) { handled = false; if (!CanExecuteCommand(commandName, executeOption)) { return; } try { #if RELOAD_EVERY_TIME ReloadPython(); #else if (execCommand == null) { //Load first time, to avoid making the VS startup slower ReloadPython(); } #endif execCommand(); } catch (Exception ex) { DisplayError(ex); } handled = true; }

The ReloadPython() method is not called on startup, instead it is called the first time the command is called. IronPython performs well, but there is still some startup cost and I don't want to add that to the startup time of Visual Studio. Instead you pay the price the first time you actually need it. Then we have the Exec method which is from a VS interface and is called when the command is invoked. There I use a compilation constant, RELOAD_EVERY_TIME, to check if I should reload my python script every time I invoke the command. This has proved to be INCREDIBLY useful, instead of constantly starting VS up again and again I just edit my python script, save it and the next time I execute the command the new code is used. And of course I turn this constant off for release builds.

Another nice trick to play around with the VS API, start up an IronPython shell and type:

>>> from System.Runtime.InteropServices import Marshal >>> app = Marshal.GetActiveObject(“VisualStudio.DTE.9.0″) >>> app.ActiveDocument.Name "ZenCodingAddIn.cs"

This gets you a reference to the running instance of VS2009 which you can then experiment with.

You can download an installer for the add-in or get the source. The add-in is licensed under the GPL v3, the same license the zen-coding library uses. The add-in works for Visual Studio 2005, 2008 and 2010 beta 2.

Now, once you've installed the add-in, nothing happens. Well, actually what happens is there's a new command available in Visual Studio, named ZenCoding.Expand, or ZenCoding.VisualStudio.ZenCodingAddIn.Expand, depending on where you are looking for it. (The keyboard mapping uses the full command id, but when adding it to a toolbar you get a shorter friendly name for it). By default it is not mapped to any keyboard combination. This is because it's late and I can't be bothered to figure it out I strongly believe that add-ins should not force a keyboard shortcut on you, possibly overriding something you have set yourself. So, map the command to your preferred keyboard shortcut (mine is Ctrl+E,Ctrl+K). Or you can drag the command onto a toolbar, which is fairly useless, but possible.


If you read this far you should probably follow me on Twitter.