Quick Look Script

The script included below displays files in a Quick-Look window. To test this script, select a folder or file in a Finder window and then open and run the script. A few comments:

  • The right and left arrow keys can be used to scroll through other files in the containing folder.
  • If a selected item cannot be viewed, or if a selected folder contains no files, the script does nothing.
  • The script descends into subfolders, but this can be changed by substituting option:7 for option:6.
  • Files are sorted alphabetically, first by the path of their containing folder and then by their name.
  • The title bar of the Quick Look window shows the word DEBUG, and I’m not aware of any method to eliminate this word.
-- revised 2023.11.14

use framework "Foundation"

on main()
	tell application "Finder"
		try
			set theSelection to selection
			if theSelection is {} then set theSelection to {target of front Finder window}
			set theSelection to (item 1 of theSelection)
			if class of theSelection is folder then
				getFiles(theSelection as alias, "") of me -- a folder is selected
			else
				set theFolder to container of theSelection
				getFiles(theFolder as alias, theSelection as alias) of me -- a file is selected
			end if
		end try
	end tell
end main

on getFiles(theFolder, theFile)
	set fileManager to current application's NSFileManager's defaultManager()
	set theFiles to ((fileManager's enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:6 errorHandler:(missing value))'s allObjects())'s mutableCopy()
	set isFileKey to current application's NSURLIsRegularFileKey -- regular file not folder or package
	repeat with i from theFiles's |count|() to 1 by -1
		set {theResult, isFile} to ((theFiles's objectAtIndex:(i - 1))'s getResourceValue:(reference) forKey:isFileKey |error|:(missing value))
		if isFile as boolean is false then (theFiles's removeObjectAtIndex:(i - 1))
	end repeat
	if theFiles's |count|() is 0 then return
	set pathDescriptor to current application's NSSortDescriptor's sortDescriptorWithKey:"stringByDeletingLastPathComponent" ascending:true selector:"localizedStandardCompare:" -- sort by file path
	set nameDescriptor to current application's NSSortDescriptor's sortDescriptorWithKey:"lastPathComponent" ascending:true selector:"localizedStandardCompare:" -- sort by file name
	set theFiles to ((theFiles's valueForKey:"path")'s sortedArrayUsingDescriptors:{pathDescriptor, nameDescriptor})
	if theFile is not "" then -- the selected item is a file
		set theIndex to (theFiles's indexOfObject:(POSIX path of theFile))
		set filesAhead to theFiles's subarrayWithRange:{theIndex, ((theFiles's |count|()) - theIndex)}
		set filesBehind to theFiles's subarrayWithRange:{0, theIndex}
		set theFiles to filesAhead's arrayByAddingObjectsFromArray:filesBehind
	end if
	showFiles(theFiles)
end getFiles

on showFiles(theFiles)
	set theTask to current application's NSTask's new()
	theTask's setExecutableURL:(current application's NSURL's fileURLWithPath:"/usr/bin/qlmanage")
	theTask's setArguments:({"-p"} & theFiles)
	theTask's launchAndReturnError:(missing value)
end showFiles

main()
1 Like

Hi peavine.

Pretty cool. :sunglasses:

According to the Xcode 15.0.1 documentation, NSTask’s launchPath() property’s now deprecated. The preferred alternative is given as executableURL(), which was apparently introduced in macOS 10.13.

set theTask to current application's NSTask's new()
theTask's setExecutableURL:(current application's NSURL's fileURLWithPath:"/usr/bin/qlmanage")
1 Like

Thanks Nigel. I’ve edited my script to incorporate your suggestion.

BTW, I was unable to find the correct syntax for a portion of the showFiles handler, and forum member StefanK provided a solution (here).

This irked me for a long time whenever using qlmanage to preview file. However, since Shortcuts came to macOS, it’s offered a really simple way to preview files using the QuickLook action. In its simplest form, an AppleScript can invoke a Shortcut that contains only this action, then the files sent through from the AppleScript get passed into the QuickLook action.

If you wanted to implement the previewing of a folder’s contents as your script does, this can be done in one of two ways, either by having the AppleScript read the folder contents and send this through to the Shortcut, or by having the Shortcut determine when it has been sent a single folder item and have it enumerate its contents before passing that through to QuickLook. I’m not certain which would be faster, but I suspect that enumerating the folder’s contents within AppleScript by way of some sensible (i.e. not Finder) method would be the more performant.

CJK. Thanks for looking at my script and for the suggestions.

I could never get a shortcut to work with a folder of files with the Quick Look action, and the following is a simple example. I have been able to view a single file with the Quick Look action but that’s not very useful given the intended purpose of my script.

BTW, I encounter the same issue when passing a Quick Look shortcut a list of files from an AppleScript.

My script in post 1 uses NSTask instead of do shell script to run qlmanage, and I wondered if one of these should be preferred over the other. I ran some tests with Script Geek and thought I would report the results, FWIW.

I used the sips command for testing purposes because it allowed me to investigate a particular issue. Script one took 149 milliseconds and script two took 1 millisecond.

-- SCRIPT ONE
do shell script "sips -s format png " & quoted form of "/Users/Robert/Working/Test 1.jpg" & " --out " & quoted form of "/Users/Robert/Working/Test 1.png"

-- SCRIPT TWO
use framework "Foundation"
set theTask to current application's NSTask's new()
theTask's setExecutableURL:(current application's NSURL's fileURLWithPath:"/usr/bin/sips")
theTask's setArguments:{"-s", "format", "png", "/Users/Robert/Working/Test 1.jpg", "--out", "/Users/Robert/Working/Test 1.png"}
theTask's launchAndReturnError:(missing value)

The reason for this discrepancy seemed clear, so I modified both scripts to open the created image in Preview. The waitUntilExit method was required for the NSTask script to work properly–otherwise Preview was unable to find the created PNG file. Script one took 732 milliseconds and script two took 735 milliseconds.

-- SCRIPT ONE
do shell script "sips -s format png " & quoted form of "/Users/Robert/Working/Test 1.jpg" & " --out " & quoted form of "/Users/Robert/Working/Test 1.png"
tell application "Preview" to open file "Macintosh HD:Users:Robert:Working:Test 1.png"

-- SCRIPT TWO
use framework "Foundation"
set theTask to current application's NSTask's new()
theTask's setExecutableURL:(current application's NSURL's fileURLWithPath:"/usr/bin/sips")
theTask's setArguments:{"-s", "format", "png", "/Users/Robert/Working/Test 1.jpg", "--out", "/Users/Robert/Working/Test 1.png"}
theTask's launchAndReturnError:(missing value)
theTask's waitUntilExit()
tell application "Preview" to open file "Macintosh HD:Users:Robert:Working:Test 1.png"

So, there’s no compelling reason to use one approach over the other, but three issues are worthy of note:

  • The do shell script command does not require the Foundation framework.
  • NSTask does not require quoting of files.
  • Getting standard output from the shell command with NSTask is a chore.

I created a Shortcut using the setup from your image, and it worked fine for me. Occasionally, it stutters when rendering the initial file, which simply requires one of the navigation arrows to be clicked (or one of keyboard left-/right-arrows to be pressed, provided the QuickLook window has focus)—in this situation, rather than navigating to another file, it renders the preview of the current file correctly.

I’ve sent in a bug report to Apple regarding this, but as bugs go, it’s pretty minor and doesn’t detract all that much from the user experience.

Here’s what I just created for integration with Finder as a Quick Action:

1 Like

My script in post 1 descends into subdirectories but can be made not to descend into subdirectories by changing option 6 to 7. The following script also does not descend into subdirectories but is a bit more compact and marginally faster.

-- revised 2023.12.08

use framework "Foundation"

on main()
	tell application "Finder"
		try
			set theSelection to selection
			if theSelection is {} then set theSelection to {target of front Finder window}
			set theSelection to (item 1 of theSelection)
			if class of theSelection is folder then
				getFiles(theSelection as alias, "") of me -- a folder is selected
			else
				set theFolder to container of theSelection
				getFiles(theFolder as alias, theSelection as alias) of me -- a file is selected
			end if
		end try
	end tell
end main

on getFiles(theFolder, theFile)
	set fileManager to current application's NSFileManager's defaultManager()
	set theFiles to (fileManager's contentsOfDirectoryAtURL:theFolder includingPropertiesForKeys:{} options:4 |error|:(missing value))'s mutableCopy()
	set isFileKey to current application's NSURLIsRegularFileKey -- regular file not folder or package
	repeat with i from theFiles's |count|() to 1 by -1
		set {theResult, isFile} to ((theFiles's objectAtIndex:(i - 1))'s getResourceValue:(reference) forKey:isFileKey |error|:(missing value))
		if isFile as boolean is false then (theFiles's removeObjectAtIndex:(i - 1))
	end repeat
	if theFiles's |count|() is 0 then return
	set theFiles to (theFiles's valueForKey:"path")'s sortedArrayUsingSelector:"localizedStandardCompare:"
	if theFile is not "" then -- a file is selected
		set theIndex to (theFiles's indexOfObject:(POSIX path of theFile))
		set filesAhead to theFiles's subarrayWithRange:{theIndex, ((theFiles's |count|()) - theIndex)}
		set filesBehind to theFiles's subarrayWithRange:{0, theIndex}
		set theFiles to filesAhead's arrayByAddingObjectsFromArray:filesBehind
	end if
	showFiles(theFiles)
end getFiles

on showFiles(theFiles)
	set theTask to current application's NSTask's new()
	theTask's setExecutableURL:(current application's NSURL's fileURLWithPath:"/usr/bin/qlmanage")
	theTask's setArguments:({"-p"} & theFiles)
	theTask's launchAndReturnError:(missing value)
end showFiles

main()