Blog

WebTools 2.0.0

WebTools

WebTools is a collection of Xcode Source Editor extensions designed to improve the experience of writing web code in Xcode. In this blog post I'll explain what exactly WebTools is and why/how I wrote it, and what's new in version 2.0.

What/Why

WebTools is actually three separate Xcode Source Editor extensions and their containing Mac App. The Mac App doesn't do anything right now except display installation instructions and screenshots.

The extensions are each targeted at a specific language: HTML, CSS, and JavaScript. The current functionality is very limited. The extensions contain templates to insert into source code (new document templates, new CSS variables, new JS classes, etc.).

I'm sure some people will ask, "Why bother writing web code in Xcode? Just use a separate text editor.". Well, with the advent of Swift on the server, it's probably going to be more common to have Xcode projects that include web code. I'd like to think that maybe these tools could help improve the current experience.

What's New

In v2.0.0, the CSS extension has been updated to include a source formatter that will "pretty print" your CSS. The formatter is written in C, and contained in a library that is statically linked to the extension. I was reading the K & R C book and wanted to get more experience with the language, which is why I decided to write this code in pure C. In fact, it requires only the standard C libraries for building and running, and can be used anywhere you can use C code. The process of formatting fits nicely with the procedural nature of C. The code consumes source chode characters one at a time, requiring only a little bit of state management to determine what needs to come next ,such as if a space needs to come before or after a character. Whitespace is important in CSS; "button.disabled" and "button .disabled" are two different selectors.

Testing an Xcode Source Editor Extension

Encapsulating business logic in a library is already a technique that Apple recommendeds. Extracting your custom logic and text handling code from an extension is even more important for unit testing. Every extension command must conform to the XCSourceEditorCommand protocol, which has the required method performCommandWithInvocation:(XCSourceEditorCommandInvocation *)invocation completionHandler:(void (^)(NSError * _Nullable nilOrError))completionHandler. Normally, in a unit test, you would probably write something like this:

-(void)testCommand {
    EditorExtension *extension = [[EditorExtension alloc] init];
    XCSourceEditorCommandInvocation *command = [XCSourceEditorCommandInvocation alloc] init];

    // ... configure command here

    [extension performCommandWithInvocation:command completionHandler:^(NSError * _Nullable nilOrError) {
        // test assertions here
    }];
}

However, take a look at the header file for XCSourceEditorCommandInvocation:

/** An XCSourceEditorCommandInvocation is not directly instantiable. */
- (instancetype)init NS_UNAVAILABLE;

We can't instantiate a command invocation in our unit tests. A way to work around this is to move all of the work that you would normally do in the implementation of performCommandInvocation:completionHandler: to methods in a library or framework. You can then use the properties of XCSourceTextBuffer and XCSourceTextRange to grab the text or text selections a user made and pass those to your library/framework. The advantage of putting your custom code in a library or framework is that it makes your code more reusable. You can now use this logic anywhere, not just in an Xcode extension.

Closing thoughts

The formatter code is definitely not polished right now. It currently only works with CSS; Less and Sass are not supported. It also doesn't minify CSS (yet). But overall, the process of writing a CSS formatter was a fun and easy way of becoming more comfortable with C and reinforcing what I had learned from reading.

Daniel Strokis