All this stuff is filed under "custom solution"

Understanding Espresso actions

A fair number of people in the Espresso forums ask questions like, “Is there a shortcut to delete the current line the way there is in Textmate?” Usually the answer is, “No, why don’t you create one?” Yet people remain leery of creating text actions by hand. I understand that; heck it’s extra work, and who wants that? What I think most people don’t realize is that, particularly for text manipulations like deleting the current line, the amount of work is minimal. All you need is a basic understanding of how Espresso works and the ability to author simple Javascript and XML.

Anatomy of an action

Espresso actions come in two parts:

  1. The XML definition, which tells Espresso the action’s title, what Objective-C class to execute when the action is invoked, and additional information like keyboard shortcuts or tab triggers that should invoke the action.
  2. The Objective-C class (or, if using a plugin like TEA or Spice, the script that defines the action’s logic)

Most often, XML action definitions are included with a Sugar (you can make a Sugar for custom text actions simply by adding a .sugar extension to a folder that contains a TextActions folder with the action XML files in it). If you wish, you can also define action XML files outside of a Sugar by enabling TEA custom user actions in the Advanced pane of the Espresso Preferences (more info about TEA custom user actions).

As of Espresso 1.1 there isn’t an in-program GUI for editing actions yet, but hopefully that will come in time.

Spice up your actions

Spice is an Espresso Sugar that I’m currently developing that provides access to the Espresso API using Javascript (thanks to JSCocoa), and additionally provides a selection of utility classes that allow you to interact with the API without needing to know anything about Objective-C or the JSCocoa bridge.

The benefits of this should be fairly obvious, but there’s another reason creating actions with Spice is easier than using TEA or Objective-C; not only are the action scripts not compiled, but you don’t need to relaunch Espresso to see your changes. When I’m coding a Spice action, I usually open the Javascript action in Espresso, and then run the action right there on the Javascript when I need to test it.

Although Spice’s utility classes make creating custom Espresso actions easier, you still need a basic understanding of how the Espresso API works.

Contexts, ranges, recipes, and snippets

When a user invokes an action from the Actions menu, Espresso executes the action’s class and sends it an object representing the text or file context (depending on whether you’re using a text or file action; I’ll be focusing only on text actions because the file action API is currently underwhelming). The text context allows you to access the active file’s text, information about the selection (or selections, since you can select multiple things at once), line information, preferences (like what line ending or indentation is being used), and access to the syntax system.

When you are ready to change some text in the file, your action messages the text context and tells it what to change and how to do it.

As of Espresso 1.1, the easiest actions to create are ones that are invoked by the user, access and change something about the active file’s text, and then immediately exit. It’s possible to do things that are a bit more complicated or on-going, but at the moment Espresso doesn’t make it easy.

When you’re working with text, you’ll mainly be dealing with ranges. A range represents a section of text in the active file and is composed of a location and a length. Internally Espresso keeps track of text by counting all of the characters in the active file starting at zero, so the range location is the index of the starting character and the length is the number of characters contained within the range. Typically you’ll be dealing with selections, so you’ll have ranges like {0, 10} (the first ten characters in the file) or {100, 30} (characters 100 through 130). However, it’s also possible to have ranges like {10, 0}, which represents the cursor location at character index #10 in the file.

A typical text action will fetch the currently selected ranges from Espresso, manipulate the text within them somehow, and then tell Espresso to change the range(s) accordingly. In order to queue up changes like this, you’ll use text recipes.

Text recipes are something that is unique to Espresso, so far as I know. Basically, you create a recipe and tell it things like “delete the text in this range”, “replace the text in this second range”, and “add this text at a third range”. You can compose a multiple step recipe without worrying about tracking how ranges will change; for instance, if you add characters to an early range, you don’t have to adjust the location of later ranges. Instead, the recipe does that for you when you apply it to the document.

Text recipes allow you to make complex changes to multiple ranges in the active file, but you can instead insert text snippets, as well. A text snippet is a specially formatted string that allows Espresso to create tab stops, mirrored segments, and more after you’ve inserted it. Many of the custom actions bundled with Espresso are little more than text snippets; for instance Wrap Selection In Tag merely grabs the selected text and wraps it in a simple snippet that mirrors the HTML element from the opening tag to the closing tag. You can do some pretty magical-seeming things with very simple actions simply through tricky use of text snippets. The one downside to keep in mind for text snippets, however, is that you can only insert one at a time (unlike text recipes, you can’t insert multiple text snippets over discontiguous selections).

Bringing it all together

With an understanding of how the Espresso API works and a quick peek at the Spice docs, setting up an action to delete the current line (to take one example) becomes simple enough:

  1. First, we’ll need to define an action in XML, and create a Javascript file with the action logic
  2. In the Javascript, we’ll need to query the text context to get the range of the current line, and then create a text recipe to delete that range

The action XML is easy enough; simply create the file Actions.xml and place it either in a Sugar’s TextAction folder or in your Espresso Support folder located here:

~/Library/Application Support/Espresso/Support/TextActions/

The contents of the action XML definition should be this:

<action-recipes>
    <action id="com.onecrayon.DeleteLine" category="actions.text.generic">
        <class>Spice</class>
        <title>Delete Line</title>

        <setup>
            <script>delete_line</script>
            <undo_name>Delete Line</undo_name>
        </setup>
    </action>
</action-recipes>

(If you wish to add a keyboard shortcut, you can do that with the key-equivalent entity; see the Spice action XML docs.)

If you’re using the Support folder option, you’ll need to enable TEA custom user actions in the preferences and restart the program twice. Otherwise you’ll need to make sure the Sugar is installed and restart the program once (action XML definitions are loaded when Espresso boots up; unfortunately as of Espresso 1.1 there isn’t a way to refresh action XML definitions without a relaunch).

Now that you’ve got the action defined (and showing up in the Actions menu) you can create the Javascript file delete_line.js (referenced in the script element of the action XML). If you are using a Sugar, it should be located here:

MySugar.sugar/Support/Scripts/

If you’re using custom user actions in the Espresso Support folder, you’ll save it here:

~/Library/Application Support/Espresso/Support/Scripts/

The simplest way to delete the current line would be this:

// Deletes the current line

// require() allows you easy modular access to Spice's helper classes
var textContext = require('text_action_context').textContext;
var TextRecipe = require('text_recipe').TextRecipe;

// exports.main is your primary function, run automatically by Spice
exports.main = function() {
    // Grab the range of the current line
    var linerange = textContext.rangeForLine();
    // Run the actual removal
    return new TextRecipe().remove(linerange).apply();
}

First, the script uses the universal require() function to include the Spice utility object textContext (which contains methods for interacting with the Espresso text context) and utility class TextRecipe (which allows access to Espresso text recipes).

Spice’s utility classes are provided in a modular system that allows you to only require what you need in order to run your action. Spice modules have the following naming conventions:

  • The module is named for the primary class, converted to lowercase and with underscores between words (so primary class TextActionContext becomes module text_action_context)
  • Classes are all camel-case with the first letter capitalized (TextActionContext)
  • Objects (instantiated versions of a class) are camel-case with the first letter lowercase (textContext is an instantiated version of TextActionContext)

You can see what a given module exports in the Spice docs (along with full references to the object methods available). The text_action_context module is one of the few that exports an object as well as the class, since you’ll never need more than one object for referencing the text context.

Once you’ve saved the Javascript file, you’ve officially created your first Espresso action! You can immediately run the action from the Actions menu, and it will delete the current line. If you test it out, though, you may notice that the action behaves differently when you delete the final line in the document; instead of removing the line completely, it only removes all the text. To fix this, you could modify the action like so:

// Deletes the current line

// require() allows you easy modular access to Spice's helper classes
var textContext = require('text_action_context').textContext;
var TextRecipe = require('text_recipe').TextRecipe;
var Range = require('range').Range;

// exports.main is your primary function, run automatically by Spice
exports.main = function() {
    // Grab the range of the current line
    var linerange = textContext.rangeForLine();
    // If on the last line of the doc, remove the line break prior to the line
    // This isn't strictly necessary, but it's nice to have
    if (textContext.lineNumber() != 1 && textContext.rangeForLine(textContext.lineNumber() + 1) === false) {
        linerange = new Range(linerange.location - 1, linerange.length + 1);
    }
    // Run the actual removal
    return new TextRecipe().remove(linerange).apply();
}

The only addition is some logic to check if we’re on the last line of the document, and if so create a custom Range object that includes the linebreak from the previous line.

Debugging

As you work on your own custom actions, you will of course run into problems and errors. The best way to debug is to keep Console.app open (located in your Applications/Utilities folder), since most errors will be output there. You can also use the globally available system.log('message') to output directly to the console. Many Spice utility classes also include a log() method to quickly log the contents of an object.

When using Spice, keep in mind that it’s still under development (at the time of this writing, it’s at version 1.0 beta 6). If you run into something that seems buggy or a limitation of the utility classes, please let me know.

Parting thoughts

Even if you don’t use Spice, the basic strategy for adding a custom action to Espresso remains the same. TEA offers several alternative methods to creating text actions (from Python scripts with full access to the API to scripts in arbitrary languages like Ruby or PHP that have more limited capabilities). Although the basic editor has a nice interface and some great features, I’ve found that the extensibility of Espresso is what keeps me coming back for more. A modicum of effort is often all that’s required to add a custom text action, so there’s little excuse for not giving it a try if you find yourself missing functionality you’ve grown used to in other editors.

If you do write any great custom actions, please think about sharing them either via a Sugar, GitHub, or the Espresso forums! I’d also love to hear how people are using Spice; I’m still planning out the additions I want to make now that I’ve got most of the basic Espresso API for text actions covered by the utility classes, so your input is extremely valuable.

Wrap Selection In Link in any program on Mac OS 10.6

The Mac OS X Services menu got a serious bit of love for 10.6, but until now I haven’t really played around with it. Today, however, I finally got around to trying it out, and for the first time since I started using OS X years ago, Services are actually becoming useful for me.

The specific need I wanted to address was my inability to easily insert some common HTML elements (particularly links) in WriteRoom. I’ve owned WriteRoom for years thanks to a software bundle, but I never had any use for it until I discovered QuickCursor. Now I can’t get enough of it; full screen editing for text fields in Safari is brilliant.

The only thing I haven’t been happy about was needing to type out every blessed character when I needed light HTML. Although WriteRoom’s Applescript support does not apparently provide access to selected text, the new Services menu does.

To add HTML link insertion to WriteRoom (or any other text editor that supports Services, for that matter) first boot up Automator and create a new Service workflow:

service_workflow.jpg

By default, Automator will have you processing selected text in any application. Check the “Replaces selected text” checkbox, and if you’re only going to be using the action in WriteRoom (or similar application) make sure to use the dropdown to target that app and avoid cluttering up your Services menu:

service_config.jpg

Over at the top right, type “Applescript” in the search box to filter the list for the “Run Applescript” action (make sure you have the Library highlighted, or it may not show up). Drag the action into your workflow:

services_action.jpg

Select everything in the text box, and replace it with this:

on run {input, parameters}
    set linkDefault to (the clipboard as string)
    set linkDefault to my switchText(linkDefault, "\\\"", "\"")
    set targetLink to do shell script "echo \"" & linkDefault & "\"|sed -E \"s/(mailto:)?(.+@.+\\..+)/mailto:\\2/\""
    if targetLink does not start with "mailto:" then
        set targetLink to do shell script "echo \"" & linkDefault & "\"|sed -E \"s/^(([a-zA-Z0-9-]+\\.)*[a-zA-Z0-9-]+\\.[a-zA-Z]{2,4}(\\/.*)?)/http:\\/\\/\\1/\""
        if targetLink does not start with "http" then
            set targetLink to "http://"
        end if
    end if

    return "<a href=\"" & targetLink & "\">" & input & "</a>"
end run

on switchText(fromText, targetText, replaceText)
    set d to text item delimiters
    set text item delimiters to replaceText
    set fromText to fromText's text items
    set text item delimiters to targetText
    tell fromText to set fromText to item 1 & ({""} & rest)
    set text item delimiters to d
    return fromText
end switchText

Save your workflow, and you’re basically done! When you select some text in WriteRoom or elsewhere and run the action from the Services menu, it will wrap the text with a link. It also will check the clipboard to see if there’s a recognizable email address or URL on it, and if so will use it for the link (otherwise defaults to http://).

For maximum productivity benefits, of course, you’ll want to visit the System Preferences Keyboard pane and give your new Service action a shortcut; I’m using control-shift-L since that’s what I’m used to from Textmate and Espresso.

Worth noting is the fact that the above Applescript doesn’t display a dialog box to query you for a URL if it can’t detect one on the clipboard, so for maximum effect you’ll want to copy your target URL prior to running the action if possible. The reason for this minor problem is that the dialogs spawned by my Service action never received focus, and for myself mousing over to give the dialog focus is more work than option arrowing a few times to edit the default URL.

You can, of course, add simple Service actions to surround text in other arbitrary HTML tags, as well. Simply use an Applescript similar to this instead:

on run {input, parameters}
	return "<strong>" & input & "</strong>"
end run

The only major downside that I’ve found of using Services this way is that it isn’t as quick as I’d like. I experimented with using shell scripts or Python instead of Applescript, but there were no significant speed boosts; as best I can tell, however Services is harvesting the selected text and then replacing it afterward is simply a slow process.

I’d love to hear how other people are leveraging the new 10.6 Services menu! I have the feeling that I’m just scratching the surface with this script.

Text Editor Actions for Espresso

I am extremely happy to announce that my Text Editor Actions for Espresso (or “TEA” for short) has at last been released as version 1.0. Version 1.0 is available for download, or you’ll also find it bundled in the upcoming Espresso 1.0.7.

So just what the heck is TEA for Espresso? Simply this:

  1. A selection of my favorite text actions, mostly (but not entirely) copied from Textmate
  2. Generic actions that allow you to create variations on TEA’s bundled functionality to suit your workflow by editing simple XML
  3. A general framework for coding and running text actions in arbitrary languages without needing to create a Sugar or (for third party Sugars) without needing compiled Objective-C classes

Espresso’s Sugar API was already pretty sweet. TEA makes it that much better by lowering the barrier to creating custom text actions for users and Sugar developers alike.

Documentation for TEA is currently limited to info on creating your own actions, so I’ll walk you through the basic actions included with the plugin.

The vast majority of TEA’s built-in actions focus on making HTML easier to edit, because editing HTML is most often why I need a text editor.

Generic text actions

Spaces To Tabs… and Tabs To Spaces…
As you might expect, these actions convert the type of indentation in your document or (if it exists) your selected text. When you run the actions you’ll be prompted to enter the number of spaces per tabs you wish to use (it defaults to whatever is in your Espresso preferences, so you can just hit enter most of the time).

Trim Line(s)
Trim Line(s) will, when invoked, either trim all of the lines in your selection or the current line the cursor is on (if no selection exists). Unlike some trim lines actions, TEA’s Trim Line(s) attempts to be smart about what whitespace it removes:

  • All whitespace at the end of the line will be stripped
  • Any whitespace at the beginning of the line that isn’t part of the indentation will be stripped

What the latter means is that if in the Espresso preferences you have the program set to use spaces instead of tabs with four spaces per tab, and the beginning of a line has ten spaces, two of the spaces will be stripped.

Select → Word, Select → Line, Select → Line Contents
As you might expect, these actions select the word under the cursor, the line under the cursor (including leading and trailing whitespace), or the textual contents of the line under the cursor (excluding leading and trailing whitespace), respectively.

Sorting → Sort Lines (Ascending) and Sorting → Sort Lines (Descending)
As you might expect, these actions sort all lines in the selection (or document, if no selection) in ascending and descending order, respectively.

Sorting → Randomize Lines
This randomly sorts all lines in the selection (or document, if no selection).

Sorting → Remove Duplicate Lines
If for some reason you need to strip all duplicate lines from your selection or document, this is the command for you.

Formatting commands

Indent New Line (command-shift-enter)
One of my favorite parts of Textmate is that after creating an HTML tag, I only have to hit enter once to get a perfectly indented tag pair with the cursor in between and bumped in a level. The fact that Espresso doesn’t do this irks me greatly, and so this action allows you to force the issue. Indent New Line will turn this (pipe represents cursor):

<div>|</div>

Into this:

<div>
    |
</div>

If you have any text selected when you run the action, the selected text will be moved to the middle line and indented.

Insert Linebreak(s) (control-enter)
In HTML, Insert Linebreak(s) will insert a break tag (<br />). In some other contexts (like PHP double quoted strings), it will insert \n. In Markdown it will insert two spaces and a linebreak. If you have one or more selections, the tag or textual linebreak will be inserted at the end of each selection.

In case you didn’t quite catch that, Espresso allows you to have multiple selections (hold down command while you select multiple items with your mouse), and this action will affect all of them. This is extremely cool, and one of the features that I’m still learning to use; before now, I’d never come across a text editor that allowed me to so much as select multiple items at once. Of course, it isn’t all that often that you need to append br tags in a whole bunch of places around a document, but what about when you want tags for…

Strong (command-B) and Emphasize (command-shift-I)
These do about what you’d expect. If you have one or more selections, they’re surrounded with strong or em tags. If no selection, you get a tag wrapping your cursor. Incidentally, if you’re working with a single selection (or no selection) you’ll get a text snippet with tab stops, so hit the tab key to edit what’s inside the tag.

A note on Emphasize’s shortcut; command-I by default is used to show and hide the navigator sidebar, hence this somewhat odd shortcut for italics. If you wish to switch the shortcuts, you can do so through the System Preferences Keyboard & Mouse controls.

HTML actions

Entities → Convert To Named Entities (control-&) and Entities → Convert To Numeric Entities
Run one of these actions to have the character immediately to the left of the cursor converted from Unicode into an HTML character entity. If you have one or more selections, all non-ASCII Unicode characters will be converted to entities of the desired variety. If using named entities, Unicode characters without a named entity will still be converted to their numeric equivalent. These actions will also convert ampersands (but will ignore ampersands that are already part of an entity).

Entities → Insert Non-Breaking Space, etc.
Use these actions to quickly insert the named HTML entity for the given character.

Expand Abbreviation (control-,)
This action is much like Textmate’s “Insert Open/Close Tag (With Current Word)” which, when I saw it demoed in a screencast, changed my life. For far too long had I been toiling away, typing out every blessed less than/greater than symbol. With Expand Abbreviation, I merely type the HTML tag, hit the shortcut, and voilà. I have the complete tag ready to go with barely any effort at all.

And the fun doesn’t stop there! The reason for the action’s name change is that Expand Abbreviation is powered by the fantastic zen coding project, so in addition to Textmate’s functionality Expand Abbreviation offers the full range of zen coding abbreviations and CSS-selector style syntax to create complex markup from very simple declarations. Here’s a quick example of zen coding’s awesomeness:

div#stuff.things.booyah

Type that, hit control-, and you’ll end up with this (pipe represents cursor):

<div id="stuff" class="things booyah">|</div>

Or if you want to do something a little more complicated:

div#nav+div#content>p.item$*2

Which leads to this:

<div id="nav">|</div>
<div id="content">
	<p class="item1"></p>
	<p class="item2"></p>
</div>

And that’s just the tip of the iceberg. Zen coding offers numerous other selectors and scads of abbreviations for HTML and CSS. All of them will work with Expand Abbreviation.

You may also, if you need, use the old Textmate-style tag creation where you type out everything in the tag except the carets, highlight it, and run it through Expand Abbreviation to get a full tag. For instance, this:

div style="width:100%;"

Once selected and run through Expand Abbreviation leads to this markup:

<div style="width:100%;">|</div>

If there is no selection, this action will use the current word regardless of where the cursor falls in it (Textmate will only parse to the left of the cursor).

Wrap Selection In Tag (control-shift-W)
As you might expect, if you select some text and invoke Wrap Selection In Tag, the selection will be wrapped in an HTML tag. Just like in Textmate, you can type out tag attributes and they won’t be mirrored to the closing tag, and moving outside the tag is a tab away.

Wrap Selected Lines In Tag (command-control-shift-W)
This one acts just like Wrap Selection In Tag, except that each line in the selection is wrapped.

Wrap Selection In Link (control-shift-L)
Unsurprisingly, selecting some text and invoking this command will wrap it in an HTML link tag. What makes this action more worthwhile than some of the others is that if you have a recognizable link on your clipboard it will be inserted, and there are several tab stops set up to make removing or editing the link’s title extremely easy. Unlike Textmate, this action does not attempt to populate the title from the actual webpage’s title. I’ve had Textmate hang while it waits to retrieve the webpage too many times to want to implement that functionality myself.

If you use this action while editing Markdown or Textile, the selection will be wrapped in a Markdown or Textile link rather than an HTML anchor.

Documentation For Tag (control-H)
If your cursor is inside an HTML tag, you can run Documentation For Tag to have the word under the cursor (or the selection) searched for in HTMLHelp.com’s HTML reference. If the cursor is inside an HTML tag, you’ll be taken straight to the first result (almost always the correct tag page). Otherwise, you’ll get a Google result listing.

TEA Preferences

TEA → Preferences offers a GUI to modify some TEA-related preferences. You’ll need to have a document open in order to access the prefs due to limitations in how Espresso sets up actions.

General Prefs
Checking “use XHTML by default” will cause TEA-based snippets that use the $E_XHTML variable to leave it blank. At some point in the future, TEA will hopefully be more intelligent about detecting whether a document is HTML or XHTML, but for now you’ll need to control it using this preference.

Similar to Textmate, anything entered in the Custom Shell Variables section of the preferences will be available as an environmental variable to any shell scripts you run through TEA. For instance, if you add a variable with the name “MY_CUSTOM_VARIABLE” and the contents “I love TEA!” then wherever you use the shell environmental variable $MY_CUSTOM_VARIABLE you’ll get “I love TEA!”

Actions
If you check “Enable custom user actions” you will be able to create custom actions without needing a custom sugar. This is useful not only for custom TEA-based actions, but for custom actions using third party sugars, as well.

Beyond the bundle

If TEA’s included actions aren’t enough for you, it’s extremely easy to add your own custom actions, port actions from Textmate bundles, and otherwise use TEA to jumpstart your own Espresso customizations. The TEA for Espresso wiki has lots of info on this sort of thing, or you can take a look at the HTMLBundle.sugar’s source for an example of porting Textmate snippets and bundle items (the HTMLBundle.sugar may also be of use to other folks who want Textmate’s HTML tab completions, among other things; download it here).

I’m also usually available in the forums or Espresso IRC channel if you have questions about using TEA, feature requests, bug reports, or other comments. Alternatively if you have a GitHub account, you can file bug reports and feature requests directly into the TEA for Espresso Issues tracker.

I hope you enjoy TEA with your Espresso!

TextSoap 6 and my XHTML Suite of custom cleaners

In case you hadn’t heard, TextSoap was updated to version 6.0 a few weeks ago. I’ve waited on posting about it because I wanted to share some of my custom cleaners (you can jump straight to the download if you’re so inclined), and now I’ve finally found the time.

For those who know about TextSoap, version 6.0’s main benefit (at least from my point of view) is a vastly redesigned custom cleaner editor. Cleaners can now run text through sub-routines, there’s a quick regex reference right in the window, and you can attach notes to cleaner actions to remind yourself what the heck that complicated regex pattern is supposed to be doing. There are other improvements, as well, but the custom cleaners interface is where it’s at for me. If you’re curious, check out the release notes for the full scoop.

For those not in the know, TextSoap is a fantastic piece of software that allows you to make changes to plain and rich text both using built-in cleaners or custom cleaners that you define yourself by combining regular expressions and any of the built-in cleaners with familiar Automator-style rules.

Yeah, I know, it doesn’t sound too impressive, does it? But that’s only because you’re used to wasting a lot of your time on mindless repetitive tasks involving text. TextSoap not only provides an easy way to save sets of common text-based find and replace actions, but it allows you access to them from pretty much anywhere on your computer by integrating with popular programs via plugins, offering a system-wide contextual menu, or hanging out in the Services menu.

When I first bought TextSoap, I regretted it because I barely ever used it (this was back in version 4.0, I think). Then one day I was doing something incredibly repetitive with text (I don’t even remember what), and I got fed up, launched TextSoap, and took a look at the custom cleaners. I’ve never looked back. Although the most powerful custom cleaners require knowledge of regular expressions, there are still hundreds of things you can do without ever worrying about regex simply by combining TextSoap’s provided cleaners with the building blocks available in custom cleaners. TextSoap provides an approach to text manipulation that has saved me hundreds of hours of drudgery.

Over time, I’ve found that the custom cleaners I create tend to fall into two categories:

  1. Cleaners that address specific problems that either recur or only happen once but require the same actions repeated a bunch in that sitting. For instance, for one client I have to convert a Word document into a newsletter every two weeks, observing their byzantine rules for HTML formatting. The first time I did it, it took a mind-numbing four hours. The second time, I created a custom cleaner while I worked and it took me two. The third time all I had to do was use the custom cleaner, and it took me one. With practice, I’m now down to about forty minutes.
  2. Cleaners that address generic recurring actions. These are cleaners that I’ve slowly tweaked over time, and now use primarily as building blocks for my task-specific cleaners.

It’s this last type of cleaner that I would like to share with you.

My XHTML suite of custom cleaners

My main use for TextSoap is manipulating HTML, and because I know a lot of other people out there have to do this on a regular basis I’ve decided to share the basic cleaners that serve as the foundation for my workflow. TextSoap has revolutionized how I perform certain tasks (particularly converting styled text to HTML and converting really hideous HTML into tasty XHTML), and I strongly recommend it to any web junkie who has cursed out a previous developer for their table-filled monstrosity of a website. Before I get into the nitty-gritty details of what’s included, here’s the download:

Download TextSoap XHTML Suite

Included are eight custom cleaners (if you’re only interested in one or two, see the ReadMe for details on which cleaners require one of their brethren):

  • Encode Ampersands. This encodes every ampersand that isn’t already part of an HTML entity.
  • Escape Single Quotes. Primarily useful for Javascript, PHP, etc., this escapes all single quotes with a backslash.
  • HTML Curly Quotes. For those clients who must have curly quotes, this is your solution. It converts every quote outside of HTML tags into a curly. (Please note: only works for English curly quotes.)
  • HTML Paragraphs. This converts text blocks separated by double line breaks into paragraphs, and converts single line breaks to <br /> tags.
  • Style to HTML. One of my workhorses, this cleaner takes richly formatted text and turns it into simple, paragraph-delineated HTML with appropriately placed strong and em tags.
  • URLs to HTML Links. This cleaner finds all of the easily recognizable URLs in a document (starting with http, https, or www) and converts them into HTML anchor links.
  • WebHappy. This happy little cleaner simply converts richly styled italics and bold into strong and em tags, straightens all quotes, and converts any problematic characters into HTML entities.
  • XHTML Cleaner. This is a pretty hefty cleaner, and I run it by default on any HTML that needs serious love to turn into XHTML. The cleaner performs a laundry list of common tasks (properly escaping self-closing tags, b to strong, lowercased tag and attribute names, etc.) and also attempts to add and remove linebreaks so that you can easily indent the code in your favorite editor (like Textmate). I rarely use XHTML Cleaner directly, but it offers a great starting point for any custom cleaner that needs to deal with poorly written HTML.

Although some of these cleaners are great on their own (I have a special place in my workflow for WebHappy, for instance, even if I never did think of a good descriptive name for it), a lot of them work best as the starting place for your own task-specific custom cleaners. I’ve tried to add notes to all of the regex rules, as well, so they may help you figure out how best to perform your own tasks (keep in mind that some of these cleaners were developed while I was still figuring regex out, so some of those regular expressions are narsty). If you improve or otherwise modify any of the core suite of cleaners, drop me a line because I’d love to see what you’ve done.

Enjoy!

Coda 1.6 released, minor TEA for Coda update

Coda 1.6 has been released, and boy howdy is it exciting! Coda now includes a plugin architecture, including Cocoa plugins for people who need to be able to manipulate the interface and an easy-to-use plugin creator that will allow you to run plugins using command-line languages (similar to Textmate). This is a fantastic update, and as we start to see plugins being produced I don’t doubt that Coda will become more and more appealing for Textmate users who have been holding out. It’s certainly not as powerful and flexible (no tab stops, for instance), but the addition of user-generated plugins will certainly allow people to do great things.

Along with the update to Coda, I’ve updated the TEA for Coda bundle (find the most recent version on the dedicated TEA for Coda page); all of the scripts will now perform their actions in the active document even if multiple windows are open. Additionally Indent New Line should be significantly improved performance-wise. The scripts do now require Coda 1.6, though; if you’re running an old version of Coda expect buggy behavior.

I am unlikely to improve the TEA for Coda scripts anymore; my new goal will be to get the Textmate bundle items that I know and love into Coda using the plugin interface. This will necessitate learning some Cocoa, though (since some of the key actions require user input), so it may be a little bit of time before I’m able to get a working plugin up and running. In the interim (or until someone else does it), TEA for Coda is still the best way that I know to get Coda to behave like a full-blooded HTML editor.

TEA for Coda now Universal Binary

I’ve posted a minor update to my TEA for Coda bundle. This update includes two changes:

  • The HTML Tidy script is now a universal binary! At long last, our PPC friends can run it just as easily as those with newer hardware.
  • The Format with Em and Format with Strong scripts now keep your text highlighted rather than moving the cursor to the end. This is not only more congruent with Textmate’s behavior, but I think it makes better sense, particularly if you ever need to wrap the text in more than one tag (or wish to add a class name, or…).

You can get the update from the dedicated TEA for Coda page. Unless you use PathFinder or have some other way of viewing invisible files, you’ll need to replace the entire HTML directory (if you’ve made changes to the scripts, you should copy them into the new directory before replacing the old one). If you are comfortable with invisible files, then you’ll just need to replace the .lib directory and the Format directory.

TEA for Coda adds Wrap Each Selected Line in Tag

At last! Galvanized by bug fixes and minor improvements, I finally tackled my two biggest wishlist items for my Textmate Emulation Applescripts for Coda: Wrap Each Selected Line in Tag and Indent New Line. Download the updated scripts, or read on for the details.

Wrap Each Selected Line in Tag does about what it sounds like. Select a bunch of lines, run the script, and you’ll get a dialog where you can enter the tag and its attributes (identical to the current Wrap Selection in Tag). Once you’ve entered your tag, the script will wrap all of the lines with it (and ignore lines that are nothing but whitespace). This is incredibly handy for creating lists.

Indent New Line isn’t inspired by anything in the Textmate HTML bundle; rather it’s inspired by Textmate’s automatic indenting. When you run the script it will take your cursor (or any selected text) and stick it on a new line at one more level of indentation (based on what you’ve selected in the Coda preferences). This script is a great complement to Insert Open/Close Tag: type div, run Insert Open/Close Tag, and run Indent New Line to end up with:

<div>
	|
</div>

(pipe represents cursor)

Indent New Line is also super useful for keeping your indentation clean when working with Javascript functions; just type your curly brace and run the script to get a nicely indented place to start coding.

I have now completed all my “must-have” items for TEA for Coda, so let me know if you have any favorite actions from Textmate (or elsewhere) that you’d like to see added to the bundle. I’m always open to suggestions. As always, you can get the most up-to-date info about the script on the dedicated TEA for Coda page.

Minor TEA for Coda update

I’ve posted a minor update to my Textmate Emulation Applescripts for Coda and created an official TEA for Coda landing page since the scripts have been reasonably popular and I’d like people to be able to reach them without digging through blog archives.

The most recent release (2008-09-25) adds Insert BR, a script that inserts a <br /> element at the cursor (warning: will replace any selected text), fixes an annoying cursor position bug for anything that positions the cursor (cursor was previously overshooting by a character), and vastly improves on the behavior of Insert Open/Close Tag by allowing it to intelligently handle self-closing XHTML tags like img and link.

If you’re already using the scripts, you can update by replacing the HTML folder in your Coda scripts folder with the one in the download. Newcomers should follow the installation instructions in the Read Me.

Textmate Emulation Applescripts for Coda

Update: TEA for Coda documentation and other information has now moved over to One Crayon:

http://onecrayon.com/tea/coda/

Textmate is a fantastic editor. Every once in a while I try a different text editor, but none of them allow me to get even close to the speed and productivity that I enjoy with Textmate.

However, I recently took another look at Coda when it was updated to version 1.5 because the update came out at the same time that a project which benefited a lot from Coda’s all-in-one interface hit my plate (gotta love remote editing with SSH access in the other split). Although nothing can salvage Coda’s craptastic CSS editing (nothing but a straight-up duplication of CSSEdit’s fantastic auto-complete and intelligent indentation, that is) with a little Applescript I was able to emulate four of the functions from Textmate that I find absolutely critical to my coding happiness and productivity. Without further ado, I give you the Textmate Emulation Applescripts for Coda:

Download TEA for Coda

TEA for Coda currently offers four of my favorite actions from the Textmate HTML and Hyperlink Helper bundles (in slightly less elegant and feature-rich implementations, of course, given that it’s Applescript and Coda’s less-powerful editing environment):

  • Insert Open/Close Tag: this is the functionality that I can’t live without. Just type the tag you want and run the script to have the tag automatically created and your cursor inserted inside (you can also type a tag with attributes, select it, and the script will intelligently exclude the attributes from the closing tag). The only thing that makes me sad is that Coda doesn’t have Textmate’s intelligent text indentation, so adding divs with the script is still a little frustrating.
  • Tidy HTML: this runs Tidy HTML optimized to correctly indent your code rather than clean up messy code (Coda lacks automatic code indentation, and I write clean code anyway; see the Read Me for info on changing Tidy’s default options if you’d rather use it to fix Word HTML, for instance).
  • Wrap Selection in Link: this mimics the basic functionality of Textmate’s Hyperlink Helper Wrap Word as Link, but without the cool automatic title attribute generation or tab controls. It will automatically grab whatever’s on the clipboard, though, making it easy to create links if you’ve copied them from elsewhere.
  • Wrap Selection in Tag: use this script to wrap the selected text in an arbitrary tag. Like Textmate, it allows you to enter attributes but doesn’t stick them in the closing tag. Unlike Textmate, it forces you to use enter the tag in a dialog box; about the same speed, but much less elegant.

Make sure after installing the scripts that you customize yourself some shortcuts either using the System Preference Keyboard and Mouse area, or with a utility like FastScripts. The ReadMe includes a list of the default Textmate shortcuts for your reference if you’re a Textmate user.

Hopefully these scripts will help other people who want to leverage the awesome HTML editing capabilities of Textmate along with the all-in-one sweetness of Coda. If you’re an Applescript ninja and decide to improve on or add to the scripts, please let me know! I’d love to implement better solutions to my own workflow, and I’m a bit of an Applescript noob so the scripts could doubtless be improved.

As far as my own plans for the bundle go, I intend to add a script to emulate Textmate’s Wrap Each Selection in Open/Close Tag and I’m also hoping to increase the intelligence of some of the scripts by offloading some of the logic to shell scripts; just need to work on my Python-fu or something before that can happen.

While Coda is still a jack of all trades and master of none, the TEA for Coda bundle has at least made it usable for me on those projects where its collection of features and awesome split abilities make it a better choice than Textmate. I just hope that Panic will implement some intelligent indentation for a future version, as that’s still the most glaring omission when trying to perform basic text editing, even with TEA for Coda installed.

Minor update Sept. 1, 2008: With thanks to Brian Haslanger, Format with Em (menu title: Em) and Format with Strong (menu title: Strong) are now a part of the TEA for Coda. If you’ve already installed the bundle, just redownload, copy the HTML/Format folder into your ~/Library/Scripts/Applications/Coda/HTML folder, and add the cmd-I and cmd-B shortcuts in System Preferences.

Minor update Sept. 2, 2008: TEA for Coda now includes the most recent version of Tidy (the HTML Tidy site provides binaries that are several years out of date). Additionally, the Tidy script will now accept selected text and only tidy that text (without inserting a bunch of useless body tags and so forth). If you don’t select any text, it will tidy the entire document. I’ve also revised the tidy_config.txt file to some more sensical defaults. Updating procedure is the same as before.

Minor update Sept. 25, 2008: TEA for Coda now has a dedicated landing page; please reference that for the most up-do-date information about the scripts. I’ve also updated the scripts with bug fixes and more, but I won’t be updating this post any more when I update the scripts down the road.

Minor update Nov. 10, 2008: So I lied about never updating this again; since this post continues to be a popular landing spot for Google, I wanted to let folks know that TEA for Coda now includes a Universal Binary version of Tidy HTML. If you’re using a PPC, definitely grab the download above and update (more info on updating in the Read Me, or here).

Minor update Nov. 11, 2008: TEA for Coda now requires Coda 1.6 (which is a free update for all Coda users), and the scripts will now perform their actions in the active document, even if there are multiple windows open.

Track me like a stalker:
  • Tagamac
  • Twitter
  • beckbits
Clicky Web Analytics