December 22, 2009

My top two Mac annoyances... and workarounds!

Since I bought my Mac, several months ago, I've had trouble getting used to the Finder. Coming from Windows and Linux environments, I've had to learn to replace Enter by Cmd+O, Alt+Enter by Cmd+I, Backspace by Cmd+(, etc.

I will get used to all this, no doubt. But two things I miss dearly. And all over the Internet, people complain about these two things:
  • No way to cut and paste files and folders with the keyboard: I'm used to cutting files (Ctrl+X) where I think they don't belong, then navigate through the folders to the place they should go and paste them there (Ctrl+V). As simple as that: that's the way I'm used to doing it. Finder doesn't allow this and, especially on my laptop, I hate having to drag and drop with the mouse (the trackpad in that case).
  • No way to suppress a file directly: it is only possible to sent it to the Trash. And no way to delete individual files from the Trash.


For the first annoyance, workarounds used to be available in the form of plugins for Finder but they were not adapted to Snow Leopard. There is PathFinder but it costs $40 and it means using an additional application. I've also tried using Konqueror/Dolphin, the KDE tools which were now ported to Mac, but I didn't like that solution either.

Here and there on Internet posts, I saw that writing a Service with Automator and assigning it a keyboard shortcut was a solution, but I didn't find specific instructions on how to do that.

So today, as I was sick and couldn't go to work, I worked on that and after some painful hours navigating through Automator, the shell and AppleScript, I got to the point where I have something satisfying - to me at least.

So here's my solution. In Automator, create three services for Finder.app. I gave them the following names:
  1. Prepare to move the selected files and folders...

  2. Paste the files and folders here...

  3. Delete selected files and folders without moving to trash...
The first and third services need to be defined to operate on files and folders as input, the second is defined as without input.

In Services Preferences, I gave them the shortcuts Shift+Cmd+X, Shift+Cmd+V (for cut and paste) and Shift+Cmd+- (for delete).

Each of these services is made of a single action, an AppleScript and here's the code for each one. I'm sure that a lot that can be improved, but that's a basis and I give explanations for each.



The first script, the "cut" script, receives the list of selected files and folders as a list of strings. After displaying a message (which you can remove if you prefer), the list of files and folders is written, one by line to a file called /private/tmp/move_me_next. This script only writes the list of files/folders to the temporary file, it doesn't do anything else. The file will be read by the "paste" script. For some reason, I've had problems reading the file when I named it with hyphens "move-me-next", but no problem when it has underlines "move_me_next". Also note that a good programmer would have given the temporary file a safer name and/or implemented some kind of locking on the file.


on run {input, parameters}

if (class of input) is not equal to list then set input to {input}
if input = {} then return

set textList to ""
repeat with a from 1 to length of input
set textList to textList & return & item a of input as string
end repeat

tell application "Finder" to display dialog "The following files and folders will be moved when you paste them: " & textList

do shell script "rm -f /private/tmp/move_me_next"

-- Create a text file with newline-separated list of files to be moved
set moveFileDescriptor to (open for access (POSIX file "/private/tmp/move_me_next") with write permission)
repeat with a from 1 to length of input
set filename to POSIX path of item a of input as string
write filename & linefeed to moveFileDescriptor
end repeat
close access moveFileDescriptor

end run



The second script, the "paste" script does not receive any input but it reads the file /private/tmp/move_me_next and moves each file or folder mentioned there to the current location called "insertion location" by the Finder (the location where it would create a new folder for you if you pressed Shift+Cmd+N). This is not exactly the Windows behavior (if a folder is selected, then the files should be pasted inside that folder), but it's good enough for a start. I haven't implemented special treatment for the cases where a file with the same name exists. In that case, since each file is moved individually, an error message will be displayed for the conflicting file and it will not be moved. The list of "cut" files, i.e. pending move is not deleted nor updated.


on run {input, parameters}

set moveFileDescriptor to (open for access (POSIX file "/private/tmp/move_me_next"))
set fileNameList to (read moveFileDescriptor as list using delimiter linefeed)
close access moveFileDescriptor

set fileNameListString to ""
set fileList to {}
repeat with filename in fileNameList
set fileNameListString to fileNameListString & return & filename
set end of fileList to POSIX file filename
end repeat

tell application "Finder"
activate
set myDest to insertion location
display dialog "The following files and folders will be moved now to " & myDest & return & fileNameListString
end tell

repeat with myFile in fileList
try
tell application "Finder" to move myFile to myDest
on error m number n
tell application "Finder" to display dialog "Encountered error number " & n & ":" & return & m
end try
end repeat

return

end run


The third script, the "delete" script looks closely like the first one. The principle is really the same, but instead of writing the list of files/folders to a file, we run the command "rm -rf" on them. A file with the delete commands is written but it's not used (it could either be removed or expanded to a logging mechanism).


on run {input, parameters}

if (class of input) is not equal to list then set input to {input}
if input = {} then return

set textList to ""
repeat with a from 1 to length of input
set textList to textList & return & item a of input as string
end repeat

tell application "Finder" to display dialog "Are you sure you want to delete the following files and folders: " & return & textList

do shell script "rm -f /tmp/delete-commands"

repeat with a from 1 to length of input
set filename to POSIX path of item a of input as string
do shell script "echo 'rm -rf' " & quoted form of filename & ">> /tmp/delete-commands"
do shell script "rm -rf " & quoted form of filename
end repeat

return quoted form of textList

end run


Code beautifying courtesy of hilite.me.

No comments: