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.
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).
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
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.
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.
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.
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.
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.
Is this part of the standard suite so all applications support it? Specifically, Firefox? I know Firefox AS support is very limited, hence my question. I just tried it with Firefox and got an error:
error "Firefox got an error: Can’t get selection." number -1728 from selection
No, that was specific to Script Editor, which has its own suite which includes a selection class.
As you suggest, Firefox has minimal applescript support. All you can do with it —that I’m aware of— is go the UI scripting route, ie menu or keyboard. A -1728 error generally means that something doesn’t exist, which is the case for the selection.
FWIW, while I haven’t used Chrome, the chrome-based browsers, such as Opera and Vivaldi, each have a copy selection command (and also cut selection and paste selection).