1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-11-24 11:37:32 +03:00

First commit for redesign, experimenting around

This commit is contained in:
Tae Won Ha 2017-01-17 19:47:59 +01:00
parent c6d485ff80
commit 7fd7181437
No known key found for this signature in database
GPG Key ID: E40743465B5B8B44
10 changed files with 1178 additions and 5 deletions

View File

@ -8,13 +8,16 @@
/* Begin PBXBuildFile section */
1929B0E0C3BC59F52713D5A2 /* FoundationCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B9AF20D7BD6E5C975128 /* FoundationCommons.swift */; };
1929B0FF696312F754BC96E2 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD8CBADC191CF8C85309 /* MainWindow.swift */; };
1929B165820D7177743B537A /* Component.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B39DA7AC4A9B62D7CD39 /* Component.swift */; };
1929B18A0D7C7407C51DB642 /* DataWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 1929BB6CFF4CC0B5E8B00C62 /* DataWrapper.m */; };
1929B1E05C116514C1D3A384 /* CocoaCategories.m in Sources */ = {isa = PBXBuildFile; fileRef = 1929B5C3F2F1CA4113DABFFD /* CocoaCategories.m */; };
1929B29B95AD176D57942E08 /* UiRootTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B457B9D0FA4D21F3751E /* UiRootTransformer.swift */; };
1929B3CEE0C1A1850E9CCE2F /* BasicTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B2FBE11048569391E092 /* BasicTypes.swift */; };
1929B3F5743967125F357C9F /* Matcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BEEB33113B0E33C3830F /* Matcher.swift */; };
1929B4145AA81F006BAF3B5C /* PreviewService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BB8BCA48637156F92945 /* PreviewService.swift */; };
1929B462CD4935AFF6D69457 /* FileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B7CB4863F80230C32D3C /* FileItem.swift */; };
1929B4FEE6EB56EF3F56B805 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B34FC23D805A8B29E8F7 /* Context.swift */; };
1929B53876E6952D378C2B30 /* ScoredFileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BDF9EBAF1D9D44399045 /* ScoredFileItem.swift */; };
1929B5CF6ECCBCC3FB5292CE /* HttpServerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B0D767ED19EE1281ECD9 /* HttpServerService.swift */; };
1929B6388EAF16C190B82955 /* FileItemIgnorePattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B69499B2569793350CEC /* FileItemIgnorePattern.swift */; };
@ -25,11 +28,15 @@
1929B93DBAD09835E428F610 /* PrefPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BB251F74BEFC82CEEF84 /* PrefPane.swift */; };
1929BA120290D6A2A61A4468 /* ArrayCommonsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B477E1E62433BC48E10B /* ArrayCommonsTest.swift */; };
1929BA3BB94B77E9AE051FE5 /* PreviewComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B8DA5AA33536F0082200 /* PreviewComponent.swift */; };
1929BAFF1E011321D3186EE6 /* UiRoot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD4149D5A25C82064DD8 /* UiRoot.swift */; };
1929BB4A9B2FA42A64CCCC76 /* MainWindowTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD83A13BF133741766CC /* MainWindowTransformer.swift */; };
1929BCF444CE7F1D14D421DE /* FileItemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B4778E20696E3AAFB69B /* FileItemTest.swift */; };
1929BD2F41D93ADFF43C1C98 /* NetUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1929B02440BC99C42F9EBD45 /* NetUtils.m */; };
1929BD3878A3A47B8D685CD2 /* AppDelegateTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B7A68B7109CEFAF105E8 /* AppDelegateTransformer.swift */; };
1929BD3F9E609BFADB27584B /* Scorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B9D510177918080BE39B /* Scorer.swift */; };
1929BD4CA2204E061A86A140 /* MatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BC19C1BC19246AFF1621 /* MatcherTests.swift */; };
1929BD52275A6570C666A7BA /* PreviewRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1EC32D8A26958FB39B1 /* PreviewRenderer.swift */; };
1929BE0DAEE9664C5BCFA211 /* States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BB6608B4F0E037CA0F4C /* States.swift */; };
1929BEB90DCDAF7A2B68C886 /* ColorUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BA6128BFDD54CA92F46E /* ColorUtils.swift */; };
1929BEFEABA0448306CDB6D4 /* FileItemIgnorePatternTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BBC84557C8351EC6183E /* FileItemIgnorePatternTest.swift */; };
1929BF81A40B4154D3EA33CE /* server_ui.m in Sources */ = {isa = PBXBuildFile; fileRef = 1929B93013228985F509C8F6 /* server_ui.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
@ -288,13 +295,16 @@
1929B1A51F076E088EF4CCA4 /* server_globals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = server_globals.h; sourceTree = "<group>"; };
1929B1EC32D8A26958FB39B1 /* PreviewRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewRenderer.swift; sourceTree = "<group>"; };
1929B2FBE11048569391E092 /* BasicTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicTypes.swift; sourceTree = "<group>"; };
1929B34FC23D805A8B29E8F7 /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = "<group>"; };
1929B39DA7AC4A9B62D7CD39 /* Component.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Component.swift; sourceTree = "<group>"; };
1929B3A98687DF171307AAC8 /* FileItemService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemService.swift; sourceTree = "<group>"; };
1929B457B9D0FA4D21F3751E /* UiRootTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UiRootTransformer.swift; sourceTree = "<group>"; };
1929B4778E20696E3AAFB69B /* FileItemTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemTest.swift; sourceTree = "<group>"; };
1929B477E1E62433BC48E10B /* ArrayCommonsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayCommonsTest.swift; sourceTree = "<group>"; };
1929B5C3F2F1CA4113DABFFD /* CocoaCategories.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CocoaCategories.m; sourceTree = "<group>"; };
1929B5D977261F1EBFA9E8F1 /* FileUtilsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtilsTest.swift; sourceTree = "<group>"; };
1929B69499B2569793350CEC /* FileItemIgnorePattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemIgnorePattern.swift; sourceTree = "<group>"; };
1929B7A68B7109CEFAF105E8 /* AppDelegateTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegateTransformer.swift; sourceTree = "<group>"; };
1929B7CB4863F80230C32D3C /* FileItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItem.swift; sourceTree = "<group>"; };
1929B8DA5AA33536F0082200 /* PreviewComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewComponent.swift; sourceTree = "<group>"; };
1929B93013228985F509C8F6 /* server_ui.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = server_ui.m; sourceTree = "<group>"; };
@ -304,10 +314,14 @@
1929BA8AC40B901B20F20B71 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
1929BADEB143008EFA6F3318 /* NetUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetUtils.h; sourceTree = "<group>"; };
1929BB251F74BEFC82CEEF84 /* PrefPane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefPane.swift; sourceTree = "<group>"; };
1929BB6608B4F0E037CA0F4C /* States.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = States.swift; sourceTree = "<group>"; };
1929BB6CFF4CC0B5E8B00C62 /* DataWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataWrapper.m; sourceTree = "<group>"; };
1929BB8BCA48637156F92945 /* PreviewService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewService.swift; sourceTree = "<group>"; };
1929BBC84557C8351EC6183E /* FileItemIgnorePatternTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileItemIgnorePatternTest.swift; sourceTree = "<group>"; };
1929BC19C1BC19246AFF1621 /* MatcherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatcherTests.swift; sourceTree = "<group>"; };
1929BD4149D5A25C82064DD8 /* UiRoot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UiRoot.swift; sourceTree = "<group>"; };
1929BD83A13BF133741766CC /* MainWindowTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowTransformer.swift; sourceTree = "<group>"; };
1929BD8CBADC191CF8C85309 /* MainWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = "<group>"; };
1929BDF9EBAF1D9D44399045 /* ScoredFileItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScoredFileItem.swift; sourceTree = "<group>"; };
1929BE69CF9AB1A10D0DD4F2 /* CocoaCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CocoaCategories.h; sourceTree = "<group>"; };
1929BEEB33113B0E33C3830F /* Matcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matcher.swift; sourceTree = "<group>"; };
@ -503,6 +517,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1929B32401E8914DE9BF76CA /* UI */ = {
isa = PBXGroup;
children = (
1929BD8CBADC191CF8C85309 /* MainWindow.swift */,
1929BD4149D5A25C82064DD8 /* UiRoot.swift */,
);
name = UI;
sourceTree = "<group>";
};
1929B41F745CDCDFE09ACDCF /* resources */ = {
isa = PBXGroup;
children = (
@ -511,6 +534,16 @@
path = resources;
sourceTree = "<group>";
};
1929B5E773BDB3B4EE9D00C1 /* Transformers */ = {
isa = PBXGroup;
children = (
1929B7A68B7109CEFAF105E8 /* AppDelegateTransformer.swift */,
1929B457B9D0FA4D21F3751E /* UiRootTransformer.swift */,
1929BD83A13BF133741766CC /* MainWindowTransformer.swift */,
);
name = Transformers;
sourceTree = "<group>";
};
1929BA610ADEA2BA4424FBE5 /* Preview */ = {
isa = PBXGroup;
children = (
@ -521,6 +554,17 @@
name = Preview;
sourceTree = "<group>";
};
1929BA652D3B88FC071531EC /* Redesign */ = {
isa = PBXGroup;
children = (
1929BB6608B4F0E037CA0F4C /* States.swift */,
1929B34FC23D805A8B29E8F7 /* Context.swift */,
1929B32401E8914DE9BF76CA /* UI */,
1929B5E773BDB3B4EE9D00C1 /* Transformers */,
);
name = Redesign;
sourceTree = "<group>";
};
1929BFC86BF38D341F2DDCBD /* NeoVim Objects */ = {
isa = PBXGroup;
children = (
@ -815,6 +859,7 @@
4B6423941D8EFD6100FC78C8 /* Workspace */,
4B97E2CF1D33F92200FC0660 /* resources */,
1929B0D767ED19EE1281ECD9 /* HttpServerService.swift */,
1929BA652D3B88FC071531EC /* Redesign */,
);
path = VimR;
sourceTree = "<group>";
@ -1310,6 +1355,13 @@
1929BD52275A6570C666A7BA /* PreviewRenderer.swift in Sources */,
1929BD2F41D93ADFF43C1C98 /* NetUtils.m in Sources */,
1929B5CF6ECCBCC3FB5292CE /* HttpServerService.swift in Sources */,
1929BE0DAEE9664C5BCFA211 /* States.swift in Sources */,
1929B4FEE6EB56EF3F56B805 /* Context.swift in Sources */,
1929B0FF696312F754BC96E2 /* MainWindow.swift in Sources */,
1929BD3878A3A47B8D685CD2 /* AppDelegateTransformer.swift in Sources */,
1929BAFF1E011321D3186EE6 /* UiRoot.swift in Sources */,
1929B29B95AD176D57942E08 /* UiRootTransformer.swift in Sources */,
1929BB4A9B2FA42A64CCCC76 /* MainWindowTransformer.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -19,6 +19,11 @@ private enum VimRUrlAction: String {
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
enum Action {
case newMainWindow(urls: [URL], cwd: URL)
}
@IBOutlet var debugMenu: NSMenuItem?
@IBOutlet var updater: SUUpdater?
@ -45,6 +50,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
fileprivate var launching = true
override init() {
let source = self.stateContext.stateSource.mapOmittingNil { $0 as? MainWindowStates }
self.uiRoot = UiRoot(source: source,
emitter: self.stateContext.actionEmitter,
state: AppState.default.mainWindows)
self.actionSink = self.actionSubject.asObservable()
self.changeSink = self.changeSubject.asObservable()
let actionAndChangeSink = [self.changeSink, self.actionSink].toMergedObservables()
@ -116,6 +127,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
}
fileprivate let stateContext = StateContext()
fileprivate let uiRoot: UiRoot
}
// MARK: - NSApplicationDelegate
@ -243,6 +257,10 @@ extension AppDelegate {
// MARK: - IBActions
extension AppDelegate {
@IBAction func newDocument(_ sender: Any?) {
self.stateContext.actionEmitter.emit(Action.newMainWindow(urls: [], cwd: FileUtils.userHomeUrl))
}
@IBAction func openInNewWindow(_ sender: Any?) {
self.openDocument(sender)
}
@ -251,10 +269,6 @@ extension AppDelegate {
self.prefWindowComponent.show()
}
@IBAction func newDocument(_ sender: Any?) {
_ = self.mainWindowManager.newMainWindow()
}
// Invoked when no main window is open.
@IBAction func openDocument(_ sender: Any?) {
let panel = NSOpenPanel()
@ -268,7 +282,7 @@ extension AppDelegate {
let urls = panel.urls
let commonParentUrl = FileUtils.commonParent(of: urls)
_ = self.mainWindowManager.newMainWindow(urls: urls, cwd: commonParentUrl)
self.stateContext.actionEmitter.emit(Action.newMainWindow(urls: urls, cwd: commonParentUrl))
}
}
}

View File

@ -0,0 +1,32 @@
//
// Created by Tae Won Ha on 1/16/17.
// Copyright (c) 2017 Tae Won Ha. All rights reserved.
//
import Foundation
import RxSwift
class AppDelegateTransformer: Transformer {
typealias Pair = StateActionPair<MainWindowStates, AppDelegate.Action>
func transform(_ source: Observable<Pair>) -> Observable<Pair> {
return source.map { pair in
switch pair.action {
case let .newMainWindow(urls, cwd):
var state = pair.state
var mainWindow = state.last
mainWindow.uuid = UUID().uuidString
mainWindow.urlsToOpen = urls.toDict { url in MainWindow.OpenMode.default }
mainWindow.cwd = cwd
state.current[mainWindow.uuid] = mainWindow
return StateActionPair(state: state, action: pair.action)
}
}
}
}

98
VimR/Context.swift Normal file
View File

@ -0,0 +1,98 @@
//
// Created by Tae Won Ha on 1/16/17.
// Copyright (c) 2017 Tae Won Ha. All rights reserved.
//
import Foundation
import RxSwift
class DummyService: Transformer {
typealias State = MainWindowStates
typealias Action = AppDelegate.Action
func transform(_ source: Observable<StateActionPair<State, Action>>) -> Observable<StateActionPair<State, Action>> {
NSLog("\(#function) dummy transform")
return source
}
}
class StateContext {
let stateSource: Observable<Any>
let actionEmitter = Emitter<Any>()
init() {
self.stateSource = self.stateSubject.asObservable()
let actionSource = self.actionEmitter.observable
actionSource
.mapOmittingNil { $0 as? AppDelegate.Action }
.map { StateActionPair(state: self.appState.mainWindows, action: $0) }
.transform(by: [appDelegateTransformer])
.map { $0.state }
.subscribe(onNext: { state in
self.appState.mainWindows = state
self.stateSubject.onNext(state)
})
.addDisposableTo(self.disposeBag)
actionSource
.mapOmittingNil { $0 as? UuidAction<MainWindow.Action> }
.map { StateActionPair(state: self.appState.mainWindows, action: $0) }
.transform(by: self.uiRootTransformer)
.map { $0.state }
.subscribe(onNext: { state in
self.appState.mainWindows = state
self.stateSubject.onNext(state)
})
.addDisposableTo(self.disposeBag)
actionSource
.mapOmittingNil { $0 as? UuidAction<MainWindow.Action> }
.mapOmittingNil { action in
guard let mainWindowState = self.appState.mainWindows.current[action.uuid] else {
return nil
}
return StateActionPair(state: UuidState(uuid: action.uuid, state: mainWindowState), action: action.payload)
}
.transform(by: self.mainWindowTransformer)
.subscribe(onNext: { pair in
self.appState.mainWindows.current[pair.state.uuid] = pair.state.payload
self.stateSubject.onNext(pair.state)
})
.addDisposableTo(self.disposeBag)
actionSource
.subscribe(onNext: { action in
NSLog("ACTION: \(action)")
})
.addDisposableTo(self.disposeBag)
stateSource
.subscribe(onNext: { state in
NSLog("STATE : \(self.appState.mainWindows.current)")
})
.addDisposableTo(self.disposeBag)
}
fileprivate let stateSubject = PublishSubject<Any>()
fileprivate let disposeBag = DisposeBag()
fileprivate var appState = AppState.default
fileprivate let appDelegateTransformer = AppDelegateTransformer()
fileprivate let uiRootTransformer = UiRootTransformer()
fileprivate let mainWindowTransformer = MainWindowTransformer()
}
extension Observable {
fileprivate func transform<T:Transformer>(by transformers: [T]) -> Observable<Element> where T.Pair == Element {
return transformers.reduce(self) { (prevSource, transformer) in transformer.transform(prevSource) }
}
fileprivate func transform<T:Transformer>(by transformer: T) -> Observable<Element> where T.Pair == Element {
return transformer.transform(self)
}
}

215
VimR/MainWindow.swift Normal file
View File

@ -0,0 +1,215 @@
//
// Created by Tae Won Ha on 1/16/17.
// Copyright (c) 2017 Tae Won Ha. All rights reserved.
//
import Cocoa
import RxSwift
import SwiftNeoVim
import PureLayout
protocol UiComponent {
associatedtype StateType
init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType)
}
class MainWindow: NSObject,
UiComponent,
NeoVimViewDelegate,
NSWindowDelegate {
typealias StateType = State
enum Action {
case cd(to: URL)
case setBufferList([NeoVimBuffer])
case becomeKey
case close
}
enum OpenMode {
case `default`
case currentTab
case newTab
case horizontalSplit
case verticalSplit
}
required init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType) {
self.uuid = state.uuid
self.emitter = emitter
self.neoVimView = NeoVimView(frame: CGRect.zero,
config: NeoVimView.Config(useInteractiveZsh: state.isUseInteractiveZsh))
self.neoVimView.configureForAutoLayout()
self.workspace = Workspace(mainView: self.neoVimView)
self.windowController = NSWindowController(windowNibName: "MainWindow")
super.init()
self.addViews()
self.windowController.window?.delegate = self
source
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [unowned self] state in
})
.addDisposableTo(self.disposeBag)
let neoVimView = self.neoVimView
neoVimView.delegate = self
neoVimView.font = state.font
neoVimView.linespacing = state.linespacing
neoVimView.usesLigatures = state.isUseLigatures
if neoVimView.cwd != state.cwd {
self.neoVimView.cwd = state.cwd
}
// If we don't call the following in the next tick, only half of the existing swap file warning is displayed.
// Dunno why...
DispatchUtils.gui {
state.urlsToOpen.forEach { (url: URL, openMode: OpenMode) in
switch openMode {
case .default:
self.neoVimView.open(urls: [url])
case .currentTab:
self.neoVimView.openInCurrentTab(url: url)
case .newTab:
self.neoVimView.openInNewTab(urls: [url])
case .horizontalSplit:
self.neoVimView.openInHorizontalSplit(urls: [url])
case .verticalSplit:
self.neoVimView.openInVerticalSplit(urls: [url])
}
}
}
self.window.makeFirstResponder(neoVimView)
}
func show() {
self.windowController.showWindow(self)
}
fileprivate func addViews() {
let contentView = self.window.contentView!
contentView.addSubview(self.workspace)
self.workspace.autoPinEdgesToSuperviewEdges()
}
fileprivate let emitter: ActionEmitter
fileprivate let disposeBag = DisposeBag()
fileprivate let uuid: String
fileprivate let windowController: NSWindowController
fileprivate var window: NSWindow { return self.windowController.window! }
fileprivate let workspace: Workspace
fileprivate let neoVimView: NeoVimView
}
// MARK: - NeoVimViewDelegate
extension MainWindow {
func neoVimStopped() {
self.windowController.close()
}
func set(title: String) {
self.window.title = title
}
func set(dirtyStatus: Bool) {
self.windowController.setDocumentEdited(dirtyStatus)
}
func cwdChanged() {
self.emitter.emit(UuidAction(uuid: self.uuid, action: Action.cd(to: self.neoVimView.cwd)))
}
func bufferListChanged() {
let buffers = self.neoVimView.allBuffers()
self.emitter.emit(UuidAction(uuid: self.uuid, action: Action.setBufferList(buffers)))
}
func currentBufferChanged(_ currentBuffer: NeoVimBuffer) {
// self.publish(event: MainWindowAction.currentBufferChanged(mainWindow: self, buffer: currentBuffer))
}
func tabChanged() {
// guard let currentBuffer = self.neoVimView.currentBuffer() else {
// return
// }
//
// self.publish(event: MainWindowAction.currentBufferChanged(mainWindow: self, buffer: currentBuffer))
}
func ipcBecameInvalid(reason: String) {
let alert = NSAlert()
alert.addButton(withTitle: "Close")
alert.messageText = "Sorry, an error occurred."
alert.informativeText = "VimR encountered an error from which it cannot recover. This window will now close.\n"
+ reason
alert.alertStyle = .critical
alert.beginSheetModal(for: self.window) { response in
self.windowController.close()
}
}
func scroll() {
// self.scrollFlow.publish(event: ScrollAction.scroll(to: self.neoVimView.currentPosition))
}
func cursor(to position: Position) {
// self.scrollFlow.publish(event: ScrollAction.cursor(to: self.neoVimView.currentPosition))
}
}
// MARK: - NSWindowDelegate
extension MainWindow {
func windowDidBecomeKey(_: Notification) {
self.emitter.emit(UuidAction(uuid: self.uuid, action: Action.becomeKey))
}
func windowWillClose(_: Notification) {
self.emitter.emit(UuidAction(uuid: self.uuid, action: Action.close))
}
func windowShouldClose(_: Any) -> Bool {
guard self.neoVimView.isCurrentBufferDirty() else {
self.neoVimView.closeCurrentTab()
return false
}
let alert = NSAlert()
alert.addButton(withTitle: "Cancel")
alert.addButton(withTitle: "Discard and Close")
alert.messageText = "The current buffer has unsaved changes!"
alert.alertStyle = .warning
alert.beginSheetModal(for: self.window, completionHandler: { response in
if response == NSAlertSecondButtonReturn {
self.neoVimView.closeCurrentTabWithoutSaving()
}
})
return false
}
}

View File

@ -0,0 +1,38 @@
//
// Created by Tae Won Ha on 1/17/17.
// Copyright (c) 2017 Tae Won Ha. All rights reserved.
//
import Foundation
import RxSwift
class MainWindowTransformer: Transformer {
typealias Pair = StateActionPair<UuidState<MainWindow.State>, MainWindow.Action>
func transform(_ source: Observable<Pair>) -> Observable<Pair> {
return source.map { pair in
var state = pair.state.payload
switch pair.action {
case let .cd(to: cwd):
if state.cwd != cwd {
state.cwd = cwd
}
case let .setBufferList(buffers):
buffers
.flatMap { $0.url }
.forEach { state.urlsToOpen.removeValue(forKey: $0) }
state.buffers = buffers
default:
break
}
return StateActionPair(state: UuidState(uuid: state.uuid, state: state), action: pair.action)
}
}
}

628
VimR/States.swift Normal file
View File

@ -0,0 +1,628 @@
//
// Created by Tae Won Ha on 1/13/17.
// Copyright (c) 2017 Tae Won Ha. All rights reserved.
//
import Foundation
import RxSwift
typealias ActionEmitter = Emitter<Any>
class Emitter<T> {
let observable: Observable<T>
init() {
self.observable = self.subject.asObservable().observeOn(scheduler)
}
func emit(_ action: T) {
self.subject.onNext(action)
}
deinit {
self.subject.onCompleted()
}
fileprivate let scheduler = SerialDispatchQueueScheduler(qos: .userInitiated)
fileprivate let subject = PublishSubject<T>()
}
class StateActionPair<S, A> {
let state: S
let action: A
init(state: S, action: A) {
self.state = state
self.action = action
}
}
class UuidAction<A>: CustomStringConvertible {
let uuid: String
let payload: A
var description: String {
return "UuidAction(uuid: \(uuid), payload: \(String(reflecting: payload)))"
}
init(uuid: String, action: A) {
self.uuid = uuid
self.payload = action
}
}
class UuidState<S>: CustomStringConvertible {
let uuid: String
let payload: S
var description: String {
return "UuidAction(uuid: \(uuid), payload: \(String(reflecting: payload)))"
}
init(uuid: String, state: S) {
self.uuid = uuid
self.payload = state
}
}
protocol Transformer {
associatedtype Pair
func transform(_ source: Observable<Pair>) -> Observable<Pair>
}
protocol PersistableState {
init?(dict: [String: Any])
func dict() -> [String: Any]
}
struct MainWindowStates: PersistableState {
var last: MainWindow.State
var current: [String: MainWindow.State]
init(last: MainWindow.State) {
self.last = last
self.current = [:]
}
init?(dict: [String: Any]) {
guard let lastDict: [String: Any] = PrefUtils.value(from: dict, for: MainWindowStates.last) else {
return nil
}
guard let last = MainWindow.State(dict: lastDict) else {
return nil
}
self.init(last: last)
}
func dict() -> [String: Any] {
return [
MainWindowStates.last: self.last.dict(),
]
}
fileprivate static let last = "last"
}
struct AppState: PersistableState {
static let `default` = AppState(general: GeneralPrefState.default,
appearance: AppearancePrefState.default,
advanced: AdvancedPrefState.default,
mainWindow: MainWindow.State.default)
var general: GeneralPrefState
var appearance: AppearancePrefState
var advanced: AdvancedPrefState
var mainWindows: MainWindowStates
init(general: GeneralPrefState,
appearance: AppearancePrefState,
advanced: AdvancedPrefState,
mainWindow: MainWindow.State) {
self.general = general
self.appearance = appearance
self.advanced = advanced
self.mainWindows = MainWindowStates(last: mainWindow)
}
init?(dict: [String: Any]) {
guard let generalDict: [String: Any] = PrefUtils.value(from: dict, for: AppState.general),
let appearanceDict: [String: Any] = PrefUtils.value(from: dict, for: AppState.appearance),
let advancedDict: [String: Any] = PrefUtils.value(from: dict, for: AppState.advanced),
let mainWindowDict: [String: Any] = PrefUtils.value(from: dict, for: AppState.mainWindow)
else {
return nil
}
guard let general = GeneralPrefState(dict: generalDict),
let appearance = AppearancePrefState(dict: appearanceDict),
let advanced = AdvancedPrefState(dict: advancedDict),
let mainWindow = MainWindow.State(dict: mainWindowDict)
else {
return nil
}
self.init(general: general, appearance: appearance, advanced: advanced, mainWindow: mainWindow)
}
func dict() -> [String: Any] {
return [
AppState.general: self.general.dict(),
AppState.appearance: self.appearance.dict(),
AppState.advanced: self.advanced.dict(),
AppState.mainWindow: self.mainWindows.dict(),
]
}
fileprivate static let general = "general"
fileprivate static let appearance = "appearance"
fileprivate static let advanced = "advanced"
fileprivate static let mainWindow = "mainWindow"
}
struct GeneralPrefState: Equatable, PersistableState {
static func ==(left: GeneralPrefState, right: GeneralPrefState) -> Bool {
return left.openNewWindowWhenLaunching == right.openNewWindowWhenLaunching
&& left.openNewWindowOnReactivation == right.openNewWindowOnReactivation
&& left.ignorePatterns == right.ignorePatterns
}
static let `default` = GeneralPrefState(openNewWindowWhenLaunching: true,
openNewWindowOnReactivation: true,
ignorePatterns: GeneralPrefState.defaultIgnorePatterns)
var openNewWindowWhenLaunching: Bool
var openNewWindowOnReactivation: Bool
var ignorePatterns: Set<FileItemIgnorePattern>
init(openNewWindowWhenLaunching: Bool,
openNewWindowOnReactivation: Bool,
ignorePatterns: Set<FileItemIgnorePattern>) {
self.openNewWindowWhenLaunching = openNewWindowWhenLaunching
self.openNewWindowOnReactivation = openNewWindowOnReactivation
self.ignorePatterns = ignorePatterns
}
init?(dict: [String: Any]) {
guard let openNewWinWhenLaunching = PrefUtils.bool(from: dict, for: GeneralPrefState.openNewWindowWhenLaunching),
let openNewWinOnReactivation = PrefUtils.bool(from: dict, for: GeneralPrefState.openNewWindowOnReactivation),
let ignorePatternsStr = dict[GeneralPrefState.ignorePatterns] as? String
else {
return nil
}
self.init(openNewWindowWhenLaunching: openNewWinWhenLaunching,
openNewWindowOnReactivation: openNewWinOnReactivation,
ignorePatterns: PrefUtils.ignorePatterns(fromString: ignorePatternsStr))
}
func dict() -> [String: Any] {
return [
GeneralPrefState.openNewWindowWhenLaunching: self.openNewWindowWhenLaunching,
GeneralPrefState.openNewWindowOnReactivation: self.openNewWindowOnReactivation,
GeneralPrefState.ignorePatterns: PrefUtils.ignorePatternString(fromSet: self.ignorePatterns),
]
}
fileprivate static let defaultIgnorePatterns = Set(
["*/.git", "*.o", "*.d", "*.dia"].map(FileItemIgnorePattern.init)
)
fileprivate static let openNewWindowWhenLaunching = "open-new-window-when-launching"
fileprivate static let openNewWindowOnReactivation = "open-new-window-on-reactivation"
fileprivate static let ignorePatterns = "ignore-patterns"
}
struct AppearancePrefState: Equatable, PersistableState {
static func ==(left: AppearancePrefState, right: AppearancePrefState) -> Bool {
return left.editorUsesLigatures == right.editorUsesLigatures
&& left.editorFont.isEqual(to: right.editorFont)
&& left.editorLinespacing == right.editorLinespacing
}
static let `default` = AppearancePrefState(editorFont: NeoVimView.defaultFont,
editorLinespacing: NeoVimView.defaultLinespacing,
editorUsesLigatures: false)
var editorFont: NSFont
var editorLinespacing: CGFloat
var editorUsesLigatures: Bool
init(editorFont: NSFont, editorLinespacing: CGFloat, editorUsesLigatures: Bool) {
self.editorFont = editorFont
self.editorLinespacing = editorLinespacing
self.editorUsesLigatures = editorUsesLigatures
}
init?(dict: [String: Any]) {
guard let editorFontName = dict[AppearancePrefState.editorFontName] as? String,
let fEditorFontSize = PrefUtils.float(from: dict, for: AppearancePrefState.editorFontSize),
let fEditorLinespacing = PrefUtils.float(from: dict, for: AppearancePrefState.editorLinespacing),
let editorUsesLigatures = PrefUtils.bool(from: dict, for: AppearancePrefState.editorUsesLigatures)
else {
return nil
}
self.init(editorFont: PrefUtils.saneFont(editorFontName, fontSize: CGFloat(fEditorFontSize)),
editorLinespacing: CGFloat(fEditorLinespacing),
editorUsesLigatures: editorUsesLigatures)
}
func dict() -> [String: Any] {
return [
AppearancePrefState.editorFontName: self.editorFont.fontName,
AppearancePrefState.editorFontSize: Float(self.editorFont.pointSize),
AppearancePrefState.editorLinespacing: Float(self.editorLinespacing),
AppearancePrefState.editorUsesLigatures: self.editorUsesLigatures,
]
}
fileprivate static let editorFontName = "editor-font-name"
fileprivate static let editorFontSize = "editor-font-size"
fileprivate static let editorLinespacing = "editor-linespacing"
fileprivate static let editorUsesLigatures = "editor-uses-ligatures"
}
struct AdvancedPrefState: Equatable, PersistableState {
static func ==(left: AdvancedPrefState, right: AdvancedPrefState) -> Bool {
return left.useSnapshotUpdateChannel == right.useSnapshotUpdateChannel
&& left.useInteractiveZsh == right.useInteractiveZsh
}
static let `default` = AdvancedPrefState(useSnapshotUpdateChannel: false, useInteractiveZsh: false)
let useSnapshotUpdateChannel: Bool
let useInteractiveZsh: Bool
init(useSnapshotUpdateChannel: Bool, useInteractiveZsh: Bool) {
self.useSnapshotUpdateChannel = useSnapshotUpdateChannel
self.useInteractiveZsh = useInteractiveZsh
}
init?(dict: [String: Any]) {
guard let useSnapshot = PrefUtils.bool(from: dict, for: AdvancedPrefState.useSnapshotUpdateChannel),
let useInteractiveZsh = PrefUtils.bool(from: dict, for: AdvancedPrefState.useInteractiveZsh)
else {
return nil
}
self.init(useSnapshotUpdateChannel: useSnapshot, useInteractiveZsh: useInteractiveZsh)
}
func dict() -> [String: Any] {
return [
AdvancedPrefState.useSnapshotUpdateChannel: self.useSnapshotUpdateChannel,
AdvancedPrefState.useInteractiveZsh: self.useInteractiveZsh,
]
}
fileprivate static let useSnapshotUpdateChannel = "use-snapshot-update-channel"
fileprivate static let useInteractiveZsh = "use-interactive-zsh"
}
extension MainWindow {
struct State: PersistableState {
static let `default` = State(isAllToolsVisible: true,
isToolButtonsVisible: true)
var isAllToolsVisible = true
var isToolButtonsVisible = true
// transient
var uuid = UUID().uuidString
var buffers = [NeoVimBuffer]()
var cwd = FileUtils.userHomeUrl
var isDirty = false
var font = NSFont.userFixedPitchFont(ofSize: 13)!
var linespacing: CGFloat = 1
var isUseLigatures = false
var isUseInteractiveZsh = false
// transient^2
var urlsToOpen = [URL: OpenMode]()
init(isAllToolsVisible: Bool, isToolButtonsVisible: Bool) {
self.isAllToolsVisible = isAllToolsVisible
self.isToolButtonsVisible = isToolButtonsVisible
}
init?(dict: [String: Any]) {
guard let isAllToolsVisible = PrefUtils.bool(from: dict, for: State.isAllToolsVisible),
let isToolButtonsVisible = PrefUtils.bool(from: dict, for: State.isToolButtonsVisible)
else {
return nil
}
self.init(isAllToolsVisible: isAllToolsVisible, isToolButtonsVisible: isToolButtonsVisible)
}
func dict() -> [String: Any] {
return [
State.isAllToolsVisible: self.isAllToolsVisible,
State.isToolButtonsVisible: self.isToolButtonsVisible,
]
}
fileprivate static let isAllToolsVisible = "is-all-tools-visible"
fileprivate static let isToolButtonsVisible = "is-tool-buttons-visible"
}
}
//struct ToolsState: PersistableState {
//
// static let `default` = ToolsState(fileBrowser: FileBrowserComponent.State.default,
// bufferList: BufferListComponent.State.default,
// preview: PreviewComponent.State.default)
//
// var fileBrowser: FileBrowserComponent.State
// var bufferList: BufferListComponent.State
// var preview: PreviewComponent.State
//
// init(fileBrowser: FileBrowserComponent.State,
// bufferList: BufferListComponent.State,
// preview: PreviewComponent.State) {
// self.fileBrowser = fileBrowser
// self.bufferList = bufferList
// self.preview = preview
// }
//
// init?(dict: [String: Any]) {
// guard let fileBrowserDict = dict[FileBrowserComponent.identifier] as? [String: Any],
// let bufferListDict = dict[BufferListComponent.identifier] as? [String: Any],
// let previewDict = dict[PreviewComponent.identifier] as? [String: Any]
// else {
// return nil
// }
//
// guard let fileBrowser = FileBrowserComponent.State(dict: fileBrowserDict),
// let bufferList = BufferListComponent.State(dict: bufferListDict),
// let preview = PreviewComponent.State(dict: previewDict)
// else {
// return nil
// }
//
// self.init(fileBrowser: fileBrowser, bufferList: bufferList, preview: preview)
// }
//
// func dict() -> [String: Any] {
// return [
// FileBrowserComponent.identifier: self.fileBrowser.dict(),
// BufferListComponent.identifier: self.bufferList.dict(),
// PreviewComponent.identifier: self.preview.dict(),
// ]
// }
//}
//
//struct ToolState: PersistableState {
//
// static let identifier = "tool-state"
// static let `default` = ToolState(location: .left, isVisible: false, dimension: 200)
//
// var location: WorkspaceBarLocation
// var isVisible: Bool
// var dimension: CGFloat
//
// init(location: WorkspaceBarLocation, isVisible: Bool, dimension: CGFloat) {
// self.location = location
// self.isVisible = isVisible
// self.dimension = dimension
// }
//
// init?(dict: [String: Any]) {
// guard let locationRawValue = dict[ToolState.location] as? String,
// let isVisible = PrefUtils.bool(from: dict, for: ToolState.isVisible),
// let fDimension = PrefUtils.float(from: dict, for: ToolState.dimension)
// else {
// return nil
// }
//
// guard let location = PrefUtils.location(from: locationRawValue) else {
// return nil
// }
//
// self.init(location: location, isVisible: isVisible, dimension: CGFloat(fDimension))
// }
//
// func dict() -> [String: Any] {
// return [
// ToolState.location: PrefUtils.locationAsString(for: self.location),
// ToolState.isVisible: self.isVisible,
// ToolState.dimension: Float(self.dimension),
// ]
// }
//
// fileprivate static let location = "location"
// fileprivate static let isVisible = "is-visible"
// fileprivate static let dimension = "dimension"
//}
//
//extension FileBrowserComponent {
//
// struct State: PersistableState {
//
// static let `default` = State(isShowHidden: false, toolState: ToolState.default)
//
// var isShowHidden: Bool
// var toolState: ToolState
//
// init(isShowHidden: Bool, toolState: ToolState) {
// self.isShowHidden = isShowHidden
// self.toolState = toolState
// }
//
// init?(dict: [String: Any]) {
// guard let isShowHidden = PrefUtils.bool(from: dict, for: State.isShowHidden),
// let toolStateDict = dict[ToolState.identifier] as? [String: Any]
// else {
// return nil
// }
//
// guard let toolState = ToolState(dict: toolStateDict) else {
// return nil
// }
//
// self.init(isShowHidden: isShowHidden, toolState: toolState)
// }
//
// func dict() -> [String: Any] {
// return [
// ToolState.identifier: self.toolState,
// State.isShowHidden: self.isShowHidden,
// ]
// }
//
// fileprivate static let isShowHidden = "is-show-hidden"
// }
//}
//
//extension BufferListComponent {
//
// struct State: PersistableState {
//
// static let `default` = State(toolState: ToolState.default)
//
// var toolState: ToolState
//
// init(toolState: ToolState) {
// self.toolState = toolState
// }
//
// init?(dict: [String: Any]) {
// guard let toolStateDict = dict[ToolState.identifier] as? [String: Any] else {
// return nil
// }
//
// guard let toolState = ToolState(dict: toolStateDict) else {
// return nil
// }
//
// self.init(toolState: toolState)
// }
//
// func dict() -> [String: Any] {
// return [
// ToolState.identifier: self.toolState,
// ]
// }
// }
//}
//
//extension PreviewComponent {
//
// struct State: PersistableState {
//
// static let `default` = State(markdown: MarkdownRenderer.State.default, toolState: ToolState.default)
//
// var markdown: MarkdownRenderer.State
// var toolState: ToolState
//
// init(markdown: MarkdownRenderer.State, toolState: ToolState) {
// self.markdown = markdown
// self.toolState = toolState
// }
//
// init?(dict: [String: Any]) {
// guard let markdownDict = dict[MarkdownRenderer.identifier] as? [String: Any],
// let toolStateDict = dict[ToolState.identifier] as? [String: Any]
// else {
// return nil
// }
//
// guard let markdown = MarkdownRenderer.State(dict: markdownDict) else {
// return nil
// }
//
// guard let toolState = ToolState(dict: toolStateDict) else {
// return nil
// }
//
// self.init(markdown: markdown, toolState: toolState)
// }
//
// func dict() -> [String: Any] {
// return [
// ToolState.identifier: self.toolState,
// MarkdownRenderer.identifier: self.markdown.dict(),
// ]
// }
// }
//}
//
//extension MarkdownRenderer {
//
// struct State: PersistableState {
//
// static let `default` = State(isForwardSearchAutomatically: false,
// isReverseSearchAutomatically: false,
// isRefreshOnWrite: true,
// renderTime: Date.distantPast)
//
// var isForwardSearchAutomatically: Bool
// var isReverseSearchAutomatically: Bool
// var isRefreshOnWrite: Bool
//
// // transient
// var renderTime: Date
//
// init(isForwardSearchAutomatically: Bool,
// isReverseSearchAutomatically: Bool,
// isRefreshOnWrite: Bool,
// renderTime: Date) {
// self.isForwardSearchAutomatically = isForwardSearchAutomatically
// self.isReverseSearchAutomatically = isReverseSearchAutomatically
// self.isRefreshOnWrite = isRefreshOnWrite
// self.renderTime = renderTime
// }
//
// init?(dict: [String: Any]) {
// guard let isForward = PrefUtils.bool(from: dict, for: State.isForwardSearchAutomatically) else {
// return nil
// }
//
// guard let isReverse = PrefUtils.bool(from: dict, for: State.isReverseSearchAutomatically) else {
// return nil
// }
//
// guard let isRefreshOnWrite = PrefUtils.bool(from: dict, for: State.isRefreshOnWrite) else {
// return nil
// }
//
// self.init(isForwardSearchAutomatically: isForward,
// isReverseSearchAutomatically: isReverse,
// isRefreshOnWrite: isRefreshOnWrite,
// renderTime: Date.distantPast)
// }
//
// func dict() -> [String: Any] {
// return [
// State.isForwardSearchAutomatically: self.isForwardSearchAutomatically,
// State.isReverseSearchAutomatically: self.isReverseSearchAutomatically,
// State.isRefreshOnWrite: self.isRefreshOnWrite,
// ]
// }
//
// fileprivate static let isForwardSearchAutomatically = "is-forward-search-automatically"
// fileprivate static let isReverseSearchAutomatically = "is-reverse-search-automatically"
// fileprivate static let isRefreshOnWrite = "is-refresh-on-write"
// }
//}

View File

@ -56,6 +56,16 @@ extension Array {
}
}
extension Array where Element: Hashable {
func toDict<V>(by mapper: @escaping (Element) -> V) -> Dictionary<Element, V> {
var result = Dictionary<Element, V>(minimumCapacity: self.count)
self.forEach { result[$0] = mapper($0) }
return result
}
}
func toDict<K: Hashable, V, S: Sequence>(_ sequence: S) -> Dictionary<K, V> where S.Iterator.Element == (K, V) {
var result = Dictionary<K, V>(minimumCapacity: sequence.underestimatedCount)

52
VimR/UiRoot.swift Normal file
View File

@ -0,0 +1,52 @@
//
// Created by Tae Won Ha on 1/16/17.
// Copyright (c) 2017 Tae Won Ha. All rights reserved.
//
import Cocoa
import RxSwift
class UiRoot: UiComponent {
typealias StateType = MainWindowStates
required init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType) {
self.source = source
self.emitter = emitter
source
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [unowned self] state in
let keys = Set(self.mainWindows.keys)
let keysInState = Set(state.current.keys)
keysInState
.subtracting(self.mainWindows.keys)
.flatMap { state.current[$0] }
.forEach(self.createNewMainWindow)
keys
.subtracting(keysInState)
.forEach {
self.mainWindows.removeValue(forKey: $0)
}
})
.addDisposableTo(self.disposeBag)
}
fileprivate func createNewMainWindow(with state: MainWindow.State) {
let mainWindow = MainWindow(source: self.source.mapOmittingNil { $0.current[state.uuid] },
emitter: self.emitter,
state: state)
self.mainWindows[state.uuid] = mainWindow
mainWindow.show()
}
fileprivate let source: Observable<StateType>
fileprivate let emitter: ActionEmitter
fileprivate let disposeBag = DisposeBag()
fileprivate var mainWindows = [String: MainWindow]()
}

View File

@ -0,0 +1,34 @@
//
// Created by Tae Won Ha on 1/17/17.
// Copyright (c) 2017 Tae Won Ha. All rights reserved.
//
import Foundation
import RxSwift
class UiRootTransformer: Transformer {
typealias Pair = StateActionPair<MainWindowStates, UuidAction<MainWindow.Action>>
func transform(_ source: Observable<Pair>) -> Observable<Pair> {
return source.map { pair in
var state = pair.state
let uuid = pair.action.uuid
switch pair.action.payload {
case .becomeKey:
state.last = state.current[uuid] ?? state.last
case .close:
state.current.removeValue(forKey: uuid)
default:
break
}
return StateActionPair(state: state, action: pair.action)
}
}
}