SCRIPT: Edit Script/Sub-Macro in Selected KM Action

Use Case

  • Need to quickly open the script, or sub-macro, in a KM Script Action (or Execute Macro Action)

How To Use

Select an Action in the KM Editor, THEN Run this Script

  • The KM Action MUST be either an Execute Script or Execute Macro

  • You can run this script from any of the following:

  • IF Action is set to use TEXT, then the text will be opened in the appropriate editor

  • ELSE the Script File Path will be used to open the file
    using the default app, except for JXA scripts,
    which will use Script Editor.

  • IF the Action is ExecuteMacro, the Sub-Macro will be opened in the KM Editor.

  • This script handles these Script Action Types:

    • Execute AppleScript (AS)
    • Execute JavaScriptForAutomation (JXA)
    • Execute JavaScript (JS)
    • Execute ShellScript (SS)

    and also:

    • Execute Macro (Sub-Macro)

Requires

  • KM Ver 8.2+
  • macOS 10.12.5 + (Sierra+)
  • BBEdit 12+

Notes

  1. This script has been highly customized for my Mac, and my preferences
    • You may need to make some adjustments for your environment and/or preferences

Comments

  • Please feel free to reply with any issues, comments, and/or suggestions you have.

AppleScript

property ptyScriptName : "Edit Script/Sub-Macro in Selected KM Action"
property ptyScriptVer : "2.7.1" -- ADD Open New KM Editor Window if Open ExecuteMacro
property ptyScriptDate : "2019-12-20"
property ptyScriptAuthor : "JMichaelTX"
(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Select an Action in the KM Editor, THEN Run this Script ###

  โ€ข IF Action is set to use TEXT, then the text will be opened in the appropriate editor
  โ€ข ELSE the Script File Path will be used to open the file
      using the default app, except for JXA scripts,
      which will use Script Editor.
  * IF the Action is ExecuteMacro, the Sub-Macro will be opened in the KM Editor.
  
  โ€ข This script handles these Script Action Types:
      โ€ข Execute AppleScript (AS)
      โ€ข Execute JavaScriptForAutomation (JXA)
      โ€ข Execute JavaScript (JS)
      โ€ข Execute ShellScript (SS)
      
    and also:
      โ€ข Execute Macro (Sub-Macro)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

property LF : linefeed
property ptySEAppID : "com.apple.ScriptEditor2" -- Script Editor App ID

try --=========== TRY ==============
  
  set subMacroUID to ""
  set scriptPath to ""
  set scriptText to ""
  set useTextBool to false
  
  tell application "Keyboard Maestro"
    set actionList to selection
    set oAction to item 1 in actionList
    
    if class of oAction โ‰  action then
      error "The selected item is NOT an action!"
    end if
    
    tell oAction
      set actXML to xml
      set actionName to name
    end tell -- oAction
  end tell
  
  
  set oDict to my getDict(actXML)
  
  --set keyList to oDict's allKeys() as list
  
  set actionType to my getValueForKey(actionName, oDict, "MacroActionType")
  
  if (actionType = "ExecuteMacro") then
    set subMacroUID to my getValueForKey(actionName, oDict, "MacroUID")
    
  else if (actionType = "SetVariableToText") then
    
    set useTextBool to true
    set scriptText to my getValueForKey(actionName, oDict, "Text")
    
    
  else --- GET SCRIPT ACTION DATA ---
    
    set scriptPath to my getValueForKey(actionName, oDict, "Path")
    set scriptText to my getValueForKey(actionName, oDict, "Text")
    set useTextBool to my getValueForKey(actionName, oDict, "UseText")
    
  end if
  
  set scriptResults to "OK"
  
  if (useTextBool) then
    -----------------------------------
    --  OPEN TEXT IN EDITOR
    -----------------------------------
    
    if (actionType = "ExecuteAppleScript") then
      --- OPEN SCRIPT in DEFAULT SCRIPT EDITOR ---
      set appInfoRec to getDefautltApp("scpt")
      if ((name of appInfoRec) contains "Script Debugger") then
        openScriptInSD(scriptText)
      else
        openScriptInSE(scriptText)
      end if
      
    else if (actionType = "ExecuteJavaScriptForAutomation") then
      --- OPEN SCRIPT in SCRIPT EDITOR ---
      openScriptInSE(scriptText)
      
    else if (actionType = "ExecuteJavaScript") then
      --- OPEN SCRIPT in BBEdit | Atom | VisualStudioCode ---
      openTextInBBEdit(scriptText, "JavaScript")
      
    else if (actionType = "ExecuteShellScript") then
      --- OPEN SCRIPT in BBEdit | Atom | VisualStudioCode ---
      openTextInBBEdit(scriptText, "Unix Shell Script")
      
    else if (actionType = "ExecuteSwift") then
      --- OPEN SCRIPT in BBEdit | Atom | VisualStudioCode ---
      openTextInBBEdit(scriptText, "Unix Shell Script")
      
    else if (actionType = "SetVariableToText") then
      --- OPEN SCRIPT in BBEdit ---
      openTextInBBEdit(scriptText, "")
      
    else ## ???
      set msgStr to "Could not find an Editor for Script Type: " & actionType
      my dispDialog(msgStr, {})
      set scriptResults to "[ERROR]" & LF & msgStr
    end if
    
  else if (scriptPath โ‰  "") then
    -----------------------------------
    --  OPEN FILE
    -----------------------------------
    
    tell application "Keyboard Maestro Engine"
      set scriptPath to process tokens scriptPath
    end tell
    
    if (actionType = "ExecuteJavaScriptForAutomation") then
      my openFileInFinder(scriptPath, ptySEAppID)
    else
      my openFileInFinder(scriptPath, "")
    end if
    
  else if (actionType = "ExecuteMacro") then
    -----------------------------------
    --  OPEN SUB-MACRO TO EDIT
    -----------------------------------
    tell application "Keyboard Maestro" to activate
    
    tell application "Keyboard Maestro Engine"
      
      --- OPEN NEW KM EDITOR WINDOW ---
      do script "12F56BD3-F2DE-4EDF-84D2-41AA8E2E5AB2"
      delay 0.5
      
      tell application "Keyboard Maestro" to editMacro subMacroUID
      
    end tell
    
  end if
  
on error errMsg number errNum --======= ON ERROR =========
  
  if errNum = -128 then ## User Canceled
    set errMsg to "[USER_CANCELED]"
  end if
  
  set scriptResults to "[ERROR]" & return & errMsg & return & return ยฌ
    & "SCRIPT: " & ptyScriptName & "   Ver: " & ptyScriptVer & return ยฌ
    & "Error Number: " & errNum
  
  my dispDialog(scriptResults, {"Cancel"})
  
end try --======== END TRY ==========


return scriptResults


--~~~~~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~~~~

on getDict(pXML)
  
  set theString to current application's NSString's stringWithString:pXML
  
  -- convert string to data
  set stringData to theString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
  
  -- convert plist to mutable dictionary
  set {mutableDict, theError} to current application's NSPropertyListSerialization's propertyListWithData:stringData options:(current application's NSPropertyListMutableContainersAndLeaves) format:(missing value) |error|:(reference)
  
  if mutableDict is missing value then error (theError's localizedDescription() as text)
  
  return mutableDict
  
end getDict

on getValueForKey(pActionName, poDict, pKeyStr)
  
  set LF to linefeed
  
  --- extract Object for Key ---
  set nsValueForKey to poDict's objectForKey:pKeyStr
  
  --- Convert to nsArray to Get Proper AS Class ---
  (*
  FROM:  Shane Stanley (2016-06-13)
  https://lists.apple.com/archives/applescript-users/2016/Jun/msg00080.html
  
  What this does is makes a single-item array containing theResult. 
  When that's converted to an AppleScript list, the item within it 
  will also be converted to an AppleScript item if it can. 
  So this covers all the bridgeable AS classes 
    -- text, numbers, lists, records, whatever [including missing value]
  *)
  set nsValueForKeyArray to current application's NSArray's arrayWithArray:{nsValueForKey}
  set valueForKey to item 1 of (nsValueForKeyArray as list)
  
  if (valueForKey is missing value) then ## ERROR Msg ##
    set valueForKey to ""
    set msgStr to "โฏโฏโฏ ERROR โฎโฎโฎ" & LF & ยฌ
      "'" & pKeyStr & "' key was NOT found in XML for Action: " & LF & pActionName
    set titleStr to "Handler: getValueForKey"
    beep
    my dispDialog(msgStr, {"Cancel"})
  end if
  
  return valueForKey
  
end getValueForKey


on openScriptInSD(pScriptStr)
  
  --- USE CODE FOR Script Debugger 5,6 & 7 ---
  --  This will run the latest version of SD installed on the user's Mac
  
  repeat with i from 8 to 5 by -1
    try
      set sdApp to application id ("com.latenightsw.ScriptDebugger" & i)
      exit repeat
    end try
  end repeat
  using terms from application "Script Debugger 7"
    tell sdApp
      
      activate
      set oDoc to make new document with properties {source text:pScriptStr, debugger enabled:false}
      tell oDoc to compile
      
    end tell -- sdApp
  end using terms from -- application "Script Debugger"
  
end openScriptInSD

on openScriptInSE(pScriptStr)
  tell application "Script Editor"
    activate
    
    set oDocSE to make new document with properties {text:pScriptStr}
    tell oDocSE to compile
  end tell
end openScriptInSE

on openFileInFinder(pPosixPath, pOpenWithAppID)
  set pPosixPath to expandPath(pPosixPath)
  
  tell application "Finder"
    if ((pOpenWithAppID โ‰  "") and (pOpenWithAppID is not missing value)) then
      open (pPosixPath as POSIX file) using application file id pOpenWithAppID
    else
      open (pPosixPath as POSIX file)
    end if
  end tell
end openFileInFinder

on openTextInBBEdit(pString, pLangStr)
  tell application "BBEdit"
    activate
    set oDoc to make new text document with properties {contents:pString, source language:pLangStr}
  end tell
end openTextInBBEdit

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on expandPath(pPosixPath) -- @Path @Posix @ASObjC
  (*  VER: 2.0    2017-03-02
---------------------------------------------------------------------------------
  PURPOSE:  Expand Tilde in Path to return full Path
  PARAMETERS:
    โ€ข pPOSIXPath    | text  | Path that may contain tilde (~)
  RETURNS:  text โˆฃ Full Path
  AUTHOR:  JMichaelTX
  REF:
    1. Based on script from @ccstone (Chris Stone)

โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”
*)
  ## Requires:  use framework "Foundation"
  
  set fullPath to (current application's NSString's stringWithString:pPosixPath)'s stringByExpandingTildeInPath
  return fullPath as text
  
end expandPath
--~~~~~~~~~~~~~~~ END OF handler expandPath ~~~~~~~~~~~~~~~~~~~~~~~~~


--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on getDefautltApp(pFileExt)
  (*  VER: 2.0    2018-03-21
  ---------------------------------------------------------------------------------
    PURPOSE:  Get Info about the Default App for a Given File Extension
    PARAMETERS:
      โ€ข pFileExt      โ•‘ text      โ•‘ File extension like "scpt"
    RETURNS:          โ•‘ record  โ•‘ Info about the Default App
                                        name:    Name of App
                                        id:       ID of App (for use in tell blocks)
                                        url:     URL to App file
    AUTHOR:  JMichaelTX based on scripts by @ShaneStanley & @JonasWhale
    REF:
      1. 2017-11-26, ShaneStanley
          How Do I get the Default App?
          http://forum.latenightsw.com/t/how-do-i-get-the-default-app/830/2
  โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”
  *)
  
  set thePath to current application's NSTemporaryDirectory()'s stringByAppendingString:("temp." & pFileExt)
  current application's NSFileManager's defaultManager()'s createFileAtPath:thePath |contents|:(missing value) attributes:(missing value)
  set theWorkspace to current application's NSWorkspace's sharedWorkspace()
  set defaultAppURL to theWorkspace's URLForApplicationToOpenURL:(current application's |NSURL|'s fileURLWithPath:thePath)
  if defaultAppURL = missing value then return missing value -- or false
  
  set appInfo to ยฌ
    {name:defaultAppURL's URLByDeletingPathExtension()'s lastPathComponent() as text, id:(current application's NSBundle's bundleWithURL:defaultAppURL)'s bundleIdentifier() as text, URL:defaultAppURL as ยซclass furlยป} ยฌ
      
  return appInfo
  
end getDefautltApp
--~~~~~~~~~~~~~~~ END OF handler getDefaultApp ~~~~~~~~~~~~~~~~~~~~~~~~~

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on dispDialog(pMsgStr, pButtonsList)
  (*  VER: 2.0    2018-03-23
---------------------------------------------------------------------------------
  PURPOSE:  Simplify Calling of display dialog Command
  PARAMETERS:
    โ€ข pMsgStr          | text  |             Text of Message
    โ€ข pButtonsList    | list or text  |    Text list of Buttons, last will be default  
                                              IF empty, Then {"Cancel", "OK"}
  RETURNS:             | text |  Button that was selected
  AUTHOR:  JMichaelTX
โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”โ€”
*)
  
  try
    set currentApp to ptyCurrentApp
  on error
    set currentApp to path to frontmost application as text
  end try
  
  
  --- SET DIALOG TITLE ---
  
  try
    set titleStr to ptyScriptName
    if (titleStr = "") then set titleStr to (name of me)
  on error
    set titleStr to (name of me)
  end try
  
  --- SET DIALOG BUTTONS ---
  
  if (class of pButtonsList โ‰  list) then
    set oldTID to AppleScript's text item delimiters
    set AppleScript's text item delimiters to {",", ", "}
    set pButtonsList to text items of pButtonsList
    set AppleScript's text item delimiters to oldTID
  end if
  
  if ((pButtonsList = {}) or (pButtonsList = "") or (pButtonsList = missing value)) then
    set pButtonsList to {"Cancel", "OK"}
  end if
  
  set defaultButton to item -1 of pButtonsList
  
  -----------------------------------
  --  DISPLAY DIALOG --
  -----------------------------------
  
  tell application currentApp
    set oAns to display dialog pMsgStr with title titleStr ยฌ
      buttons pButtonsList default button defaultButton with icon caution
  end tell
  
  return (button returned of oAns)
  
end dispDialog
--~~~~~~~~~~~~~~~ END OF handler dispDialog ~~~~~~~~~~~~~~~~~~~~~~~~~

3 Likes