mirror of
https://github.com/qvacua/vimr.git
synced 2024-12-25 06:43:24 +03:00
Store tools states
This commit is contained in:
parent
b3d2c0fff7
commit
808dbd0c8d
@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1929B00BA624DA8DC75F7E02 /* SerializableStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BA42AB6F1BF631B57399 /* SerializableStates.swift */; };
|
||||
1929B05B9D664052EC2D23EF /* FileOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BCE3E156C06EDF1F2806 /* FileOutlineView.swift */; };
|
||||
1929B0E0C3BC59F52713D5A2 /* FoundationCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B9AF20D7BD6E5C975128 /* FoundationCommons.swift */; };
|
||||
1929B0F599D1F62C7BE53D2C /* HttpServerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B1DC584C89C477E83FA2 /* HttpServerService.swift */; };
|
||||
@ -328,7 +329,9 @@
|
||||
1929B93013228985F509C8F6 /* server_ui.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = server_ui.m; sourceTree = "<group>"; };
|
||||
1929B9A55E12B703DDD673FD /* Debouncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
|
||||
1929B9AF20D7BD6E5C975128 /* FoundationCommons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationCommons.swift; sourceTree = "<group>"; };
|
||||
1929B9B97596868A9552E2EB /* GeneralPrefPane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralPrefPane.swift; sourceTree = "<group>"; };
|
||||
1929B9D510177918080BE39B /* Scorer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scorer.swift; sourceTree = "<group>"; };
|
||||
1929BA42AB6F1BF631B57399 /* SerializableStates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SerializableStates.swift; sourceTree = "<group>"; };
|
||||
1929BA43449BA41666CD55ED /* OpenedFileList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenedFileList.swift; sourceTree = "<group>"; };
|
||||
1929BA5C7099CDEB04B76BA4 /* FileBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileBrowser.swift; sourceTree = "<group>"; };
|
||||
1929BA6128BFDD54CA92F46E /* ColorUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorUtils.swift; sourceTree = "<group>"; };
|
||||
@ -354,6 +357,7 @@
|
||||
1929BE69CF9AB1A10D0DD4F2 /* CocoaCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CocoaCategories.h; sourceTree = "<group>"; };
|
||||
1929BED01F5D94BFCA4CF80F /* AppearancePrefTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppearancePrefTransformer.swift; sourceTree = "<group>"; };
|
||||
1929BEEB33113B0E33C3830F /* Matcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matcher.swift; sourceTree = "<group>"; };
|
||||
1929BEEC52800C8BA6333E5E /* AppearancePrefPane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppearancePrefPane.swift; sourceTree = "<group>"; };
|
||||
1929BF00B466B40629C2AABE /* NeoVimView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeoVimView.swift; sourceTree = "<group>"; };
|
||||
1929BFB0F294F3714D5E095F /* PreviewToolTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewToolTransformer.swift; sourceTree = "<group>"; };
|
||||
1929BFC0A5A9C6DB09BE1368 /* Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = "<group>"; };
|
||||
@ -625,6 +629,7 @@
|
||||
1929B452B8E7E8DF65FC5F78 /* Utils */,
|
||||
1929BFC0A5A9C6DB09BE1368 /* Types.swift */,
|
||||
1929B8401A4EC1F8ABAAD338 /* FileHandler.swift */,
|
||||
1929BA42AB6F1BF631B57399 /* SerializableStates.swift */,
|
||||
);
|
||||
name = Redesign;
|
||||
sourceTree = "<group>";
|
||||
@ -711,6 +716,8 @@
|
||||
4BBDD3EC1D967DA2008FCC92 /* AdvancedPrefPane.swift */,
|
||||
4B37ADBA1D6EC11600970D55 /* TestPane.swift */,
|
||||
4B705BD91DE04F83005F844B /* Pref38To128Converter.swift */,
|
||||
1929B9B97596868A9552E2EB /* GeneralPrefPane.swift */,
|
||||
1929BEEC52800C8BA6333E5E /* AppearancePrefPane.swift */,
|
||||
);
|
||||
name = Preferences;
|
||||
sourceTree = "<group>";
|
||||
@ -1458,6 +1465,7 @@
|
||||
1929B50D933A369A86A165DE /* AdvencedPref.swift in Sources */,
|
||||
1929BCC7908DD899999B70BE /* AppearancePrefTransformer.swift in Sources */,
|
||||
1929B387E6B085D96945CCAB /* AdvancedPrefTransformer.swift in Sources */,
|
||||
1929B00BA624DA8DC75F7E02 /* SerializableStates.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
330
VimR/AppearancePrefPane.swift
Normal file
330
VimR/AppearancePrefPane.swift
Normal file
@ -0,0 +1,330 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
import PureLayout
|
||||
import RxSwift
|
||||
|
||||
struct AppearancePrefData: Equatable, StandardPrefData {
|
||||
|
||||
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"
|
||||
|
||||
static func ==(left: AppearancePrefData, right: AppearancePrefData) -> Bool {
|
||||
return left.editorUsesLigatures == right.editorUsesLigatures
|
||||
&& left.editorFont.isEqual(to: right.editorFont)
|
||||
&& left.editorLinespacing == right.editorLinespacing
|
||||
}
|
||||
|
||||
static let `default` = AppearancePrefData(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[AppearancePrefData.editorFontName] as? String,
|
||||
let fEditorFontSize = PrefUtils.float(from: dict, for: AppearancePrefData.editorFontSize),
|
||||
let fEditorLinespacing = PrefUtils.float(from: dict, for: AppearancePrefData.editorLinespacing),
|
||||
let editorUsesLigatures = PrefUtils.bool(from: dict, for: AppearancePrefData.editorUsesLigatures)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(editorFont: PrefUtils.saneFont(editorFontName, fontSize: CGFloat(fEditorFontSize)),
|
||||
editorLinespacing: CGFloat(fEditorLinespacing),
|
||||
editorUsesLigatures: editorUsesLigatures)
|
||||
}
|
||||
|
||||
func dict() -> [String: Any] {
|
||||
return [
|
||||
AppearancePrefData.editorFontName: self.editorFont.fontName,
|
||||
AppearancePrefData.editorFontSize: Float(self.editorFont.pointSize),
|
||||
AppearancePrefData.editorLinespacing: Float(self.editorLinespacing),
|
||||
AppearancePrefData.editorUsesLigatures: self.editorUsesLigatures,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class AppearancePrefPane: PrefPane, NSComboBoxDelegate, NSControlTextEditingDelegate {
|
||||
|
||||
override var displayName: String {
|
||||
return "Appearance"
|
||||
}
|
||||
|
||||
override var pinToContainer: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
fileprivate var data: AppearancePrefData {
|
||||
didSet {
|
||||
self.updateViews(newData: self.data)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let fontManager = NSFontManager.shared()
|
||||
|
||||
fileprivate let sizes = [9, 10, 11, 12, 13, 14, 16, 18, 24, 36, 48, 64]
|
||||
fileprivate let sizeCombo = NSComboBox(forAutoLayout: ())
|
||||
fileprivate let fontPopup = NSPopUpButton(frame: CGRect.zero, pullsDown: false)
|
||||
fileprivate let linespacingField = NSTextField(forAutoLayout: ())
|
||||
fileprivate let ligatureCheckbox = NSButton(forAutoLayout: ())
|
||||
fileprivate let previewArea = NSTextView(frame: CGRect.zero)
|
||||
|
||||
fileprivate let exampleText =
|
||||
"abcdefghijklmnopqrstuvwxyz\n" +
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ\n" +
|
||||
"0123456789\n" +
|
||||
"(){}[] +-*/= .,;:!?#&$%@|^\n" +
|
||||
"<- -> => >> << >>= =<< .. \n" +
|
||||
":: -< >- -<< >>- ++ /= =="
|
||||
|
||||
init(source: Observable<Any>, initialData: AppearancePrefData) {
|
||||
self.data = initialData
|
||||
super.init(source: source)
|
||||
|
||||
self.updateViews(newData: initialData)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
fileprivate func set(data: AppearancePrefData) {
|
||||
self.data = data
|
||||
self.publish(event: data)
|
||||
}
|
||||
|
||||
override func subscription(source: Observable<Any>) -> Disposable {
|
||||
return source
|
||||
.filter { $0 is PrefData }
|
||||
.map { ($0 as! PrefData).appearance }
|
||||
.filter { [unowned self] data in data != self.data }
|
||||
.subscribe(onNext: { [unowned self] data in
|
||||
self.data = data
|
||||
})
|
||||
}
|
||||
|
||||
override func addViews() {
|
||||
let paneTitle = self.paneTitleTextField(title: "Appearance")
|
||||
|
||||
let fontTitle = self.titleTextField(title: "Default Font:")
|
||||
|
||||
let fontPopup = self.fontPopup
|
||||
fontPopup.translatesAutoresizingMaskIntoConstraints = false
|
||||
fontPopup.target = self
|
||||
fontPopup.action = #selector(AppearancePrefPane.fontPopupAction)
|
||||
fontPopup.addItems(withTitles: self.fontManager.availableFontNames(with: .fixedPitchFontMask)!)
|
||||
|
||||
let sizeCombo = self.sizeCombo
|
||||
sizeCombo.delegate = self
|
||||
sizeCombo.target = self
|
||||
sizeCombo.action = #selector(AppearancePrefPane.sizeComboBoxDidEnter(_:))
|
||||
self.sizes.forEach { string in
|
||||
sizeCombo.addItem(withObjectValue: string)
|
||||
}
|
||||
|
||||
let linespacingTitle = self.titleTextField(title: "Line Spacing:")
|
||||
let linespacingField = self.linespacingField
|
||||
|
||||
let ligatureCheckbox = self.ligatureCheckbox
|
||||
self.configureCheckbox(button: ligatureCheckbox,
|
||||
title: "Use Ligatures",
|
||||
action: #selector(AppearancePrefPane.usesLigaturesAction(_:)))
|
||||
|
||||
let previewArea = self.previewArea
|
||||
previewArea.isEditable = true
|
||||
previewArea.maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
|
||||
previewArea.isVerticallyResizable = true
|
||||
previewArea.isHorizontallyResizable = true
|
||||
previewArea.textContainer?.heightTracksTextView = false
|
||||
previewArea.textContainer?.widthTracksTextView = false
|
||||
previewArea.autoresizingMask = [ .viewWidthSizable, .viewHeightSizable]
|
||||
previewArea.textContainer?.containerSize = CGSize.init(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
|
||||
previewArea.layoutManager?.replaceTextStorage(NSTextStorage(string: self.exampleText))
|
||||
previewArea.isRichText = false
|
||||
previewArea.turnOffLigatures(self)
|
||||
|
||||
let previewScrollView = NSScrollView(forAutoLayout: ())
|
||||
previewScrollView.hasVerticalScroller = true
|
||||
previewScrollView.hasHorizontalScroller = true
|
||||
previewScrollView.autohidesScrollers = true
|
||||
previewScrollView.borderType = .bezelBorder
|
||||
previewScrollView.documentView = previewArea
|
||||
|
||||
self.addSubview(paneTitle)
|
||||
|
||||
self.addSubview(fontTitle)
|
||||
self.addSubview(fontPopup)
|
||||
self.addSubview(sizeCombo)
|
||||
self.addSubview(linespacingTitle)
|
||||
self.addSubview(linespacingField)
|
||||
self.addSubview(ligatureCheckbox)
|
||||
self.addSubview(previewScrollView)
|
||||
|
||||
paneTitle.autoPinEdge(toSuperviewEdge: .top, withInset: 18)
|
||||
paneTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18)
|
||||
|
||||
fontTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18, relation: .greaterThanOrEqual)
|
||||
fontTitle.autoAlignAxis(.baseline, toSameAxisOf: fontPopup)
|
||||
|
||||
fontPopup.autoPinEdge(.top, to: .bottom, of: paneTitle, withOffset: 18)
|
||||
fontPopup.autoPinEdge(.left, to: .right, of: fontTitle, withOffset: 5)
|
||||
fontPopup.autoSetDimension(.width, toSize: 240)
|
||||
|
||||
sizeCombo.autoSetDimension(.width, toSize: 60)
|
||||
// If we use .Baseline the combo box is placed one pixel off...
|
||||
sizeCombo.autoAlignAxis(.horizontal, toSameAxisOf: fontPopup)
|
||||
sizeCombo.autoPinEdge(.left, to: .right, of: fontPopup, withOffset: 5)
|
||||
|
||||
linespacingTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18, relation: .greaterThanOrEqual)
|
||||
linespacingTitle.autoPinEdge(.right, to: .right, of: fontTitle)
|
||||
linespacingTitle.autoAlignAxis(.baseline, toSameAxisOf: linespacingField)
|
||||
|
||||
linespacingField.autoPinEdge(.top, to: .bottom, of: sizeCombo, withOffset: 18)
|
||||
linespacingField.autoPinEdge(.left, to: .right, of: linespacingTitle, withOffset: 5)
|
||||
linespacingField.autoSetDimension(.width, toSize: 60)
|
||||
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSControlTextDidEndEditing,
|
||||
object: linespacingField,
|
||||
queue: nil)
|
||||
{ [unowned self] _ in
|
||||
self.linespacingAction()
|
||||
}
|
||||
|
||||
ligatureCheckbox.autoPinEdge(.top, to: .bottom, of: linespacingField, withOffset: 18)
|
||||
ligatureCheckbox.autoPinEdge(.left, to: .right, of: fontTitle, withOffset: 5)
|
||||
|
||||
previewScrollView.autoSetDimension(.height, toSize: 200, relation: .greaterThanOrEqual)
|
||||
previewScrollView.autoPinEdge(.top, to: .bottom, of: ligatureCheckbox, withOffset: 18)
|
||||
previewScrollView.autoPinEdge(toSuperviewEdge: .right, withInset: 18)
|
||||
previewScrollView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 18)
|
||||
previewScrollView.autoPinEdge(toSuperviewEdge: .left, withInset: 18)
|
||||
|
||||
self.fontPopup.selectItem(withTitle: self.data.editorFont.fontName)
|
||||
self.sizeCombo.stringValue = String(Int(self.data.editorFont.pointSize))
|
||||
self.ligatureCheckbox.state = self.data.editorUsesLigatures ? NSOnState : NSOffState
|
||||
self.previewArea.font = self.data.editorFont
|
||||
if self.data.editorUsesLigatures {
|
||||
self.previewArea.useAllLigatures(self)
|
||||
} else {
|
||||
self.previewArea.turnOffLigatures(self)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func updateViews(newData: AppearancePrefData) {
|
||||
let newFont = newData.editorFont
|
||||
|
||||
self.fontPopup.selectItem(withTitle: newFont.fontName)
|
||||
self.sizeCombo.stringValue = String(Int(newFont.pointSize))
|
||||
self.linespacingField.stringValue = String(format: "%.2f", newData.editorLinespacing)
|
||||
self.ligatureCheckbox.boolState = newData.editorUsesLigatures
|
||||
self.previewArea.font = newData.editorFont
|
||||
|
||||
if newData.editorUsesLigatures {
|
||||
self.previewArea.useAllLigatures(self)
|
||||
} else {
|
||||
self.previewArea.turnOffLigatures(self)
|
||||
}
|
||||
}
|
||||
|
||||
override func windowWillClose() {
|
||||
self.linespacingAction()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
extension AppearancePrefPane {
|
||||
|
||||
func usesLigaturesAction(_ sender: NSButton) {
|
||||
self.set(data: AppearancePrefData(editorFont: self.data.editorFont,
|
||||
editorLinespacing: self.data.editorLinespacing,
|
||||
editorUsesLigatures: sender.boolState))
|
||||
}
|
||||
|
||||
func fontPopupAction(_ sender: NSPopUpButton) {
|
||||
guard let selectedItem = self.fontPopup.selectedItem else {
|
||||
return
|
||||
}
|
||||
|
||||
guard selectedItem.title != self.data.editorFont.fontName else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let newFont = NSFont(name: selectedItem.title, size: self.data.editorFont.pointSize) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.set(data: AppearancePrefData(editorFont: newFont,
|
||||
editorLinespacing: self.data.editorLinespacing,
|
||||
editorUsesLigatures: self.data.editorUsesLigatures))
|
||||
}
|
||||
|
||||
func comboBoxSelectionDidChange(_ notification: Notification) {
|
||||
guard (notification.object as! NSComboBox) === self.sizeCombo else {
|
||||
return
|
||||
}
|
||||
|
||||
let newFontSize = self.cappedFontSize(Int(self.sizes[self.sizeCombo.indexOfSelectedItem]))
|
||||
let newFont = self.fontManager.convert(self.data.editorFont, toSize: newFontSize)
|
||||
|
||||
self.set(data: AppearancePrefData(editorFont: newFont,
|
||||
editorLinespacing: self.data.editorLinespacing,
|
||||
editorUsesLigatures: self.data.editorUsesLigatures))
|
||||
}
|
||||
|
||||
func sizeComboBoxDidEnter(_ sender: AnyObject!) {
|
||||
let newFontSize = self.cappedFontSize(self.sizeCombo.integerValue)
|
||||
let newFont = self.fontManager.convert(self.data.editorFont, toSize: newFontSize)
|
||||
|
||||
self.set(data: AppearancePrefData(editorFont: newFont,
|
||||
editorLinespacing: self.data.editorLinespacing,
|
||||
editorUsesLigatures: self.data.editorUsesLigatures))
|
||||
}
|
||||
|
||||
func linespacingAction() {
|
||||
let newLinespacing = self.cappedLinespacing(self.linespacingField.floatValue)
|
||||
self.set(data: AppearancePrefData(editorFont: self.data.editorFont,
|
||||
editorLinespacing: newLinespacing,
|
||||
editorUsesLigatures: self.data.editorUsesLigatures))
|
||||
}
|
||||
|
||||
fileprivate func cappedLinespacing(_ linespacing: Float) -> CGFloat {
|
||||
let cgfLinespacing = CGFloat(linespacing)
|
||||
|
||||
guard cgfLinespacing >= NeoVimView.minLinespacing else {
|
||||
return NeoVimView.defaultLinespacing
|
||||
}
|
||||
|
||||
guard cgfLinespacing <= NeoVimView.maxLinespacing else {
|
||||
return NeoVimView.maxLinespacing
|
||||
}
|
||||
|
||||
return cgfLinespacing
|
||||
}
|
||||
|
||||
fileprivate func cappedFontSize(_ size: Int) -> CGFloat {
|
||||
let cgfSize = CGFloat(size)
|
||||
|
||||
guard cgfSize >= NeoVimView.minFontSize else {
|
||||
return NeoVimView.defaultFont.pointSize
|
||||
}
|
||||
|
||||
guard cgfSize <= NeoVimView.maxFontSize else {
|
||||
return NeoVimView.maxFontSize
|
||||
}
|
||||
|
||||
return cgfSize
|
||||
}
|
||||
}
|
@ -137,7 +137,11 @@ class Context {
|
||||
|
||||
#if DEBUG
|
||||
// actionSource.debug().subscribe().addDisposableTo(self.disposeBag)
|
||||
// stateSource.debug().subscribe().addDisposableTo(self.disposeBag)
|
||||
// stateSource
|
||||
// .debug()
|
||||
// .subscribe(onNext: { state in
|
||||
// })
|
||||
// .addDisposableTo(self.disposeBag)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
296
VimR/GeneralPrefPane.swift
Normal file
296
VimR/GeneralPrefPane.swift
Normal file
@ -0,0 +1,296 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
import PureLayout
|
||||
import RxSwift
|
||||
|
||||
struct GeneralPrefData: Equatable, StandardPrefData {
|
||||
|
||||
fileprivate static let openNewWindowWhenLaunching = "open-new-window-when-launching"
|
||||
fileprivate static let openNewWindowOnReactivation = "open-new-window-on-reactivation"
|
||||
fileprivate static let ignorePatterns = "ignore-patterns"
|
||||
|
||||
fileprivate static let defaultIgnorePatterns = Set(
|
||||
[ "*/.git", "*.o", "*.d", "*.dia" ].map(FileItemIgnorePattern.init)
|
||||
)
|
||||
|
||||
static func ==(left: GeneralPrefData, right: GeneralPrefData) -> Bool {
|
||||
return left.openNewWindowWhenLaunching == right.openNewWindowWhenLaunching
|
||||
&& left.openNewWindowOnReactivation == right.openNewWindowOnReactivation
|
||||
&& left.ignorePatterns == right.ignorePatterns
|
||||
}
|
||||
|
||||
static let `default` = GeneralPrefData(openNewWindowWhenLaunching: true,
|
||||
openNewWindowOnReactivation: true,
|
||||
ignorePatterns: GeneralPrefData.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: GeneralPrefData.openNewWindowWhenLaunching),
|
||||
let openNewWinOnReactivation = PrefUtils.bool(from: dict, for: GeneralPrefData.openNewWindowOnReactivation),
|
||||
let ignorePatternsStr = dict[GeneralPrefData.ignorePatterns] as? String
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(openNewWindowWhenLaunching: openNewWinWhenLaunching,
|
||||
openNewWindowOnReactivation: openNewWinOnReactivation,
|
||||
ignorePatterns: PrefUtils.ignorePatterns(fromString: ignorePatternsStr))
|
||||
}
|
||||
|
||||
func dict() -> [String: Any] {
|
||||
return [
|
||||
GeneralPrefData.openNewWindowWhenLaunching: self.openNewWindowWhenLaunching,
|
||||
GeneralPrefData.openNewWindowOnReactivation: self.openNewWindowOnReactivation,
|
||||
GeneralPrefData.ignorePatterns: PrefUtils.ignorePatternString(fromSet: self.ignorePatterns),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class GeneralPrefPane: PrefPane, NSTextFieldDelegate {
|
||||
|
||||
override var displayName: String {
|
||||
return "General"
|
||||
}
|
||||
|
||||
override var pinToContainer: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
fileprivate var data: GeneralPrefData
|
||||
|
||||
fileprivate let openWhenLaunchingCheckbox = NSButton(forAutoLayout: ())
|
||||
fileprivate let openOnReactivationCheckbox = NSButton(forAutoLayout: ())
|
||||
fileprivate let ignoreField = NSTextField(forAutoLayout: ())
|
||||
|
||||
init(source: Observable<Any>, initialData: GeneralPrefData) {
|
||||
self.data = initialData
|
||||
super.init(source: source)
|
||||
|
||||
self.updateViews(newData: initialData)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func addViews() {
|
||||
let paneTitle = self.paneTitleTextField(title: "General")
|
||||
|
||||
let openUntitledWindowTitle = self.titleTextField(title: "Open Untitled Window:")
|
||||
self.configureCheckbox(button: self.openWhenLaunchingCheckbox,
|
||||
title: "On launch",
|
||||
action: #selector(GeneralPrefPane.openUntitledWindowWhenLaunchingAction(_:)))
|
||||
self.configureCheckbox(button: self.openOnReactivationCheckbox,
|
||||
title: "On re-activation",
|
||||
action: #selector(GeneralPrefPane.openUntitledWindowOnReactivationAction(_:)))
|
||||
|
||||
let whenLaunching = self.openWhenLaunchingCheckbox
|
||||
let onReactivation = self.openOnReactivationCheckbox
|
||||
|
||||
let ignoreListTitle = self.titleTextField(title: "Files To Ignore:")
|
||||
let ignoreField = self.ignoreField
|
||||
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSControlTextDidEndEditing,
|
||||
object: ignoreField,
|
||||
queue: nil)
|
||||
{ [unowned self] _ in
|
||||
self.ignorePatternsAction()
|
||||
}
|
||||
let ignoreInfo =
|
||||
self.infoTextField(markdown:
|
||||
"Comma-separated list of ignore patterns \n"
|
||||
+ "Matching files will be ignored in \"Open Quickly\" and the file browser. \n"
|
||||
+ "Example: `*/.git, */node_modules` \n"
|
||||
+ "For detailed information see [VimR Wiki](https://github.com/qvacua/vimr/wiki)."
|
||||
)
|
||||
|
||||
let cliToolTitle = self.titleTextField(title: "CLI Tool:")
|
||||
let cliToolButton = NSButton(forAutoLayout: ())
|
||||
cliToolButton.title = "Copy 'vimr' CLI Tool..."
|
||||
cliToolButton.bezelStyle = .rounded
|
||||
cliToolButton.isBordered = true
|
||||
cliToolButton.setButtonType(.momentaryPushIn)
|
||||
cliToolButton.target = self
|
||||
cliToolButton.action = #selector(GeneralPrefPane.copyCliTool(_:))
|
||||
let cliToolInfo = self.infoTextField(
|
||||
markdown: "Put the executable `vimr` in your `$PATH` and execute `vimr -h` for help."
|
||||
)
|
||||
|
||||
self.addSubview(paneTitle)
|
||||
self.addSubview(openUntitledWindowTitle)
|
||||
self.addSubview(whenLaunching)
|
||||
self.addSubview(onReactivation)
|
||||
|
||||
self.addSubview(ignoreListTitle)
|
||||
self.addSubview(ignoreField)
|
||||
self.addSubview(ignoreInfo)
|
||||
|
||||
self.addSubview(cliToolTitle)
|
||||
self.addSubview(cliToolButton)
|
||||
self.addSubview(cliToolInfo)
|
||||
|
||||
paneTitle.autoPinEdge(toSuperviewEdge: .top, withInset: 18)
|
||||
paneTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18)
|
||||
paneTitle.autoPinEdge(toSuperviewEdge: .right, withInset: 18, relation: .greaterThanOrEqual)
|
||||
|
||||
openUntitledWindowTitle.autoAlignAxis(.baseline, toSameAxisOf: whenLaunching, withOffset: 0)
|
||||
openUntitledWindowTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18)
|
||||
|
||||
whenLaunching.autoPinEdge(.top, to: .bottom, of: paneTitle, withOffset: 18)
|
||||
whenLaunching.autoPinEdge(.left, to: .right, of: openUntitledWindowTitle, withOffset: 5)
|
||||
whenLaunching.autoPinEdge(toSuperviewEdge: .right, withInset: 18, relation: .greaterThanOrEqual)
|
||||
|
||||
onReactivation.autoPinEdge(.top, to: .bottom, of: whenLaunching, withOffset: 5)
|
||||
onReactivation.autoPinEdge(.left, to: .left, of: whenLaunching)
|
||||
onReactivation.autoPinEdge(toSuperviewEdge: .right, withInset: 18, relation: .greaterThanOrEqual)
|
||||
|
||||
ignoreListTitle.autoAlignAxis(.baseline, toSameAxisOf: ignoreField)
|
||||
ignoreListTitle.autoPinEdge(.right, to: .right, of: openUntitledWindowTitle)
|
||||
ignoreListTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18, relation: .greaterThanOrEqual)
|
||||
|
||||
ignoreField.autoPinEdge(.top, to: .bottom, of: onReactivation, withOffset: 18)
|
||||
ignoreField.autoPinEdge(toSuperviewEdge: .right, withInset: 18)
|
||||
ignoreField.autoPinEdge(.left, to: .right, of: ignoreListTitle, withOffset: 5)
|
||||
|
||||
ignoreInfo.autoPinEdge(.top, to: .bottom, of: ignoreField, withOffset: 5)
|
||||
ignoreInfo.autoPinEdge(toSuperviewEdge: .right, withInset: 18)
|
||||
ignoreInfo.autoPinEdge(.left, to: .right, of: ignoreListTitle, withOffset: 5)
|
||||
|
||||
cliToolTitle.autoAlignAxis(.baseline, toSameAxisOf: cliToolButton)
|
||||
cliToolTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18, relation: .greaterThanOrEqual)
|
||||
cliToolTitle.autoPinEdge(.right, to: .right, of: openUntitledWindowTitle)
|
||||
|
||||
cliToolButton.autoPinEdge(.top, to: .bottom, of: ignoreInfo, withOffset: 18)
|
||||
cliToolButton.autoPinEdge(toSuperviewEdge: .right, withInset: 18, relation: .greaterThanOrEqual)
|
||||
cliToolButton.autoPinEdge(.left, to: .right, of: cliToolTitle, withOffset: 5)
|
||||
|
||||
cliToolInfo.autoPinEdge(.top, to: .bottom, of: cliToolButton, withOffset: 5)
|
||||
cliToolInfo.autoPinEdge(toSuperviewEdge: .right, withInset: 18, relation: .greaterThanOrEqual)
|
||||
cliToolInfo.autoPinEdge(.left, to: .right, of: cliToolTitle, withOffset: 5)
|
||||
|
||||
self.openWhenLaunchingCheckbox.boolState = self.data.openNewWindowWhenLaunching
|
||||
self.openOnReactivationCheckbox.boolState = self.data.openNewWindowOnReactivation
|
||||
}
|
||||
|
||||
override func subscription(source: Observable<Any>) -> Disposable {
|
||||
return source
|
||||
.filter { $0 is PrefData }
|
||||
.map { ($0 as! PrefData).general }
|
||||
.filter { [unowned self] data in data != self.data }
|
||||
.subscribe(onNext: { [unowned self] data in
|
||||
self.updateViews(newData: data)
|
||||
self.data = data
|
||||
})
|
||||
}
|
||||
|
||||
override func windowWillClose() {
|
||||
self.ignorePatternsAction()
|
||||
}
|
||||
|
||||
fileprivate func set(data: GeneralPrefData) {
|
||||
self.data = data
|
||||
self.publish(event: data)
|
||||
}
|
||||
|
||||
fileprivate func ignoreInfoText() -> NSAttributedString {
|
||||
let markdown = "Comma-separated list of ignore patterns\n\n"
|
||||
+ "Matching files will be ignored in \"Open Quickly\" and the file browser.\n\n"
|
||||
+ "Example: `*/.git, */node_modules`\n\n"
|
||||
+ "For detailed information see [VimR Wiki](https://github.com/qvacua/vimr/wiki)."
|
||||
|
||||
return NSAttributedString.infoLabel(markdown: markdown)
|
||||
}
|
||||
|
||||
fileprivate func updateViews(newData: GeneralPrefData) {
|
||||
self.openWhenLaunchingCheckbox.boolState = newData.openNewWindowWhenLaunching
|
||||
self.openOnReactivationCheckbox.boolState = newData.openNewWindowOnReactivation
|
||||
self.ignoreField.stringValue = PrefUtils.ignorePatternString(fromSet: newData.ignorePatterns)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
extension GeneralPrefPane {
|
||||
|
||||
func copyCliTool(_ sender: NSButton) {
|
||||
let panel = NSOpenPanel()
|
||||
panel.canChooseFiles = false
|
||||
panel.canChooseDirectories = true
|
||||
|
||||
panel.beginSheetModal(for: self.window!) { result in
|
||||
guard result == NSFileHandlingPanelOKButton else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let vimrUrl = Bundle.main.url(forResource: "vimr", withExtension: nil) else {
|
||||
self.alert(title: "Something Went Wrong.",
|
||||
info: "The CLI tool 'vimr' could not be found. Please re-download VimR and try again.")
|
||||
return
|
||||
}
|
||||
|
||||
guard let targetUrl = panel.url?.appendingPathComponent("vimr") else {
|
||||
self.alert(title: "Something Went Wrong.",
|
||||
info: "The target directory could not be determined. Please try again with a different directory.")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try FileManager.default.copyItem(at: vimrUrl, to: targetUrl)
|
||||
} catch let err as NSError {
|
||||
self.alert(title: "Error copying 'vimr'", info: err.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openUntitledWindowWhenLaunchingAction(_ sender: NSButton) {
|
||||
self.set(data: GeneralPrefData(
|
||||
openNewWindowWhenLaunching: self.openWhenLaunchingCheckbox.boolState,
|
||||
openNewWindowOnReactivation: self.data.openNewWindowOnReactivation,
|
||||
ignorePatterns: self.data.ignorePatterns)
|
||||
)
|
||||
}
|
||||
|
||||
func openUntitledWindowOnReactivationAction(_ sender: NSButton) {
|
||||
self.set(data: GeneralPrefData(
|
||||
openNewWindowWhenLaunching: self.data.openNewWindowWhenLaunching,
|
||||
openNewWindowOnReactivation: self.openOnReactivationCheckbox.boolState,
|
||||
ignorePatterns: self.data.ignorePatterns)
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func ignorePatternsAction() {
|
||||
let patterns = PrefUtils.ignorePatterns(fromString: self.ignoreField.stringValue)
|
||||
if patterns == self.data.ignorePatterns {
|
||||
return
|
||||
}
|
||||
|
||||
self.set(data: GeneralPrefData(
|
||||
openNewWindowWhenLaunching: self.data.openNewWindowWhenLaunching,
|
||||
openNewWindowOnReactivation: self.data.openNewWindowOnReactivation,
|
||||
ignorePatterns: patterns)
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func alert(title: String, info: String) {
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .warning
|
||||
alert.messageText = title
|
||||
alert.informativeText = info
|
||||
alert.runModal()
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ import PureLayout
|
||||
class MainWindow: NSObject,
|
||||
UiComponent,
|
||||
NeoVimViewDelegate,
|
||||
NSWindowDelegate {
|
||||
NSWindowDelegate,
|
||||
NSUserInterfaceValidations,
|
||||
WorkspaceDelegate {
|
||||
|
||||
typealias StateType = State
|
||||
|
||||
@ -34,7 +36,13 @@ class MainWindow: NSObject,
|
||||
|
||||
case openQuickly
|
||||
|
||||
case toggleAllTools(Bool)
|
||||
case toggleToolButtons(Bool)
|
||||
case setState(for: Tools, with: WorkspaceTool)
|
||||
|
||||
// not pretty...
|
||||
case close
|
||||
case closed
|
||||
}
|
||||
|
||||
enum FocusableView {
|
||||
@ -44,6 +52,14 @@ class MainWindow: NSObject,
|
||||
case preview
|
||||
}
|
||||
|
||||
enum Tools: String {
|
||||
|
||||
case fileBrowser = "com.qvacua.vimr.tools.file-browser"
|
||||
case openedFilesList = "com.qvacua.vimr.tools.opened-files-list"
|
||||
case preview = "com.qvacua.vimr.tools.preview"
|
||||
|
||||
}
|
||||
|
||||
enum OpenMode {
|
||||
|
||||
case `default`
|
||||
@ -79,27 +95,29 @@ class MainWindow: NSObject,
|
||||
view: self.preview,
|
||||
customMenuItems: self.preview.menuItems)
|
||||
self.previewContainer = WorkspaceTool(previewConfig)
|
||||
previewContainer.dimension = 300
|
||||
previewContainer.dimension = state.tools[.preview]?.dimension ?? 250
|
||||
|
||||
let fileBrowserConfig = WorkspaceTool.Config(title: "Files",
|
||||
view: self.fileBrowser,
|
||||
customToolbar: self.fileBrowser.innerCustomToolbar,
|
||||
customMenuItems: self.fileBrowser.menuItems)
|
||||
self.fileBrowserContainer = WorkspaceTool(fileBrowserConfig)
|
||||
fileBrowserContainer.dimension = 200
|
||||
fileBrowserContainer.dimension = state.tools[.fileBrowser]?.dimension ?? 200
|
||||
|
||||
let openedFileListConfig = WorkspaceTool.Config(title: "Opened", view: self.openedFileList)
|
||||
self.openedFileListContainer = WorkspaceTool(openedFileListConfig)
|
||||
self.openedFileListContainer.dimension = 200
|
||||
self.openedFileListContainer.dimension = state.tools[.openedFilesList]?.dimension ?? 200
|
||||
|
||||
self.workspace.append(tool: previewContainer, location: .right)
|
||||
self.workspace.append(tool: fileBrowserContainer, location: .left)
|
||||
self.workspace.append(tool: openedFileListContainer, location: .left)
|
||||
self.workspace.append(tool: previewContainer, location: state.tools[.preview]?.location ?? .right)
|
||||
self.workspace.append(tool: fileBrowserContainer, location: state.tools[.fileBrowser]?.location ?? .left)
|
||||
self.workspace.append(tool: openedFileListContainer, location: state.tools[.openedFilesList]?.location ?? .left)
|
||||
|
||||
fileBrowserContainer.toggle()
|
||||
|
||||
super.init()
|
||||
|
||||
self.workspace.delegate = self
|
||||
|
||||
Observable
|
||||
.of(self.scrollDebouncer.observable, self.cursorDebouncer.observable)
|
||||
.merge()
|
||||
@ -377,6 +395,10 @@ extension MainWindow {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func windowWillClose(_: Notification) {
|
||||
self.emitter.emit(self.uuidAction(for: .closed))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - File Menu Item Actions
|
||||
@ -492,10 +514,13 @@ extension MainWindow {
|
||||
@IBAction func toggleAllTools(_ sender: Any?) {
|
||||
self.workspace.toggleAllTools()
|
||||
self.focusNeoVimView(self)
|
||||
|
||||
self.emitter.emit(self.uuidAction(for: .toggleAllTools(self.workspace.isAllToolsVisible)))
|
||||
}
|
||||
|
||||
@IBAction func toggleToolButtons(_ sender: Any?) {
|
||||
self.workspace.toggleToolButtons()
|
||||
self.emitter.emit(self.uuidAction(for: .toggleToolButtons(self.workspace.isToolButtonsVisible)))
|
||||
}
|
||||
|
||||
@IBAction func toggleFileBrowser(_ sender: Any?) {
|
||||
@ -522,6 +547,52 @@ extension MainWindow {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WorkspaceDelegate
|
||||
extension MainWindow {
|
||||
|
||||
func resizeWillStart(workspace: Workspace, tool: WorkspaceTool?) {
|
||||
self.neoVimView.enterResizeMode()
|
||||
}
|
||||
|
||||
func resizeDidEnd(workspace: Workspace, tool: WorkspaceTool?) {
|
||||
self.neoVimView.exitResizeMode()
|
||||
|
||||
if let workspaceTool = tool, let toolIdentifier = self.toolIdentifier(for: workspaceTool) {
|
||||
self.emitter.emit(self.uuidAction(for: .setState(for: toolIdentifier, with: workspaceTool)))
|
||||
}
|
||||
}
|
||||
|
||||
func toggled(tool: WorkspaceTool) {
|
||||
if let toolIdentifier = self.toolIdentifier(for: tool) {
|
||||
self.emitter.emit(self.uuidAction(for: .setState(for: toolIdentifier, with: tool)))
|
||||
}
|
||||
}
|
||||
|
||||
func moved(tool: WorkspaceTool) {
|
||||
if let toolIdentifier = self.toolIdentifier(for: tool) {
|
||||
self.emitter.emit(self.uuidAction(for: .setState(for: toolIdentifier, with: tool)))
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func toolIdentifier(for tool: WorkspaceTool) -> Tools? {
|
||||
switch tool {
|
||||
|
||||
case self.fileBrowserContainer:
|
||||
return .fileBrowser
|
||||
|
||||
case self.openedFileListContainer:
|
||||
return .openedFilesList
|
||||
|
||||
case self.previewContainer:
|
||||
return .preview
|
||||
|
||||
default:
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSUserInterfaceValidationsProtocol
|
||||
extension MainWindow {
|
||||
|
||||
|
@ -47,6 +47,17 @@ class MainWindowTransformer: Transformer {
|
||||
case let .focus(view):
|
||||
state.focusedView = view
|
||||
|
||||
case let .setState(for: tool, with: workspaceTool):
|
||||
state.tools[tool]?.location = workspaceTool.location
|
||||
state.tools[tool]?.dimension = workspaceTool.dimension
|
||||
state.tools[tool]?.open = workspaceTool.isSelected
|
||||
|
||||
case let .toggleAllTools(value):
|
||||
state.isAllToolsVisible = value
|
||||
|
||||
case let .toggleToolButtons(value):
|
||||
state.isToolButtonsVisible = value
|
||||
|
||||
case .close:
|
||||
state.isClosed = true
|
||||
|
||||
|
221
VimR/SerializableStates.swift
Normal file
221
VimR/SerializableStates.swift
Normal file
@ -0,0 +1,221 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
fileprivate class Keys {
|
||||
|
||||
static let openNewOnLaunch = "open-new-window-when-launching"
|
||||
static let openNewOnReactivation = "open-new-window-on-reactivation"
|
||||
static let useSnapshotUpdateChannel = "use-snapshot-update-channel"
|
||||
|
||||
class OpenQuickly {
|
||||
|
||||
static let key = "open-quickly"
|
||||
|
||||
static let ignorePatterns = "ignore-patterns"
|
||||
}
|
||||
|
||||
class Appearance {
|
||||
|
||||
static let key = "appearance"
|
||||
|
||||
static let editorFontName = "editor-font-name"
|
||||
static let editorFontSize = "editor-font-size"
|
||||
static let editorLinespacing = "editor-linespacing"
|
||||
static let editorUsesLigatures = "editor-uses-ligatures"
|
||||
}
|
||||
|
||||
class MainWindow {
|
||||
|
||||
static let key = "main-window"
|
||||
|
||||
static let allToolsVisible = "is-all-tools-visible"
|
||||
static let toolButtonsVisible = "is-tool-buttons-visible"
|
||||
}
|
||||
|
||||
class WorkspaceTool {
|
||||
|
||||
static let key = "workspace-tool"
|
||||
|
||||
static let location = "location"
|
||||
static let open = "is-visible"
|
||||
static let dimension = "dimension"
|
||||
}
|
||||
}
|
||||
|
||||
protocol SerializableState {
|
||||
|
||||
func dict() -> [String: Any]
|
||||
}
|
||||
|
||||
extension AppState: SerializableState {
|
||||
|
||||
func dict() -> [String: Any] {
|
||||
return [
|
||||
Keys.openNewOnLaunch: self.openNewMainWindowOnLaunch,
|
||||
Keys.openNewOnReactivation: self.openNewMainWindowOnReactivation,
|
||||
Keys.useSnapshotUpdateChannel: self.useSnapshotUpdate,
|
||||
|
||||
Keys.OpenQuickly.key: self.openQuickly.dict(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
extension OpenQuicklyWindow.State: SerializableState {
|
||||
|
||||
func dict() -> [String: Any] {
|
||||
return [
|
||||
Keys.OpenQuickly.ignorePatterns: FileItemIgnorePattern.toString(self.ignorePatterns)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
extension AppearanceState: SerializableState {
|
||||
|
||||
func dict() -> [String: Any] {
|
||||
return [
|
||||
Keys.Appearance.editorFontName: self.font.fontName,
|
||||
Keys.Appearance.editorFontSize: Float(self.font.pointSize),
|
||||
Keys.Appearance.editorLinespacing: Float(self.linespacing),
|
||||
Keys.Appearance.editorUsesLigatures: self.usesLigatures,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
extension WorkspaceToolState: SerializableState {
|
||||
|
||||
func dict() -> [String: Any] {
|
||||
return [
|
||||
Keys.WorkspaceTool.location: PrefUtils.locationAsString(for: self.location),
|
||||
Keys.WorkspaceTool.open: self.open,
|
||||
Keys.WorkspaceTool.dimension: Float(self.dimension),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
extension MainWindow.State: SerializableState {
|
||||
|
||||
func dict() -> [String: Any] {
|
||||
return [
|
||||
Keys.MainWindow.allToolsVisible: self.isAllToolsVisible,
|
||||
Keys.MainWindow.toolButtonsVisible: self.isToolButtonsVisible,
|
||||
|
||||
Keys.Appearance.key: self.appearance.dict(),
|
||||
Keys.WorkspaceTool.key: Array(self.tools.keys).toDict { self.tools[$0]!.dict() }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class PrefUtils {
|
||||
|
||||
fileprivate static let whitespaceCharSet = CharacterSet.whitespaces
|
||||
|
||||
static func ignorePatterns(fromString str: String) -> Set<FileItemIgnorePattern> {
|
||||
if str.trimmingCharacters(in: self.whitespaceCharSet).characters.count == 0 {
|
||||
return Set()
|
||||
}
|
||||
|
||||
let patterns: [FileItemIgnorePattern] = str
|
||||
.components(separatedBy: ",")
|
||||
.flatMap {
|
||||
let trimmed = $0.trimmingCharacters(in: self.whitespaceCharSet)
|
||||
if trimmed.characters.count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return FileItemIgnorePattern(pattern: trimmed)
|
||||
}
|
||||
|
||||
return Set(patterns)
|
||||
}
|
||||
|
||||
static func ignorePatternString(fromSet set: Set<FileItemIgnorePattern>) -> String {
|
||||
return Array(set)
|
||||
.map { $0.pattern }
|
||||
.sorted()
|
||||
.joined(separator: ", ")
|
||||
}
|
||||
|
||||
static func value<T>(from dict: [String: Any], for key: String) -> T? {
|
||||
return dict[key] as? T
|
||||
}
|
||||
|
||||
static func value<T>(from dict: [String: Any], for key: String, default defaultValue: T) -> T {
|
||||
return dict[key] as? T ?? defaultValue
|
||||
}
|
||||
|
||||
static func dict(from dict: [String: Any], for key: String) -> [String: Any]? {
|
||||
return dict[key] as? [String: Any]
|
||||
}
|
||||
|
||||
static func float(from dict: [String: Any], for key: String, default defaultValue: Float) -> Float {
|
||||
return (dict[key] as? NSNumber)?.floatValue ?? defaultValue
|
||||
}
|
||||
|
||||
static func float(from dict: [String: Any], for key: String) -> Float? {
|
||||
guard let number = dict[key] as? NSNumber else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return number.floatValue
|
||||
}
|
||||
|
||||
static func bool(from dict: [String: Any], for key: String) -> Bool? {
|
||||
guard let number = dict[key] as? NSNumber else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return number.boolValue
|
||||
}
|
||||
|
||||
static func bool(from dict: [String: Any], for key: String, default defaultValue: Bool) -> Bool {
|
||||
return (dict[key] as? NSNumber)?.boolValue ?? defaultValue
|
||||
}
|
||||
|
||||
static func string(from dict: [String: Any], for key: String) -> String? {
|
||||
return dict[key] as? String
|
||||
}
|
||||
|
||||
static func saneFont(_ fontName: String, fontSize: CGFloat) -> NSFont {
|
||||
var editorFont = NSFont(name: fontName, size: fontSize) ?? NeoVimView.defaultFont
|
||||
if !editorFont.isFixedPitch {
|
||||
editorFont = NSFontManager.shared().convert(NeoVimView.defaultFont, toSize: editorFont.pointSize)
|
||||
}
|
||||
if editorFont.pointSize < NeoVimView.minFontSize || editorFont.pointSize > NeoVimView.maxFontSize {
|
||||
editorFont = NSFontManager.shared().convert(editorFont, toSize: NeoVimView.defaultFont.pointSize)
|
||||
}
|
||||
|
||||
return editorFont
|
||||
}
|
||||
|
||||
static func saneLinespacing(_ fLinespacing: Float) -> CGFloat {
|
||||
let linespacing = CGFloat(fLinespacing)
|
||||
guard linespacing >= NeoVimView.minLinespacing && linespacing <= NeoVimView.maxLinespacing else {
|
||||
return NeoVimView.defaultLinespacing
|
||||
}
|
||||
|
||||
return linespacing
|
||||
}
|
||||
|
||||
static func location(from strValue: String) -> WorkspaceBarLocation? {
|
||||
switch strValue {
|
||||
case "top": return .top
|
||||
case "right": return .right
|
||||
case "bottom": return .bottom
|
||||
case "left": return .left
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func locationAsString(for loc: WorkspaceBarLocation) -> String {
|
||||
switch loc {
|
||||
case .top: return "top"
|
||||
case .right: return "right"
|
||||
case .bottom: return "bottom"
|
||||
case .left: return "left"
|
||||
}
|
||||
}
|
||||
}
|
@ -112,6 +112,12 @@ extension MainWindow {
|
||||
var root = FileItem(URL(fileURLWithPath: "/", isDirectory: true))
|
||||
var lastFileSystemUpdate = Marked(FileItem(URL(fileURLWithPath: "/", isDirectory: true)))
|
||||
|
||||
var tools = [
|
||||
MainWindow.Tools.fileBrowser: WorkspaceToolState(location: .left, dimension: 200, open: true),
|
||||
MainWindow.Tools.openedFilesList: WorkspaceToolState(location: .left, dimension: 200, open: false),
|
||||
MainWindow.Tools.preview: WorkspaceToolState(location: .right, dimension: 250, open: false),
|
||||
]
|
||||
|
||||
var preview = PreviewState.default
|
||||
var previewTool = PreviewTool.State.default
|
||||
|
||||
@ -140,6 +146,15 @@ extension MainWindow {
|
||||
}
|
||||
}
|
||||
|
||||
struct WorkspaceToolState {
|
||||
|
||||
static let `default` = WorkspaceToolState()
|
||||
|
||||
var location = WorkspaceBarLocation.left
|
||||
var dimension = CGFloat(200)
|
||||
var open = false
|
||||
}
|
||||
|
||||
extension PreviewTool {
|
||||
|
||||
struct State {
|
||||
|
@ -21,7 +21,7 @@ class UiRootTransformer: Transformer {
|
||||
appState.currentMainWindowUuid = uuid
|
||||
appState.mainWindowTemplate = appState.mainWindows[uuid] ?? appState.mainWindowTemplate
|
||||
|
||||
case .close:
|
||||
case .closed:
|
||||
if appState.currentMainWindowUuid == uuid {
|
||||
appState.currentMainWindowUuid = nil
|
||||
}
|
||||
|
@ -18,10 +18,11 @@ enum WorkspaceBarLocation {
|
||||
|
||||
protocol WorkspaceDelegate: class {
|
||||
|
||||
func resizeWillStart(workspace: Workspace)
|
||||
func resizeDidEnd(workspace: Workspace)
|
||||
func resizeWillStart(workspace: Workspace, tool: WorkspaceTool?)
|
||||
func resizeDidEnd(workspace: Workspace, tool: WorkspaceTool?)
|
||||
|
||||
func toggled(tool: WorkspaceTool)
|
||||
func moved(tool: WorkspaceTool)
|
||||
}
|
||||
|
||||
class Workspace: NSView, WorkspaceBarDelegate {
|
||||
@ -94,6 +95,8 @@ class Workspace: NSView, WorkspaceBarDelegate {
|
||||
func move(tool: WorkspaceTool, to location: WorkspaceBarLocation) {
|
||||
tool.bar?.remove(tool: tool)
|
||||
self.bars[location]?.append(tool: tool)
|
||||
|
||||
self.delegate?.moved(tool: tool)
|
||||
}
|
||||
|
||||
func toggleAllTools() {
|
||||
@ -190,17 +193,21 @@ extension Workspace {
|
||||
// MARK: - WorkspaceBarDelegate
|
||||
extension Workspace {
|
||||
|
||||
func resizeWillStart(workspaceBar: WorkspaceBar) {
|
||||
self.delegate?.resizeWillStart(workspace: self)
|
||||
func resizeWillStart(workspaceBar: WorkspaceBar, tool: WorkspaceTool?) {
|
||||
self.delegate?.resizeWillStart(workspace: self, tool: tool)
|
||||
}
|
||||
|
||||
func resizeDidEnd(workspaceBar: WorkspaceBar) {
|
||||
self.delegate?.resizeDidEnd(workspace: self)
|
||||
func resizeDidEnd(workspaceBar: WorkspaceBar, tool: WorkspaceTool?) {
|
||||
self.delegate?.resizeDidEnd(workspace: self, tool: tool)
|
||||
}
|
||||
|
||||
func toggle(tool: WorkspaceTool) {
|
||||
self.delegate?.toggled(tool: tool)
|
||||
}
|
||||
|
||||
func moved(tool: WorkspaceTool) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Layout
|
||||
|
@ -8,8 +8,8 @@ import PureLayout
|
||||
|
||||
protocol WorkspaceBarDelegate: class {
|
||||
|
||||
func resizeWillStart(workspaceBar: WorkspaceBar)
|
||||
func resizeDidEnd(workspaceBar: WorkspaceBar)
|
||||
func resizeWillStart(workspaceBar: WorkspaceBar, tool: WorkspaceTool?)
|
||||
func resizeDidEnd(workspaceBar: WorkspaceBar, tool: WorkspaceTool?)
|
||||
|
||||
func toggle(tool: WorkspaceTool)
|
||||
}
|
||||
@ -399,7 +399,7 @@ extension WorkspaceBar {
|
||||
}
|
||||
|
||||
self.isMouseDownOngoing = true
|
||||
self.delegate?.resizeWillStart(workspaceBar: self)
|
||||
self.delegate?.resizeWillStart(workspaceBar: self, tool: self.selectedTool)
|
||||
self.dimensionConstraint.priority = NSLayoutPriorityDragThatCannotResizeWindow - 1
|
||||
|
||||
var dragged = false
|
||||
@ -441,7 +441,7 @@ extension WorkspaceBar {
|
||||
|
||||
self.dimensionConstraint.priority = NSLayoutPriorityDragThatCannotResizeWindow
|
||||
self.isMouseDownOngoing = false
|
||||
self.delegate?.resizeDidEnd(workspaceBar: self)
|
||||
self.delegate?.resizeDidEnd(workspaceBar: self, tool: self.selectedTool)
|
||||
}
|
||||
|
||||
override func resetCursorRects() {
|
||||
@ -817,7 +817,7 @@ extension WorkspaceBar {
|
||||
extension WorkspaceBar {
|
||||
|
||||
func toggle(_ tool: WorkspaceTool) {
|
||||
self.delegate?.resizeWillStart(workspaceBar: self)
|
||||
self.delegate?.resizeWillStart(workspaceBar: self, tool: self.selectedTool)
|
||||
|
||||
if self.isOpen {
|
||||
let curTool = self.selectedTool!
|
||||
@ -835,7 +835,7 @@ extension WorkspaceBar {
|
||||
|
||||
self.relayout()
|
||||
|
||||
self.delegate?.resizeDidEnd(workspaceBar: self)
|
||||
self.delegate?.resizeDidEnd(workspaceBar: self, tool: self.selectedTool)
|
||||
self.delegate?.toggle(tool: tool)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user