Text Processing Macros

I have a small arsenal of text processing macros in my Keyboard Maestro bunker. I’ve described some of my macros in the past. There are several that do one specific task but I also try to build more generic macros that ask for input too. All of the following macros work on a text selection, rather than performing a snippet expansion. I use TextExpander for snippet expansion. However, Some have sibling macros that works just on the clipboard (like text cleaning). They have different contexts. Generally I want to modify text I am currently writing. However, when pasting between applications it is very handy to be able clean the clipboard contents before pasting. Use these macros as you see fit.

I have several more macros for text processing. Hopefully I’ll find time to write about them in the future. Feel free to use the comments to share macros that you use.

Search and Filter Actions

Keyboard Maestro comes with actions for filtering text on the clipboard. On the surface the actions seem basic. But they are more than find “this” and replace with “that”. While that is useful, KM includes an option for using Regex matches for matching text. That means I can find generic text and formatting characters linke newlines. Some of the macros below use this functionality.

The Filter Clipboard action has a large number of pre-defined filters. They are self explanatory for the most part.

The Search and Replace action is more dependent on user defined matching and replacing.

Replace Spaces

This is a very flexible macro. It’s function is to replace all spaces in a selection with the user entered character or string. The macro uses the Prompt for Input action to request the replacement characters. After performing a cut operation it filters the clipboard for spaces (the Search box actually contains one space character) and replaces with the value of the user input.

Select some text:

Run the macro:

The string is replaced:

Here’s the macro:

As a bonus, I have a macro for replacing multiple sequential white space characters with just a single space. The “replace with” box contains a single space character. This is a quick way to clean up bad type.

Prepend Macro

This is one of those generic macros. I select some text and trigger the macro. I then get a user input dialog to specify the text I want to use for prepending to the selected lines. The macro then performs a cut action. Once the text is on the clipboard, I use the KM search and replace action. I am using one of the more powerful features of this action. Search with matching by regular expression.

This regular expression matches the beginning of each line. Importantly, I am using the multiline flag so that it finds the beginning of each line rather then just the first line, which is the default. The macro then inserts the selected text.

At the end, I clean up my clipboard. I don’t want any cruft added to my clipboard history so I delete the entry off of the stack.

Here are some use cases:

  1. Convert lines to a bulleted MD list by prepending with “*” or “-”
  2. Convert lines to a numbered list. Just insert “1. ” and MD conversion fixes the numbering later.
  3. Select text and prepend with four spaces to get an MMD code block
  4. Insert “>” to create a quote block
  5. Insert a new line to space out lines

Merge Paragraphs

This macro is also using the regex matching but this time I just match on the newline character and replace with a single space. This works great for converting multiple paragraphs into a single block.

Remove Empty Lines

This is another macro that relies on Regex matching. In this case it removes any blank lines between paragraphs.

Fence Text

This macro also asks for user input to avoid needing multiple macros. The dropdown is populated by using the pipe character “|” in the default value field. The rest of the macro is pretty standard stuff. It simply cuts the selected text and surrounds it with the repeated fence string. It then cleans up the clipboard.

Select some text:

Trigger the macro:

Get the new fenced text:

Here’s the macro:

Cleanup Table

This one is based entirely on Dr.Drang’s Python script for cleaning up a Markdown table. I just made a small modification to the script so it integrates with KM.

The Execute Shell Script action is set to paste the results directly so there is no need to include an additional paste action in the macro.

It takes an ugly Markdown table like this:

|Left align|Right align|Center align|
|:---------|----------:|:----------:|
|This|This|This|
|column|column|column|
|will|will|will|
|be|be|be|
|left|right|center|
|aligned|aligned|aligned|

And converts it to a nice table like this:

| Left align | Right align | Center align |
|:-----------|------------:|:------------:|
| This       |        This |     This     |
| column     |      column |    column    |
| will       |        will |     will     |
| be         |          be |      be      |
| left       |       right |    center    |
| aligned    |     aligned |   aligned    |

Here’s the modified version of Dr.Drang’s script:

#!/usr/bin/python

import sys
import os
pyClip = os.getenv('KMVAR_tempClip')

def just(string, type, n):
    "Justify a string to length n according to type."

    if type == '::':
        return string.center(n)
    elif type == '-:':
        return string.rjust(n)
    elif type == ':-':
        return string.ljust(n)
    else:
        return string


def normtable(text):
    "Aligns the vertical bars in a text table."

    # Start by turning the text into a list of lines.
    lines = text.splitlines()
    rows = len(lines)

    # Figure out the cell formatting.
    # First, find the separator line.
    for i in range(rows):
        if set(lines[i]).issubset('|:.-'):
            formatline = lines[i]
            formatrow = i
            break

    # Delete the separator line from the content.
    del lines[formatrow]

    # Determine how each column is to be justified. 
    formatline = formatline.strip('| ')
    fstrings = formatline.split('|')
    justify = []
    for cell in fstrings:
        ends = cell[0] + cell[-1]
        if ends == '::':
            justify.append('::')
        elif ends == '-:':
            justify.append('-:')
        else:
            justify.append(':-')

    # Assume the number of columns in the separator line is the number
    # for the entire table.
    columns = len(justify)

    # Extract the content into a matrix.
    content = []
    for line in lines:
        line = line.strip('| ')
        cells = line.split('|')
        # Put exactly one space at each end as "bumpers."
        linecontent = [ ' ' + x.strip() + ' ' for x in cells ]
        content.append(linecontent)

    # Append cells to rows that don't have enough.
    rows = len(content)
    for i in range(rows):
        while len(content[i]) < columns:
            content[i].append('')

    # Get the width of the content in each column. The minimum width will
    # be 2, because that's the shortest length of a formatting string and
    # because that matches an empty column with "bumper" spaces.
    widths = [2] * columns
    for row in content:
        for i in range(columns):
            widths[i] = max(len(row[i]), widths[i])

    # Add whitespace to make all the columns the same width and 
    formatted = []
    for row in content:
        formatted.append('|' + '|'.join([ just(s, t, n) for (s, t, n) in zip(row, justify, widths) ]) + '|')

    # Recreate the format line with the appropriate column widths.
    formatline = '|' + '|'.join([ s[0] + '-'*(n-2) + s[-1] for (s, n) in zip(justify, widths) ]) + '|'

    # Insert the formatline back into the table.
    formatted.insert(formatrow, formatline)

    # Return the formatted table.
    return '\n'.join(formatted)
print normtable(pyClip)

Scrubbing Text

I have two macros for stripping text clean of junk. One scrubs the clipboard and the other scrubs the current selection (shown). This macro is pretty self explanatory and uses the built-in KM text cleaning functions.

Triggering Macros

I trigger all of these macros from a single system wide hot key that brings up a palette of macros. I’ve described this in the past.