I like to have application dictionaries open as a reference when I frustrate myself in AppleScript. A time-honoured tradition. The problem now is that I have backup drives, render drives, other drives…all on my Mac Pro box. I’m thinking it’s the culprit in that Script Editor takes several minutes to display the Open Dictionaries… dialog box, then it takes several more to scroll to the application I want, then choose the selected dictionary.
In the time it takes to open the desired dictionary, I could have completed the script’s purpose manually.
How can I fix this? Can I restrict the scope of the dictionaries to my startup volume only?
EDITED: It seems it was polling for scriptable applications through my LAN to my SAN, which would probably take hours to compile items. That’s not good.
Cheers
Model: Mac Pro
Browser: Safari 603.3.8
Operating System: Mac OS X (10.11.6)
I’ve been using this for years. It only offers the dictionaries of scriptable apps which are already open. It could probably do with a rewrite.
main()
on main()
tell application "Finder" to set sysv to (system attribute "sysv")
if (sysv < 4160) then abort("This version of the script only supports Mac OS X 10.4.0 or later.")
-- Nobbled always to open dictionaries in Script Editor, as Script Debugger’s dictionary viewer’s pretty awful and ASObjC Explorer 4 doesn’t have one.
set editorName to "Script Editor" --getEditorName()
openDictionaries(editorName)
end main
on getEditorName()
-- Check that either Script Editor, Smile, or Script Debugger is running.
tell application "System Events" to set {editorNames, editorFrontmosts} to {name, frontmost} of (every application process whose creator type is "ToyS" or creator type is "VIZF" or creator type is "asDB")
set editorCount to (count editorNames)
if (editorCount is 0) then
my abort("Please open (Apple)Script Editor, Smile, or Script Debugger before using this script.")
else if (editorCount is 1) then
-- One of the editors is running.
set editorName to beginning of editorNames
else if (editorFrontmosts contains true) then
-- More than one of the editors is running. If one is the frontmost app, use that.
repeat with i from 1 to editorCount
if (item i of editorFrontmosts) then
set editorName to item i of editorNames
exit repeat
end if
end repeat
else
-- Otherwise consult the user for which editor to use.
tell application (path to frontmost application as Unicode text) to set theChoice to (choose from list editorNames with prompt "In which editor do you want to view the dictionaries?" with title "Dictionaries - Running Apps")
if (theChoice is false) then error number -128
set editorName to beginning of theChoice
end if
return editorName
end getEditorName
on abort(msg)
tell application (path to frontmost application as Unicode text) to display dialog msg buttons {"Cancel"} default button 1 with icon stop cancel button 1 with title "Dictionaries - Running Apps"
end abort
on openDictionaries(editorName)
set specialNames to {"Finder"}
tell application "System Events" to tell (application processes whose has scripting terminology is true and name is not "Image Capture Scripting") to set {procNames, procFiles} to {its name, its file}
specialSort(procNames, procFiles, specialNames)
tell application (path to frontmost application as Unicode text) to set theChoice to (choose from list procNames default items specialNames with prompt "Which dictionaries do you want to open?" with title "Dictionaries - Running Apps" with multiple selections allowed)
if (theChoice is false) then error number -128
set theFiles to {}
repeat with i from 1 to (count procNames)
if (item i of procNames is in theChoice) then set end of theFiles to item i of procFiles as alias -- Explicit coercion for OS 10.10, where the 'files' we've obtained are System Events references.
end repeat
tell application editorName
activate
open theFiles
end tell
end openDictionaries
on specialSort(procNames, procFiles, specialNames)
-- This script customises the sort to group any "special" application names before the others. It does this by judging non-special names to have higher comparison values.
script specialsFirst
property specials : specialNames
on isGreater(a, b)
set |b's special| to (b is in specials)
if ((|b's special|) = (a is in specials)) then
(a > b)
else
(|b's special|)
end if
end isGreater
end script
-- This script duplicates in procFiles moves performed in procNames by the custom insertion sort.
script parallel
property lst : procFiles
on shift(a, b)
tell item b of my lst
repeat with i from b - 1 to a by -1
set item (i + 1) of my lst to item i of my lst
end repeat
set item a of my lst to it
end tell
end shift
end script
CustomInsertionSort(procNames, 1, -1, {comparer:specialsFirst, slave:parallel})
end specialSort
-- Custom insertion sort from A Dose of Sorts by Nigel Garvey.
on CustomInsertionSort(theList, l, r, customiser)
script o
property comparer : me
property slave : me
property lst : theList
on isrt(l, r)
set u to item l of o's lst
repeat with j from (l + 1) to r
set v to item j of o's lst
if (comparer's isGreater(u, v)) then
set here to l
set item j of o's lst to u
repeat with i from (j - 2) to l by -1
tell item i of o's lst
if (comparer's isGreater(it, v)) then
set item (i + 1) of o's lst to it
else
set here to i + 1
exit repeat
end if
end tell
end repeat
set item here of o's lst to v
slave's shift(here, j)
else
set u to v
end if
end repeat
end isrt
-- Default comparison and slave handlers to customise for an ordinary sort.
on isGreater(a, b)
(a > b)
end isGreater
on shift(a, b)
end shift
end script
-- Process the input parmeters.
set listLen to (count theList)
if (listLen > 1) then
-- Negative and/or transposed range indices.
if (l < 0) then set l to listLen + l + 1
if (r < 0) then set r to listLen + r + 1
if (l > r) then set {l, r} to {r, l}
-- Supplied or default customisation scripts.
if (customiser's class is record) then set {comparer:o's comparer, slave:o's slave} to (customiser & {comparer:o, slave:o})
-- Do the sort.
o's isrt(l, r)
end if
return -- nothing.
end CustomInsertionSort
I realized there is an actual window for these dictionaries, and I can drop the app onto the Script Editor dock icon and the dictionary comes up. Works great with Launchbar as well.
Here it is rewritten using ASObjC. It doesn’t seem to be any more effective in this form than the original, but it is more — er — modern.
main()
on main()
script ASObjC
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions
property specialNames : {"Finder"} -- Any localised application names that should appear already selected at the top of the 'choose from list' dialog.
property |⌘| : missing value
property workspace : missing value
property runningApps : missing value
on main()
set |⌘| to current application
set workspace to |⌘|'s class "NSWorkspace"'s sharedWorkspace()
set runningApps to workspace's runningApplications()
-- Nobbled always to open dictionaries in Script Editor, as Script Debugger’s dictionary viewer’s pretty awful (and has its own "running applications" selector anyway) and ASObjC Explorer 4 doesn’t have one.
set theEditor to getScriptEditor() -- getEditor()
openDictionaries(theEditor)
end main
(* Open Script Editor and return an NSRunningApplication object for it. *)
on getScriptEditor()
set SEURL to workspace's URLForApplicationWithBundleIdentifier:("com.apple.ScriptEditor2")
return workspace's launchApplicationAtURL:(SEURL) options:(0) configuration:(|⌘|'s class "NSDictionary"'s new()) |error|:(missing value)
end getScriptEditor
(* Return an NSRunningApplication object for the most likely running editor. *)
on getEditor()
-- Check that either Script Editor or Script Debugger is running. Smile's bundle identifier can be added to the array if known.
set editorIDPredicate to |⌘|'s class "NSPredicate"'s predicateWithFormat:("bundleIdentifier IN %@") argumentArray:({{"com.apple.ScriptEditor2", "com.latenightsw.ScriptDebugger6"}})
set runningEditors to runningApps's filteredArrayUsingPredicate:(editorIDPredicate)
set editorCount to runningEditors's |count|()
if (editorCount is 0) then
abort("Please open AppleScript Editor or Script Debugger before using this script.")
else if (editorCount is 1) then
-- One of the editors is running.
set theEditor to runningEditors's firstObject()
else
-- More than one of the editors is running. If one currently owns the Menu Bar owner, use it.
set menuBarOwner to workspace's menuBarOwningApplication()
if (runningEditors's containsObject:(menuBarOwner)) then
set theEditor to menuBarOwner
else
-- Otherwise consult the user for which editor to use.
set editorNames to runningEditors's valueForKey:("localizedName")
tell application (path to frontmost application as text) to set theChoice to (choose from list (editorNames as list) with prompt "In which editor do you want to view the dictionaries?" with title "Dictionaries - Running Apps")
if (theChoice is false) then error number -128
set chosenEditorPredicate to |⌘|'s class "NSPredicate"'s predicateWithFormat:("localizedName == %@") argumentArray:(theChoice)
set theEditor to (runningEditors's filteredArrayUsingPredicate:(chosenEditorPredicate))'s firstObject()
end if
end if
return theEditor
end getEditor
on abort(msg)
tell application (path to frontmost application as text) to display dialog msg buttons {"Cancel"} default button 1 with icon stop cancel button 1 with title "Dictionaries - Running Apps"
end abort
(* Choose and open the required dictionaries. *)
on openDictionaries(theEditor)
set runningAppBundleURLs to runningApps's valueForKey:("bundleURL")
set runningAppNames to runningApps's valueForKey:("localizedName")
-- Preset some stuff for identifying scriptable applications.
set isScriptableKey to |⌘|'s NSURLApplicationIsScriptableKey
set isScriptableKeyArray to |⌘|'s class "NSArray"'s arrayWithObject:(isScriptableKey)
set isScriptableResult to |⌘|'s class "NSDictionary"'s dictionaryWithObject:(true) forKey:(isScriptableKey)
set specialNames to |⌘|'s class "NSArray"'s arrayWithArray:(specialNames)
-- Initialise separate arrays in which to collect the names of any "special" scriptable applications and of any others.
set scriptableAppNames to |⌘|'s class "NSMutableArray"'s new()
set unspecialScriptableAppNames to |⌘|'s class "NSMutableArray"'s new()
-- Work through the bundle URLs in turn. If any has a resource value indicating scriptability, add the corresponding localised name to the appropriate array.
repeat with i from 1 to (count runningAppBundleURLs)
set thisURL to item i of runningAppBundleURLs
if (((thisURL's resourceValuesForKeys:(isScriptableKeyArray) |error|:(missing value))'s isEqualToDictionary:(isScriptableResult)) as boolean) then
set thisAppName to item i of runningAppNames
if ((specialNames's containsObject:(thisAppName)) as boolean) then
tell scriptableAppNames to addObject:(thisAppName)
else
tell unspecialScriptableAppNames to addObject:(thisAppName)
end if
end if
end repeat
-- Sort the two collections of names separately, then concatenate them together with the "special" ones in front.
tell scriptableAppNames to sortUsingSelector:("compare:")
tell unspecialScriptableAppNames to sortUsingSelector:("compare:")
tell scriptableAppNames to addObjectsFromArray:(unspecialScriptableAppNames)
-- Ask the user to choose from the found names.
tell application (path to frontmost application as text) to set theChoice to (choose from list (scriptableAppNames as list) default items (specialNames as list) with prompt "Which dictionaries do you want to open?" with title "Dictionaries - Running Apps" with multiple selections allowed)
if (theChoice is false) then error number -128
set theChoice to |⌘|'s class "NSArray"'s arrayWithArray:(theChoice)
-- Open the application files corresponding to the chosen names using the editor passed to this handler.
set editorPath to theEditor's bundleURL()'s |path|()
repeat with thisName in theChoice
set i to (runningAppNames's indexOfObject:(thisName))
set chosenAppPath to (runningAppBundleURLs's objectAtIndex:(i))'s |path|()
tell workspace to openFile:(chosenAppPath) withApplication:(editorPath)
end repeat
end openDictionaries
end script
ASObjC's main()
end main