JXA: How do I use Finder to trash a file?

Preface

I've been using $.NSFileManager.defaultManager.trashItemAtURLResultingItemURLError to move files to the trash, and it works great.

Unfortunately, when you right-click on the file in Trash, there's no "Put Back" item. This is apparently a deficiency of that OBJ-C method.

I asked ChatGPT about it, and it said I could use Finder to trash a file and retain the "Put Back" menu item.

Using Finder

So ChatGPT gave me this code:

// Define the Finder application
const finder = Application('Finder');

// Specify the file path to be moved to Trash
const filePath = "/Users/username/Documents/sample.txt";

// Convert the file path to a Finder file object
const file = finder.items.byName(filePath);

// Move the file to Trash
finder.delete(file);

For the sake of complete transparency, I refactored it to the following, where path is the file path (obviously):

const finder = Application("Finder");
const item = finder.items.byName(path);
finder.delete(item);

Unfortunately, unless I'm just doing something stupid, it fails on the finder.delete(item) statement, with this helpful message:

Can't get object.

Anyone got any ideas?

Does it have to be JXA? I think you can do this in straight AppleScript:

tell application "Finder"
	set theSelectedItems to selection
	if (count of theSelectedItems) > 0 then
		move theSelectedItems to trash
	else
		-- nothing selected, do nothing
	end if
end tell

This works in my testing, and the Put Back option is there in the trash and works.

-rob.

2 Likes

How about this?

const app = Application.currentApplication();
app.includeStandardAdditions = true;

const filePath = "/tmp/abc.txt";

const appleScript = `
tell application "Finder"
    move POSIX file "${filePath}" to trash
end tell
`;

app.doShellScript(`osascript -e '${appleScript}'`);
2 Likes

Old docs and may have been superseded, but this suggests you need a path object rather than a string:

Paths
When you need to interact with files, such as a document in TextEdit, you will need a path object, not just a string with a path in it. You can use the Path constructor to instantiate paths.

TextEdit = Application('TextEdit')
path = Path('/Users/username/Desktop/foo.rtf')
TextEdit.open(path)

I saw that too, and it doesn't work.

I'll probably just use a shell script, or AS, depending on which seems fastest.

This seems to -- though I don't pretend to understand how or why!

const path = Path("/path/to/file/toDelete.txt");
const finder = Application("Finder");
finder.delete(path);
1 Like

Thanks, this DOES work, and it's something I tried before, except... it turns out I specified the path to a file that didn't exist (fat-finger error), and I got this incredibly misleading error:

Handler can't handle objects of this class.

If it had just said something like

file doesn't exist

or even

path is empty

Or something like that, I could have figured it out.

I truly appreciate your not giving up on this! Thanks again!!

"Documentation", and I use that term lightly, for anyone who cares: OS X 10.10 Release Notes

Just to follow up on this, this sequence of events appears to cause problems, but I'm done trying to debug this further:

  1. From JXA, have the KM Engine execute a macro that creates some files and folders.

  2. Immediately after that, try to delete some of those files or folders using the above-mentioned method.

  3. Sometimes it can fail.

But if I put a Pause action at the end of the macro, then my JXA code is able to delete the files.

It's almost as if Application("Finder") doesn't immediately get notified of the file system changes that KM made.

The only time I do this type of thing (call a macro from JXA to create some files and folders) is in some unit testing code that tests my FileUtils methods. So adding a Pause there doesn't hurt anything.

But I think, just to be careful, that if the "Finder" delete code fails, I'll fallback to deleting it with the Obj-C code.

I'm so over this.

1 Like