mirror of
https://github.com/qvacua/vimr.git
synced 2024-11-24 03:25:03 +03:00
First commit for redesign, experimenting around
This commit is contained in:
parent
c6d485ff80
commit
7fd7181437
@ -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;
|
||||
};
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
VimR/AppDelegateTransformer.swift
Normal file
32
VimR/AppDelegateTransformer.swift
Normal 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
98
VimR/Context.swift
Normal 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
215
VimR/MainWindow.swift
Normal 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
|
||||
}
|
||||
}
|
38
VimR/MainWindowTransformer.swift
Normal file
38
VimR/MainWindowTransformer.swift
Normal 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
628
VimR/States.swift
Normal 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"
|
||||
// }
|
||||
//}
|
@ -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
52
VimR/UiRoot.swift
Normal 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]()
|
||||
}
|
34
VimR/UiRootTransformer.swift
Normal file
34
VimR/UiRootTransformer.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user