10

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.

9
  • Could you clarify what you're trying to do by providing some practical examples. Is the selected text editable, yes, no, both? Feb 1, 2017 at 3:36
  • 1
    @user3439894 The selected text will necessarily be editable. I would like the code to take the selected text, perform some operation on it, and then replace the selected text. An example would be converting the letter case of the selected text to title case. (I already have the code that converts case, so I don't need help with that. And keystroking the new text is simple enough. I just don't know how to get the selection into AppleScript, outside of the clipboard.) Feb 1, 2017 at 4:00
  • Sorry, but that doesn't really clarify things. Hopefully someone else gets it and can offer some help. Feb 1, 2017 at 4:02
  • @user3439894 Can you explain what is unclear? Suppose that the selected text is: "This is a sentence made up of words." I want my AppleScript to replace the selected text with a new string: "This Is A Sentence Made Up Of Words." The selected text will always be editable. The AppleScript will be triggered by FastScripts, so I cannot use Automator's "Service receives selected text" feature. Feb 1, 2017 at 4:11
  • I have never used FastScripts before, so I'll have to look at it tomorrow. Feb 1, 2017 at 4:42

1 Answer 1

8

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.

1
  • 1
    I am implementing a similar feature like LaunchBar's Instant Send. I wonder how they achieve it. As far as I am concerned, Instant Send needs Accessibility permission to work and it does also imitate Copy command. Because when I change some application's copy command shortcut, it stopped working. Dec 24, 2020 at 10:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .