MacOS – How to get the selected text into an AppleScript, without copying the text to the clipboard

applescriptmacostext;

I've researched methods on how to get the selection as a text variable in an AppleScript. But these methods rely on copying the selection to the clipboard (for example, by keystroking the copy command at the top of the script) in order to introduce this text to the AppleScript (by using the clipboard).

This is less than ideal, of course, because the actual clipboard text is overwritten.

Is there another way to get the selected text, system-wide, in AppleScript, without disturbing the clipboard?

Obviously, one can easily get the selected text without touching the clipboard in Automator. However, in this case, I don't want to use Automator; I need a pure AppleScript solution.

Best Answer

Here is a recent blog post that focuses on this very mission:

Michael Tsai - Blog - Processing the Selected Text via Script

One way to get the currently selected text into an AppleScript, without overwriting the clipboard contents, is to simply save the clipboard contents to a new variable before the selected text is copied. Then, at the end of the script, place the original clipboard contents back onto the clipboard.

Here is what this might look like:

-- Back up clipboard contents:
set savedClipboard to the clipboard

-- Copy selected text to clipboard:
tell application "System Events" to keystroke "c" using {command down}
delay 1 -- Without this, the clipboard may have stale data.

set theSelectedText to the clipboard

-- Makes the selected text all uppercase:
-- From: http://apple.stackexchange.com/a/171196/184907
set theModifiedSelectedText to (do shell script ("echo " & theSelectedText & " | tr a-z A-Z;"))

-- Overwrite the old selection with the desired text:
set the clipboard to theModifiedSelectedText
tell application "System Events" to keystroke "v" using {command down}
delay 0.1 -- Without this delay, may restore clipboard before pasting.

-- Instead of the above three lines, you could instead use:
--      tell application "System Events" to keystroke theModifiedSelectedText
-- But this way is a little slower.

-- Restore clipboard:
set the clipboard to savedClipboard

But, this method is imperfect, as Michael notes:

This is ugly and has some downsides: there are delays necessitated by the GUI scripting, and certain types of clipboard data are not preserved.


However, Shane Stanley left a comment on the blog post with a method to retain the original formatting of the clipboard contents.

If using the previous example, replace the first line with:

set savedClipboard to my fetchStorableClipboard()

Replace the last line with:

my putOnClipboard:savedClipboard

And add the following code to the end:

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"


on fetchStorableClipboard()
    set aMutableArray to current application's NSMutableArray's array() -- used to store contents
    -- get the pasteboard and then its pasteboard items
    set thePasteboard to current application's NSPasteboard's generalPasteboard()
    -- loop through pasteboard items
    repeat with anItem in thePasteboard's pasteboardItems()
        -- make a new pasteboard item to store existing item's stuff
        set newPBItem to current application's NSPasteboardItem's alloc()'s init()
        -- get the types of data stored on the pasteboard item
        set theTypes to anItem's types()
        -- for each type, get the corresponding data and store it all in the new pasteboard item
        repeat with aType in theTypes
            set theData to (anItem's dataForType:aType)'s mutableCopy()
            if theData is not missing value then
                (newPBItem's setData:theData forType:aType)
            end if
        end repeat
        -- add new pasteboard item to array
        (aMutableArray's addObject:newPBItem)
    end repeat
    return aMutableArray
end fetchStorableClipboard


on putOnClipboard:theArray
    -- get pasteboard
    set thePasteboard to current application's NSPasteboard's generalPasteboard()
    -- clear it, then write new contents
    thePasteboard's clearContents()
    thePasteboard's writeObjects:theArray
end putOnClipboard:

I've tested this solution provided by Shane, and it does indeed retain all of the original formatting of the clipboard contents if you have rich text on the clipboard.


Shane later left a second comment, this time with the intent of minimizing the runtime of the script.

Replace this code:

-- Copy selected text to clipboard:
tell application "System Events" to keystroke "c" using {command down}
delay 1 -- Without this, the clipboard may have stale data.

with this code:

set thePasteboard to current application's NSPasteboard's generalPasteboard()
set theCount to thePasteboard's changeCount()
-- Copy selected text to clipboard:
tell application "System Events" to keystroke "c" using {command down}
-- Check for changed clipboard:
repeat 20 times
    if thePasteboard's changeCount() is not theCount then exit repeat
    delay 0.1
end repeat

I tested this new code and I did find that the script was in fact noticeably faster, just by a hair.


Overall, this is not a bad workaround. The contents of the clipboard are preserved and the delay is much less noticeable if you employ the code provided in Shane's second comment.

I tested the solution against a Service created in Automator.app that receives the selected text as the input. The Service and the pure AppleScript solution pretty much took equal amounts of time to complete (i.e, about one second).


If one wants a method to get and replace the selected text without having to touch the clipboard, Michael suggests in his blog post that one can utilize a piece of third-party software entitled LaunchBar. However, this is "cheating," because, at that point, we have moved beyond the scope of my original question, which is strictly concerned about AppleScript.