Copy OmniFocus Task Outline Text to Clipboard

Here’s a quick script that will take the selected tasks in OmniFocus 1.9.4 and put a simple space-indented text representation on the clipboard:

tell application "OmniFocus"
    tell first document window of front document
        set selectedItems to selected trees of content
        if ((count of selectedItems) < 1) then
            error "Please first select a task"
        end if
        set output to ""
        repeat with itemItr in selectedItems
            set output to output & my printTree(itemItr, 0)
        end repeat
    end tell
end tell
set the clipboard to output

on printTree(node, indent)
    set output to ""
    repeat indent times
        set output to output & "    "
    end repeat
    set output to output & name of node & (ASCII character 13)
    tell application "OmniFocus"
        repeat with treeItr in trees of node
            set treeItr to treeItr as any
            set output to output & my printTree(treeItr, indent + 1)
        end repeat
    end tell
    return output
end printTree

applescript omnifocus Nov 8 2011

Swap Word Instances in BBEdit

Sometimes I want to swap all instances of “foo” with “bar”. That is, I want every instance of “foo” turned into “bar” and vice-versa.

This may sound odd, but I encounter often it enough that I formed a strategy long ago, three replacement operations:

  1. Replace “foo” with something unique (a sentinal) in the document. I often use a bullet (•) for this.

  2. Replace “bar” with “foo”.

  3. Finally replace the sentinel string with “bar”.

Encountering this again tonight, I decided to finally automate the three-operation replacement:

set uuid to "1DB2FC05-1011-4467-BA2C-A8F3A8B530BC"
set findPasteboard to do shell script "pbpaste -pboard find"
set findPasteboard to findPasteboard & "↔"
display dialog "Swap a↔b" default answer findPasteboard
set swapSet to split of (text returned of result) by "↔"
set term1 to item 1 of swapSet
set term2 to item 2 of swapSet
tell application "BBEdit"
    tell text window 1
        replace term1 using uuid
        replace term2 using term1
        replace uuid using term2
    end tell
end tell

to split of textToSplit by splitter
    set oldTextItemDelimiters to AppleScript's text item delimiters
    if the class of splitter is list then
        set AppleScript's text item delimiters to splitter
    else
        set AppleScript's text item delimiters to {splitter}
    end if
    set theResult to text items of textToSplit
    set AppleScript's text item delimiters to oldTextItemDelimiters
    return theResult
end split

I now use a hard-coded UUID instead of a bullet for my sentinel, but the idea is the same.

AppleScript’s simple display dialog doesn’t allow two separate text input fields, so I delimit the input with ↔. Hacky.

applescript bbedit Sep 24 2011

Switch Between Tabs and Spaces in BBEdit

Here’s a script I wrote to make it easy to switch between tab-based and space-based indention in BBEdit 10:

tell application "BBEdit"
    tell text window 1
        if expand tabs then
            set currentMode to "Spaces (" & tab width & ")"
        else
            set currentMode to "Tabs"
        end if
        display dialog "Current Mode: " & currentMode & ".
Enter new mode (blank for tabs):" default answer "4"
        set newTabWidth to text returned of result as number
        if newTabWidth = 0 then
            set expand tabs to false
        else
            set expand tabs to true
            set tab width to newTabWidth
        end if
    end tell
end tell

Indention control in BBEdit is painful. Whether tabs or spaces are used is in the Edit > Text Options sheet (confusingly named “Auto-expand tabs”) and the amount of spaces inserted in space-indention mode in stuck to the bottom of the Fonts panel (accessible via View > Text Display > Show Fonts).

This script makes it one action to figure out the current mode and modify it if necessary.

applescript bbedit Sep 21 2011

Mail.app Script: Bottom Post

I’ve made peace with mostly top-posting my email replies in the name of efficiency.

But there are often times when I want to bottom-post my reply to address a specific section.

I finally broke down a wrote a script to automate the transformation of Mail.app’s top-post format to my preferred bottom-post format.

That is, it turns this:

|   

-jwr

On Nov 30, 2010, at 1:13 AM, Grumble Mumble wrote:

> we made some turkey and fixings...  time for christmas cookies...

into this:

On Nov 30, 2010, at 1:13 AM, Grumble Mumble wrote:
> we made some turkey and fixings...  time for christmas cookies...

|

-jwr

Where I denote the insertion point’s location with the pipe character.

The script assumes your .sig is one line and your reply message window is frontmost:

-- delay a second to give me time to release the
-- FastScript-invocation command key modifiers before
-- the script runs
delay 1
tell application "System Events"
    set downArrow to 125
    set upArrow to 126
    set backspace to (ASCII character 8)

    -- select first 4 lines
    key code upArrow using command down
    repeat 4 times
        key code downArrow using shift down
    end repeat

    -- move those 4 lines to the bottom
    keystroke "x" using command down
    key code downArrow using command down
    keystroke "v" using command down

    -- delete 2 trailing lines
    keystroke backspace
    keystroke backspace

    -- delete extra line between "On $DATE $SENDER wrote" and quote
    key code upArrow using command down
    key code downArrow
    keystroke backspace

    -- position insertion point to reply area, adding another line
    key code downArrow using command down
    key code upArrow
    key code upArrow
    keystroke (ASCII character 13)
end tell

It would be nice if I didn’t trash the clipboard’s contents when moving the top lines to the bottom, but I’m out of patience for this little project.

I’ve bound it to Command-Shift-Option-Down-Arrow using FastScripts.

Update: Apparently there’s a plugin for Mail.app for bottom posting: QuoteFix. Thanks to Caio Chassot and Peter Hosey.

mail.app applescript Nov 30 2010

XCPushPop

Xcode 3 and Interface Builder 3 don’t like it when you switch files out from underneath them, like what happens when you switch branches in git and mercurial.

Here’s a handy AppleScript that will remember and close all your open Xcode projects and IB documents. Run it again, and all your projects+docs will be reopened, ready again for action.

tell application "System Events"
    if exists file ".xcpushpop.plist" of home folder then
        -- pop/restore
        set plist to property list file "~/.xcpushpop.plist"
        set xcList to value of property list item "xc" of plist
        set ibList to value of property list item "ib" of plist

        repeat with xcItem in xcList
            tell application "Xcode" to open xcItem
        end repeat
        repeat with ibItem in ibList
            tell application "Interface Builder" to open ibItem
        end repeat

        delete file ".xcpushpop.plist" of home folder
    else
        -- push/save
        set openedXcodeProjects to {}
        set openedIBDocs to {}

        if my isRunning("com.apple.Xcode") then
            tell application "Xcode"
                repeat with p in project documents
                    set openedXcodeProjects to openedXcodeProjects & path of p
                end repeat
                close every project document
            end tell
        end if
        if my isRunning("com.apple.InterfaceBuilder3") then
            tell application "Interface Builder"
                repeat with d in documents
                    set openedIBDocs to openedIBDocs & path of d
                end repeat
                close every document
            end tell
        end if

        set plist to make new property list file with properties {name:"~/.xcpushpop.plist"}
        make new property list item at end of property list items of contents of plist ¬
            with properties {kind:list, name:"xc", value:openedXcodeProjects}
        make new property list item at end of property list items of contents of plist ¬
            with properties {kind:list, name:"ib", value:openedIBDocs}
    end if
end tell

on isRunning(bundleID)
    tell application "System Events"
        set procList to every process whose bundle identifier is bundleID
    end tell
    return procList is not {}
end isRunning

xcode applescript Nov 14 2010

BBEdit Script: Set Tab Width to Widest Cell

Often I’ll have textual data in columnar (tab-delimited) form.

For serious manipulation, I’ll eventually copy and paste it to a spreadsheet and work with it there, but usually I don’t need to go that far.

I’ve been using this BBEdit AppleScript for years that greatly eases viewing and editing tabular data:

--  Get the input.
tell application "BBEdit"
    set input to the selection of text window 1 as text
    if input is "" then
        set input to the text of window 1
    end if
end tell

--  Break the input into rows.
set inputRows to paragraphs of input
if (count of inputRows) is 0 then error "no rows found"

--  Find the longest cell.
set maximumInputCellLength to 0
set oldTextItemDelimiters to AppleScript's text item delimiters
set AppleScript's text item delimiters to {tab}
--  Iterate over the rows.
repeat with inputRow in inputRows
    set inputCells to text items of inputRow
    --  Iterate over the cells in each row.
    repeat with inputCell in inputCells
        set inputCellLength to the length of inputCell
        if inputCellLength > maximumInputCellLength then ¬
            set maximumInputCellLength to inputCellLength
    end repeat
end repeat
set AppleScript's text item delimiters to oldTextItemDelimiters

tell application "BBEdit"
    set the tab width of text window 1 to maximumInputCellLength + 1
end tell

It turns this:

untitled text 42

Into this:

untitled text 42

This is the important point: the script doesn’t alter the data itself. Instead, it merely finds the “widest” tab-delimited cell and sets BBEdit’s tab width to match.

Works like a charm.

bbedit applescript Dec 16 2009

iTunes Script: Delete Played Podcast Episodes

I burn through a lot of podcasts at the gym, and tend to listen to them out-of-order. With 32 subscriptions, I develop a lot of plaque in the form of listened-to podcast episodes.

This AppleScript deletes all played episodes and their associated files in one fell swoop:

tell application "iTunes"
    set librarySource to item 1 of (every source whose kind is library)
    set podcastPlaylist to item 1 of (every playlist of librarySource whose special kind is Podcasts)
    set playedEpisodes to every file track of podcastPlaylist whose unplayed is false
    repeat with playedEpisode in playedEpisodes
        set playedEpisodeFile to location of playedEpisode
        ignoring application responses
            tell application "Finder"
                try
                    delete playedEpisodeFile
                end try
            end tell
        end ignoring
        delete playedEpisode
    end repeat
    --delete playedEpisodes -- this used to work under iTunes 8.2 on Mac OS X 10.5.8... Sigh.
end tell

itunes applescript Dec 3 2009

Mail.app Script: Move Selected Message to Read Folder

I guess I’ve never publicly mentioned it, but I use this script for Mail.app extensively:

tell application "Mail"
    set nextSelectedMessageID to missing value

    set selectedMessages to the selection

    if (count of selectedMessages) = 1 then
        set selectedMessageID to id of item 1 of selectedMessages

        -- Find the selected message's position in the list.
        set selectedMessageIndex to missing value
        set msgList to every message of message viewer 1
        set msgCount to count of msgList
        repeat with msgIndex from 1 to msgCount
            if id of item msgIndex of msgList = selectedMessageID then
                set selectedMessageIndex to msgIndex
                exit repeat
            end if
        end repeat

        if selectedMessageIndex = 1 then
            -- On the "last" msg: select the one previous
            if (count of msgList) > 1 then
                set nextSelectedMessageID to id of item 2 of msgList
            end if
        else
            set nextSelectedMessageID to id of item (selectedMessageIndex - 1) of msgList
        end if
    else
        -- TODO handle multiple selection. Somewhat thorny.
    end if

    repeat with selectedMessage in selectedMessages
        set the mailbox of selectedMessage to mailbox "Read"
    end repeat

    if nextSelectedMessageID is not missing value then
        set x to item 1 of (every message of message viewer 1 whose id is nextSelectedMessageID)
        set selected messages of message viewer 1 to {x}
    end if
end tell

I’ve bound it to ⌘-\ using FastScripts, and the script’s selection-updating heuristic means most of the time I can read, reply (⌘-shift-R, type-type-type, ⌘-shift-D), file the original away and move to the next message (⌘-\) without leaving the keyboard.

Most of the script’s work is figuring out the current selection in composite message viewers (such as “Inbox”) in such a way I can figure out what the logical “next” message is.

mail.app applescript Dec 2 2009

[IMPROVED] Twitterrific+MarsEdit Script: Blog a Tweet

About a month back I wrote and posted an AppleScript that makes it easy to blog up a tweet. I wrote:

I haven’t used the script yet, and may never. It’s more an interesting experiment to have it lying around to see if it comes in handy.

Turns out it’s very handy, and I’ve made great use of it.

But it’s been annoying that the script creates a new blog posting each time it’s invoked, so I tweaked it to just inject the quoted tweet into the frontmost document. Enjoy:

property kReturn : ASCII character 13

tell application "Twitterrific"
    set t to selection
    set tweetScreenName to screen name of t
    set tweetUserName to user name of t
    set tweetID to id of t
    set tweetText to text of t
end tell
tell application "MarsEdit"
    set input to selected text in document 1
    set output to "[" & ¬
        tweetUserName & ¬
        " / @" & ¬
        tweetScreenName & ¬
        "](" & "https://twitter.com/" & ¬
        tweetScreenName & ¬
        "/status/" & tweetID & "):" & ¬
        kReturn & ¬
        "> " & tweetText
    if length of input > 0 then
        set output to kReturn & kReturn & output
    end if
    set (selected text in document 1) to input & output
end tell

twitterific marsedit applescript Nov 29 2009

HoseyifyXcodeWarnings.scpt

I usually recommend .xcconfig files, but if you don’t want to mess with them, here’s an AppleScript that will Hoseyify all your currently-open Xcode project warnings:

property kHoseyXcodeWarningSettings : {¬
    {"GCC_WARN_CHECK_SWITCH_STATEMENTS", "YES"}, ¬
    {"GCC_WARN_SHADOW", "YES"}, ¬
    {"GCC_WARN_64_TO_32_BIT_CONVERSION", "YES"}, ¬
    {"GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED", "YES"}, ¬
    {"GCC_WARN_ABOUT_RETURN_TYPE", "YES"}, ¬
    {"GCC_WARN_MISSING_PARENTHESES", "YES"}, ¬
    {"GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS", "YES"}, ¬
    {"GCC_WARN_ABOUT_MISSING_NEWLINE", "YES"}, ¬
    {"GCC_WARN_SIGN_COMPARE", "YES"}, ¬
    {"GCC_WARN_STRICT_SELECTOR_MATCH", missing value}, ¬
    {"GCC_WARN_TYPECHECK_CALLS_TO_PRINTF", "YES"}, ¬
    {"GCC_WARN_UNDECLARED_SELECTOR", "YES"}, ¬
    {"GCC_WARN_UNUSED_FUNCTION", "YES"}, ¬
    {"GCC_WARN_UNUSED_LABEL", "YES"}, ¬
    {"GCC_WARN_UNUSED_VALUE", "YES"}, ¬
    {"GCC_WARN_UNUSED_VARIABLE", "YES"}, ¬
    {"GCC_TREAT_WARNINGS_AS_ERRORS", "YES"}, ¬
    {"RUN_CLANG_STATIC_ANALYZER", "YES", "Release"} ¬
        }

repeat with setting in kHoseyXcodeWarningSettings
    if (count of setting) is 2 then
        set setting to setting & missing value
    end if
    setBuildSetting(item 1 of setting, item 2 of setting, item 3 of setting)
end repeat

on setBuildSetting(buildSettingName, buildSettingValue, buildConfigurationName)
    if buildSettingValue is missing value then
        tell application "Xcode"
            set projectList to every project
            repeat with projectIt in projectList
                set buildConfigurationList to every build configuration of projectIt
                repeat with buildConfigurationIt in buildConfigurationList
                    if buildConfigurationName is missing value or name of buildConfigurationIt is buildConfigurationName then
                        delete (every build setting of buildConfigurationIt whose name is buildSettingName)
                    end if
                end repeat
            end repeat
        end tell
    else
        tell application "Xcode"
            if buildConfigurationName is missing value then
                set value of build setting buildSettingName of every build configuration of every project to buildSettingValue
            else
                set value of build setting buildSettingName of (every build configuration whose name is buildConfigurationName) of every project to buildSettingValue
            end if
        end tell
    end if
end setBuildSetting

You can download it here.

See also: @schwa’s script for enabling Cocoa debugging options.

software-quality xcode applescript Nov 8 2009