1

The AppleScript duplicate command is supposed to return the copied objects.

And while apps using the original AE-based functions seem to do that, apps based on the Cocoa Scripting framework seem to never return anything but missing value.

It appears that the command handler of NSCloneCommand is responsible for not returning the specifiers for the cloned objects.

I was attempting to fix this in my scriptable app by subclassing the command, collecting the cloned object specifiers and then returning them.

This works well if only one item is duplicated.

It also works if multiple items are cloned along with using the to parameter with the command (as in duplicate every widget to end): Then I can return a specifier of type NSRangeSpecifier that designates the first and last of those cloned items.

However, if one uses the duplicate command on multiple items without the to parameter, then the items get sorted into the array in a non-consecutive manner. For instance, if there are initially 2 "x" elements, with id 1 and 2, duplicate every x will insert a copy of each element right after its original, so that we'll have them in this order: 1, 3, 2, 4.

Now, how would one return a specifier for this, i.e. a specifier for items 3 and 4?

There is no "list" specifier in the sub classes of NSScriptObjectSpecifier, and I cannot return an NSArray for each individual NSScriptObjectSpecifier either, it seems. And while NSAppleEventDescriptor supports creation of lists, I cannot figure out how I'd convert the object specifiers into NSAppleEventDescriptors.

How can I solve this other than enforcing a consecutive order of the cloned objects (which would require me to re-implement the NSCloneCommand's operation entirely, I'm afraid).

BTW, Mark Aldritt, author of Script Debugger, confirms the issue that duplicate (also: move, open) do not return values as they're supposed to.

Community
  • 1
  • 1
Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
  • 1
    In the subclass you could get a list of the current items, then call `super.performDefaultImplementation()` to perform the duplication, get the list again and filter the items from the first list. – vadian May 15 '16 at 14:28
  • That was not the question, though. I wrote what works, and that includes what you suggest (though I did it in a different manner, so your answer is still useful). The real question is: What do you do when they're not in consecutive order, because then you need to create a list of specifiers, and I don't know how to do that. Although, if you think that's smarter, I could split this into its own question and take this one as answered, pending solution of the other one. I just didn't want to ask the other one alone because then I'd probably get questions like "why do you need this"? :) – Thomas Tempelmann May 15 '16 at 15:41

2 Answers2

1

Mark Aldritt helped me a little further, telling me about some private API methods:

@interface NSScriptObjectSpecifier (NSPrivate)
+ (id) _scriptingSpecifierWithDescriptor:(NSAppleEventDescriptor*) descriptor;
+ (id) _objectSpecifierFromDescriptor:(NSAppleEventDescriptor*) descriptor inCommandConstructionContext:(id) context;
- (NSAppleEventDescriptor*) _asDescriptor;
@end

The _asDescriptor was what I was looking for - a way to turn an object specifier into a NSAppleEventDescriptor so that I can add that to a list object. The code for that would look like this:

- (NSAppleEventDescriptor*) objectSpecifiersAsList:(NSArray*) objectSpecifiers {
    NSAppleEventDescriptor* result = [NSAppleEventDescriptor listDescriptor];
    for (NSScriptObjectSpecifier* specifier in objectSpecifiersArray) {
        [result insertDescriptor:specifier._asDescriptor atIndex:0];
    }
    return  result;
}

When I tried this to return the non-consecutive items, I found, however, that this doesn't work. In fact, it has the same effect as returning an NSArray of the same NSScriptObjectSpecifiers. Here's an example:

set x to duplicate widgets 1 thru 2

With the custom duplicate command handler returning a list of specifiers for the copied items 3 and 4, AppleScript ends up calling the same command handler a second time and after that it gives the error -10006 with the message:

Can't set widgets 1 thru 2 to widgets 1 thru 2

Mind you - it does not say "widgets 3 thru 4" or "{widget 3, widget 4}". No, it always reports the items that were given at the first parameter to the duplicate command.

As soon as I change my code to returning a single specifiers or a range specifier, the command behaves normally again.

So it seems like this is a hidden bug in Cocoa Scripting (or AppleScript?) wherein it cannot handle returned object specifiers in a list.

Update & Solution

After more trial-and-error I figured out a way that works:

The type for the result has to be changed from "descriptor", and there are two possibilities:

  • To use the code above that returns a listDescriptor, the result type needs to be "any", i.e.:

    <result>
        <type type="any"/>
    </result>
    
  • Alternatively, if the result type is changed to "list of any", then one can return an NSArray containing the NSAppleEventDescriptor values:

    <result>
        <type type="any" list="yes"/>
    </result>
    

Both solutions require the use of the private _asDescriptor method, however, as there is no other known way to turn a scriptable object into a NSAppleEventDescriptor.

(Of course, if your app supports the duplicate command for only one type of element, then you can change the type to "list of yourtype" and return simply an NSArray of your objects, without the need for the private method - that's only needed for returning results of type any.)

Mark says this about using the private method:

If you are concerned about Mac App store issues, these private methods were give to me by Apple as there is no alternative API. I’m pretty sure you can get permission to use them.

I hope to submit my own app implementing this solution to the App Store soon. I shall then update this answer with the outcome of using the private function.

Community
  • 1
  • 1
Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
0

Thomas, I'm not sure if it was a typo, but I'm not seeing an "at" parameter for the duplicate command in the Standard Suite:

<command name="duplicate" code="coreclon" description="Copy an object.">
    <cocoa class="NSCloneCommand"/>
    <direct-parameter type="specifier" .../>
    <parameter name="to" ...</parameter>
    <parameter name="with properties" ...</parameter>
</command>

There is also no <result ...> element, so the command should not return any value or values, by definition. Am I missing something?

Ron Reuter
  • 1,287
  • 1
  • 8
  • 14
  • Right - that's a conundrum with the various default Sdefs I found, including the ones offered by the Sdef Editor. If you have a look around at other apps, some will include the result, while others do not. I assume that those that include it are usually older ones, while those that do not include the result type are those where developers have learned that the `NSCloneCommand` does not work right and thus have removed it to avoid confusion. – Thomas Tempelmann May 15 '16 at 21:53
  • Also, I have to correct myself on the `at` parameter - I meant the `to` parameter. I'll correct the question accordingly (at only is used with make, which I looked at a lot as well, hence I got confused). – Thomas Tempelmann May 15 '16 at 21:57
  • The SDEF I use is installed by Apple on the Mac at /System/Library/ScriptingDefinitions/CocoaStandard.sdef, so I take it to be the sanctioned suite definition. – Ron Reuter May 16 '16 at 03:21
  • I guess Apple went the easy way out after realizing that their NSCloneCommand is not working right and simply removed the result from the command in the sdef. That's pretty bad, IMO. And plenty of apps still contain it. And it's needed by scripters, same as with open and make. See the last line I added to my question. – Thomas Tempelmann May 16 '16 at 09:13