mirror of
https://github.com/ypresto/SwiftLintXcode.git
synced 2026-04-26 14:47:43 +00:00
Implement the plugin 🎉
This commit is contained in:
parent
96f08b2a33
commit
a27da654ad
8 changed files with 514 additions and 16 deletions
|
|
@ -7,17 +7,25 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
0498717F1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498717E1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.m */; };
|
||||||
|
049871811CB28EBF00C5F7B5 /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 049871801CB28EBF00C5F7B5 /* Formatter.swift */; };
|
||||||
04E4BF341CB25D3200BC7305 /* SwiftLintAutoCorrect.xcscheme in Resources */ = {isa = PBXBuildFile; fileRef = 04E4BF331CB25D3200BC7305 /* SwiftLintAutoCorrect.xcscheme */; };
|
04E4BF341CB25D3200BC7305 /* SwiftLintAutoCorrect.xcscheme in Resources */ = {isa = PBXBuildFile; fileRef = 04E4BF331CB25D3200BC7305 /* SwiftLintAutoCorrect.xcscheme */; };
|
||||||
04E4BF361CB25D3200BC7305 /* SwiftLintAutoCorrect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04E4BF351CB25D3200BC7305 /* SwiftLintAutoCorrect.swift */; };
|
04E4BF361CB25D3200BC7305 /* SwiftLintAutoCorrect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04E4BF351CB25D3200BC7305 /* SwiftLintAutoCorrect.swift */; };
|
||||||
04E4BF381CB25D3200BC7305 /* NSObject_Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04E4BF371CB25D3200BC7305 /* NSObject_Extension.swift */; };
|
04E4BF381CB25D3200BC7305 /* NSObject_Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04E4BF371CB25D3200BC7305 /* NSObject_Extension.swift */; };
|
||||||
|
04E4BF411CB273A700BC7305 /* SaveHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04E4BF401CB273A700BC7305 /* SaveHook.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
0498717C1CB27F8800C5F7B5 /* SwiftLintAutoCorrect-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftLintAutoCorrect-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
0498717D1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwiftLintAutoCorrectTRVSXcode.h; sourceTree = "<group>"; };
|
||||||
|
0498717E1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwiftLintAutoCorrectTRVSXcode.m; sourceTree = "<group>"; };
|
||||||
|
049871801CB28EBF00C5F7B5 /* Formatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Formatter.swift; sourceTree = "<group>"; };
|
||||||
04E4BF2F1CB25D3100BC7305 /* SwiftLintAutoCorrect.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftLintAutoCorrect.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; };
|
04E4BF2F1CB25D3100BC7305 /* SwiftLintAutoCorrect.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftLintAutoCorrect.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
04E4BF331CB25D3200BC7305 /* SwiftLintAutoCorrect.xcscheme */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = SwiftLintAutoCorrect.xcscheme; path = SwiftLintAutoCorrect.xcodeproj/xcshareddata/xcschemes/SwiftLintAutoCorrect.xcscheme; sourceTree = SOURCE_ROOT; };
|
04E4BF331CB25D3200BC7305 /* SwiftLintAutoCorrect.xcscheme */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = SwiftLintAutoCorrect.xcscheme; path = SwiftLintAutoCorrect.xcodeproj/xcshareddata/xcschemes/SwiftLintAutoCorrect.xcscheme; sourceTree = SOURCE_ROOT; };
|
||||||
04E4BF351CB25D3200BC7305 /* SwiftLintAutoCorrect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLintAutoCorrect.swift; sourceTree = "<group>"; };
|
04E4BF351CB25D3200BC7305 /* SwiftLintAutoCorrect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLintAutoCorrect.swift; sourceTree = "<group>"; };
|
||||||
04E4BF371CB25D3200BC7305 /* NSObject_Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSObject_Extension.swift; sourceTree = "<group>"; };
|
04E4BF371CB25D3200BC7305 /* NSObject_Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSObject_Extension.swift; sourceTree = "<group>"; };
|
||||||
04E4BF391CB25D3200BC7305 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
04E4BF391CB25D3200BC7305 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
04E4BF401CB273A700BC7305 /* SaveHook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveHook.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
|
@ -40,10 +48,15 @@
|
||||||
04E4BF311CB25D3200BC7305 /* SwiftLintAutoCorrect */ = {
|
04E4BF311CB25D3200BC7305 /* SwiftLintAutoCorrect */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
0498717D1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.h */,
|
||||||
|
0498717E1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.m */,
|
||||||
04E4BF351CB25D3200BC7305 /* SwiftLintAutoCorrect.swift */,
|
04E4BF351CB25D3200BC7305 /* SwiftLintAutoCorrect.swift */,
|
||||||
04E4BF371CB25D3200BC7305 /* NSObject_Extension.swift */,
|
04E4BF371CB25D3200BC7305 /* NSObject_Extension.swift */,
|
||||||
|
04E4BF401CB273A700BC7305 /* SaveHook.swift */,
|
||||||
|
049871801CB28EBF00C5F7B5 /* Formatter.swift */,
|
||||||
04E4BF391CB25D3200BC7305 /* Info.plist */,
|
04E4BF391CB25D3200BC7305 /* Info.plist */,
|
||||||
04E4BF321CB25D3200BC7305 /* Supporting Files */,
|
04E4BF321CB25D3200BC7305 /* Supporting Files */,
|
||||||
|
0498717C1CB27F8800C5F7B5 /* SwiftLintAutoCorrect-Bridging-Header.h */,
|
||||||
);
|
);
|
||||||
path = SwiftLintAutoCorrect;
|
path = SwiftLintAutoCorrect;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -123,7 +136,10 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
0498717F1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.m in Sources */,
|
||||||
04E4BF361CB25D3200BC7305 /* SwiftLintAutoCorrect.swift in Sources */,
|
04E4BF361CB25D3200BC7305 /* SwiftLintAutoCorrect.swift in Sources */,
|
||||||
|
049871811CB28EBF00C5F7B5 /* Formatter.swift in Sources */,
|
||||||
|
04E4BF411CB273A700BC7305 /* SaveHook.swift in Sources */,
|
||||||
04E4BF381CB25D3200BC7305 /* NSObject_Extension.swift in Sources */,
|
04E4BF381CB25D3200BC7305 /* NSObject_Extension.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
@ -209,6 +225,7 @@
|
||||||
04E4BF3D1CB25D3200BC7305 /* Debug */ = {
|
04E4BF3D1CB25D3200BC7305 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEPLOYMENT_LOCATION = YES;
|
DEPLOYMENT_LOCATION = YES;
|
||||||
DSTROOT = "$(HOME)";
|
DSTROOT = "$(HOME)";
|
||||||
|
|
@ -219,6 +236,8 @@
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = net.ypresto.SwiftLintAutoCorrect;
|
PRODUCT_BUNDLE_IDENTIFIER = net.ypresto.SwiftLintAutoCorrect;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "SwiftLintAutoCorrect/SwiftLintAutoCorrect-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
WRAPPER_EXTENSION = xcplugin;
|
WRAPPER_EXTENSION = xcplugin;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
|
@ -226,6 +245,7 @@
|
||||||
04E4BF3E1CB25D3200BC7305 /* Release */ = {
|
04E4BF3E1CB25D3200BC7305 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEPLOYMENT_LOCATION = YES;
|
DEPLOYMENT_LOCATION = YES;
|
||||||
DSTROOT = "$(HOME)";
|
DSTROOT = "$(HOME)";
|
||||||
|
|
@ -236,6 +256,7 @@
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = net.ypresto.SwiftLintAutoCorrect;
|
PRODUCT_BUNDLE_IDENTIFIER = net.ypresto.SwiftLintAutoCorrect;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "SwiftLintAutoCorrect/SwiftLintAutoCorrect-Bridging-Header.h";
|
||||||
WRAPPER_EXTENSION = xcplugin;
|
WRAPPER_EXTENSION = xcplugin;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
@ -259,6 +280,7 @@
|
||||||
04E4BF3E1CB25D3200BC7305 /* Release */,
|
04E4BF3E1CB25D3200BC7305 /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
};
|
};
|
||||||
|
|
|
||||||
145
SwiftLintAutoCorrect/Formatter.swift
Normal file
145
SwiftLintAutoCorrect/Formatter.swift
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
//
|
||||||
|
// Formatter.swift
|
||||||
|
// SwiftLintAutoCorrect
|
||||||
|
//
|
||||||
|
// Created by yuya.tanaka on 2016/04/04.
|
||||||
|
// Copyright (c) 2016 Yuya Tanaka. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
final class Formatter {
|
||||||
|
static var sharedInstance = Formatter()
|
||||||
|
private static let pathExtension = "swiftlintautocorrect"
|
||||||
|
|
||||||
|
let fileManager = NSFileManager.defaultManager()
|
||||||
|
let tempDirURL: NSURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("SwiftLintAutoCorrect-\(NSUUID().UUIDString)")
|
||||||
|
|
||||||
|
struct CursorPosition {
|
||||||
|
let line: Int
|
||||||
|
let column: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class func isFormattableDocument(document: NSDocument) -> Bool {
|
||||||
|
return (document.fileURL?.pathExtension?.lowercaseString == "swift") ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryFormatDocument(document: IDESourceCodeDocument) -> Bool {
|
||||||
|
do {
|
||||||
|
try formatDocument(document)
|
||||||
|
return true
|
||||||
|
} catch let error as NSError {
|
||||||
|
NSAlert(error: error).runModal()
|
||||||
|
} catch {
|
||||||
|
NSAlert(error: NSError(domain: "net.ypresto.swiftlintautocorrect", code: 0, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Unknown error occured: \(error)"
|
||||||
|
])).runModal()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDocument(document: IDESourceCodeDocument) throws {
|
||||||
|
let textStorage: DVTSourceTextStorage = document.textStorage()
|
||||||
|
let originalString = textStorage.string
|
||||||
|
let formattedString = try formatString(originalString)
|
||||||
|
if formattedString == originalString { return }
|
||||||
|
|
||||||
|
let selectedRange = SwiftLintAutoCorrectTRVSXcode.textView().selectedRange()
|
||||||
|
let cursorPosition = cursorPositionForSelectedRange(selectedRange, textStorage: textStorage)
|
||||||
|
|
||||||
|
textStorage.beginEditing()
|
||||||
|
textStorage.replaceCharactersInRange(NSRange(location: 0, length: textStorage.length), withString: formattedString, withUndoManager: document.undoManager())
|
||||||
|
textStorage.endEditing()
|
||||||
|
|
||||||
|
let newLocation = locationForCursorPosition(cursorPosition, textStorage: textStorage)
|
||||||
|
SwiftLintAutoCorrectTRVSXcode.textView().setSelectedRange(NSRange(location: newLocation, length: 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cursorPositionForSelectedRange(selectedRange: NSRange, textStorage: DVTSourceTextStorage) -> CursorPosition {
|
||||||
|
let line = textStorage.lineRangeForCharacterRange(selectedRange).location
|
||||||
|
let column = selectedRange.location - startLocationOfLine(line, textStorage: textStorage)
|
||||||
|
return CursorPosition(line: line, column: column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func locationForCursorPosition(cursorPosition: CursorPosition, textStorage: DVTSourceTextStorage) -> Int {
|
||||||
|
let startOfLine = startLocationOfLine(cursorPosition.line, textStorage: textStorage)
|
||||||
|
let locationOfNextLine = textStorage.characterRangeForLineRange(NSRange(location: cursorPosition.line + 1, length: 0)).location
|
||||||
|
// XXX: Can reach EOF..? Cursor position may be trimmed one charactor when cursor is on EOF.
|
||||||
|
return min(startOfLine + cursorPosition.column, locationOfNextLine - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startLocationOfLine(line: Int, textStorage: DVTSourceTextStorage) -> Int {
|
||||||
|
return textStorage.characterRangeForLineRange(NSRange(location: line, length: 0)).location
|
||||||
|
}
|
||||||
|
|
||||||
|
private func formatString(string: String) throws -> String {
|
||||||
|
return try withTempporaryFile { (filePath) in
|
||||||
|
try string.writeToFile(filePath, atomically: false, encoding: NSUTF8StringEncoding)
|
||||||
|
let swiftlintPath = try self.getExecutableOnPath("swiftlint")
|
||||||
|
let task = NSTask.launchedTaskWithLaunchPath(swiftlintPath, arguments: [
|
||||||
|
"autocorrect", "--path", filePath
|
||||||
|
])
|
||||||
|
task.waitUntilExit()
|
||||||
|
if task.terminationStatus != 0 {
|
||||||
|
throw NSError(domain: "net.ypresto.swiftlintautocorrect", code: 0, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Executing swiftlint exited with non-zero status."
|
||||||
|
])
|
||||||
|
}
|
||||||
|
return try String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getExecutableOnPath(name: String) throws -> String {
|
||||||
|
let pipe = NSPipe()
|
||||||
|
let task = NSTask()
|
||||||
|
task.launchPath = "/bin/bash"
|
||||||
|
task.arguments = [
|
||||||
|
"-l", "-c", "which \(name)"
|
||||||
|
]
|
||||||
|
task.standardOutput = pipe
|
||||||
|
task.launch()
|
||||||
|
task.waitUntilExit()
|
||||||
|
if task.terminationStatus != 0 {
|
||||||
|
throw NSError(domain: "net.ypresto.swiftlintautocorrect", code: 0, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Executing `which swiftlint` exited with non-zero status."
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||||
|
guard let pathString = String(data: data, encoding: NSUTF8StringEncoding) else {
|
||||||
|
throw NSError(domain: "net.ypresto.swiftlintautocorrect", code: 0, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Cannot read result of `which swiftlint`."
|
||||||
|
])
|
||||||
|
}
|
||||||
|
let path = pathString.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
|
||||||
|
if !fileManager.isExecutableFileAtPath(path) {
|
||||||
|
throw NSError(domain: "net.ypresto.swiftlintautocorrect", code: 0, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "swiftlint at \(path) is not executable."
|
||||||
|
])
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
private func withTempporaryFile<T>(@noescape callback: (filePath: String) throws -> T) throws -> T {
|
||||||
|
try ensureTemporaryDirectory()
|
||||||
|
let filePath = createTemporaryPath()
|
||||||
|
if fileManager.fileExistsAtPath(filePath) {
|
||||||
|
throw NSError(domain: "net.ypresto.swiftlintautocorrect", code: 0, userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Cannot write to \(filePath), file already exists."
|
||||||
|
])
|
||||||
|
}
|
||||||
|
defer { _ = try? fileManager.removeItemAtPath(filePath) }
|
||||||
|
return try callback(filePath: filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createTemporaryPath() -> String {
|
||||||
|
return tempDirURL.URLByAppendingPathComponent(NSUUID().UUIDString).path! + ".swift"
|
||||||
|
}
|
||||||
|
|
||||||
|
private func ensureTemporaryDirectory() throws {
|
||||||
|
if fileManager.fileExistsAtPath(tempDirURL.path!) { return }
|
||||||
|
try fileManager.createDirectoryAtURL(tempDirURL, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// NSObject_Extension.swift
|
// NSObject_Extension.swift
|
||||||
//
|
//
|
||||||
// Created by yuya.tanaka on 2016/04/04.
|
// Created by yuya.tanaka on 2016/04/04.
|
||||||
// Copyright © 2016年 Yuya Tanaka. All rights reserved.
|
// Copyright (c) 2016 Yuya Tanaka. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
|
||||||
50
SwiftLintAutoCorrect/SaveHook.swift
Normal file
50
SwiftLintAutoCorrect/SaveHook.swift
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// SaveHook.swift
|
||||||
|
// SwiftLintAutoCorrect
|
||||||
|
//
|
||||||
|
// Created by yuya.tanaka on 2016/04/04.
|
||||||
|
// Copyright (c) 2016 Yuya Tanaka. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// http://blog.waft.me/method-swizzling/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
final class SaveHook {
|
||||||
|
|
||||||
|
private static var swizzled = false
|
||||||
|
static var enabled = true
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
class func swizzle() {
|
||||||
|
if swizzled { return }
|
||||||
|
swizzled = true
|
||||||
|
|
||||||
|
let fromMethod = class_getInstanceMethod(NSDocument.self, #selector(NSDocument.saveDocumentWithDelegate(_:didSaveSelector:contextInfo:)))
|
||||||
|
let toMethod = class_getInstanceMethod(NSDocument.self, #selector(NSDocument.swiftLintAutoCorrectSaveDocumentWithDelegate(_:didSaveSelector:contextInfo:)))
|
||||||
|
method_exchangeImplementations(fromMethod, toMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
class func tryOnSaveDocument(document: NSDocument) -> Bool {
|
||||||
|
if !enabled { return true }
|
||||||
|
Formatter.isFormattableDocument(document)
|
||||||
|
let sourceCodeDocument: IDESourceCodeDocument = SwiftLintAutoCorrectTRVSXcode.sourceCodeDocument()
|
||||||
|
guard sourceCodeDocument == document else { return true }
|
||||||
|
return Formatter.sharedInstance.tryFormatDocument(sourceCodeDocument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/travisjeffery/ClangFormat-Xcode/blob/a22114907592fb5d5b1043a4919d7be3e1496741/ClangFormat/NSDocument+TRVSClangFormat.m
|
||||||
|
extension NSDocument {
|
||||||
|
|
||||||
|
dynamic func swiftLintAutoCorrectSaveDocumentWithDelegate(delegate: AnyObject?, didSaveSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) -> Void {
|
||||||
|
if SaveHook.tryOnSaveDocument(self) {
|
||||||
|
// NOTE: Call original method
|
||||||
|
swiftLintAutoCorrectSaveDocumentWithDelegate(delegate, didSaveSelector: didSaveSelector, contextInfo: contextInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
//
|
||||||
|
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SwiftLintAutoCorrectTRVSXcode.h"
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// SwiftLintAutoCorrect.swift
|
// SwiftLintAutoCorrect.swift
|
||||||
//
|
//
|
||||||
// Created by yuya.tanaka on 2016/04/04.
|
// Created by yuya.tanaka on 2016/04/04.
|
||||||
// Copyright © 2016年 Yuya Tanaka. All rights reserved.
|
// Copyright (c) 2016 Yuya Tanaka. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
@ -14,36 +14,76 @@ class SwiftLintAutoCorrect: NSObject {
|
||||||
var bundle: NSBundle
|
var bundle: NSBundle
|
||||||
lazy var center = NSNotificationCenter.defaultCenter()
|
lazy var center = NSNotificationCenter.defaultCenter()
|
||||||
|
|
||||||
|
var enableMenuItem: NSMenuItem!
|
||||||
|
var disableMenuItem: NSMenuItem!
|
||||||
|
|
||||||
init(bundle: NSBundle) {
|
init(bundle: NSBundle) {
|
||||||
self.bundle = bundle
|
self.bundle = bundle
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
center.addObserver(self, selector: Selector("createMenuItems"), name: NSApplicationDidFinishLaunchingNotification, object: nil)
|
center.addObserver(self, selector: #selector(SwiftLintAutoCorrect.onApplicationDidFinishLaunching), name: NSApplicationDidFinishLaunchingNotification, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
removeObserver()
|
removeObserver()
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeObserver() {
|
private func removeObserver() {
|
||||||
center.removeObserver(self)
|
center.removeObserver(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMenuItems() {
|
func onApplicationDidFinishLaunching() {
|
||||||
|
SaveHook.swizzle()
|
||||||
|
createMenuItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createMenuItems() {
|
||||||
removeObserver()
|
removeObserver()
|
||||||
|
|
||||||
var item = NSApp.mainMenu!.itemWithTitle("Edit")
|
guard let item = NSApp.mainMenu!.itemWithTitle("Edit") else { return }
|
||||||
if item != nil {
|
|
||||||
var actionMenuItem = NSMenuItem(title:"Do Action", action:"doMenuAction", keyEquivalent:"")
|
let pluginMenu = NSMenu(title:"SwiftLint Auto Correct")
|
||||||
actionMenuItem.target = self
|
let pluginMenuItem = NSMenuItem(title:"SwiftLint Auto Correct", action: nil, keyEquivalent: "")
|
||||||
item!.submenu!.addItem(NSMenuItem.separatorItem())
|
pluginMenuItem.submenu = pluginMenu
|
||||||
item!.submenu!.addItem(actionMenuItem)
|
|
||||||
}
|
let autoCorrectMenuItem = NSMenuItem(title:"Auto Correct", action:#selector(SwiftLintAutoCorrect.doAutoCorrect), keyEquivalent:"")
|
||||||
|
autoCorrectMenuItem.target = self
|
||||||
|
pluginMenu.addItem(autoCorrectMenuItem)
|
||||||
|
|
||||||
|
let enableMenuItem = NSMenuItem(title:"Enable Format on Save", action:#selector(SwiftLintAutoCorrect.doEnableFormatOnSave), keyEquivalent:"")
|
||||||
|
enableMenuItem.target = self
|
||||||
|
pluginMenu.addItem(enableMenuItem)
|
||||||
|
self.enableMenuItem = enableMenuItem
|
||||||
|
|
||||||
|
let disableMenuItem = NSMenuItem(title:"Disable Format on Save", action:#selector(SwiftLintAutoCorrect.doDisableFormatOnSave), keyEquivalent:"")
|
||||||
|
disableMenuItem.target = self
|
||||||
|
pluginMenu.addItem(disableMenuItem)
|
||||||
|
self.disableMenuItem = disableMenuItem
|
||||||
|
|
||||||
|
item.submenu!.addItem(NSMenuItem.separatorItem())
|
||||||
|
item.submenu!.addItem(pluginMenuItem)
|
||||||
|
|
||||||
|
updateMenuVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
func doMenuAction() {
|
func doAutoCorrect() {
|
||||||
let error = NSError(domain: "Hello World!", code:42, userInfo:nil)
|
let sourceCodeDocument: IDESourceCodeDocument = SwiftLintAutoCorrectTRVSXcode.sourceCodeDocument()
|
||||||
NSAlert(error: error).runModal()
|
guard Formatter.isFormattableDocument(sourceCodeDocument) else { return }
|
||||||
|
Formatter.sharedInstance.tryFormatDocument(sourceCodeDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doEnableFormatOnSave() {
|
||||||
|
SaveHook.enabled = true
|
||||||
|
updateMenuVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
func doDisableFormatOnSave() {
|
||||||
|
SaveHook.enabled = false
|
||||||
|
updateMenuVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMenuVisibility() {
|
||||||
|
self.enableMenuItem.hidden = SaveHook.enabled
|
||||||
|
self.disableMenuItem.hidden = !SaveHook.enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
134
SwiftLintAutoCorrect/SwiftLintAutoCorrectTRVSXcode.h
Normal file
134
SwiftLintAutoCorrect/SwiftLintAutoCorrectTRVSXcode.h
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
//
|
||||||
|
// TRVSXcode.h
|
||||||
|
// ClangFormat
|
||||||
|
//
|
||||||
|
// Created by Travis Jeffery on 1/9/14.
|
||||||
|
// Copyright (c) 2014 Travis Jeffery. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
// here be dragons... have to declare these private apis
|
||||||
|
|
||||||
|
@interface DVTTextDocumentLocation : NSObject
|
||||||
|
@property (readonly) NSRange characterRange;
|
||||||
|
@property (readonly) NSRange lineRange;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface DVTTextPreferences : NSObject
|
||||||
|
+ (id)preferences;
|
||||||
|
@property BOOL trimWhitespaceOnlyLines;
|
||||||
|
@property BOOL trimTrailingWhitespace;
|
||||||
|
@property BOOL useSyntaxAwareIndenting;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface DVTSourceTextStorage : NSTextStorage
|
||||||
|
- (void)replaceCharactersInRange:(NSRange)range
|
||||||
|
withString:(NSString *)string
|
||||||
|
withUndoManager:(id)undoManager;
|
||||||
|
- (NSRange)lineRangeForCharacterRange:(NSRange)range;
|
||||||
|
- (NSRange)characterRangeForLineRange:(NSRange)range;
|
||||||
|
- (void)indentCharacterRange:(NSRange)range undoManager:(id)undoManager;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface DVTFileDataType : NSObject
|
||||||
|
@property (readonly) NSString *identifier;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface DVTFilePath : NSObject
|
||||||
|
@property (readonly) NSURL *fileURL;
|
||||||
|
@property (readonly) DVTFileDataType *fileDataTypePresumed;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEContainerItem : NSObject
|
||||||
|
@property (readonly) DVTFilePath *resolvedFilePath;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEGroup : IDEContainerItem
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEFileReference : IDEContainerItem
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDENavigableItem : NSObject
|
||||||
|
@property (readonly) IDENavigableItem *parentItem;
|
||||||
|
@property (readonly) id representedObject;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEFileNavigableItem : IDENavigableItem
|
||||||
|
@property (readonly) DVTFileDataType *documentType;
|
||||||
|
@property (readonly) NSURL *fileURL;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEStructureNavigator : NSObject
|
||||||
|
@property (retain) NSArray *selectedObjects;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDENavigableItemCoordinator : NSObject
|
||||||
|
- (id)structureNavigableItemForDocumentURL:(id)arg1
|
||||||
|
inWorkspace:(id)arg2
|
||||||
|
error:(id *)arg3;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDENavigatorArea : NSObject
|
||||||
|
- (id)currentNavigator;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEWorkspaceTabController : NSObject
|
||||||
|
@property (readonly) IDENavigatorArea *navigatorArea;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEDocumentController : NSDocumentController
|
||||||
|
+ (id)editorDocumentForNavigableItem:(id)arg1;
|
||||||
|
+ (id)retainedEditorDocumentForNavigableItem:(id)arg1 error:(id *)arg2;
|
||||||
|
+ (void)releaseEditorDocument:(id)arg1;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDESourceCodeDocument : NSDocument
|
||||||
|
- (DVTSourceTextStorage *)textStorage;
|
||||||
|
- (NSUndoManager *)undoManager;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDESourceCodeComparisonEditor : NSObject
|
||||||
|
@property (readonly) NSTextView *keyTextView;
|
||||||
|
@property (retain) NSDocument *primaryDocument;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDESourceCodeEditor : NSObject
|
||||||
|
@property (retain) NSTextView *textView;
|
||||||
|
- (IDESourceCodeDocument *)sourceCodeDocument;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEEditorContext : NSObject
|
||||||
|
- (id)editor; // returns the current editor. If the editor is the code editor,
|
||||||
|
// the class is `IDESourceCodeEditor`
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEEditorArea : NSObject
|
||||||
|
- (IDEEditorContext *)lastActiveEditorContext;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEWorkspaceWindowController : NSObject
|
||||||
|
@property (readonly) IDEWorkspaceTabController *activeWorkspaceTabController;
|
||||||
|
- (IDEEditorArea *)editorArea;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEWorkspace : NSObject
|
||||||
|
@property (readonly) DVTFilePath *representingFilePath;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface IDEWorkspaceDocument : NSDocument
|
||||||
|
@property (readonly) IDEWorkspace *workspace;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface SwiftLintAutoCorrectTRVSXcode : NSObject
|
||||||
|
|
||||||
|
+ (IDESourceCodeDocument *)sourceCodeDocument;
|
||||||
|
+ (NSTextView *)textView;
|
||||||
|
+ (BOOL)textViewHasSelection;
|
||||||
|
+ (NSRange)wholeRangeOfTextView;
|
||||||
|
+ (NSArray *)selectedFileNavigableItems;
|
||||||
|
|
||||||
|
@end
|
||||||
102
SwiftLintAutoCorrect/SwiftLintAutoCorrectTRVSXcode.m
Normal file
102
SwiftLintAutoCorrect/SwiftLintAutoCorrectTRVSXcode.m
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
//
|
||||||
|
// TRVSXcode.m
|
||||||
|
// ClangFormat
|
||||||
|
//
|
||||||
|
// Created by Travis Jeffery on 1/9/14.
|
||||||
|
// Copyright (c) 2014 Travis Jeffery. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SwiftLintAutoCorrectTRVSXcode.h"
|
||||||
|
|
||||||
|
@implementation SwiftLintAutoCorrectTRVSXcode
|
||||||
|
|
||||||
|
+ (id)currentEditor {
|
||||||
|
if ([[self windowController]
|
||||||
|
isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) {
|
||||||
|
IDEWorkspaceWindowController *workspaceController =
|
||||||
|
(IDEWorkspaceWindowController *)[self windowController];
|
||||||
|
IDEEditorArea *editorArea = [workspaceController editorArea];
|
||||||
|
IDEEditorContext *editorContext = [editorArea lastActiveEditorContext];
|
||||||
|
return [editorContext editor];
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (IDESourceCodeDocument *)sourceCodeDocument {
|
||||||
|
if ([[self currentEditor]
|
||||||
|
isKindOfClass:NSClassFromString(@"IDESourceCodeEditor")]) {
|
||||||
|
return [[self currentEditor] sourceCodeDocument];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([[self currentEditor]
|
||||||
|
isKindOfClass:NSClassFromString(@"IDESourceCodeComparisonEditor")] &&
|
||||||
|
[[[self currentEditor] primaryDocument]
|
||||||
|
isKindOfClass:NSClassFromString(@"IDESourceCodeDocument")]) {
|
||||||
|
return (IDESourceCodeDocument *)[[self currentEditor] primaryDocument];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSTextView *)textView {
|
||||||
|
if ([[self currentEditor]
|
||||||
|
isKindOfClass:NSClassFromString(@"IDESourceCodeEditor")]) {
|
||||||
|
return [[self currentEditor] textView];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([[self currentEditor]
|
||||||
|
isKindOfClass:NSClassFromString(@"IDESourceCodeComparisonEditor")]) {
|
||||||
|
return [[self currentEditor] keyTextView];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (BOOL)textViewHasSelection {
|
||||||
|
return [[self textView] selectedRange].length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSRange)wholeRangeOfTextView {
|
||||||
|
return NSMakeRange(0, [[[self textView] textStorage] length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSArray *)selectedFileNavigableItems {
|
||||||
|
if (![[self windowController]
|
||||||
|
isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")])
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
IDEWorkspaceWindowController *workspaceController =
|
||||||
|
(IDEWorkspaceWindowController *)[self windowController];
|
||||||
|
IDEWorkspaceTabController *workspaceTabController =
|
||||||
|
[workspaceController activeWorkspaceTabController];
|
||||||
|
IDENavigatorArea *navigatorArea = [workspaceTabController navigatorArea];
|
||||||
|
id currentNavigator = [navigatorArea currentNavigator];
|
||||||
|
|
||||||
|
if (![currentNavigator
|
||||||
|
isKindOfClass:NSClassFromString(@"IDEStructureNavigator")])
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
NSMutableArray *array = [NSMutableArray array];
|
||||||
|
|
||||||
|
[[currentNavigator selectedObjects]
|
||||||
|
enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||||
|
if (![obj isKindOfClass:NSClassFromString(@"IDEFileNavigableItem")])
|
||||||
|
return;
|
||||||
|
|
||||||
|
IDEFileNavigableItem *fileNavigableItem = obj;
|
||||||
|
NSString *uti = fileNavigableItem.documentType.identifier;
|
||||||
|
if ([[NSWorkspace sharedWorkspace]
|
||||||
|
type:uti
|
||||||
|
conformsToType:(NSString *)kUTTypeSourceCode]) {
|
||||||
|
[array addObject:fileNavigableItem];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSWindowController *)windowController {
|
||||||
|
return [[NSApp keyWindow] windowController];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
Loading…
Reference in a new issue