Getting the selected text

I’m trying to make a little script that can count the characters in the current text selection, in the frontmost app.

So within the script editor, if I select a word of this scripts own code, then run the command:

selection

the result I get is:

*characters* 14 **thru** 24 **of** *document* "Selected Text Length.scpt"

But that’s it, I don’t get the actual STRING that is selected!
Now if I copy that result and paste it back into my code in place of “selection”, then I get a list of characters of the selected string. Which would be perfect, because all Im’ doing is counting characters anyway.

So how do I get from the there to there? How do I execute the result returned from “selection”?

Note that I’m just wrapping this selection command inside a simple “tell current application” handler.

contents of selection

If you have only the following line in a script, select the last word and then run it…

properties of selection

You should see the following result:

{class:selection-object, character range:{15, 23}, contents:"selection"}

And to get the count of characters in the selection, obviously you could calculate it from the character range property but you can also get it like so:

set s to contents of selection as text
length of s

Of course, each application will handle selections in its own way (or not at all).

2 Likes

AHHHHH! I tried every possible phrasing I could think of but could not come up with the magic word to get the CONTENTS of the selection! Thanks!

This is odd. So my script works perfectly from inside script editor, but it doesn’t work at all from the script menu. There’s a “say” and a “display dialog” (say is just for debugging) but neither happen. No error messages either. Just silent failure. Is there some way to debug this? An AS error console or something?

tell current application
	set preview_length to 128
	
	set input_text to contents of selection
	
	if length of input_text > preview_length then
		set string_preview to (characters 1 thru preview_length of input_text) & "…"
	else
		set string_preview to input_text
	end if
	
	say (length of input_text) using "Junior"
	display dialog "Selected string is " & (length of input_text) & " characters.
	
“" & string_preview & "”" buttons {" OK "} default button 1 with icon note
	length of input_text
end tell

Ewww I got a little more info by putting everything in a ‘try’ loop.

Apparently when a script is running from the menu, it cannot access the current selection. Error -1728

Is there a way around this?

Ok getting a little closer. When a script is run form the script menu, the frontmost application becomes “osascript”

Is there a way to get the frontmost GUI application? Or the 2nd frontmost application? In the GUI, the frontmost app doesn’t change when the script is running. But that would explain why my script can’t get the selection in the “current application”

l008com. Some apps are scriptable and some are not. Some scriptable apps can directly get selected text and some cannot. So, a script that works with most apps on your computer is probably not possible.

The following will work with many apps, but it uses GUI scripting, which is generally best avoided. I successfully tested the script on my Sonoma computer, and I ran it by way of the Script Menu.

set characterCount to getCharacterCount()
display dialog "The character count is " & characterCount buttons {"OK"} default button 1

on getCharacterCount()
	set the clipboard to ""
	tell application "System Events"
		set activeApp to name of first process whose frontmost is true
		tell process activeApp
			set frontmost to true
			click menu item "Copy" of menu "Edit" of menu bar 1 -- edit if required to localize
		end tell
	end tell
	repeat 5 times
		delay 0.1
		set clipboardContents to (the clipboard)
		if clipboardContents is not "" then exit repeat
	end repeat
	return (count (characters of clipboardContents))
end getCharacterCount

If the frontmost app is scriptable and is able to directly get selected text, it might be best to tailor the script to that app. For example:

tell application "Script Editor"
	activate
	set theText to contents of selection
end tell

set characterCount to count (characters of theText)
display dialog "There are " & characterCount & " characters in the selection" buttons {"OK"}

Thing is, I’m specifically trying to have this not touch the clipboard at all.

The code i have may be well supported enough. The thing I’m hung up on now is that when a script runs from the script menu, the “current application” is “osascript” not the program I was in when I initiated the script from the script menu.

Is there a way to determine and send commands to the current GUI application I’m in from a script running in the script menu? Then I can see how well suppoed my current code is, and that may be good enough.

The first tell statement of the following will do what you want. The second tell statement is just to demonstrate that the first one works. Technically, the first tell statement returns process name, but it’s rare that process name and app name are different.

tell application "System Events" -- get process name of frontmost app
	set activeApp to name of first process whose frontmost is true
end tell

tell application activeApp -- this is for testing purposes only
	quit
end tell

BTW, current application is defined in the AppleScript Language Guide as shown below. When you run a script by way of the Script Menu, current application is osascript.

The current application constant refers to the application that is executing the current AppleScript script (for example, Script Editor).

Here is a solution that will work with any application that has a “copy” menu item.
It uses the clipboard and then restores it in all cases (even if an error occurs).
You may need to change the delay value, depending on how fast your computer is.

try
	set textString to "1z4*5eiur_45r|uyt}r4"
	set oldClip to the clipboard
	set the clipboard to textString
	tell application "System Events" to set theID to bundle identifier of application process 1 whose frontmost = true
	tell application id theID to activate
	tell application "System Events" to keystroke "c" using command down
	delay 0.4
	set theString to the clipboard as string
	if theString = textString then error
	set theCount to count characters of theString
	set the clipboard to oldClip
	display dialog "Character count: " & theCount
on error
	set the clipboard to oldClip
end try

FWIW, I wrote a version of the OP’s script that works on my Sonoma computer when run by way of the Script Menu. This will work with many but not all apps.

set previewLength to 128
set {theSelection, selectionCount} to getSelection()
if selectionCount > previewLength then
	set stringPreview to (text 1 thru previewLength of theSelection) & "…"
else
	set stringPreview to theSelection
end if
display alert "The selected string contains " & selectionCount & " characters." message stringPreview

on getSelection()
	set the clipboard to ""
	tell application "System Events"
		set activeApp to name of first process whose frontmost is true
		tell process activeApp
			set frontmost to true
			click menu item "Copy" of menu "Edit" of menu bar 1 -- edit if required to localize
		end tell
	end tell
	repeat 5 times
		delay 0.2
		set clipboardContents to (the clipboard)
		if clipboardContents is not "" then exit repeat
	end repeat
	set characterCount to (count (characters of clipboardContents))
	return {clipboardContents, characterCount}
end getSelection

I found this script that counts words and characters in Textedit.

tell application “TextEdit”
set wc to count words of document 1
set cc to count characters of document 1
if wc is equal to 1 then
set txt to " word and "
else
set txt to " words and "
end if
if cc is equal to 1 then
set txtcount to " character."
else
set txtcount to " characters."
end if
set result to (wc as string) & txt & (cc as string) & txtcount
display dialog result with title “Word and character count” buttons {“OK”} default button “OK”
end tell

Are there any tricks to getting this to work in Safari? (I’ve never gotten selections to work properly in Safari, but maybe this UI access method will work.)

Or, even better, does Safari have a way to get the selection directly via AppleScript? (All things considered, not touching the clipboard seems like a positive thing.)

Or, even better, I don’t suppose there’s a reference list of applications that allow direct access to the selection?

Yes, I can look in an app dictionary, but that’s not always easy or quick to parse, especially when the app does something in a surprising way. And it might be nice to have a reference that shows the specific syntax to use.

Getting the selection seems like something semi-universal. Though what “the selection” is can be pretty different between apps, or even different parts of one app’s UI. It would be great to see how to get different types of selections, for example, in the Contacts app you might have a few contacts selected, or you might have text in a contact field selected, and those would be quite different to work with.

Unfortunately, it’s not as easy as it might be and it’s absolutely not universal. As you suggest, the problem is that each app might have something different to select — messages in Mail, contacts in Contacts, events in Calendar, etc…

Safari deals with it by not offering any form of selection so as far as I know, you would have to depend on using the clipboard, which would look something like this:

tell application "Safari"
	tell application "System Events" to tell application process "Safari"
		key code 8 using command down
	end tell
end tell

Key code 8 refers to the ‘c’ key on a US English keyboard (and presumably others as well).

I’m not aware of any such list so you would have to peruse the dictionary although it’s easy enough to search for the term once the dictionary is open.

I guess you could also just run a script with the would selection inside a tell block for the app in question. It should generate a can’t get selection error. Dunno how reliable it would be though.

Have you tried something like this?

tell application "Safari"
	
	tell window 1 to ¬
		set curSel to do JavaScript ¬
			"document.getSelection().toString()" in current tab
	set charCount to count of characters in curSel
	
end tell

It could probably use some error checking and such, but as a basic idea it should be a good start.

2 Likes

You could use DEVONtechnologies’ free WordService. It adds new options to the contextual menu, one of them is Statistics which will show the selected text’s word count.

There’s also blog post How to Use WordService

I was curious and the following is a short list of apps; their selection command or property; and what they return:

THE APP - SELECTION COMMAND/PROPERTY - RETURNS
Calendar - none - N/A
Contacts - selection - person id of selected contacts
Finder - selection - a file specifier
Google Chrome - copy selection - places text on clipboard
Mail - selection - the selected message id
Notes - selection - the selected note id
Pages - selection - the document id
Photos - selection - the selected media id
Preview - none - N/A
Safari - none - N/A
Script Debugger - selection - selected text
Script Editor - selection - selected text
Terminal - none - N/A
TextEdit - none - N/A

BTW, roosterboys suggestion works great and seems a simple way to get selected text in Safari without involving the clipboard.

1 Like

I have tried so. many. different JavaScript techniques in Safari, and never got any of them to work. But the one from @roosterboy looks like it might be different, so I’ll give that a whirl.

In the meantime, based on various posts here, this is what I’ve come up with as a general technique — which does work in Safari, so this might be where I stop:

on getSelection()
	try
		-- Preserve clipboard, detect delay in receiving clipboard contents
		set clipboardNotSetString to "NOT_THE_SELECTION"
		set oldClipboardContents to the clipboard
		set the clipboard to clipboardNotSetString
		
		-- Use UI Scripting to select Edit > Copy from the menubar
		tell application "System Events"
			set activeApp to name of first process whose frontmost is true
			tell process activeApp
				set frontmost to true
				click menu item "Copy" of menu "Edit" of menu bar 1 -- edit if required to localize
			end tell
		end tell
		
		-- Get the contents of the clipboard; sometimes there's a delay
		set clipboardContents to ""
		repeat 5 times
			delay 0.2
			set clipboardContents to (the clipboard)
			if clipboardContents is not clipboardNotSetString then exit repeat
		end repeat
		if clipboardContents is clipboardNotSetString then error
		
		set the clipboard to oldClipboardContents
		return clipboardContents
	on error
		set the clipboard to oldClipboardContents
	end try
end getSelection

My use for getting the selection in Safari is (almost?) always text, and I’ve not tested this in contexts where the selection might be non-text. So I’m not sure if there are assumptions about the selection’s data type embedded in the code. YMMV.