blob: 39820561264b7f430a8ce1d2e4f8b6b1139400a2 [file] [log] [blame]
//
// EditFunctionCommand.swift
// ExtensionTest
//
// Created by whisk on 1/13/17.
// Copyright © 2017 whisk. All rights reserved.
//
import Foundation
import XcodeKit
import AppKit
class EditFunctionCommand: NSObject, XCSourceEditorCommand {
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void) {
let selections = invocation.buffer.selections
for item in selections {
if let rangeElement = item as? XCSourceTextRange{
let lineStart = rangeElement.start.line
let lineEnd = rangeElement.end.line
var curlyCount = 0
var curlyCountActive = false
var startFunc = -1
var endFunc = -1
// Checking for function header
for lineNum in lineStart...lineEnd{
let line = invocation.buffer.lines[lineNum] as! String
if line.contains("func"){
curlyCountActive = true
startFunc = lineNum
break
}
}
if startFunc == -1{
displayAlert(message: "No function signature detected. Please select a function header.")
completionHandler(nil)
return
}
//Looking for end of function
var lineNum = startFunc
while lineNum < invocation.buffer.lines.count {
let line = invocation.buffer.lines[lineNum] as! String
if line.contains("{"){
curlyCount += 1
}
if line.contains("}"){
curlyCount -= 1
}
if curlyCount == 0 {
endFunc = lineNum
break
}
lineNum += 1
}
if endFunc == -1{
displayAlert(message: "Error finding function")
completionHandler(nil)
return
}
var fullFunc = ""
let paramValues = checkUpdateParameters(startLine: startFunc, endLine: endFunc, lines: invocation.buffer.lines)
if paramValues.count > 0{
// get the param values
do{
print("Params - \(paramValues["params"])")
let jsonParamData = try JSONSerialization.data(withJSONObject: paramValues["params"]!, options: .prettyPrinted)
let jsonParamString = NSString(data: jsonParamData, encoding: String.Encoding.ascii.rawValue) as! String
fullFunc += jsonParamString
let jsonOutputData = try JSONSerialization.data(withJSONObject: paramValues["expectedOut"], options: .prettyPrinted)
let jsonOutputString = NSString(data: jsonOutputData, encoding: String.Encoding.ascii.rawValue) as! String
fullFunc += jsonOutputString
//Check if OWPlayground is open already
let owPlaygroundFound = self.checkForOWPlayground()
//Open OWPlayground Background version if it is not open
if owPlaygroundFound == false{
self.openOWPlayground()
}
// Get path to files to monitor
//Comes from Info.plist
let dictionary = Bundle.main.infoDictionary
let playgroundPath = dictionary!["PLAYGROUND_PATH"] as! String
print("Playground Path - \(playgroundPath)")
print("Expected Output - \(jsonOutputString)")
//open URL for OWPlayground
let urlString : String = "http://localhost:6018/?name=\(playgroundPath)&paramaters=\(jsonParamString)&results=\(jsonOutputString)".replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "\\", with: "").replacingOccurrences(of: "\n", with: "")
let urlStringEncoded = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let url = URL(string: urlStringEncoded!)
//Delays the opening of the url if the owPlayground isnt found
let invokeGroup = DispatchGroup()
invokeGroup.enter()
//Fancy queues and functions to make the url open after the app is opened/server is running
let queue = DispatchQueue(label: "OpenURLQueue", qos: DispatchQoS.userInitiated, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
queue.async{
while true {
var found = false
DispatchQueue.main.sync {
let OWFound = self.checkForOWPlayground()
if OWFound {
found = true
let timeAfterOpen = 1.0
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(timeAfterOpen * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: {
print("opening url")
NSWorkspace.shared().open(url!)
invokeGroup.leave()
})
}
}
if found {
break
}
}
}
switch invokeGroup.wait(timeout: DispatchTime.now() + 10) {
case DispatchTimeoutResult.success:
break
case DispatchTimeoutResult.timedOut:
print("Unable to open url as server has not launched")
break
}
}catch{
print("Didn't work")
}
}else{
// wait for user to fill out param values
}
print(fullFunc)
//displayAlert(message: fullFunc)
}
}
completionHandler(nil)
}
/// Checks for parameters above the function and adds parameters with initial values if none are found
///
/// - Parameters:
/// - startLine: the beginning of the function : 21123r1231
/// - endLine: the end of the function
/// - lines: the lines of the file
/// - Returns: return true if documentation was created, false if documentation existed already
func checkUpdateParameters(startLine: Int, endLine: Int, lines: NSMutableArray)-> [String: Any]{
// - Look for args that are used
var argsFound = [String]()
for lineNum in startLine...endLine{
if let line = lines[lineNum] as? String {
if let startRange = line.range(of:"args[\""),
let substringFrom = line.substring(from: startRange.upperBound) as? String,
let endRange = substringFrom.range(of: "\"]"),
let finalParamater = substringFrom.substring(to:endRange.lowerBound) as? String {
if !argsFound.contains(finalParamater){
argsFound.append(finalParamater)
}
}
}
}
if argsFound.count > 0 {
argsFound.sort()
// Determine if placeholders were already created
var lineNum = startLine - 1
var startDocumentation = -1
var createDocumentation = true
while lineNum > 0{
if let line = lines[lineNum] as? String{
if !line.contains("///"){
startDocumentation = lineNum + 1
break
}
lineNum -= 1
}
}
if startDocumentation == -1 {
startDocumentation = 0
}
var paramsFound = 0
var paramFoundLines = [String]()
for lineNum in startDocumentation..<startLine {
if let line = lines[lineNum] as? String{
argsFound.forEach{
if line.contains("- \($0):"){
paramsFound += 1
paramFoundLines.append(line)
}
}
}
}
if paramsFound == argsFound.count{
createDocumentation = false
var paramValues = [String:Any]()
var paramsDict = [String:String]()
paramValues["params"] = paramsDict
for paramLine in paramFoundLines{
let components = paramLine.components(separatedBy: ":")
if components.count == 3{
//Strip Param documentation lines for values
var testValue = components[2]
["\n", " ", "#>", "<#", "\""].forEach{
testValue = testValue.replacingOccurrences(of: $0, with: "")
}
var paramName = components[0]
["/", " ", "-"].forEach{
paramName = paramName.replacingOccurrences(of: $0, with: "")
}
paramsDict[paramName] = testValue
}else{
createDocumentation = true
break
}
}
paramValues["params"] = paramsDict
if createDocumentation == false{
// Look for expected output
for lineNum in startDocumentation ..< startLine {
if let line = lines[lineNum] as? String{
if line.contains("Expected Output --"){
var expectedOutString = line.components(separatedBy: "--").last!
["\n", " ", "#>", "<#"].forEach({
expectedOutString = expectedOutString.replacingOccurrences(of: $0, with: "")
})
do{
let expectedOutJSON = try JSONSerialization.jsonObject(with: expectedOutString.data(using: String.Encoding.utf8)!, options: .mutableContainers)
paramValues["expectedOut"] = expectedOutJSON as! [String : Any]
}catch{
print("Expected output could not be parsed")
paramValues["expectedOut"] = [:]
}
}
}
}
return paramValues
}
}
// Create documentation placeholders if placeholders not found
if createDocumentation{
var indentation = ""
let funcLine = lines[startLine] as! String
let funcLineComponents = funcLine.components(separatedBy: "func")
if funcLineComponents.count > 1 {
indentation = funcLineComponents[0]
}
var textToAdd = ""
textToAdd += "\(indentation)/// <#Description#> \n"
textToAdd += "\(indentation)/// \n"
if argsFound.count > 0{
textToAdd += "\(indentation)/// - Parameters: \n"
argsFound.forEach { textToAdd += "\(indentation)/// - \($0): <# description #> : <# test value #> \n" }
}
textToAdd += "\(indentation)/// - Returns: <# return value description #> \n"
textToAdd += "\(indentation)/// - Expected Output -- <#Put expected JSON here (optional)#> \n"
lines.insert(textToAdd, at: startLine)
return [:]
}
return [:]
}else{
return [:]
}
}
func checkForOWPlayground()->Bool{
let runningApps = NSWorkspace.shared().runningApplications
var OWPlaygroundFound = false
for app in runningApps{
if app.bundleIdentifier == "com.electron.openwhisk-playground-osx"{
OWPlaygroundFound = true
}
}
return OWPlaygroundFound
}
func openOWPlayground(){
let args = [ "-a", "openwhisk-playground-osx", "--args", "asdfasdfddd"]
let task = Process()
task.launchPath = "/usr/bin/open"
task.arguments = args
task.launch()
}
/// Displays a pop up alert for debugging
///
/// - Parameter message: message description
func displayAlert(message: String){
DispatchQueue.main.sync {
let alert = NSAlert()
alert.messageText = message
alert.window.level = 999
NSApp.beginModalSession(for: alert.window)
NSApp.activate(ignoringOtherApps: true)
alert.window.makeKeyAndOrderFront(nil)
alert.runModal()
}
}
}