1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-18 11:11:34 +03:00
vimr/VimR/AppDelegate.swift

275 lines
8.0 KiB
Swift
Raw Normal View History

/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
2016-06-03 23:13:59 +03:00
import Cocoa
import RxSwift
import PureLayout
2016-10-15 11:05:11 +03:00
import Sparkle
2016-06-03 23:13:59 +03:00
/// Keep the rawValues in sync with Action in the `vimr` Python script.
private enum VimRUrlAction: String {
case activate = "activate"
case open = "open"
2016-08-21 15:02:20 +03:00
case newWindow = "open-in-new-window"
case separateWindows = "open-in-separate-windows"
}
2016-06-03 23:13:59 +03:00
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
2016-06-03 23:13:59 +03:00
2016-09-25 19:29:42 +03:00
@IBOutlet var debugMenu: NSMenuItem?
2016-10-15 11:05:11 +03:00
@IBOutlet var updater: SUUpdater?
2016-09-25 19:29:42 +03:00
fileprivate static let filePrefix = "file="
fileprivate static let cwdPrefix = "cwd="
2016-09-25 18:50:33 +03:00
fileprivate let disposeBag = DisposeBag()
2016-07-26 23:51:05 +03:00
2016-09-25 18:50:33 +03:00
fileprivate let changeSubject = PublishSubject<Any>()
fileprivate let changeSink: Observable<Any>
2016-07-27 00:40:20 +03:00
2016-09-25 18:50:33 +03:00
fileprivate let actionSubject = PublishSubject<Any>()
fileprivate let actionSink: Observable<Any>
2016-07-26 23:51:05 +03:00
2016-09-25 18:50:33 +03:00
fileprivate let prefStore: PrefStore
2016-07-24 21:32:07 +03:00
2016-09-25 18:50:33 +03:00
fileprivate let mainWindowManager: MainWindowManager
fileprivate let openQuicklyWindowManager: OpenQuicklyWindowManager
fileprivate let prefWindowComponent: PrefWindowComponent
fileprivate let fileItemService: FileItemService
2016-09-25 18:50:33 +03:00
fileprivate var quitWhenAllWindowsAreClosed = false
fileprivate var launching = true
2016-08-14 16:38:41 +03:00
2016-07-26 22:42:30 +03:00
override init() {
2016-07-27 00:40:20 +03:00
self.actionSink = self.actionSubject.asObservable()
2016-07-26 23:51:05 +03:00
self.changeSink = self.changeSubject.asObservable()
let actionAndChangeSink = [self.changeSink, self.actionSink].toMergedObservables()
2016-07-26 23:51:05 +03:00
self.prefStore = PrefStore(source: self.actionSink)
self.fileItemService = FileItemService(source: self.changeSink)
2016-09-11 15:28:56 +03:00
self.fileItemService.set(ignorePatterns: self.prefStore.data.general.ignorePatterns)
2016-07-27 00:40:20 +03:00
self.prefWindowComponent = PrefWindowComponent(source: self.changeSink, initialData: self.prefStore.data)
2016-09-07 21:12:18 +03:00
self.mainWindowManager = MainWindowManager(source: self.changeSink,
fileItemService: self.fileItemService,
initialData: self.prefStore.data)
self.openQuicklyWindowManager = OpenQuicklyWindowManager(source: actionAndChangeSink,
fileItemService: self.fileItemService)
2016-07-27 00:40:20 +03:00
2016-07-26 22:42:30 +03:00
super.init()
2016-09-01 21:10:40 +03:00
self.mainWindowManager.sink
.filter { $0 is MainWindowManagerAction }
.map { $0 as! MainWindowManagerAction }
2016-09-25 19:10:07 +03:00
.subscribe(onNext: { [unowned self] event in
2016-09-01 21:10:40 +03:00
switch event {
case .allWindowsClosed:
2016-09-01 21:10:40 +03:00
if self.quitWhenAllWindowsAreClosed {
NSApp.stop(self)
}
}
})
2016-09-25 19:10:07 +03:00
.addDisposableTo(self.disposeBag)
2016-07-26 23:51:05 +03:00
2016-10-15 11:05:11 +03:00
self.prefStore.sink
.filter { $0 is PrefData }
.map { $0 as! PrefData }
.subscribe(onNext: { [unowned self] prefData in
self.setSparkleUrl()
})
.addDisposableTo(self.disposeBag)
self.setSparkleUrl()
let changeFlows: [Flow] = [ self.prefStore, self.fileItemService ]
let actionFlows: [Flow] = [ self.prefWindowComponent, self.mainWindowManager ]
changeFlows
2016-07-26 23:51:05 +03:00
.map { $0.sink }
.toMergedObservables()
2016-07-26 23:51:05 +03:00
.subscribe(self.changeSubject)
.addDisposableTo(self.disposeBag)
2016-07-27 00:40:20 +03:00
actionFlows
2016-07-27 00:40:20 +03:00
.map { $0.sink }
.toMergedObservables()
2016-07-27 00:40:20 +03:00
.subscribe(self.actionSubject)
.addDisposableTo(self.disposeBag)
2016-07-26 22:42:30 +03:00
}
2016-10-15 11:05:11 +03:00
fileprivate func setSparkleUrl() {
DispatchUtils.gui {
if self.prefStore.data.advanced.useSnapshotUpdateChannel {
self.updater?.feedURL = URL(
string: "https://raw.githubusercontent.com/qvacua/vimr/develop/appcast_snapshot.xml"
2016-10-15 11:05:11 +03:00
)
} else {
self.updater?.feedURL = URL(
string: "https://raw.githubusercontent.com/qvacua/vimr/master/appcast.xml"
)
}
}
}
}
// MARK: - NSApplicationDelegate
extension AppDelegate {
2016-09-25 18:50:33 +03:00
func applicationWillFinishLaunching(_: Notification) {
self.launching = true
2016-09-25 18:50:33 +03:00
let appleEventManager = NSAppleEventManager.shared()
appleEventManager.setEventHandler(self,
2016-09-25 19:29:42 +03:00
andSelector: #selector(AppDelegate.handle(getUrlEvent:replyEvent:)),
forEventClass: UInt32(kInternetEventClass),
andEventID: UInt32(kAEGetURL))
}
2016-09-25 18:50:33 +03:00
func applicationDidFinishLaunching(_: Notification) {
self.launching = false
#if DEBUG
2016-09-25 19:29:42 +03:00
self.debugMenu?.isHidden = false
#endif
}
2016-09-25 18:50:33 +03:00
func applicationOpenUntitledFile(_ sender: NSApplication) -> Bool {
2016-08-14 16:38:41 +03:00
if self.launching {
if self.prefStore.data.general.openNewWindowWhenLaunching {
self.newDocument(self)
return true
}
} else {
if self.prefStore.data.general.openNewWindowOnReactivation {
self.newDocument(self)
return true
}
}
return false
}
2016-09-25 18:50:33 +03:00
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplicationTerminateReply {
if self.mainWindowManager.hasDirtyWindows() {
let alert = NSAlert()
2016-09-25 18:50:33 +03:00
alert.addButton(withTitle: "Cancel")
alert.addButton(withTitle: "Discard and Quit")
alert.messageText = "There are windows with unsaved buffers!"
2016-09-25 18:50:33 +03:00
alert.alertStyle = .warning
if alert.runModal() == NSAlertSecondButtonReturn {
self.quitWhenAllWindowsAreClosed = true
self.mainWindowManager.closeAllWindowsWithoutSaving()
}
2016-09-25 18:50:33 +03:00
return .terminateCancel
}
if self.mainWindowManager.hasMainWindow() {
self.quitWhenAllWindowsAreClosed = true
self.mainWindowManager.closeAllWindows()
2016-09-25 18:50:33 +03:00
return .terminateCancel
}
// There are no open main window, then just quit.
2016-09-25 18:50:33 +03:00
return .terminateNow
}
// For drag & dropping files on the App icon.
2016-09-25 18:50:33 +03:00
func application(_ sender: NSApplication, openFiles filenames: [String]) {
let urls = filenames.map { URL(fileURLWithPath: $0) }
2016-09-25 19:10:07 +03:00
_ = self.mainWindowManager.newMainWindow(urls: urls)
2016-09-25 19:29:42 +03:00
2016-09-25 18:50:33 +03:00
sender.reply(toOpenOrPrint: .success)
}
2016-06-03 23:13:59 +03:00
}
2016-08-09 23:18:46 +03:00
2016-08-20 23:35:31 +03:00
// MARK: - AppleScript
extension AppDelegate {
2016-09-25 19:29:42 +03:00
func handle(getUrlEvent event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
2016-09-25 18:50:33 +03:00
guard let urlString = event.paramDescriptor(forKeyword: UInt32(keyDirectObject))?.stringValue else {
return
}
2016-09-25 18:50:33 +03:00
guard let url = URL(string: urlString) else {
2016-08-20 23:35:31 +03:00
return
}
guard url.scheme == "vimr" else {
2016-08-20 23:35:31 +03:00
return
}
guard let rawAction = url.host else {
return
}
guard let action = VimRUrlAction(rawValue: rawAction) else {
return
}
2016-09-25 18:50:33 +03:00
let queryParams = url.query?.components(separatedBy: "&")
2016-08-25 00:06:39 +03:00
let urls = queryParams?
2016-09-25 19:29:42 +03:00
.filter { $0.hasPrefix(AppDelegate.filePrefix) }
.flatMap { $0.without(prefix: AppDelegate.filePrefix).removingPercentEncoding }
2016-09-25 18:50:33 +03:00
.map { URL(fileURLWithPath: $0) } ?? []
2016-08-25 00:06:39 +03:00
let cwd = queryParams?
2016-09-25 19:29:42 +03:00
.filter { $0.hasPrefix(AppDelegate.cwdPrefix) }
.flatMap { $0.without(prefix: AppDelegate.cwdPrefix).removingPercentEncoding }
2016-09-25 18:50:33 +03:00
.map { URL(fileURLWithPath: $0) }
2016-10-02 15:11:39 +03:00
.first ?? FileUtils.userHomeUrl
2016-08-21 15:02:20 +03:00
switch action {
2016-08-25 00:06:39 +03:00
case .activate, .newWindow:
2016-09-25 19:10:07 +03:00
_ = self.mainWindowManager.newMainWindow(urls: urls, cwd: cwd)
2016-08-21 15:02:20 +03:00
return
2016-08-25 00:06:39 +03:00
case .open:
self.mainWindowManager.openInKeyMainWindow(urls: urls, cwd: cwd)
2016-08-21 15:02:20 +03:00
return
case .separateWindows:
2016-09-25 19:10:07 +03:00
urls.forEach { _ = self.mainWindowManager.newMainWindow(urls: [$0], cwd: cwd) }
2016-08-21 15:02:20 +03:00
return
}
2016-08-20 23:35:31 +03:00
}
}
2016-08-09 23:18:46 +03:00
// MARK: - IBActions
extension AppDelegate {
@IBAction func openInNewWindow(_ sender: Any?) {
self.openDocument(sender)
}
@IBAction func showPrefWindow(_ sender: Any?) {
self.prefWindowComponent.show()
}
@IBAction func newDocument(_ sender: Any?) {
2016-09-25 19:10:07 +03:00
_ = self.mainWindowManager.newMainWindow()
2016-08-09 23:18:46 +03:00
}
2016-08-12 16:02:24 +03:00
// Invoked when no main window is open.
@IBAction func openDocument(_ sender: Any?) {
2016-08-11 23:37:41 +03:00
let panel = NSOpenPanel()
panel.canChooseDirectories = true
panel.allowsMultipleSelection = true
2016-09-25 18:50:33 +03:00
panel.begin { result in
2016-08-11 23:37:41 +03:00
guard result == NSFileHandlingPanelOKButton else {
return
}
let urls = panel.urls
let commonParentUrl = FileUtils.commonParent(of: urls)
_ = self.mainWindowManager.newMainWindow(urls: urls, cwd: commonParentUrl)
2016-08-11 23:37:41 +03:00
}
}
2016-08-09 23:18:46 +03:00
}