User input+backspace?

When I use the Prompt For User Input action, typing the delete key deletes the entire input string instead of deleting one letter. Is there any way to change this? Would I have to use a custom HTML input interface to be able to delete just a single character at a time?

Only if the whole of the input string is selected. Otherwise the delete key deletes one letter at a time. Or am I missing something?

My guess is that they're using a default initial search string because the prompt populates with it fully selected. I looked but didn't see any obvious way to change this behaviour and tried adding a keystroke action to send a right arrow press even though I was sure it wouldn't work (it didn't). This might be (at least partly) why I never use default initial search strings.

2 Likes

You could trigger the right arrow key as an asynchronous submacro. In this example, it works without the included pause, but it might be necessary on older (slower) macs.

User Input → Example.kmmacros (20 KB)

Macro screenshot

Press → Key (async).kmmacros (20 KB)

Macro screenshot

I've often thought it might be useful if Peter were able to add an Asynchronous Group action for cases such as these. The idea is that you could engroup a number of actions that would run asynchronously (i.e. as though they were an independent macro), in line within the caller macro. It's a bit bloaty to add a dedicated macro for single keypresses like this, but it's the only recourse as things stand (as far as I'm aware).

5 Likes

I've had the same thought myself, as I have a number of one or two line async macros that do nothing other than control a box in another macro.

-rob.

4 Likes

That could get very confusing -- the async Group would be a separate instance, without access to local and instance variables for example.

Challenge accepted :wink:

Make your Group (remembering the limitations above). Right-click the Group and "Copy as XML". Paste into your favourite text editor and remove the first four lines, including <array>. Remove the last two lines, </array> and </plist>. You should be left with a <dict> containing your Group of actions.

In your macro add an "Execute an AppleScript" action with:

set theXML to "xml_goes_here"

tell application "Keyboard Maestro Engine"
	do script theXML
end tell

...and paste in your edited XML, replacing xml_goes here.

Set the action to "Asynchronously" and you're done!

2 Likes

Brilliant!

And obviously we're going to want to automate that...

I've got close but I must have made a basic mistake. Can you see what it is?

Make Async Group.kmmacros (54 KB)

Macro screenshot

1 Like

Hello Neil (@noisneil):wave:

the Code inside of the Script Actions is always escaped. So you have to escape everything inside the Script Action that’s related to the created Actions - and leave only the parts unescaped which are necessary to create the other Actions.

Once you’ve understood this principle you can create everything inside a Script Action - even other Script Actions which would be executed. This is to use for synchronous as well as asynchronous execution.

Best wishes and merry Christmas from Germany :de:

Tobias

1 Like

Right yeah. But haven't I done that with the HTML encoding filter?

Ah... I think this :point_up_2:t3: is the issue. There's no such option for the Execute AppleScript action.

It’s in the output options — ignore, set variable, etc. IIRC it’s the bottom option.

1 Like

A thousand apols. I was looking under the gear icon. Still not sure where I've gone wrong... (Actually maybe I do...)

In case you're interested, this is why I was confounded:

New computer; hadn't yet turned off smart quotes! :see_no_evil:

So here it is... Merry Christmas (etc...)!

Convert Selected Group of Actions to Async AppleScript.kmmacros (46 KB)

Macro screenshot

3 Likes

Here (finally) is an AppleScript to do the same. By pulling the original XML we get the double-quotes escaped for us which I think means we only have to handle < and > in the XML.

Select an action or "Group" action and this will create a new async AS action below it that executes that functionality directly, and will also disable and "undisclose" the original (keeping the original means you can easily edit it and then regenerate the AS action).

You can, of course, put the script into a macro for easy triggering. As usual I've not included error handling -- you should probably at least check that only one action (which would include a single selected Group containing many actions) is selected.

AppleScript
set preXML to "<dict>
	<key>DisplayKind</key>
	<string>Asynchronously</string>
	<key>HonourFailureSettings</key>
	<true/>
	<key>IncludeStdErr</key>
	<false/>
	<key>IncludedVariables</key>
	<array>
		<string>9999</string>
	</array>
	<key>MacroActionType</key>
	<string>ExecuteAppleScript</string>
	<key>Path</key>
	<string></string>
	<key>Text</key>
	<string>set theXML to \""

set postXML to "
\"
tell application \"Keyboard Maestro Engine\"
	do script theXML
end tell
</string>
	<key>TimeOutAbortsMacro</key>
	<true/>
	<key>TrimResults</key>
	<true/>
	<key>TrimResultsNew</key>
	<true/>
	<key>UseText</key>
	<true/>
</dict>
"

tell application "Keyboard Maestro"
	set theAction to item 1 of (get selection)
	set theXML to paragraphs 4 thru -3 of (get xml of theAction)
	set AppleScript's text item delimiters to return
	set theXML to theXML as text
	set AppleScript's text item delimiters to "<"
	set theXML to every text item of theXML
	set AppleScript's text item delimiters to "&lt;"
	set theXML to theXML as text
	set AppleScript's text item delimiters to ">"
	set theXML to every text item of theXML
	set AppleScript's text item delimiters to "&gt;"
	set theXML to theXML as text
	set theXML to preXML & theXML & postXML
	make new action at after theAction with properties {xml:theXML}
	set disclosed of theAction to false
	set enabled of theAction to false
end tell
3 Likes

Wow … Nige (@Nige_S) & Neil (@noisneil) both ways look great.

Nige, I like the way you’re using TID‘s on the XML … never seen that…

If I where either one of you I would use some code posted by Chris (@ccstone) using ASObjC and combine this code with yours.

I apparently don’t have either the code or the link to it at hand currently so I can not help with that …

The Code I am talking about lets you make sure that at least one Action is selected and you could use the other bits of that code to initiate an abort of the Macro when the user has no Actions selected or build something that waits for the user to let him/ her selecting Action(s) first to continue.

Maybe if I could build Macros only with my voice like I can dictate my replies - I would possibly have done this by myself.

I hope you had a beautiful Christmas - wish you - and of course everyone else who‘s reading this a happy new year.

Greetings from Germany :de:

Tobias

1 Like

Wow! So useful. Well done to @Nige_S and @noisneil

1 Like

Unthinkingly old-school. Since we have KM available, let's use the Engine's search and replace:

	tell application "Keyboard Maestro Engine"
		set theXML to search theXML for "<" replace "&lt;"
		set theXML to search theXML for ">" replace "&gt;"
	end tell

No need -- you can do it with vanilla AS. Just confirm the number of selected items is 1 and that the class of that item is action:

	if (count of theAction) is not 1 or class of item 1 of theAction is not action then

Another fun fact is that if the action we're turning into AS is disabled, so is the resulting AS action (should have thought of that...). So let's add in a "force enable" so we don't have to remember to enable it when editing/regenerating.

Putting that all together:

Revised Script
set preXML to "<dict>
	<key>DisplayKind</key>
	<string>Asynchronously</string>
	<key>HonourFailureSettings</key>
	<true/>
	<key>IncludeStdErr</key>
	<false/>
	<key>IncludedVariables</key>
	<array>
		<string>9999</string>
	</array>
	<key>MacroActionType</key>
	<string>ExecuteAppleScript</string>
	<key>Path</key>
	<string></string>
	<key>Text</key>
	<string>set theXML to \""

set postXML to "
\"
tell application \"Keyboard Maestro Engine\"
	do script theXML
end tell
</string>
	<key>TimeOutAbortsMacro</key>
	<true/>
	<key>TrimResults</key>
	<true/>
	<key>TrimResultsNew</key>
	<true/>
	<key>UseText</key>
	<true/>
</dict>
"

tell application "Keyboard Maestro"
	set theSelection to (get selection)
	if (count of theSelection) is not 1 or class of item 1 of theSelection is not action then
		display alert "Wrong Number of Actions Selected!" message "Try again but with exactly 1 action selected
	
	Cancelling script..." as critical
		return 1
	end if
	set theAction to item 1 of theSelection
	set enabled of theAction to true
	set theXML to paragraphs 4 thru -3 of (get xml of theAction)
	set AppleScript's text item delimiters to return
	set theXML to theXML as text
	tell application "Keyboard Maestro Engine"
		set theXML to search theXML for "<" replace "&lt;"
		set theXML to search theXML for ">" replace "&gt;"
	end tell
	make new action at after theAction with properties {xml:(preXML & theXML & postXML)}
	set disclosed of theAction to false
	set enabled of theAction to false
end tell
2 Likes

I'm away until the 11th but can't wait to try this when I'm back. Well done Nige!

Hey, I've just realised that this technique can be used to run a Subroutine Asynchronously. This is something that cannot be done at present using the inbuilt Action settings.

1 Like

You can do that in a roundabout way by having a macro (Macro 1) execute a dummy macro (Macro 2) asynchronously which in turn executes the subroutine (Macro 3).

Doing so means the subroutine Macro 3 executes independently of the Macro 1.

All I know about this is it works!

Here's the test:
[WIP] Asynch Test Macros.kmmacros|attachment (7.1 KB)

1 Like