The steps for getting output from an asynchronous task like this are:
- Create an
NSTask;
- set its output to an NSPipe's fileHandleForReading;
- register for a notification so you can get data to put into the textView as it becomes available.
To help with converting from Objective-C, Apple provided a conversion guide with their AppleScriptObjC Release Notes, but other than examples posted on various web sites and forums, that is about it. In general, for specific information about the various Cocoa classes and methods, you will need to look them up in Apple's documentation (for Swift you can switch to the Objective-C equivalent).
Note that an NSTextView does not have any terminal emulation (ANSI escape codes, etc), which is not trivial (take a look at iTerm2 for an example terminal application), so there won't be any cursor control. Git is also a little weird in that the progress uses standard error, so that will need to be redirected to standard output.
For a plain Xcode example, create a new AppleScriptObjC project and add the following statements to the AppDelegate:
property textView : missing value -- IBOutlet
property task : missing value -- this will be the NSTask
to startTask()
tell current application's NSTask's alloc's init() -- set up the task
its setCurrentDirectoryURL:(current application's NSURL's fileURLWithPath:(POSIX path of (path to desktop folder))) -- currentDirectoryPath deprecated in 10.13
set gitPath to "/Applications/Xcode.app/Contents/Developer/usr/bin/git"
its setExecutableURL:(current application's NSURL's fileURLWithPath:"/bin/zsh") -- launchPath deprecated in 10.13
its setArguments:{"-c", gitPath & " clone --progress https://github.com/torvalds/linux.git 2>&1"} -- combine stderr with stdout
its setStandardOutput:(current application's NSPipe's pipe())
its standardOutput's fileHandleForReading's readInBackgroundAndNotify()
set my task to it -- update script property
end tell
# set up notification observers
set notificationCenter to current application's NSNotificationCenter's defaultCenter
set readNotification to current application's NSFileHandleReadCompletionNotification
notificationCenter's addObserver:me selector:"dataAvailable:" |name|:readNotification object:(task's standardOutput's fileHandleForReading)
set terminateNotification to current application's NSTaskDidTerminateNotification
notificationCenter's addObserver:me selector:"taskTerminated:" |name|:terminateNotification object:task
set {theResult, theError} to task's launchAndReturnError:(reference) -- |launch| deprecated in 10.13
if theError is missing value then
log "Task Launched"
else
log "Error launching task: " & (theError's localizedDescription() as text)
end if
end startTask
on dataAvailable:notification -- get some output from the task
set theData to notification's userInfo()'s objectForKey:(current application's NSFileHandleNotificationDataItem)
if theData is not missing value and theData's |length|() > 0 then showResult(theData)
notification's object's readInBackgroundAndNotify() -- notify again when more data is available
end dataAvailable:
to showResult(resultData) -- append data to the end of the text view
set resultString to current application's NSString's alloc()'s initWithData:resultData encoding:(current application's NSUTF8StringEncoding)
set attributedString to current application's NSMutableAttributedString's alloc()'s initWithString:resultString
set theFont to (current application's NSFont's fontWithName:"Menlo Regular" |size|:12)
set theRange to (current application's NSMakeRange(0, attributedString's |length|()))
attributedString's addAttribute:(current application's NSFontAttributeName) value:theFont range:theRange -- use monospaced font
textView's textStorage()'s appendAttributedString:attributedString
textView's scrollToEndOfDocument:me -- 10.14+
end showResult
on taskTerminated:notification
current application's NSNotificationCenter's defaultCenter's removeObserver:me
repeat -- get any early termination leftovers
set theData to notification's object's standardOutput's fileHandleForReading's availableData
if theData is not missing value and theData's |length|() > 0 then
showResult(theData)
else
exit repeat
end if
end repeat
set my task to missing value -- clear script property
log "Task Terminated"
end taskTerminated:
In the Interface Editor, add a scrollable text view to the main window and connect it to the textView
property, edit the currentDirectory and gitPath locations as needed, and put a statement in the applicationWillFinishLaunching
hander to call startTask()
.
For something a bit simpler (shorter) to test with that still has a little output, the task arguments can be changed to something like:
its setExecutableURL:(current application's NSURL's fileURLWithPath:"/usr/sbin/system_profiler")
its setArguments:{"-detailLevel", "basic"} -- mini, basic, full
-- or --
its setExecutableURL:(current application's NSURL's fileURLWithPath:"/bin/zsh")
its setArguments:{"-c", "find /Users/$USER -iname '*.scpt'" } -- find scripts