Demo: Scripting nested actions

My goodness, this is getting interesting!

Again, thank you for giving the macro a try and your kind words.

I am delighted that what grew out of a discussion about looking up a single type of nested action has turned into something of use.

At a glance it appears that way to me too. I will give a look see for sure!

Just to be sure though, it sounds like you want to be able to have a sort of dynamic color scheme that you can toggle by enabling and disabling actions?

In the meantime, how about creating a separate macro for the sub-color schemes?
It seems it wouldn't take much to turn the "Color Code Actions" macro into a subroutine, with a parameter for the settings variable "localColorSchemeMacroName". (I had some sort of vague imagining that it could be used this way.)

Or maybe pass the parameter in an Execute Macro that calls the Color Code Actions macro?

set localColorSchemeMacroName to triggervalue

In either case, how does having a separate "color code manager" macro run your color coding system sound?

Or is a single "Action Color Scheme" that contains everything better?
The script could look for engrouped actions by name, or notes, enabled status, perhaps, but then again, maybe a separate macro...?

Sharp breath intake. :wink:. I it might be possible.

This script uses the handler by Kevin Funderburg to flatten the list of the target macro.

As such, the actions all come out as top level actions. Maybe, just maybe, there might be a way of using node indexing as a way querying level. That means, I think, having three versions of the actions list: list of AppleScript actions, array of dictionaries (aka, deserialized action xml, aka plist object), action nodes. Query the plist object with predicates (or nodes with xpath, maybe), get the index, check the level using NSXMLElement's "level" property. Set some rules, somehow. I think.

That would be an undertaking for a rainy day, maybe a monsoon month. I'm not sure it's in the forecast.

Then there is an alternative native NSXMLDocument method of querying and generating AppleScript paths, which doesn't require AppleScript actions at all. That would eliminate the triple version indexing, but...yikes, might not be any better. Given my druthers, I'd rather navigate by boolean predicates that XPathing around.

BTW, out of curiosity, if you know, how is the "turn Edit mode off" during color editing impacting usability on macros with large numbers of actions?

My pre-Cambrian setup sez it helps, but not so much when macro's actions get over 200.

Uh-oh, here comes my best Groucho Marx voice, "I've been called a lot of things, but mastermind ain't one of 'em." :disguised_face: Sorry...:grimacing:

But while we're disclosing AI aided efforts, I too should come clean and say with chagrin that I've run brainstorming by Chat and the script is not entirely free-range, organic stuff. :grimacing:

(I also overburdened my Electric Monk handling some of its more amazing notions, and am now looking for a replacement cognitive dissonance dampener...:exploding_head:)

Electric Monk

BBC Radio 4 - Comedy - Dirk Gently's Holistic Detective Agency - The Electric Monk

You are so welcome! Thank you, really, for taking the time to go through not only the AppleScriptObjC verbosity, but the human verbosity as well.

1 Like

Sorry for the run-around. It really is pretty simple.

I haven't tested but only a few times, but I think this ought to filter out disabled actions.


set nodes to doc's nodesForXPath:"//dict[key='ActionUID']" |error|:(missing value)
set flatRulesDicts to nodes's valueForKeyPath:"XMLString.propertyList"

-- ⬇️ ADD THIS LINE ONE LINE ⬇️
set flatRulesDicts to flatRulesDicts's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"self['IsActive']==nil") 

set colorByKindDicts to current application's NSMutableArray's array()

I'm thinking more about the nesting suggestion.

Is this something that you want all actions to be level aware? I'm thinking if there were only a few container type actions we might be able to deal with those in a separate macro. It would get the nodes, throw them into a dictionary with a level key, sort on key, and then color them alternately,building resolve actions as we go. It might have to update all container type action for each update though. Need to think more about it.

1 Like

@CRLF, thanks for the reply.

Great!. I've give that a try tomorrow.

For me, level aware would only be important for those actions that can include other actions (thus they contribute to action nesting). That includes:

If you right-click an action (or actions) and select Engroup, the above are included.

Notice that I did not include the Group action. That was intentional. For me, I either assign no color or purple to Groups.

Here's a contrived example.

Download: Alternating Actions Color.kmmacros (8.2 KB)

Macro-Image


If you open the example in the editor and set the window wide (e.g., full screen), I think you'll see the advantage of alternating yellow and green. At least for me, it makes it easier to follow the logic. It's not shown in the example, but this color alternation is particularly helpful if a group of embedded actions is large (e.g., if a particular condition of a Switch/Case included 20+ actions).

The example helped. Thank you.

A proof of concept script that restores color to the example is shaping up.

Still have no idea how it will perform in the wild.

2 Likes

Wow, thanks for your efforts, @CRLF. I hesitated to even ask because I thought that might be a bridge too far.

1 Like

Here's the proof-of-concept macro for color-coding nested actions with alternating colors.

If--big IF--this script works beyond the example macro you provided, color coding macro actions could be done in two passes. Once by kind,thanks @skillet , and once according to odd/even levels of container type actions, thanks @_jims .

USAGE:

1.	Enable the macro group"Color Code Nesting" and macro "Show Nesting with Color".

2.	Assign the latter a trigger of your choice.

3.	Select the sample macro named, "Color me" (the example macro's actions x2 with colors removed).

4.	Optional. Disable Edit Mode for better peformance.  

5.	Cross all free fingers. 🤞😉

6. Run the macro. It will immediately begin coloring actions

7. Make sure Edit Mode is enabled. 
Screenshot

The script makes a few changes to an earlier script's recursion handler in the "Color Code Actions" macro that flattens nested actions.

AppleScript 🪆

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use framework "Foundation"
property author : "@CRLF"


--┏━━━━━━━━━━━━━━━━━━━━━━━
--┃ PURPOSE: To color-code nested actions
--┃  with alternating colors
--┗━━━━━━━━━━━━━━━━━━━━━━━

--⚙️⚙️ GET KM INPUT VARIABLES 
set kminstance to system attribute "KMINSTANCE"

tell application id "com.stairways.keyboardmaestro.engine"
	set color1 to getvariable "localFirstColor" instance kminstance
	set color2 to getvariable "localSecondColor" instance kminstance
	set containerTypes to getvariable "localMacroActionTypes" instance kminstance
end tell

if color1 is "" then set color1 to "Green"
if color2 is "" then set color2 to "Yellow"

set {TID, text item delimiters} to {text item delimiters, {linefeed, return}}
if containerTypes ≠ "" then
	set containerTypes to ((current application's NSString's stringWithString:containerTypes)'s stringByTrimmingCharactersInSet:(current application's NSCharacterSet's whitespaceAndNewlineCharacterSet)) as text
	set containerTypes to text items of containerTypes
else
	set containerTypes to {"IfThenElse", "Switch", "Repeat", "While", "Until", "For", "TryCatch"}
end if
set text item delimiters to TID

tell application id "com.stairways.keyboardmaestro.editor"
	set actionList to actions of item 1 of (selected macros as list)
end tell



set annotatedActionRecords to flattenActionsWithLevelAnnotation(actionList, 0, containerTypes)
applyAlternatingColors(annotatedActionRecords, color1, color2)

--====HANDLER================

-- Applies alternating colors to records of container actions in  list
on applyAlternatingColors(annotatedActionRecords, color1, color2)
	tell application id "com.stairways.keyboardmaestro.editor"
		repeat with rec in annotatedActionRecords
			set {aAction, aLevel} to {item 1 of |action| of rec, level of rec}
			if (aLevel mod 2) = 0 then -- even/odd color alternation
				set aColor to color2
			else
				set aColor to color1
			end if
			set color of aAction to aColor
		end repeat
	end tell
end applyAlternatingColors

-- 🐣🐣🐣🐣🐣
-- Recursive handler that walks each root action tree, tracking depth for container types
-- Returns a list of records {action:{action}, level:int} for all container actions
on flattenActionsWithLevelAnnotation(actionList, currentLevel, containerTypes)
	local output, newLevel, colorName, xmlText, plistData, plistObj, plistError, actionType
	set output to {}
	
	tell application id "com.stairways.keyboardmaestro.editor"
		get class of actionList
		
		if (class of actionList = list or class of actionList = action list) and (count of items of actionList) > 0 then
			repeat with act in actionList
				set output to output & my flattenActionsWithLevelAnnotation(act, currentLevel, containerTypes)
			end repeat
			
		else if class of actionList = case entry then
			if (count of actionList's actions) > 0 then
				set output to output & my flattenActionsWithLevelAnnotation(actionList's actions, currentLevel, containerTypes)
			end if
			
		else if class of actionList = action then
			-- Determine type via deserialized action xml
			set plistObj to (current application's NSString's stringWithString:(actionList's xml))'s propertyList()
			if plistObj is not missing value then
				set actionType to (plistObj's objectForKey:"MacroActionType") as text
			else
				set actionType to missing value
			end if
			
			-- Increment depth only for container types
			if containerTypes contains actionType then
				set newLevel to currentLevel + 1
				set output to output & {{|action|:actionList, level:newLevel}}
			else
				set newLevel to currentLevel
			end if
			
			-- Recurse into nested actions
			try
				if (count of actionList's actions) > 0 then
					set output to output & my flattenActionsWithLevelAnnotation(actionList's actions, newLevel, containerTypes)
				end if
			end try
			try
				if (count of actionList's case entries) > 0 then
					set output to output & my flattenActionsWithLevelAnnotation(actionList's case entries, newLevel, containerTypes)
				end if
			end try
			try
				if actionList's thenactions ≠ missing value then
					set output to output & my flattenActionsWithLevelAnnotation(actionList's thenactions's actions, newLevel, containerTypes)
				end if
			end try
			try
				if actionList's elseactions ≠ missing value then
					set output to output & my flattenActionsWithLevelAnnotation(actionList's elseactions's actions, newLevel, containerTypes)
				end if
			end try
			try
				if actionList's tryactions ≠ missing value then
					set output to output & my flattenActionsWithLevelAnnotation(actionList's tryactions's actions, newLevel, containerTypes)
				end if
			end try
			try
				if actionList's catchactions ≠ missing value then
					set output to output & my flattenActionsWithLevelAnnotation(actionList's catchactions's actions, newLevel, containerTypes)
				end if
			end try
		end if
	end tell
	
	return output
end flattenActionsWithLevelAnnotation
Explanation

This version of the handler "annotates" different-level container type actions--excepting group--for each root action tree. To do this, it walks each action root's tree, increments a counter on level change, and sets a "level" property on a record for only "container" type actions. (Type identification is made via the MacroActionType key of each action's deserialized version).

The AppleScript record serves to "annotate" the container action's level change. An "action" property is set to the action. The handler's output is the accumulated list of these records.

A separate handler, inside a KM Editor tell block, loops through these records, to applying colors to actions by an odd/even level numbers.

Note: the output of this and the former script is different. The former script outputs all actions, this one a list of records of container type actions, excepting group.

To me, dealing with with recursion is like stepping on Legos over and over. But watching the KM Editor color code container actions with alternating colors by level difference served as a fun live, er, action :roll_eyes: visualization of the recursive walk and @_jims 's coloring scheme! Cool. :sunglasses:

:eight_spoked_asterisk: Turning off Edit mode before running the script will take the fun out it, but the script will run faster.

Thanks in advance to anyone giving the macro, much less the script, a look-see!

Color Code Nesting Macros.kmmacros (96.0 KB)

2 Likes

Incredible work, @CRLF. I tried this on several of my complex macros and it worked exactly as expected! :clap::clap::clap:

(One trivial point: I start with yellow, not green; but that's just my preference.)

These macros will save me so much time. Thanks for taking on and completing the challenge.

Just another personal preference: I set three of your macros to that same hot key (⌥⇧C). Then when I press it, a Conflict Palette appears.

  • Color Code Actions
  • Show Nesting with Color
  • Remove colors of all actions in selected macro

The Conflict Palette serves two purposes:

  • Reduces the required number of hot keys.
  • Prevents accidental execution of all three macros. (It could be disconcerting if one accidentally triggered one of these macros during a macro editing session.)

BTW, nice macro icon for Show Nesting with Color. :grinning:

2 Likes

Very cool you were able to figure out how to pull this off. Your AppleScripting powers are great!

2 Likes

@_jims,

Thank you for your interest, suggestion and the feedback.

I'm guessing I'm not the only one who really enjoys hearing this sort of thing!

I am pretty surprised too that walking the recursive Lego path led to a working useful macro!

Thank you so much getting the ball rolling on this!

At this point, demos of using a recursion handler to script nested actions now include:

  1. Simple one-to-one querying of action ids (xml key,ActionUID) returned by prompt with list.
  2. Complex queries for user-defined sets of actions (xml key, MacroActionType).
  3. Modificaction of the recursion handler to annotate level changes for a user-chosen set of container-type of actions (xml key, MacroActionType).

The practical purpose of these demos has been to allow setting by script the color property of actions at any level of a selected macro.

There are still more properties on the Editor script action than "color". I am encouraged that calling them on nested actions is slightly more within reach.

I'm going to play a little catch up with cleaning up some of the scripts, making the recursive handler a little more succinct, looking into speed issues, integrating, if possible, the nested action coloring script into the Color Code macro and adding an ignore disabled actions option for the Color Scheme Actions.

I wish the scripts were a bit more legible too, but the basic techniques of flattening and querying seem pretty reusable.

Ask if you run into snags using them. :slightly_smiling_face:

2 Likes

Thank you for all your work and improvement you have done with these macros. You of course have opened doors to do many other things with actions and nested actions.

2 Likes