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

281 lines
8.3 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
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
2016-06-03 23:13:59 +03:00
enum Action {
2017-06-12 19:37:42 +03:00
case newMainWindow(urls: [URL], cwd: URL, nvimArgs: [String]?, cliPipePath: String?)
case openInKeyWindow(urls: [URL], cwd: URL)
2017-02-28 00:45:26 +03:00
case preferences
}
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
2016-07-26 22:42:30 +03:00
override init() {
let baseServerUrl = URL(string: "http://localhost:\(NetUtils.openPort())")!
var initialAppState: AppState
2017-03-11 19:00:55 +03:00
if let stateDict = UserDefaults.standard.value(forKey: PrefService.compatibleVersion) as? [String: Any] {
initialAppState = AppState(dict: stateDict) ?? .default
} else {
if let oldDict = UserDefaults.standard.value(forKey: PrefService.lastCompatibleVersion) as? [String: Any] {
initialAppState = Pref128ToCurrentConverter.appState(from: oldDict)
} else {
initialAppState = .default
}
}
initialAppState.mainWindowTemplate.htmlPreview.server
= Marked(baseServerUrl.appendingPathComponent(HtmlPreviewToolReducer.selectFirstPath))
2017-03-11 19:00:55 +03:00
self.stateContext = Context(baseServerUrl: baseServerUrl, state: initialAppState)
2017-04-26 20:40:42 +03:00
self.emit = self.stateContext.actionEmitter.typedEmit()
2017-02-06 20:57:50 +03:00
2017-02-27 23:17:40 +03:00
self.openNewMainWindowOnLaunch = initialAppState.openNewMainWindowOnLaunch
self.openNewMainWindowOnReactivation = initialAppState.openNewMainWindowOnReactivation
2017-02-28 13:10:04 +03:00
self.useSnapshot = initialAppState.useSnapshotUpdate
2017-02-27 23:17:40 +03:00
2017-01-22 16:22:05 +03:00
let source = self.stateContext.stateSource
2017-03-01 18:59:23 +03:00
self.uiRoot = UiRoot(source: source, emitter: self.stateContext.actionEmitter, state: initialAppState)
2016-07-26 22:42:30 +03:00
super.init()
2016-09-01 21:10:40 +03:00
source
2017-02-28 13:10:04 +03:00
.observeOn(MainScheduler.instance)
.subscribe(onNext: { appState in
self.hasMainWindows = !appState.mainWindows.isEmpty
self.hasDirtyWindows = appState.mainWindows.values.reduce(false) { $1.isDirty ? true : $0 }
2017-02-28 00:45:26 +03:00
self.openNewMainWindowOnLaunch = appState.openNewMainWindowOnLaunch
self.openNewMainWindowOnReactivation = appState.openNewMainWindowOnReactivation
2017-02-28 13:10:04 +03:00
if self.useSnapshot != appState.useSnapshotUpdate {
self.useSnapshot = appState.useSnapshotUpdate
2017-02-28 13:10:04 +03:00
self.setSparkleUrl(self.useSnapshot)
}
})
.disposed(by: self.disposeBag)
2016-07-26 22:42:30 +03:00
}
2016-10-15 11:05:11 +03:00
2017-02-28 12:43:45 +03:00
fileprivate let stateContext: Context
2017-04-22 16:56:13 +03:00
fileprivate let emit: (Action) -> Void
fileprivate let uiRoot: UiRoot
fileprivate var hasDirtyWindows = false
fileprivate var hasMainWindows = false
2017-02-28 13:10:04 +03:00
fileprivate var openNewMainWindowOnLaunch: Bool
fileprivate var openNewMainWindowOnReactivation: Bool
fileprivate var useSnapshot: Bool
2017-02-27 23:17:40 +03:00
fileprivate let disposeBag = DisposeBag()
fileprivate var launching = true
fileprivate func setSparkleUrl(_ snapshot: Bool) {
if snapshot {
self.updater?.feedURL = URL(
string: "https://raw.githubusercontent.com/qvacua/vimr/develop/appcast_snapshot.xml"
)
} 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
self.debugMenu?.isHidden = false
#endif
}
2016-09-25 18:50:33 +03:00
func applicationOpenUntitledFile(_ sender: NSApplication) -> Bool {
2017-02-27 23:17:40 +03:00
if self.launching {
if self.openNewMainWindowOnLaunch {
self.newDocument(self)
return true
}
} else {
if self.openNewMainWindowOnReactivation {
self.newDocument(self)
return true
}
}
return false
}
2016-09-25 18:50:33 +03:00
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplicationTerminateReply {
2017-04-15 11:38:07 +03:00
if self.hasDirtyWindows && self.uiRoot.hasMainWindows {
let alert = NSAlert()
alert.addButton(withTitle: "Cancel")
alert.addButton(withTitle: "Discard and Quit")
alert.messageText = "There are windows with unsaved buffers!"
alert.alertStyle = .warning
if alert.runModal() == NSAlertSecondButtonReturn {
2017-06-11 17:28:25 +03:00
self.uiRoot.prepareQuit()
return .terminateNow
}
return .terminateCancel
}
2017-04-15 11:38:07 +03:00
if self.uiRoot.hasMainWindows {
2017-06-11 17:28:25 +03:00
self.uiRoot.prepareQuit()
return .terminateNow
}
// 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) }
2017-06-12 19:37:42 +03:00
self.emit(.newMainWindow(urls: urls, cwd: FileUtils.userHomeUrl, nvimArgs: nil, cliPipePath: nil))
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?
.filter { $0.hasPrefix(filePrefix) }
.flatMap { $0.without(prefix: filePrefix).removingPercentEncoding }
.map { URL(fileURLWithPath: $0) } ?? []
2016-08-25 00:06:39 +03:00
let cwd = queryParams?
.filter { $0.hasPrefix(cwdPrefix) }
.flatMap { $0.without(prefix: cwdPrefix).removingPercentEncoding }
.map { URL(fileURLWithPath: $0) }
.first ?? FileUtils.userHomeUrl
2017-06-12 19:37:42 +03:00
let pipePath = queryParams?
.filter { $0.hasPrefix(pipePathPrefix) }
.flatMap { $0.without(prefix: pipePathPrefix).removingPercentEncoding }
.first ?? nil
2016-08-21 15:02:20 +03:00
switch action {
2016-08-25 00:06:39 +03:00
case .activate, .newWindow:
2017-06-12 19:37:42 +03:00
self.emit(.newMainWindow(urls: urls, cwd: cwd, nvimArgs: nil, cliPipePath: pipePath))
2016-08-25 00:06:39 +03:00
case .open:
2017-04-22 16:56:13 +03:00
self.emit(.openInKeyWindow(urls: urls, cwd: cwd))
2016-08-21 15:02:20 +03:00
case .separateWindows:
2017-06-12 19:37:42 +03:00
urls.forEach { self.emit(.newMainWindow(urls: [$0], cwd: cwd, nvimArgs: nil, cliPipePath: pipePath)) }
case .nvim:
guard let nvimArgs = queryParams?
.filter({ $0.hasPrefix(nvimArgsPrefix) })
.flatMap({ $0.without(prefix: nvimArgsPrefix).removingPercentEncoding }) else {
break
}
2017-06-12 19:37:42 +03:00
self.emit(.newMainWindow(urls: [], cwd: cwd, nvimArgs: nvimArgs, cliPipePath: pipePath))
2016-08-21 15:02:20 +03:00
}
2016-08-20 23:35:31 +03:00
}
}
2016-08-09 23:18:46 +03:00
// MARK: - IBActions
extension AppDelegate {
@IBAction func newDocument(_ sender: Any?) {
2017-06-12 19:37:42 +03:00
self.emit(.newMainWindow(urls: [], cwd: FileUtils.userHomeUrl, nvimArgs: nil, cliPipePath: nil))
}
@IBAction func openInNewWindow(_ sender: Any?) {
self.openDocument(sender)
}
@IBAction func showPrefWindow(_ sender: Any?) {
2017-04-22 16:56:13 +03:00
self.emit(.preferences)
}
2016-08-12 16:02:24 +03:00
// Invoked when no main window is open.
@IBAction func openDocument(_: 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)
2017-06-12 19:37:42 +03:00
self.emit(.newMainWindow(urls: urls, cwd: commonParentUrl, nvimArgs: nil, cliPipePath: nil))
2016-08-11 23:37:41 +03:00
}
}
2016-08-09 23:18:46 +03:00
}
/// Keep the rawValues in sync with Action in the `vimr` Python script.
fileprivate enum VimRUrlAction: String {
case activate = "activate"
case open = "open"
case newWindow = "open-in-new-window"
case separateWindows = "open-in-separate-windows"
case nvim = "nvim"
}
fileprivate let filePrefix = "file="
fileprivate let cwdPrefix = "cwd="
fileprivate let nvimArgsPrefix = "nvim-args="
2017-06-12 19:37:42 +03:00
fileprivate let pipePathPrefix = "pipe-path="