diff --git a/SwiftLintAutoCorrect.xcodeproj/project.pbxproj b/SwiftLintAutoCorrect.xcodeproj/project.pbxproj index 09eda13..05d95c1 100644 --- a/SwiftLintAutoCorrect.xcodeproj/project.pbxproj +++ b/SwiftLintAutoCorrect.xcodeproj/project.pbxproj @@ -7,17 +7,25 @@ objects = { /* 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 */; }; 04E4BF361CB25D3200BC7305 /* SwiftLintAutoCorrect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04E4BF351CB25D3200BC7305 /* SwiftLintAutoCorrect.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 */ /* Begin PBXFileReference section */ + 0498717C1CB27F8800C5F7B5 /* SwiftLintAutoCorrect-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftLintAutoCorrect-Bridging-Header.h"; sourceTree = ""; }; + 0498717D1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwiftLintAutoCorrectTRVSXcode.h; sourceTree = ""; }; + 0498717E1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwiftLintAutoCorrectTRVSXcode.m; sourceTree = ""; }; + 049871801CB28EBF00C5F7B5 /* Formatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Formatter.swift; sourceTree = ""; }; 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; }; 04E4BF351CB25D3200BC7305 /* SwiftLintAutoCorrect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLintAutoCorrect.swift; sourceTree = ""; }; 04E4BF371CB25D3200BC7305 /* NSObject_Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSObject_Extension.swift; sourceTree = ""; }; 04E4BF391CB25D3200BC7305 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 04E4BF401CB273A700BC7305 /* SaveHook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveHook.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXGroup section */ @@ -40,10 +48,15 @@ 04E4BF311CB25D3200BC7305 /* SwiftLintAutoCorrect */ = { isa = PBXGroup; children = ( + 0498717D1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.h */, + 0498717E1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.m */, 04E4BF351CB25D3200BC7305 /* SwiftLintAutoCorrect.swift */, 04E4BF371CB25D3200BC7305 /* NSObject_Extension.swift */, + 04E4BF401CB273A700BC7305 /* SaveHook.swift */, + 049871801CB28EBF00C5F7B5 /* Formatter.swift */, 04E4BF391CB25D3200BC7305 /* Info.plist */, 04E4BF321CB25D3200BC7305 /* Supporting Files */, + 0498717C1CB27F8800C5F7B5 /* SwiftLintAutoCorrect-Bridging-Header.h */, ); path = SwiftLintAutoCorrect; sourceTree = ""; @@ -123,7 +136,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0498717F1CB27F8900C5F7B5 /* SwiftLintAutoCorrectTRVSXcode.m in Sources */, 04E4BF361CB25D3200BC7305 /* SwiftLintAutoCorrect.swift in Sources */, + 049871811CB28EBF00C5F7B5 /* Formatter.swift in Sources */, + 04E4BF411CB273A700BC7305 /* SaveHook.swift in Sources */, 04E4BF381CB25D3200BC7305 /* NSObject_Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -209,6 +225,7 @@ 04E4BF3D1CB25D3200BC7305 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; DEPLOYMENT_LOCATION = YES; DSTROOT = "$(HOME)"; @@ -219,6 +236,8 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = net.ypresto.SwiftLintAutoCorrect; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "SwiftLintAutoCorrect/SwiftLintAutoCorrect-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; WRAPPER_EXTENSION = xcplugin; }; name = Debug; @@ -226,6 +245,7 @@ 04E4BF3E1CB25D3200BC7305 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; DEPLOYMENT_LOCATION = YES; DSTROOT = "$(HOME)"; @@ -236,6 +256,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = net.ypresto.SwiftLintAutoCorrect; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "SwiftLintAutoCorrect/SwiftLintAutoCorrect-Bridging-Header.h"; WRAPPER_EXTENSION = xcplugin; }; name = Release; @@ -259,6 +280,7 @@ 04E4BF3E1CB25D3200BC7305 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/SwiftLintAutoCorrect/Formatter.swift b/SwiftLintAutoCorrect/Formatter.swift new file mode 100644 index 0000000..6df47d4 --- /dev/null +++ b/SwiftLintAutoCorrect/Formatter.swift @@ -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(@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) + } +} diff --git a/SwiftLintAutoCorrect/NSObject_Extension.swift b/SwiftLintAutoCorrect/NSObject_Extension.swift index ff65933..c1ab320 100644 --- a/SwiftLintAutoCorrect/NSObject_Extension.swift +++ b/SwiftLintAutoCorrect/NSObject_Extension.swift @@ -2,7 +2,7 @@ // NSObject_Extension.swift // // 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 diff --git a/SwiftLintAutoCorrect/SaveHook.swift b/SwiftLintAutoCorrect/SaveHook.swift new file mode 100644 index 0000000..054a786 --- /dev/null +++ b/SwiftLintAutoCorrect/SaveHook.swift @@ -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 { + if SaveHook.tryOnSaveDocument(self) { + // NOTE: Call original method + swiftLintAutoCorrectSaveDocumentWithDelegate(delegate, didSaveSelector: didSaveSelector, contextInfo: contextInfo); + } + } +} \ No newline at end of file diff --git a/SwiftLintAutoCorrect/SwiftLintAutoCorrect-Bridging-Header.h b/SwiftLintAutoCorrect/SwiftLintAutoCorrect-Bridging-Header.h new file mode 100644 index 0000000..68fcfb7 --- /dev/null +++ b/SwiftLintAutoCorrect/SwiftLintAutoCorrect-Bridging-Header.h @@ -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" diff --git a/SwiftLintAutoCorrect/SwiftLintAutoCorrect.swift b/SwiftLintAutoCorrect/SwiftLintAutoCorrect.swift index 6a20e78..61dddcf 100755 --- a/SwiftLintAutoCorrect/SwiftLintAutoCorrect.swift +++ b/SwiftLintAutoCorrect/SwiftLintAutoCorrect.swift @@ -2,7 +2,7 @@ // SwiftLintAutoCorrect.swift // // 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 @@ -14,36 +14,76 @@ class SwiftLintAutoCorrect: NSObject { var bundle: NSBundle lazy var center = NSNotificationCenter.defaultCenter() + var enableMenuItem: NSMenuItem! + var disableMenuItem: NSMenuItem! + init(bundle: NSBundle) { self.bundle = bundle super.init() - center.addObserver(self, selector: Selector("createMenuItems"), name: NSApplicationDidFinishLaunchingNotification, object: nil) + center.addObserver(self, selector: #selector(SwiftLintAutoCorrect.onApplicationDidFinishLaunching), name: NSApplicationDidFinishLaunchingNotification, object: nil) } deinit { removeObserver() } - func removeObserver() { + private func removeObserver() { center.removeObserver(self) } - func createMenuItems() { + func onApplicationDidFinishLaunching() { + SaveHook.swizzle() + createMenuItems() + } + + private func createMenuItems() { removeObserver() - var item = NSApp.mainMenu!.itemWithTitle("Edit") - if item != nil { - var actionMenuItem = NSMenuItem(title:"Do Action", action:"doMenuAction", keyEquivalent:"") - actionMenuItem.target = self - item!.submenu!.addItem(NSMenuItem.separatorItem()) - item!.submenu!.addItem(actionMenuItem) - } + guard let item = NSApp.mainMenu!.itemWithTitle("Edit") else { return } + + let pluginMenu = NSMenu(title:"SwiftLint Auto Correct") + let pluginMenuItem = NSMenuItem(title:"SwiftLint Auto Correct", action: nil, keyEquivalent: "") + pluginMenuItem.submenu = pluginMenu + + 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() { - let error = NSError(domain: "Hello World!", code:42, userInfo:nil) - NSAlert(error: error).runModal() + func doAutoCorrect() { + let sourceCodeDocument: IDESourceCodeDocument = SwiftLintAutoCorrectTRVSXcode.sourceCodeDocument() + 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 } } - diff --git a/SwiftLintAutoCorrect/SwiftLintAutoCorrectTRVSXcode.h b/SwiftLintAutoCorrect/SwiftLintAutoCorrectTRVSXcode.h new file mode 100644 index 0000000..1a81c85 --- /dev/null +++ b/SwiftLintAutoCorrect/SwiftLintAutoCorrectTRVSXcode.h @@ -0,0 +1,134 @@ +// +// TRVSXcode.h +// ClangFormat +// +// Created by Travis Jeffery on 1/9/14. +// Copyright (c) 2014 Travis Jeffery. All rights reserved. +// + +#import + +// 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 diff --git a/SwiftLintAutoCorrect/SwiftLintAutoCorrectTRVSXcode.m b/SwiftLintAutoCorrect/SwiftLintAutoCorrectTRVSXcode.m new file mode 100644 index 0000000..1f1071e --- /dev/null +++ b/SwiftLintAutoCorrect/SwiftLintAutoCorrectTRVSXcode.m @@ -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