mirror of
https://github.com/qvacua/vimr.git
synced 2024-12-01 10:02:36 +03:00
Merge branch 'issue/286-filebrowser'
This commit is contained in:
commit
238e7c1c74
@ -26,7 +26,7 @@ struct Cell: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
extension Position: CustomStringConvertible {
|
||||
extension Position: CustomStringConvertible, Equatable {
|
||||
|
||||
static let zero = Position(row: 0, column: 0)
|
||||
static let null = Position(row: -1, column: -1)
|
||||
@ -36,18 +36,14 @@ extension Position: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
func == (left: Position, right: Position) -> Bool {
|
||||
public func == (left: Position, right: Position) -> Bool {
|
||||
if left.row != right.row { return false }
|
||||
if left.column != right.column { return false }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func != (left: Position, right: Position) -> Bool {
|
||||
return !(left == right)
|
||||
}
|
||||
|
||||
struct Size: CustomStringConvertible {
|
||||
struct Size: CustomStringConvertible, Equatable {
|
||||
|
||||
static let zero = Size(width: 0, height: 0)
|
||||
|
||||
@ -59,6 +55,10 @@ struct Size: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
func == (left: Size, right: Size) -> Bool {
|
||||
return left.width == right.width && left.height == right.height
|
||||
}
|
||||
|
||||
struct Region: CustomStringConvertible {
|
||||
|
||||
static let zero = Region(top: 0, bottom: 0, left: 0, right: 0)
|
||||
|
@ -79,6 +79,10 @@ open class NeoVimView: NSView, NSUserInterfaceValidations {
|
||||
self.agent.vimCommand("silent cd \(escapedCwd)")
|
||||
}
|
||||
}
|
||||
|
||||
override open var acceptsFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
fileprivate var _font = NeoVimView.defaultFont
|
||||
|
||||
@ -119,12 +123,14 @@ open class NeoVimView: NSView, NSUserInterfaceValidations {
|
||||
fileprivate var pinchTargetScale = CGFloat(1)
|
||||
fileprivate var pinchImage = NSImage()
|
||||
|
||||
fileprivate var currentlyResizing = false
|
||||
|
||||
public init(frame rect: NSRect, config: Config) {
|
||||
self.drawer = TextDrawer(font: self._font, useLigatures: false)
|
||||
self.agent = NeoVimAgent(uuid: self.uuid)
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
|
||||
|
||||
self.wantsLayer = true
|
||||
self.cellSize = self.drawer.cellSize
|
||||
self.descent = self.drawer.descent
|
||||
@ -168,6 +174,17 @@ open class NeoVimView: NSView, NSUserInterfaceValidations {
|
||||
|
||||
// MARK: - API
|
||||
extension NeoVimView {
|
||||
|
||||
public func enterResizeMode() {
|
||||
self.currentlyResizing = true
|
||||
self.needsDisplay = true
|
||||
}
|
||||
|
||||
public func exitResizeMode() {
|
||||
self.currentlyResizing = false
|
||||
self.needsDisplay = true
|
||||
self.resizeNeoVimUiTo(size: self.bounds.size)
|
||||
}
|
||||
|
||||
public func currentBuffer() -> NeoVimBuffer {
|
||||
return self.agent.buffers().filter { $0.current }.first!
|
||||
@ -261,7 +278,7 @@ extension NeoVimView {
|
||||
return
|
||||
}
|
||||
|
||||
if self.inLiveResize {
|
||||
if self.inLiveResize || self.currentlyResizing {
|
||||
// TODO: Turn off live resizing for now.
|
||||
// self.resizeNeoVimUiTo(size: newSize)
|
||||
return
|
||||
@ -281,6 +298,10 @@ extension NeoVimView {
|
||||
// NSLog("\(#function): \(size)")
|
||||
let discreteSize = self.discreteSize(size: size)
|
||||
|
||||
if discreteSize == self.grid.size {
|
||||
return
|
||||
}
|
||||
|
||||
self.xOffset = floor((size.width - self.cellSize.width * CGFloat(discreteSize.width)) / 2)
|
||||
self.yOffset = floor((size.height - self.cellSize.height * CGFloat(discreteSize.height)) / 2)
|
||||
|
||||
@ -301,7 +322,7 @@ extension NeoVimView {
|
||||
return
|
||||
}
|
||||
|
||||
if self.inLiveResize {
|
||||
if self.inLiveResize || self.currentlyResizing {
|
||||
NSColor.windowBackgroundColor.set()
|
||||
dirtyUnionRect.fill()
|
||||
|
||||
@ -863,6 +884,7 @@ extension NeoVimView {
|
||||
extension NeoVimView {
|
||||
|
||||
override open func mouseDown(with event: NSEvent) {
|
||||
// self.window?.makeFirstResponder(self)
|
||||
self.mouse(event: event, vimName:"LeftMouse")
|
||||
}
|
||||
|
||||
@ -1232,7 +1254,7 @@ extension NeoVimView: NeoVimUiBridgeProtocol {
|
||||
|
||||
public func setTitle(_ title: String) {
|
||||
DispatchUtils.gui {
|
||||
self.delegate?.setTitle(title)
|
||||
self.delegate?.set(title: title)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1241,7 +1263,7 @@ extension NeoVimView: NeoVimUiBridgeProtocol {
|
||||
|
||||
public func setDirtyStatus(_ dirty: Bool) {
|
||||
DispatchUtils.gui {
|
||||
self.delegate?.setDirtyStatus(dirty)
|
||||
self.delegate?.set(dirtyStatus: dirty)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,8 @@ import Cocoa
|
||||
// See http://stackoverflow.com/a/24104371 for class
|
||||
public protocol NeoVimViewDelegate: class {
|
||||
|
||||
func setTitle(_ title: String)
|
||||
func setDirtyStatus(_ dirty: Bool)
|
||||
func set(title: String)
|
||||
func set(dirtyStatus: Bool)
|
||||
func cwdChanged()
|
||||
func neoVimStopped()
|
||||
}
|
||||
|
@ -25,7 +25,6 @@
|
||||
1929B73E5EC0B108B83F82EB /* FileItemService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B3A98687DF171307AAC8 /* FileItemService.swift */; };
|
||||
1929B741C4E58EEDAF901353 /* FileItemIgnorePattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B69499B2569793350CEC /* FileItemIgnorePattern.swift */; };
|
||||
1929B7A2F2B423AA9740FD45 /* FileUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B5D977261F1EBFA9E8F1 /* FileUtilsTest.swift */; };
|
||||
1929B7C981A0EFCC8D631E3F /* FileItemService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B3A98687DF171307AAC8 /* FileItemService.swift */; };
|
||||
1929B93DBAD09835E428F610 /* PrefPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BB251F74BEFC82CEEF84 /* PrefPane.swift */; };
|
||||
1929BD3F9E609BFADB27584B /* Scorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B9D510177918080BE39B /* Scorer.swift */; };
|
||||
1929BD4CA2204E061A86A140 /* MatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BC19C1BC19246AFF1621 /* MatcherTests.swift */; };
|
||||
@ -33,6 +32,13 @@
|
||||
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"; }; };
|
||||
4B029F1A1D45E349004EE0D3 /* PrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B029F1C1D45E349004EE0D3 /* PrefWindow.xib */; };
|
||||
4B0677371D99D9C3001A2588 /* FileBrowserComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0677361D99D9C3001A2588 /* FileBrowserComponent.swift */; };
|
||||
4B06773C1D9DCF23001A2588 /* Component.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B39DA7AC4A9B62D7CD39 /* Component.swift */; };
|
||||
4B0677401DA170D5001A2588 /* AppKitCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A70931D60E04200E12030 /* AppKitCommons.swift */; };
|
||||
4B0677411DA170D5001A2588 /* Workspace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6423951D8EFD7100FC78C8 /* Workspace.swift */; };
|
||||
4B0677421DA170D5001A2588 /* WorkspaceBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6423971D8EFDE000FC78C8 /* WorkspaceBar.swift */; };
|
||||
4B0677431DA170D5001A2588 /* WorkspaceTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6423991D8EFE3000FC78C8 /* WorkspaceTool.swift */; };
|
||||
4B0677441DA170D5001A2588 /* WorkspaceToolButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB489411D952CF6005BB0E8 /* WorkspaceToolButton.swift */; };
|
||||
4B0BCC941D70320C00D3CE65 /* Logger.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B0BCC931D70320C00D3CE65 /* Logger.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
4B0C905B1D5DED69007753A3 /* NeoVimBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B0C905A1D5DED69007753A3 /* NeoVimBuffer.m */; };
|
||||
4B0E22581D5DEDC700C072E6 /* NeoVimBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B0C90591D5DED69007753A3 /* NeoVimBuffer.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@ -79,6 +85,7 @@
|
||||
4B6A70961D6100E300E12030 /* SwiftCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A70951D6100E300E12030 /* SwiftCommons.swift */; };
|
||||
4B6A70991D65058A00E12030 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B56F29B1D29926600C1F92E /* Nimble.framework */; };
|
||||
4B6A709C1D6507A000E12030 /* Nimble.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B56F29B1D29926600C1F92E /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
4B6B0A781DA2A1A500212D6D /* FileOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6B0A771DA2A1A500212D6D /* FileOutlineView.swift */; };
|
||||
4B854A1D1D31447C00E08DE1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B854A1C1D31447C00E08DE1 /* main.m */; };
|
||||
4B97E2CC1D33F53D00FC0660 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B97E2CE1D33F53D00FC0660 /* MainWindow.xib */; };
|
||||
4B9A15241D2993DA009F9F67 /* Nimble.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B56F29B1D29926600C1F92E /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@ -253,6 +260,7 @@
|
||||
1929BEEB33113B0E33C3830F /* Matcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matcher.swift; sourceTree = "<group>"; };
|
||||
1929BF00B466B40629C2AABE /* NeoVimView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeoVimView.swift; sourceTree = "<group>"; };
|
||||
4B029F1B1D45E349004EE0D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PrefWindow.xib; sourceTree = "<group>"; };
|
||||
4B0677361D99D9C3001A2588 /* FileBrowserComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileBrowserComponent.swift; sourceTree = "<group>"; };
|
||||
4B0BCC931D70320C00D3CE65 /* Logger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logger.h; path = VimR/Logger.h; sourceTree = SOURCE_ROOT; };
|
||||
4B0C90591D5DED69007753A3 /* NeoVimBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NeoVimBuffer.h; sourceTree = "<group>"; };
|
||||
4B0C905A1D5DED69007753A3 /* NeoVimBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NeoVimBuffer.m; sourceTree = "<group>"; };
|
||||
@ -292,6 +300,7 @@
|
||||
4B6423A81D8EFE7500FC78C8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
4B6A70931D60E04200E12030 /* AppKitCommons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppKitCommons.swift; sourceTree = "<group>"; };
|
||||
4B6A70951D6100E300E12030 /* SwiftCommons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftCommons.swift; sourceTree = "<group>"; };
|
||||
4B6B0A771DA2A1A500212D6D /* FileOutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileOutlineView.swift; sourceTree = "<group>"; };
|
||||
4B854A1A1D31447C00E08DE1 /* NeoVimServer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = NeoVimServer; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4B854A1C1D31447C00E08DE1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
4B97E2CD1D33F53D00FC0660 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainWindow.xib; sourceTree = "<group>"; };
|
||||
@ -413,6 +422,15 @@
|
||||
path = resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B0677351D99D9A2001A2588 /* File Browser */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B6B0A771DA2A1A500212D6D /* FileOutlineView.swift */,
|
||||
4B0677361D99D9C3001A2588 /* FileBrowserComponent.swift */,
|
||||
);
|
||||
name = "File Browser";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B1AC1AF1D7F395300898F0B /* Open Quickly */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -569,6 +587,7 @@
|
||||
1929B39DA7AC4A9B62D7CD39 /* Component.swift */,
|
||||
4BD3BF961D32B0DB00082605 /* MainWindowManager.swift */,
|
||||
4BD3BF921D32A95800082605 /* MainWindowComponent.swift */,
|
||||
4B0677351D99D9A2001A2588 /* File Browser */,
|
||||
4B1AC1AF1D7F395300898F0B /* Open Quickly */,
|
||||
4B238BED1D3ED55300CBDD98 /* Preferences */,
|
||||
);
|
||||
@ -1023,7 +1042,9 @@
|
||||
1929B67DA3EB21A631EF1DBB /* FileUtils.swift in Sources */,
|
||||
1929B3F5743967125F357C9F /* Matcher.swift in Sources */,
|
||||
1929B53876E6952D378C2B30 /* ScoredFileItem.swift in Sources */,
|
||||
4B0677371D99D9C3001A2588 /* FileBrowserComponent.swift in Sources */,
|
||||
1929BD3F9E609BFADB27584B /* Scorer.swift in Sources */,
|
||||
4B6B0A781DA2A1A500212D6D /* FileOutlineView.swift in Sources */,
|
||||
4BAD84E81D7CA8FC00A79CC3 /* OpenQuicklyFilterOperation.swift in Sources */,
|
||||
1929B0E0C3BC59F52713D5A2 /* FoundationCommons.swift in Sources */,
|
||||
);
|
||||
@ -1033,10 +1054,15 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4B0677401DA170D5001A2588 /* AppKitCommons.swift in Sources */,
|
||||
4B0677411DA170D5001A2588 /* Workspace.swift in Sources */,
|
||||
4B0677421DA170D5001A2588 /* WorkspaceBar.swift in Sources */,
|
||||
4B0677431DA170D5001A2588 /* WorkspaceTool.swift in Sources */,
|
||||
4B0677441DA170D5001A2588 /* WorkspaceToolButton.swift in Sources */,
|
||||
4B06773C1D9DCF23001A2588 /* Component.swift in Sources */,
|
||||
4BF8EEDC1D85908400CAC08A /* PrefUtils.swift in Sources */,
|
||||
1929B43FBA7A31F39D294CD0 /* FileItem.swift in Sources */,
|
||||
1929B741C4E58EEDAF901353 /* FileItemIgnorePattern.swift in Sources */,
|
||||
1929B7C981A0EFCC8D631E3F /* FileItemService.swift in Sources */,
|
||||
1929B2E1A64297B8E05BB64A /* FileUtils.swift in Sources */,
|
||||
1929B3BF1DB87B57559DC27D /* Matcher.swift in Sources */,
|
||||
4BF8EEDB1D85903000CAC08A /* PrefUtilsTest.swift in Sources */,
|
||||
|
@ -37,63 +37,55 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
fileprivate let openQuicklyWindowManager: OpenQuicklyWindowManager
|
||||
fileprivate let prefWindowComponent: PrefWindowComponent
|
||||
|
||||
fileprivate let fileItemService = FileItemService()
|
||||
|
||||
fileprivate let fileItemService: FileItemService
|
||||
|
||||
fileprivate var quitWhenAllWindowsAreClosed = false
|
||||
fileprivate var launching = true
|
||||
|
||||
override init() {
|
||||
self.actionSink = self.actionSubject.asObservable()
|
||||
self.changeSink = self.changeSubject.asObservable()
|
||||
let actionAndChangeSink = [self.changeSink, self.actionSink].toMergedObservables()
|
||||
|
||||
self.prefStore = PrefStore(source: self.actionSink)
|
||||
|
||||
self.fileItemService = FileItemService(source: self.changeSink)
|
||||
self.fileItemService.set(ignorePatterns: self.prefStore.data.general.ignorePatterns)
|
||||
|
||||
self.prefWindowComponent = PrefWindowComponent(source: self.changeSink, initialData: self.prefStore.data)
|
||||
|
||||
self.mainWindowManager = MainWindowManager(source: self.changeSink,
|
||||
fileItemService: self.fileItemService,
|
||||
initialData: self.prefStore.data)
|
||||
self.openQuicklyWindowManager = OpenQuicklyWindowManager(source: self.changeSink,
|
||||
self.openQuicklyWindowManager = OpenQuicklyWindowManager(source: actionAndChangeSink,
|
||||
fileItemService: self.fileItemService)
|
||||
|
||||
super.init()
|
||||
|
||||
self.prefStore.sink
|
||||
.filter { $0 is PrefData }
|
||||
.map { $0 as! PrefData }
|
||||
.subscribe(onNext: { [unowned self] data in
|
||||
if data.general.ignorePatterns == self.fileItemService.ignorePatterns {
|
||||
return
|
||||
}
|
||||
|
||||
self.fileItemService.set(ignorePatterns: data.general.ignorePatterns)
|
||||
})
|
||||
.addDisposableTo(self.disposeBag)
|
||||
|
||||
self.mainWindowManager.sink
|
||||
.filter { $0 is MainWindowEvent || $0 is MainWindowAction }
|
||||
.filter { $0 is MainWindowManagerAction }
|
||||
.map { $0 as! MainWindowManagerAction }
|
||||
.subscribe(onNext: { [unowned self] event in
|
||||
switch event {
|
||||
case let MainWindowAction.openQuickly(mainWindow: mainWindow):
|
||||
self.openQuicklyWindowManager.open(forMainWindow: mainWindow)
|
||||
case MainWindowEvent.allWindowsClosed:
|
||||
case .allWindowsClosed:
|
||||
if self.quitWhenAllWindowsAreClosed {
|
||||
NSApp.stop(self)
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
.addDisposableTo(self.disposeBag)
|
||||
|
||||
[ self.prefStore ]
|
||||
let changeFlows: [Flow] = [ self.prefStore ]
|
||||
let actionFlows: [Flow] = [ self.prefWindowComponent, self.mainWindowManager ]
|
||||
|
||||
changeFlows
|
||||
.map { $0.sink }
|
||||
.toMergedObservables()
|
||||
.subscribe(self.changeSubject)
|
||||
.addDisposableTo(self.disposeBag)
|
||||
|
||||
[ self.prefWindowComponent ]
|
||||
|
||||
actionFlows
|
||||
.map { $0.sink }
|
||||
.toMergedObservables()
|
||||
.subscribe(self.actionSubject)
|
||||
@ -106,7 +98,7 @@ extension AppDelegate {
|
||||
|
||||
func applicationWillFinishLaunching(_: Notification) {
|
||||
self.launching = true
|
||||
|
||||
|
||||
let appleEventManager = NSAppleEventManager.shared()
|
||||
appleEventManager.setEventHandler(self,
|
||||
andSelector: #selector(AppDelegate.handle(getUrlEvent:replyEvent:)),
|
||||
@ -164,7 +156,7 @@ extension AppDelegate {
|
||||
// There are no open main window, then just quit.
|
||||
return .terminateNow
|
||||
}
|
||||
|
||||
|
||||
// For drag & dropping files on the App icon.
|
||||
func application(_ sender: NSApplication, openFiles filenames: [String]) {
|
||||
let urls = filenames.map { URL(fileURLWithPath: $0) }
|
||||
@ -176,28 +168,28 @@ extension AppDelegate {
|
||||
|
||||
// MARK: - AppleScript
|
||||
extension AppDelegate {
|
||||
|
||||
|
||||
func handle(getUrlEvent event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
|
||||
guard let urlString = event.paramDescriptor(forKeyword: UInt32(keyDirectObject))?.stringValue else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let url = URL(string: urlString) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard url.scheme == "vimr" else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let rawAction = url.host else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let action = VimRUrlAction(rawValue: rawAction) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let queryParams = url.query?.components(separatedBy: "&")
|
||||
let urls = queryParams?
|
||||
.filter { $0.hasPrefix(AppDelegate.filePrefix) }
|
||||
@ -207,8 +199,8 @@ extension AppDelegate {
|
||||
.filter { $0.hasPrefix(AppDelegate.cwdPrefix) }
|
||||
.flatMap { $0.without(prefix: AppDelegate.cwdPrefix).removingPercentEncoding }
|
||||
.map { URL(fileURLWithPath: $0) }
|
||||
.first ?? URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
|
||||
|
||||
.first ?? FileUtils.userHomeUrl
|
||||
|
||||
switch action {
|
||||
case .activate, .newWindow:
|
||||
_ = self.mainWindowManager.newMainWindow(urls: urls, cwd: cwd)
|
||||
@ -229,7 +221,7 @@ extension AppDelegate {
|
||||
@IBAction func showPrefWindow(_ sender: AnyObject!) {
|
||||
self.prefWindowComponent.show()
|
||||
}
|
||||
|
||||
|
||||
@IBAction func newDocument(_ sender: AnyObject!) {
|
||||
_ = self.mainWindowManager.newMainWindow()
|
||||
}
|
||||
@ -242,7 +234,7 @@ extension AppDelegate {
|
||||
guard result == NSFileHandlingPanelOKButton else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
_ = self.mainWindowManager.newMainWindow(urls: panel.urls)
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +71,14 @@ extension NSView {
|
||||
func removeAllConstraints() {
|
||||
self.removeConstraints(self.constraints)
|
||||
}
|
||||
|
||||
var isFirstResponder: Bool {
|
||||
return self.window?.firstResponder == self
|
||||
}
|
||||
|
||||
func beFirstResponder() {
|
||||
self.window?.makeFirstResponder(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSTableView {
|
||||
@ -100,6 +108,48 @@ extension NSTableView {
|
||||
}
|
||||
}
|
||||
|
||||
extension NSOutlineView {
|
||||
|
||||
static func standardOutlineView() -> NSOutlineView {
|
||||
let outlineView = NSOutlineView(frame: CGRect.zero)
|
||||
NSOutlineView.configure(toStandard: outlineView)
|
||||
return outlineView
|
||||
}
|
||||
|
||||
static func configure(toStandard outlineView: NSOutlineView) {
|
||||
let column = NSTableColumn(identifier: "name")
|
||||
column.isEditable = false
|
||||
|
||||
outlineView.addTableColumn(column)
|
||||
outlineView.outlineTableColumn = column
|
||||
outlineView.sizeLastColumnToFit()
|
||||
outlineView.allowsEmptySelection = false
|
||||
outlineView.allowsMultipleSelection = false
|
||||
outlineView.headerView = nil
|
||||
outlineView.focusRingType = .none
|
||||
}
|
||||
|
||||
/**
|
||||
The selected item. When the selection is empty, then returns `nil`. When multiple items are selected, then returns
|
||||
the last selected item.
|
||||
*/
|
||||
var selectedItem: Any? {
|
||||
if self.selectedRow < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.item(atRow: self.selectedRow)
|
||||
}
|
||||
|
||||
func toggle(item: Any) {
|
||||
if self.isItemExpanded(item) {
|
||||
self.collapseItem(item)
|
||||
} else {
|
||||
self.expandItem(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSScrollView {
|
||||
|
||||
static func standardScrollView() -> NSScrollView {
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
@ -182,10 +182,40 @@
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="NXP-NG-aHV"/>
|
||||
<menuItem title="Enter Full Screen" keyEquivalent="f" id="izE-hB-PjH">
|
||||
<menuItem title="Enter Full Screen" keyEquivalent="f" id="Co5-Y0-sqo">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleFullScreen:" target="-1" id="hl6-CI-dSN"/>
|
||||
<action selector="toggleFullScreen:" target="-1" id="b4d-vv-b5h"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Tools" id="zhG-4I-P01">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Tools" id="7MO-P5-uU3">
|
||||
<items>
|
||||
<menuItem title="Toggle All Tools" keyEquivalent="\" id="Lb6-TZ-LgL">
|
||||
<connections>
|
||||
<action selector="toggleAllTools:" target="-1" id="V97-0e-bES"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Toggle Tool Buttons" id="oK0-ZG-w1f">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleToolButtons:" target="-1" id="bxj-O1-Vo9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="UAq-9D-Jex"/>
|
||||
<menuItem title="Toggle File Browser" keyEquivalent="1" id="PWx-V8-0cQ">
|
||||
<connections>
|
||||
<action selector="toggleFileBrowser:" target="-1" id="Ggq-4w-iN7"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="XHW-5e-Vad"/>
|
||||
<menuItem title="Focus NeoVim View" keyEquivalent="." id="TtL-Gg-pCj">
|
||||
<connections>
|
||||
<action selector="focusNeoVimView:" target="-1" id="zsS-Ax-dPe"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G1004" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSWindowController">
|
||||
|
@ -6,27 +6,41 @@
|
||||
import Cocoa
|
||||
import RxSwift
|
||||
|
||||
protocol Flow {
|
||||
protocol Flow: class {
|
||||
|
||||
var sink: Observable<Any> { get }
|
||||
}
|
||||
|
||||
protocol Store: Flow {}
|
||||
|
||||
protocol Component: Flow {}
|
||||
|
||||
class StandardFlow: Flow {
|
||||
class PublishingFlow: Flow {
|
||||
|
||||
var sink: Observable<Any> {
|
||||
return self.subject.asObservable()
|
||||
}
|
||||
|
||||
let subject = PublishSubject<Any>()
|
||||
|
||||
init() {
|
||||
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
func publish(event: Any) {
|
||||
self.subject.onNext(event)
|
||||
}
|
||||
}
|
||||
|
||||
class StandardFlow: PublishingFlow {
|
||||
|
||||
let source: Observable<Any>
|
||||
let disposeBag = DisposeBag()
|
||||
|
||||
init(source: Observable<Any>) {
|
||||
self.source = source
|
||||
super.init()
|
||||
|
||||
self.subscription(source: source).addDisposableTo(self.disposeBag)
|
||||
}
|
||||
|
||||
@ -37,13 +51,36 @@ class StandardFlow: Flow {
|
||||
func subscription(source: Observable<Any>) -> Disposable {
|
||||
preconditionFailure("Please override")
|
||||
}
|
||||
}
|
||||
|
||||
class EmbeddableComponent: Flow {
|
||||
|
||||
var sink: Observable<Any> {
|
||||
return self.subject.asObservable()
|
||||
}
|
||||
|
||||
fileprivate let subject = PublishSubject<Any>()
|
||||
fileprivate let source: Observable<Any>
|
||||
fileprivate let disposeBag = DisposeBag()
|
||||
|
||||
init(source: Observable<Any>) {
|
||||
self.source = source
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
func set(subscription: ((Observable<Any>) -> Disposable)) {
|
||||
subscription(source).addDisposableTo(self.disposeBag)
|
||||
}
|
||||
|
||||
func publish(event: Any) {
|
||||
self.subject.onNext(event)
|
||||
}
|
||||
}
|
||||
|
||||
class StandardComponent: NSObject, Component {
|
||||
class StandardComponent: NSObject, Flow {
|
||||
|
||||
var sink: Observable<Any> {
|
||||
return self.subject.asObservable()
|
||||
@ -78,6 +115,64 @@ class StandardComponent: NSObject, Component {
|
||||
}
|
||||
}
|
||||
|
||||
class ViewComponent: NSView, Flow {
|
||||
|
||||
var view: NSView {
|
||||
preconditionFailure("Please override")
|
||||
}
|
||||
|
||||
var sink: Observable<Any> {
|
||||
return self.subject.asObservable()
|
||||
}
|
||||
|
||||
let subject = PublishSubject<Any>()
|
||||
let source: Observable<Any>
|
||||
let disposeBag = DisposeBag()
|
||||
|
||||
init(source: Observable<Any>) {
|
||||
self.source = source
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
self.addViews()
|
||||
self.subscription(source: source).addDisposableTo(self.disposeBag)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
func addViews() {
|
||||
preconditionFailure("Please override")
|
||||
}
|
||||
|
||||
func subscription(source: Observable<Any>) -> Disposable {
|
||||
preconditionFailure("Please override")
|
||||
}
|
||||
|
||||
func publish(event: Any) {
|
||||
self.subject.onNext(event)
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspaceToolComponent: WorkspaceTool, Flow {
|
||||
|
||||
let viewComponent: ViewComponent
|
||||
var sink: Observable<Any> {
|
||||
return self.viewComponent.sink
|
||||
}
|
||||
|
||||
init(title: String, viewComponent: ViewComponent, minimumDimension: CGFloat = 50) {
|
||||
self.viewComponent = viewComponent
|
||||
super.init(title: title, view: viewComponent, minimumDimension: minimumDimension)
|
||||
}
|
||||
}
|
||||
|
||||
class WindowComponent: StandardComponent {
|
||||
|
||||
let windowController: NSWindowController
|
||||
|
186
VimR/FileBrowserComponent.swift
Normal file
186
VimR/FileBrowserComponent.swift
Normal file
@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
import RxSwift
|
||||
|
||||
enum FileBrowserAction {
|
||||
|
||||
case open(url: URL)
|
||||
}
|
||||
|
||||
class FileBrowserComponent: ViewComponent, NSOutlineViewDataSource, NSOutlineViewDelegate {
|
||||
|
||||
fileprivate var cwd = FileUtils.userHomeUrl
|
||||
fileprivate var cwdFileItem = FileItem(FileUtils.userHomeUrl)
|
||||
|
||||
fileprivate let dumb = [NSAttributedString(string: "A"), NSAttributedString(string: "B")]
|
||||
|
||||
fileprivate let fileView: FileOutlineView
|
||||
fileprivate let fileItemService: FileItemService
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var isFirstResponder: Bool {
|
||||
return self.fileView.isFirstResponder
|
||||
}
|
||||
|
||||
init(source: Observable<Any>, fileItemService: FileItemService) {
|
||||
self.fileItemService = fileItemService
|
||||
self.fileView = FileOutlineView(source: source)
|
||||
|
||||
super.init(source: source)
|
||||
self.addReactions()
|
||||
}
|
||||
|
||||
override func beFirstResponder() {
|
||||
self.window?.makeFirstResponder(self.fileView)
|
||||
}
|
||||
|
||||
fileprivate func addReactions() {
|
||||
self.fileView.sink
|
||||
.filter { $0 is FileOutlineViewAction }
|
||||
.map { $0 as! FileOutlineViewAction }
|
||||
.subscribe(onNext: { [unowned self] action in
|
||||
switch action {
|
||||
case let .openFileItem(fileItem):
|
||||
self.doubleAction(for: fileItem)
|
||||
}
|
||||
})
|
||||
.addDisposableTo(self.disposeBag)
|
||||
}
|
||||
|
||||
override func addViews() {
|
||||
let fileView = self.fileView
|
||||
NSOutlineView.configure(toStandard: fileView)
|
||||
fileView.dataSource = self
|
||||
fileView.delegate = self
|
||||
fileView.doubleAction = #selector(FileBrowserComponent.fileViewDoubleAction)
|
||||
|
||||
let scrollView = NSScrollView.standardScrollView()
|
||||
scrollView.borderType = .noBorder
|
||||
scrollView.documentView = fileView
|
||||
|
||||
self.addSubview(scrollView)
|
||||
scrollView.autoPinEdgesToSuperviewEdges()
|
||||
}
|
||||
|
||||
override func subscription(source: Observable<Any>) -> Disposable {
|
||||
return source
|
||||
.filter { $0 is MainWindowAction }
|
||||
.map { $0 as! MainWindowAction }
|
||||
.subscribe(onNext: { [unowned self] action in
|
||||
switch action {
|
||||
case let .changeCwd(mainWindow: mainWindow):
|
||||
self.cwd = mainWindow.cwd
|
||||
self.cwdFileItem = self.fileItemService.fileItemWithChildren(for: self.cwd) ??
|
||||
self.fileItemService.fileItemWithChildren(for: FileUtils.userHomeUrl)!
|
||||
// NSLog("cwd changed to \(self.cwd) of \(mainWindow.uuid)")
|
||||
self.fileView.reloadData()
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
extension FileBrowserComponent {
|
||||
|
||||
func fileViewDoubleAction() {
|
||||
guard let item = self.fileView.selectedItem as? FileItem else {
|
||||
return
|
||||
}
|
||||
|
||||
self.doubleAction(for: item)
|
||||
}
|
||||
|
||||
fileprivate func doubleAction(for item: FileItem) {
|
||||
if item.dir {
|
||||
self.fileView.toggle(item: item)
|
||||
} else {
|
||||
self.publish(event: FileBrowserAction.open(url: item.url))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSOutlineViewDataSource
|
||||
extension FileBrowserComponent {
|
||||
|
||||
func outlineView(_: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||
if item == nil {
|
||||
return self.fileItemService.fileItemWithChildren(for: self.cwd)?.children
|
||||
.filter { !$0.hidden }
|
||||
.count ?? 0
|
||||
}
|
||||
|
||||
guard let fileItem = item as? FileItem else {
|
||||
return 0
|
||||
}
|
||||
|
||||
if fileItem.dir {
|
||||
return self.fileItemService.fileItemWithChildren(for: fileItem.url)?.children
|
||||
.filter { !$0.hidden }
|
||||
.count ?? 0
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func outlineView(_: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||
if item == nil {
|
||||
return self.fileItemService.fileItemWithChildren(for: self.cwd)!.children.filter { !$0.hidden }[index]
|
||||
}
|
||||
|
||||
guard let fileItem = item as? FileItem else {
|
||||
preconditionFailure("Should not happen")
|
||||
}
|
||||
|
||||
return self.fileItemService.fileItemWithChildren(for: fileItem.url)!.children.filter { !$0.hidden }[index]
|
||||
}
|
||||
|
||||
func outlineView(_: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||
guard let fileItem = item as? FileItem else {
|
||||
return false
|
||||
}
|
||||
|
||||
return fileItem.dir
|
||||
}
|
||||
|
||||
@objc(outlineView:objectValueForTableColumn:byItem:)
|
||||
func outlineView(_: NSOutlineView, objectValueFor: NSTableColumn?, byItem item: Any?) -> Any? {
|
||||
guard let fileItem = item as? FileItem else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fileItem
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSOutlineViewDelegate
|
||||
extension FileBrowserComponent {
|
||||
|
||||
@objc(outlineView:viewForTableColumn:item:)
|
||||
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
||||
guard let fileItem = item as? FileItem else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let cachedCell = outlineView.make(withIdentifier: "file-view-row", owner: self)
|
||||
let cell = cachedCell as? ImageAndTextTableCell ?? ImageAndTextTableCell(withIdentifier: "file-view-row")
|
||||
|
||||
cell.text = fileItem.url.lastPathComponent
|
||||
cell.image = self.fileItemService.icon(forUrl: fileItem.url)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
|
||||
return 20
|
||||
}
|
||||
}
|
@ -5,6 +5,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class Token: Equatable {}
|
||||
|
||||
func == (left: Token, right: Token) -> Bool {
|
||||
return left === right
|
||||
}
|
||||
|
||||
class FileItem : CustomStringConvertible {
|
||||
|
||||
let url: URL
|
||||
|
@ -7,19 +7,7 @@ import Cocoa
|
||||
import RxSwift
|
||||
import EonilFileSystemEvents
|
||||
|
||||
func == (left: Token, right: Token) -> Bool {
|
||||
return left === right
|
||||
}
|
||||
|
||||
class Token: Equatable {}
|
||||
|
||||
class FileItemService {
|
||||
|
||||
fileprivate(set) var ignorePatterns: Set<FileItemIgnorePattern> = [] {
|
||||
didSet {
|
||||
self.ignoreToken = Token()
|
||||
}
|
||||
}
|
||||
class FileItemService: StandardFlow {
|
||||
|
||||
/// Used to cache fnmatch calls in `FileItem`.
|
||||
fileprivate var ignoreToken = Token()
|
||||
@ -35,13 +23,22 @@ class FileItemService {
|
||||
fileprivate let fileSystemEventsLatency = Double(2)
|
||||
fileprivate var monitors = [URL: FileSystemEventMonitor]()
|
||||
fileprivate var monitorCounter = [URL: Int]()
|
||||
|
||||
|
||||
fileprivate let workspace = NSWorkspace.shared()
|
||||
fileprivate let iconsCache = NSCache<NSURL, NSImage>()
|
||||
|
||||
fileprivate var spinLock = OS_SPINLOCK_INIT
|
||||
|
||||
init() {
|
||||
|
||||
// MARK: - API
|
||||
fileprivate(set) var ignorePatterns: Set<FileItemIgnorePattern> = [] {
|
||||
didSet {
|
||||
self.ignoreToken = Token()
|
||||
}
|
||||
}
|
||||
|
||||
override init(source: Observable<Any>) {
|
||||
super.init(source: source)
|
||||
|
||||
self.iconsCache.countLimit = 2000
|
||||
self.iconsCache.name = "icon-cache"
|
||||
}
|
||||
@ -49,7 +46,7 @@ class FileItemService {
|
||||
func set(ignorePatterns patterns: Set<FileItemIgnorePattern>) {
|
||||
self.ignorePatterns = patterns
|
||||
}
|
||||
|
||||
|
||||
func icon(forUrl url: URL) -> NSImage? {
|
||||
if let cached = self.iconsCache.object(forKey: url as NSURL) {
|
||||
return cached
|
||||
@ -59,7 +56,7 @@ class FileItemService {
|
||||
let icon = workspace.icon(forFile: path)
|
||||
icon.size = CGSize(width: 16, height: 16)
|
||||
self.iconsCache.setObject(icon, forKey: url as NSURL)
|
||||
|
||||
|
||||
return icon
|
||||
}
|
||||
|
||||
@ -74,7 +71,7 @@ class FileItemService {
|
||||
{ [unowned self] events in
|
||||
let urls = events.map { URL(fileURLWithPath: $0.path) }
|
||||
let parent = FileUtils.commonParent(ofUrls: urls)
|
||||
self.fileItem(forUrl: parent)?.needsScanChildren = true
|
||||
self.fileItem(for: parent)?.needsScanChildren = true
|
||||
}
|
||||
|
||||
self.monitors[url] = monitor
|
||||
@ -106,14 +103,10 @@ class FileItemService {
|
||||
return
|
||||
}
|
||||
|
||||
self.parentFileItem(ofUrl: url).removeChild(withUrl: url)
|
||||
self.parentFileItem(of: url).removeChild(withUrl: url)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func parentFileItem(ofUrl url: URL) -> FileItem {
|
||||
return self.fileItem(forPathComponents: Array(url.pathComponents.dropLast()))!
|
||||
}
|
||||
|
||||
func flatFileItems(ofUrl url: URL) -> Observable<[FileItem]> {
|
||||
guard url.isFileURL else {
|
||||
return Observable.empty()
|
||||
@ -130,7 +123,7 @@ class FileItemService {
|
||||
}
|
||||
|
||||
self.scanDispatchQueue.async { [unowned self] in
|
||||
guard let targetItem = self.fileItem(forPathComponents: pathComponents) else {
|
||||
guard let targetItem = self.fileItem(for: pathComponents) else {
|
||||
observer.onCompleted()
|
||||
return
|
||||
}
|
||||
@ -193,16 +186,41 @@ class FileItemService {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func fileItem(forUrl url: URL) -> FileItem? {
|
||||
/// Returns the `FileItem` corresponding to the `url` parameter with children. This is like mkdir -p, i.e. it
|
||||
/// instantiates the intermediate `FileItem`s.
|
||||
///
|
||||
/// - returns: `FileItem` corresponding to `url` with children. `nil` if the file does not exist.
|
||||
func fileItemWithChildren(for url: URL) -> FileItem? {
|
||||
guard let fileItem = self.fileItem(for: url) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !fileItem.childrenScanned || fileItem.needsScanChildren {
|
||||
self.scanChildren(fileItem)
|
||||
}
|
||||
|
||||
return fileItem
|
||||
}
|
||||
|
||||
// FIXME: what if root?
|
||||
fileprivate func parentFileItem(of url: URL) -> FileItem {
|
||||
return self.fileItem(for: Array(url.pathComponents.dropLast()))!
|
||||
}
|
||||
|
||||
/// Returns the `FileItem` corresponding to the `url` parameter. This is like mkdir -p, i.e. it
|
||||
/// instantiates the intermediate `FileItem`s. The children of the result may be empty.
|
||||
///
|
||||
/// - returns: `FileItem` corresponding to `pathComponents`. `nil` if the file does not exist.
|
||||
fileprivate func fileItem(for url: URL) -> FileItem? {
|
||||
let pathComponents = url.pathComponents
|
||||
return self.fileItem(forPathComponents: pathComponents)
|
||||
return self.fileItem(for: pathComponents)
|
||||
}
|
||||
|
||||
/// Returns the `FileItem` corresponding to the `pathComponents` parameter. This is like mkdir -p, i.e. it
|
||||
/// instantiates the intermediate `FileItem`s.
|
||||
/// instantiates the intermediate `FileItem`s. The children of the result may be empty.
|
||||
///
|
||||
/// - returns: `FileItem` corresponding to `pathComponents`. `nil` if the file does not exist.
|
||||
fileprivate func fileItem(forPathComponents pathComponents: [String]) -> FileItem? {
|
||||
fileprivate func fileItem(for pathComponents: [String]) -> FileItem? {
|
||||
let result = pathComponents.dropFirst().reduce(self.root) { (resultItem, childName) -> FileItem? in
|
||||
guard let parent = resultItem else {
|
||||
return nil
|
||||
@ -241,7 +259,7 @@ class FileItemService {
|
||||
|
||||
return filteredChildren.first
|
||||
}
|
||||
|
||||
|
||||
fileprivate func scanChildren(_ item: FileItem) {
|
||||
let children = FileUtils.directDescendants(item.url).map(FileItem.init)
|
||||
self.syncAddChildren { item.children = children }
|
||||
@ -255,4 +273,17 @@ class FileItemService {
|
||||
fn()
|
||||
OSSpinLockUnlock(&self.spinLock)
|
||||
}
|
||||
|
||||
override func subscription(source: Observable<Any>) -> Disposable {
|
||||
return source
|
||||
.filter { $0 is PrefData }
|
||||
.map { $0 as! PrefData }
|
||||
.subscribe(onNext: { [unowned self] data in
|
||||
if data.general.ignorePatterns == self.ignorePatterns {
|
||||
return
|
||||
}
|
||||
|
||||
self.set(ignorePatterns: data.general.ignorePatterns)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
56
VimR/FileOutlineView.swift
Normal file
56
VimR/FileOutlineView.swift
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Tae Won Ha - http://taewon.de - @hataewon
|
||||
* See LICENSE
|
||||
*/
|
||||
|
||||
import Cocoa
|
||||
import RxSwift
|
||||
|
||||
enum FileOutlineViewAction {
|
||||
|
||||
case openFileItem(fileItem: FileItem)
|
||||
}
|
||||
|
||||
class FileOutlineView: NSOutlineView, Flow {
|
||||
|
||||
fileprivate let flow: EmbeddableComponent
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
var sink: Observable<Any> {
|
||||
return self.flow.sink
|
||||
}
|
||||
|
||||
init(source: Observable<Any>) {
|
||||
self.flow = EmbeddableComponent(source: source)
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
|
||||
override func keyDown(with event: NSEvent) {
|
||||
guard let char = event.charactersIgnoringModifiers?.characters.first else {
|
||||
super.keyDown(with: event)
|
||||
return
|
||||
}
|
||||
|
||||
guard let item = self.selectedItem as? FileItem else {
|
||||
super.keyDown(with: event)
|
||||
return
|
||||
}
|
||||
|
||||
switch char {
|
||||
case " ", "\r": // Why "\r" and not "\n"?
|
||||
if item.dir || item.package {
|
||||
self.toggle(item: item)
|
||||
} else {
|
||||
self.flow.publish(event: FileOutlineViewAction.openFileItem(fileItem: item))
|
||||
}
|
||||
|
||||
default:
|
||||
super.keyDown(with: event)
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,8 @@ class FileUtils {
|
||||
]
|
||||
|
||||
fileprivate static let fileManager = FileManager.default
|
||||
|
||||
static let userHomeUrl = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
|
||||
|
||||
static func directDescendants(_ url: URL) -> [URL] {
|
||||
guard let childUrls = try? self.fileManager.contentsOfDirectory(
|
||||
|
@ -6,47 +6,64 @@
|
||||
import Cocoa
|
||||
import PureLayout
|
||||
|
||||
class ImageAndTextTableCell: NSView {
|
||||
class ImageAndTextTableCell: NSTableCellView {
|
||||
|
||||
var text: NSAttributedString {
|
||||
fileprivate let _textField = NSTextField(forAutoLayout: ())
|
||||
fileprivate let _imageView = NSImageView(forAutoLayout: ())
|
||||
|
||||
var attributedText: NSAttributedString {
|
||||
get {
|
||||
return self.textField.attributedStringValue
|
||||
return self.textField!.attributedStringValue
|
||||
}
|
||||
|
||||
set {
|
||||
self.textField?.attributedStringValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var text: String {
|
||||
get {
|
||||
return self.textField!.stringValue
|
||||
}
|
||||
|
||||
set {
|
||||
self.textField.attributedStringValue = newValue
|
||||
self.textField?.stringValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var image: NSImage? {
|
||||
get {
|
||||
return self.imageView.image
|
||||
return self.imageView?.image
|
||||
}
|
||||
|
||||
set {
|
||||
self.imageView.image = newValue
|
||||
self.imageView?.image = newValue
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let textField: NSTextField = NSTextField(forAutoLayout: ())
|
||||
fileprivate let imageView: NSImageView = NSImageView(forAutoLayout: ())
|
||||
|
||||
init(withIdentifier identifier: String) {
|
||||
super.init(frame: CGRect.zero)
|
||||
|
||||
self.identifier = identifier
|
||||
|
||||
let textField = self.textField
|
||||
|
||||
self.textField = self._textField
|
||||
self.imageView = self._imageView
|
||||
|
||||
let textField = self._textField
|
||||
textField.isBordered = false
|
||||
textField.isBezeled = false
|
||||
textField.allowsDefaultTighteningForTruncation = true
|
||||
textField.allowsEditingTextAttributes = false
|
||||
textField.isEditable = false
|
||||
textField.usesSingleLineMode = true
|
||||
textField.lineBreakMode = .byTruncatingTail
|
||||
textField.drawsBackground = false
|
||||
|
||||
let imageView = self.imageView
|
||||
|
||||
|
||||
let imageView = self._imageView
|
||||
|
||||
self.addSubview(textField)
|
||||
self.addSubview(imageView)
|
||||
|
||||
|
||||
imageView.autoPinEdge(toSuperviewEdge: .top, withInset: 2)
|
||||
imageView.autoPinEdge(toSuperviewEdge: .left, withInset: 2)
|
||||
imageView.autoSetDimension(.width, toSize: 16)
|
||||
|
@ -10,21 +10,45 @@ import RxSwift
|
||||
enum MainWindowAction {
|
||||
case becomeKey(mainWindow: MainWindowComponent)
|
||||
case openQuickly(mainWindow: MainWindowComponent)
|
||||
case close(mainWindow: MainWindowComponent)
|
||||
case changeCwd(mainWindow: MainWindowComponent)
|
||||
case close(mainWindow: MainWindowComponent, mainWindowPrefData: MainWindowPrefData)
|
||||
}
|
||||
|
||||
class MainWindowComponent: WindowComponent, NSWindowDelegate {
|
||||
struct MainWindowPrefData {
|
||||
|
||||
fileprivate let fontManager = NSFontManager.shared()
|
||||
let isAllToolsVisible: Bool
|
||||
let isToolButtonsVisible: Bool
|
||||
|
||||
let isFileBrowserVisible: Bool
|
||||
let fileBrowserWidth: Float
|
||||
}
|
||||
|
||||
fileprivate enum Tool {
|
||||
|
||||
case fileBrowser
|
||||
}
|
||||
|
||||
class MainWindowComponent: WindowComponent, NSWindowDelegate, NSUserInterfaceValidations, WorkspaceDelegate {
|
||||
|
||||
fileprivate static let nibName = "MainWindow"
|
||||
|
||||
fileprivate var defaultEditorFont: NSFont
|
||||
fileprivate var usesLigatures: Bool
|
||||
|
||||
fileprivate var _cwd: URL = FileUtils.userHomeUrl
|
||||
|
||||
fileprivate let fontManager = NSFontManager.shared()
|
||||
fileprivate let fileItemService: FileItemService
|
||||
|
||||
fileprivate let workspace: Workspace
|
||||
fileprivate let neoVimView: NeoVimView
|
||||
fileprivate var tools = [Tool: WorkspaceToolComponent]()
|
||||
|
||||
// MARK: - API
|
||||
var uuid: String {
|
||||
return self.neoVimView.uuid
|
||||
}
|
||||
|
||||
fileprivate var _cwd: URL = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
|
||||
var cwd: URL {
|
||||
get {
|
||||
self._cwd = self.neoVimView.cwd
|
||||
@ -43,10 +67,6 @@ class MainWindowComponent: WindowComponent, NSWindowDelegate {
|
||||
self.fileItemService.monitor(url: newValue)
|
||||
}
|
||||
}
|
||||
fileprivate let fileItemService: FileItemService
|
||||
|
||||
fileprivate let workspace: Workspace
|
||||
fileprivate let neoVimView: NeoVimView
|
||||
|
||||
// TODO: Consider an option object for cwd, urls, etc...
|
||||
init(source: Observable<Any>,
|
||||
@ -66,11 +86,20 @@ class MainWindowComponent: WindowComponent, NSWindowDelegate {
|
||||
self.fileItemService = fileItemService
|
||||
self._cwd = cwd
|
||||
|
||||
super.init(source: source, nibName: "MainWindow")
|
||||
super.init(source: source, nibName: MainWindowComponent.nibName)
|
||||
|
||||
self.window.delegate = self
|
||||
|
||||
self.neoVimView.cwd = cwd
|
||||
self.workspace.delegate = self
|
||||
|
||||
let fileBrowser = FileBrowserComponent(source: self.sink, fileItemService: fileItemService)
|
||||
let fileBrowserTool = WorkspaceToolComponent(title: "Files", viewComponent: fileBrowser, minimumDimension: 100)
|
||||
self.tools[.fileBrowser] = fileBrowserTool
|
||||
self.workspace.append(tool: fileBrowserTool, location: .left)
|
||||
|
||||
self.addReactions()
|
||||
|
||||
self.neoVimView.cwd = cwd // This will publish the MainWindowAction.changeCwd action for the file browser.
|
||||
self.neoVimView.delegate = self
|
||||
self.neoVimView.font = self.defaultEditorFont
|
||||
self.neoVimView.usesLigatures = self.usesLigatures
|
||||
@ -79,12 +108,29 @@ class MainWindowComponent: WindowComponent, NSWindowDelegate {
|
||||
// We don't call self.fileItemService.monitor(url: cwd) here since self.neoVimView.cwd = cwd causes the call
|
||||
// cwdChanged() and in that function we do monitor(...).
|
||||
|
||||
// By default the tool buttons are shown and no tools are shown.
|
||||
let mainWindowData = initialData.mainWindow
|
||||
fileBrowserTool.dimension = CGFloat(mainWindowData.fileBrowserWidth)
|
||||
|
||||
if !mainWindowData.isAllToolsVisible {
|
||||
self.toggleAllTools(self)
|
||||
}
|
||||
|
||||
if !mainWindowData.isToolButtonsVisible {
|
||||
self.toggleToolButtons(self)
|
||||
}
|
||||
|
||||
if mainWindowData.isFileBrowserVisible {
|
||||
fileBrowserTool.toggle()
|
||||
}
|
||||
|
||||
self.window.makeFirstResponder(self.neoVimView)
|
||||
self.show()
|
||||
}
|
||||
|
||||
func open(urls: [URL]) {
|
||||
self.neoVimView.open(urls: urls)
|
||||
self.window.makeFirstResponder(self.neoVimView)
|
||||
}
|
||||
|
||||
func isDirty() -> Bool {
|
||||
@ -99,6 +145,24 @@ class MainWindowComponent: WindowComponent, NSWindowDelegate {
|
||||
self.neoVimView.closeAllWindowsWithoutSaving()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
fileprivate func addReactions() {
|
||||
self.tools.values
|
||||
.map { $0.sink }
|
||||
.toMergedObservables()
|
||||
.subscribe(onNext: { [unowned self] action in
|
||||
switch action {
|
||||
case let FileBrowserAction.open(url: url):
|
||||
self.open(urls: [url])
|
||||
default:
|
||||
NSLog("unrecognized action: \(action)")
|
||||
return
|
||||
}
|
||||
})
|
||||
.addDisposableTo(self.disposeBag)
|
||||
}
|
||||
|
||||
// MARK: - WindowComponent
|
||||
override func addViews() {
|
||||
self.window.contentView?.addSubview(self.workspace)
|
||||
self.workspace.autoPinEdgesToSuperviewEdges()
|
||||
@ -119,14 +183,26 @@ class MainWindowComponent: WindowComponent, NSWindowDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - File Menu Items
|
||||
// MARK: - WorkspaceDelegate
|
||||
extension MainWindowComponent {
|
||||
|
||||
func resizeWillStart(workspace: Workspace) {
|
||||
self.neoVimView.enterResizeMode()
|
||||
}
|
||||
|
||||
func resizeDidEnd(workspace: Workspace) {
|
||||
self.neoVimView.exitResizeMode()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - File Menu Item Actions
|
||||
extension MainWindowComponent {
|
||||
|
||||
@IBAction func newTab(_ sender: AnyObject!) {
|
||||
@IBAction func newTab(_ sender: Any?) {
|
||||
self.neoVimView.newTab()
|
||||
}
|
||||
|
||||
@IBAction func openDocument(_ sender: AnyObject!) {
|
||||
@IBAction func openDocument(_ sender: Any?) {
|
||||
let panel = NSOpenPanel()
|
||||
panel.canChooseDirectories = true
|
||||
panel.beginSheetModal(for: self.window) { result in
|
||||
@ -139,11 +215,11 @@ extension MainWindowComponent {
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func openQuickly(_ sender: AnyObject!) {
|
||||
@IBAction func openQuickly(_ sender: Any?) {
|
||||
self.publish(event: MainWindowAction.openQuickly(mainWindow: self))
|
||||
}
|
||||
|
||||
@IBAction func saveDocument(_ sender: AnyObject!) {
|
||||
@IBAction func saveDocument(_ sender: Any?) {
|
||||
let curBuf = self.neoVimView.currentBuffer()
|
||||
|
||||
if curBuf.fileName == nil {
|
||||
@ -154,7 +230,7 @@ extension MainWindowComponent {
|
||||
self.neoVimView.saveCurrentTab()
|
||||
}
|
||||
|
||||
@IBAction func saveDocumentAs(_ sender: AnyObject!) {
|
||||
@IBAction func saveDocumentAs(_ sender: Any?) {
|
||||
self.savePanelSheet { url in
|
||||
self.neoVimView.saveCurrentTab(url: url)
|
||||
|
||||
@ -193,21 +269,55 @@ extension MainWindowComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Font Menu Items
|
||||
// MARK: - Tools Menu Item Actions
|
||||
extension MainWindowComponent {
|
||||
|
||||
@IBAction func resetFontSize(_ sender: AnyObject!) {
|
||||
@IBAction func toggleAllTools(_ sender: Any?) {
|
||||
self.workspace.toggleAllTools()
|
||||
self.focusNeoVimView(self)
|
||||
}
|
||||
|
||||
@IBAction func toggleToolButtons(_ sender: Any?) {
|
||||
self.workspace.toggleToolButtons()
|
||||
}
|
||||
|
||||
@IBAction func toggleFileBrowser(_ sender: Any?) {
|
||||
let fileBrowserTool = self.tools[.fileBrowser]!
|
||||
|
||||
if fileBrowserTool.isSelected {
|
||||
if fileBrowserTool.viewComponent.isFirstResponder {
|
||||
fileBrowserTool.toggle()
|
||||
} else {
|
||||
fileBrowserTool.viewComponent.beFirstResponder()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
fileBrowserTool.toggle()
|
||||
fileBrowserTool.viewComponent.beFirstResponder()
|
||||
}
|
||||
|
||||
@IBAction func focusNeoVimView(_ sender: Any?) {
|
||||
self.window.makeFirstResponder(self.neoVimView)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Font Menu Item Actions
|
||||
extension MainWindowComponent {
|
||||
|
||||
@IBAction func resetFontSize(_ sender: Any?) {
|
||||
self.neoVimView.font = self.defaultEditorFont
|
||||
}
|
||||
|
||||
@IBAction func makeFontBigger(_ sender: AnyObject!) {
|
||||
@IBAction func makeFontBigger(_ sender: Any?) {
|
||||
let curFont = self.neoVimView.font
|
||||
let font = self.fontManager.convert(curFont,
|
||||
toSize: min(curFont.pointSize + 1, PrefStore.maximumEditorFontSize))
|
||||
self.neoVimView.font = font
|
||||
}
|
||||
|
||||
@IBAction func makeFontSmaller(_ sender: AnyObject!) {
|
||||
@IBAction func makeFontSmaller(_ sender: Any?) {
|
||||
let curFont = self.neoVimView.font
|
||||
let font = self.fontManager.convert(curFont,
|
||||
toSize: max(curFont.pointSize - 1, PrefStore.minimumEditorFontSize))
|
||||
@ -218,12 +328,12 @@ extension MainWindowComponent {
|
||||
// MARK: - NeoVimViewDelegate
|
||||
extension MainWindowComponent: NeoVimViewDelegate {
|
||||
|
||||
func setTitle(_ title: String) {
|
||||
func set(title: String) {
|
||||
self.window.title = title
|
||||
}
|
||||
|
||||
func setDirtyStatus(_ dirty: Bool) {
|
||||
self.windowController.setDocumentEdited(dirty)
|
||||
func set(dirtyStatus: Bool) {
|
||||
self.windowController.setDocumentEdited(dirtyStatus)
|
||||
}
|
||||
|
||||
func cwdChanged() {
|
||||
@ -231,6 +341,8 @@ extension MainWindowComponent: NeoVimViewDelegate {
|
||||
self._cwd = self.neoVimView.cwd
|
||||
self.fileItemService.unmonitor(url: old)
|
||||
self.fileItemService.monitor(url: self._cwd)
|
||||
|
||||
self.publish(event: MainWindowAction.changeCwd(mainWindow: self))
|
||||
}
|
||||
|
||||
func neoVimStopped() {
|
||||
@ -247,7 +359,14 @@ extension MainWindowComponent {
|
||||
|
||||
func windowWillClose(_ notification: Notification) {
|
||||
self.fileItemService.unmonitor(url: self._cwd)
|
||||
self.publish(event: MainWindowAction.close(mainWindow: self))
|
||||
|
||||
let fileBrowser = self.tools[.fileBrowser]!
|
||||
let prefData = MainWindowPrefData(isAllToolsVisible: self.workspace.isAllToolsVisible,
|
||||
isToolButtonsVisible: self.workspace.isToolButtonsVisible,
|
||||
isFileBrowserVisible: self.workspace.bars[.left]?.isOpen ?? true,
|
||||
fileBrowserWidth: Float(fileBrowser.dimension))
|
||||
|
||||
self.publish(event: MainWindowAction.close(mainWindow: self, mainWindowPrefData: prefData))
|
||||
}
|
||||
|
||||
func windowShouldClose(_ sender: Any) -> Bool {
|
||||
@ -270,3 +389,19 @@ extension MainWindowComponent {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSUserInterfaceValidationsProtocol
|
||||
extension MainWindowComponent {
|
||||
|
||||
public func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||
guard item.action == #selector(focusNeoVimView(_:)) else {
|
||||
return true
|
||||
}
|
||||
|
||||
if self.window.firstResponder == self.neoVimView {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,12 @@
|
||||
import Cocoa
|
||||
import RxSwift
|
||||
|
||||
enum MainWindowEvent {
|
||||
enum MainWindowManagerAction {
|
||||
|
||||
case allWindowsClosed
|
||||
}
|
||||
|
||||
class MainWindowManager: StandardFlow {
|
||||
|
||||
static fileprivate let userHomeUrl = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
|
||||
|
||||
fileprivate var mainWindowComponents = [String:MainWindowComponent]()
|
||||
fileprivate weak var keyMainWindow: MainWindowComponent?
|
||||
@ -27,10 +26,13 @@ class MainWindowManager: StandardFlow {
|
||||
super.init(source: source)
|
||||
}
|
||||
|
||||
func newMainWindow(urls: [URL] = [], cwd: URL = MainWindowManager.userHomeUrl) -> MainWindowComponent {
|
||||
let mainWindowComponent = MainWindowComponent(
|
||||
source: self.source, fileItemService: self.fileItemService, cwd: cwd, urls: urls, initialData: self.data
|
||||
)
|
||||
func newMainWindow(urls: [URL] = [], cwd: URL = FileUtils.userHomeUrl) -> MainWindowComponent {
|
||||
let mainWindowComponent = MainWindowComponent(source: self.source,
|
||||
fileItemService: self.fileItemService,
|
||||
cwd: cwd,
|
||||
urls: urls,
|
||||
initialData: self.data)
|
||||
|
||||
self.mainWindowComponents[mainWindowComponent.uuid] = mainWindowComponent
|
||||
|
||||
mainWindowComponent.sink
|
||||
@ -44,8 +46,11 @@ class MainWindowManager: StandardFlow {
|
||||
case .openQuickly:
|
||||
self.publish(event: action)
|
||||
|
||||
case let .close(mainWindow):
|
||||
self.closeMainWindow(mainWindow)
|
||||
case let .close(mainWindow, mainWindowPrefData):
|
||||
self.close(mainWindow, prefData: mainWindowPrefData)
|
||||
|
||||
case .changeCwd:
|
||||
break
|
||||
}
|
||||
})
|
||||
.addDisposableTo(self.disposeBag)
|
||||
@ -53,7 +58,11 @@ class MainWindowManager: StandardFlow {
|
||||
return mainWindowComponent
|
||||
}
|
||||
|
||||
func closeMainWindow(_ mainWindowComponent: MainWindowComponent) {
|
||||
func close(_ mainWindowComponent: MainWindowComponent, prefData: MainWindowPrefData) {
|
||||
// Save the tools settings of the last closed main window.
|
||||
// TODO: Think about a better time to save this.
|
||||
self.publish(event: prefData)
|
||||
|
||||
if self.keyMainWindow === mainWindowComponent {
|
||||
self.keyMainWindow = nil
|
||||
}
|
||||
@ -61,7 +70,7 @@ class MainWindowManager: StandardFlow {
|
||||
self.mainWindowComponents.removeValue(forKey: mainWindowComponent.uuid)
|
||||
|
||||
if self.mainWindowComponents.isEmpty {
|
||||
self.publish(event: MainWindowEvent.allWindowsClosed)
|
||||
self.publish(event: MainWindowManagerAction.allWindowsClosed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +78,7 @@ class MainWindowManager: StandardFlow {
|
||||
return self.mainWindowComponents.values.reduce(false) { $0 ? true : $1.isDirty() }
|
||||
}
|
||||
|
||||
func openInKeyMainWindow(urls:[URL] = [], cwd: URL = MainWindowManager.userHomeUrl) {
|
||||
func openInKeyMainWindow(urls:[URL] = [], cwd: URL = FileUtils.userHomeUrl) {
|
||||
guard !self.mainWindowComponents.isEmpty else {
|
||||
_ = self.newMainWindow(urls: urls, cwd: cwd)
|
||||
return
|
||||
|
@ -17,7 +17,7 @@ class OpenQuicklyWindowComponent: WindowComponent,
|
||||
var pauseScan = false
|
||||
|
||||
fileprivate(set) var pattern = ""
|
||||
fileprivate(set) var cwd = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true) {
|
||||
fileprivate(set) var cwd = FileUtils.userHomeUrl {
|
||||
didSet {
|
||||
self.cwdPathCompsCount = self.cwd.pathComponents.count
|
||||
self.cwdControl.url = self.cwd
|
||||
@ -216,7 +216,7 @@ extension OpenQuicklyWindowComponent {
|
||||
let cell = cachedCell as? ImageAndTextTableCell ?? ImageAndTextTableCell(withIdentifier: "file-view-row")
|
||||
|
||||
let url = self.fileViewItems[row].url
|
||||
cell.text = self.rowText(forUrl: url as URL)
|
||||
cell.attributedText = self.rowText(forUrl: url as URL)
|
||||
cell.image = self.fileItemService.icon(forUrl: url)
|
||||
|
||||
return cell
|
||||
@ -240,9 +240,8 @@ extension OpenQuicklyWindowComponent {
|
||||
rowText = NSMutableAttributedString(string: "\(name) — \(pathInfo)")
|
||||
rowText.addAttribute(NSForegroundColorAttributeName,
|
||||
value: NSColor.lightGray,
|
||||
range: NSRange(location:name.characters.count,
|
||||
length: pathInfo.characters.count + 3))
|
||||
|
||||
range: NSRange(location:name.characters.count, length: pathInfo.characters.count + 3))
|
||||
|
||||
return rowText
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,16 @@ class OpenQuicklyWindowManager: StandardFlow {
|
||||
}
|
||||
|
||||
override func subscription(source: Observable<Any>) -> Disposable {
|
||||
return Disposables.create()
|
||||
return source
|
||||
.filter { $0 is MainWindowAction }
|
||||
.map { $0 as! MainWindowAction }
|
||||
.subscribe(onNext: { [unowned self] event in
|
||||
switch event {
|
||||
case let .openQuickly(mainWindow: mainWindow):
|
||||
self.open(forMainWindow: mainWindow)
|
||||
default:
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -6,17 +6,8 @@
|
||||
import Cocoa
|
||||
import RxSwift
|
||||
|
||||
class PrefPane: NSView, Component {
|
||||
class PrefPane: ViewComponent {
|
||||
|
||||
let disposeBag = DisposeBag()
|
||||
|
||||
fileprivate let source: Observable<Any>
|
||||
|
||||
fileprivate let subject = PublishSubject<Any>()
|
||||
var sink: Observable<Any> {
|
||||
return self.subject.asObservable()
|
||||
}
|
||||
|
||||
// Return true to place this to the upper left corner when the scroll view is bigger than this view.
|
||||
override var isFlipped: Bool {
|
||||
return true
|
||||
@ -30,38 +21,8 @@ class PrefPane: NSView, Component {
|
||||
return false
|
||||
}
|
||||
|
||||
init(source: Observable<Any>) {
|
||||
self.source = source
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
self.addViews()
|
||||
self.subscription(source: self.source).addDisposableTo(self.disposeBag)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func addViews() {
|
||||
preconditionFailure("Please override")
|
||||
}
|
||||
|
||||
func subscription(source: Observable<Any>) -> Disposable {
|
||||
preconditionFailure("Please override")
|
||||
}
|
||||
|
||||
func publish(event: Any) {
|
||||
self.subject.onNext(event)
|
||||
}
|
||||
|
||||
func windowWillClose() {
|
||||
|
||||
// noop, override
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +64,6 @@ extension PrefPane {
|
||||
func configureCheckbox(button: NSButton, title: String, action: Selector) {
|
||||
button.title = title
|
||||
button.setButtonType(.switch)
|
||||
// button.bezelStyle = .ThickSquareBezelStyle
|
||||
button.target = self
|
||||
button.action = action
|
||||
}
|
||||
|
@ -6,6 +6,14 @@
|
||||
import Cocoa
|
||||
import RxSwift
|
||||
|
||||
struct PrefData {
|
||||
var general: GeneralPrefData
|
||||
var appearance: AppearancePrefData
|
||||
var advanced: AdvancedPrefData
|
||||
|
||||
var mainWindow: MainWindowPrefData
|
||||
}
|
||||
|
||||
private class PrefKeys {
|
||||
|
||||
static let openNewWindowWhenLaunching = "open-new-window-when-launching"
|
||||
@ -17,23 +25,22 @@ private class PrefKeys {
|
||||
static let editorUsesLigatures = "editor-uses-ligatures"
|
||||
|
||||
static let useInteractiveZsh = "use-interactive-zsh"
|
||||
|
||||
static let isAllToolsVisible = "is-all-tools-visible"
|
||||
static let isToolButtonsShown = "is-tool-buttons-visible"
|
||||
|
||||
static let isFileBrowserOpen = "is-file-browser-visible"
|
||||
static let fileBrowserWidth = "file-browser-width"
|
||||
}
|
||||
|
||||
class PrefStore: Store {
|
||||
// TODO: We should generalize the persisting of pref data.
|
||||
class PrefStore: StandardFlow {
|
||||
|
||||
fileprivate static let compatibleVersion = "38"
|
||||
fileprivate static let defaultEditorFont = NeoVimView.defaultFont
|
||||
static let minimumEditorFontSize = NeoVimView.minFontSize
|
||||
static let maximumEditorFontSize = NeoVimView.maxFontSize
|
||||
|
||||
fileprivate let source: Observable<Any>
|
||||
fileprivate let disposeBag = DisposeBag()
|
||||
|
||||
fileprivate let subject = PublishSubject<Any>()
|
||||
var sink: Observable<Any> {
|
||||
return self.subject.asObservable()
|
||||
}
|
||||
|
||||
fileprivate let userDefaults = UserDefaults.standard
|
||||
fileprivate let fontManager = NSFontManager.shared()
|
||||
|
||||
@ -42,23 +49,21 @@ class PrefStore: Store {
|
||||
openNewWindowOnReactivation: true,
|
||||
ignorePatterns: Set([ "*/.git", "*.o", "*.d", "*.dia" ].map(FileItemIgnorePattern.init))),
|
||||
appearance: AppearancePrefData(editorFont: PrefStore.defaultEditorFont, editorUsesLigatures: false),
|
||||
advanced: AdvancedPrefData(useInteractiveZsh: false)
|
||||
advanced: AdvancedPrefData(useInteractiveZsh: false),
|
||||
mainWindow: MainWindowPrefData(isAllToolsVisible: true,
|
||||
isToolButtonsVisible: true,
|
||||
isFileBrowserVisible: true,
|
||||
fileBrowserWidth: 200)
|
||||
)
|
||||
|
||||
init(source: Observable<Any>) {
|
||||
self.source = source
|
||||
override init(source: Observable<Any>) {
|
||||
super.init(source: source)
|
||||
|
||||
if let prefs = self.userDefaults.dictionary(forKey: PrefStore.compatibleVersion) {
|
||||
self.data = self.prefDataFromDict(prefs)
|
||||
} else {
|
||||
self.userDefaults.setValue(self.prefsDict(self.data), forKey: PrefStore.compatibleVersion)
|
||||
}
|
||||
|
||||
self.addReactions()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.subject.onCompleted()
|
||||
}
|
||||
|
||||
fileprivate func prefDataFromDict(_ prefs: [String: Any]) -> PrefData {
|
||||
@ -68,15 +73,20 @@ class PrefStore: Store {
|
||||
(prefs[PrefKeys.editorFontSize] as? NSNumber)?.floatValue ?? Float(PrefStore.defaultEditorFont.pointSize)
|
||||
)
|
||||
let editorFont = self.saneFont(editorFontName, fontSize: editorFontSize)
|
||||
|
||||
let usesLigatures = (prefs[PrefKeys.editorUsesLigatures] as? NSNumber)?.boolValue ?? false
|
||||
let openNewWindowWhenLaunching = (prefs[PrefKeys.openNewWindowWhenLaunching] as? NSNumber)?.boolValue ?? true
|
||||
let openNewWindowOnReactivation = (prefs[PrefKeys.openNewWindowOnReactivation] as? NSNumber)?.boolValue ?? true
|
||||
|
||||
let usesLigatures = self.bool(from: prefs, for: PrefKeys.editorUsesLigatures, default: false)
|
||||
let openNewWindowWhenLaunching = self.bool(from: prefs, for: PrefKeys.openNewWindowWhenLaunching, default: true)
|
||||
let openNewWindowOnReactivation = self.bool(from: prefs, for: PrefKeys.openNewWindowOnReactivation, default: true)
|
||||
|
||||
let ignorePatternsList = (prefs[PrefKeys.openQuicklyIgnorePatterns] as? String) ?? "*/.git, *.o, *.d, *.dia"
|
||||
let ignorePatterns = PrefUtils.ignorePatterns(fromString: ignorePatternsList)
|
||||
|
||||
let useInteractiveZsh = (prefs[PrefKeys.useInteractiveZsh] as? NSNumber)?.boolValue ?? false
|
||||
let useInteractiveZsh = self.bool(from: prefs, for: PrefKeys.useInteractiveZsh, default: false)
|
||||
|
||||
let isAllToolsVisible = self.bool(from: prefs, for: PrefKeys.isAllToolsVisible, default: true)
|
||||
let isToolButtonsVisible = self.bool(from: prefs, for: PrefKeys.isToolButtonsShown, default: true)
|
||||
let isFileBrowserVisible = self.bool(from: prefs, for: PrefKeys.isFileBrowserOpen, default: true)
|
||||
let fileBrowserWidth = (prefs[PrefKeys.fileBrowserWidth] as? NSNumber)?.floatValue ?? Float(200)
|
||||
|
||||
return PrefData(
|
||||
general: GeneralPrefData(
|
||||
@ -85,10 +95,18 @@ class PrefStore: Store {
|
||||
ignorePatterns: ignorePatterns
|
||||
),
|
||||
appearance: AppearancePrefData(editorFont: editorFont, editorUsesLigatures: usesLigatures),
|
||||
advanced: AdvancedPrefData(useInteractiveZsh: useInteractiveZsh)
|
||||
advanced: AdvancedPrefData(useInteractiveZsh: useInteractiveZsh),
|
||||
mainWindow: MainWindowPrefData(isAllToolsVisible: isAllToolsVisible,
|
||||
isToolButtonsVisible: isToolButtonsVisible,
|
||||
isFileBrowserVisible: isFileBrowserVisible,
|
||||
fileBrowserWidth: fileBrowserWidth)
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate func bool(from prefs: [String: Any], for key: String, default defaultValue: Bool) -> Bool {
|
||||
return (prefs[key] as? NSNumber)?.boolValue ?? defaultValue
|
||||
}
|
||||
|
||||
fileprivate func saneFont(_ fontName: String, fontSize: CGFloat) -> NSFont {
|
||||
var editorFont = NSFont(name: fontName, size: fontSize) ?? PrefStore.defaultEditorFont
|
||||
if !editorFont.isFixedPitch {
|
||||
@ -102,38 +120,55 @@ class PrefStore: Store {
|
||||
return editorFont
|
||||
}
|
||||
|
||||
fileprivate func prefsDict(_ prefData: PrefData) -> [String: AnyObject] {
|
||||
fileprivate func prefsDict(_ prefData: PrefData) -> [String: Any] {
|
||||
let generalData = prefData.general
|
||||
let appearanceData = prefData.appearance
|
||||
let advancedData = prefData.advanced
|
||||
let mainWindowData = prefData.mainWindow
|
||||
|
||||
let prefs: [String: AnyObject] = [
|
||||
let ignorePatterns = PrefUtils.ignorePatternString(fromSet: generalData.ignorePatterns) as Any
|
||||
|
||||
let prefs: [String: Any] = [
|
||||
// General
|
||||
PrefKeys.openNewWindowWhenLaunching: generalData.openNewWindowWhenLaunching as AnyObject,
|
||||
PrefKeys.openNewWindowOnReactivation: generalData.openNewWindowOnReactivation as AnyObject,
|
||||
PrefKeys.openQuicklyIgnorePatterns: PrefUtils.ignorePatternString(fromSet: generalData.ignorePatterns) as AnyObject,
|
||||
PrefKeys.openNewWindowWhenLaunching: generalData.openNewWindowWhenLaunching as Any,
|
||||
PrefKeys.openNewWindowOnReactivation: generalData.openNewWindowOnReactivation as Any,
|
||||
PrefKeys.openQuicklyIgnorePatterns: ignorePatterns,
|
||||
|
||||
// Appearance
|
||||
PrefKeys.editorFontName: appearanceData.editorFont.fontName as AnyObject,
|
||||
PrefKeys.editorFontSize: appearanceData.editorFont.pointSize as AnyObject,
|
||||
PrefKeys.editorUsesLigatures: appearanceData.editorUsesLigatures as AnyObject,
|
||||
PrefKeys.editorFontName: appearanceData.editorFont.fontName as Any,
|
||||
PrefKeys.editorFontSize: appearanceData.editorFont.pointSize as Any,
|
||||
PrefKeys.editorUsesLigatures: appearanceData.editorUsesLigatures as Any,
|
||||
|
||||
// Advanced
|
||||
PrefKeys.useInteractiveZsh: advancedData.useInteractiveZsh as AnyObject,
|
||||
PrefKeys.useInteractiveZsh: advancedData.useInteractiveZsh as Any,
|
||||
|
||||
// MainWindow
|
||||
PrefKeys.isAllToolsVisible: mainWindowData.isAllToolsVisible,
|
||||
PrefKeys.isToolButtonsShown: mainWindowData.isToolButtonsVisible,
|
||||
PrefKeys.isFileBrowserOpen: mainWindowData.isFileBrowserVisible,
|
||||
PrefKeys.fileBrowserWidth: mainWindowData.fileBrowserWidth
|
||||
]
|
||||
|
||||
return prefs
|
||||
}
|
||||
|
||||
fileprivate func addReactions() {
|
||||
self.source
|
||||
.filter { $0 is PrefData }
|
||||
.map { $0 as! PrefData }
|
||||
.subscribe(onNext: { [unowned self] prefData in
|
||||
self.data = prefData
|
||||
self.userDefaults.setValue(self.prefsDict(prefData), forKey: PrefStore.compatibleVersion)
|
||||
self.subject.onNext(prefData)
|
||||
})
|
||||
.addDisposableTo(self.disposeBag)
|
||||
override func subscription(source: Observable<Any>) -> Disposable {
|
||||
return source
|
||||
.filter { $0 is PrefData || $0 is MainWindowPrefData }
|
||||
.subscribe(onNext: { [unowned self] data in
|
||||
switch data {
|
||||
case let prefData as PrefData:
|
||||
self.data = prefData
|
||||
|
||||
case let mainWindowPrefData as MainWindowPrefData:
|
||||
self.data.mainWindow = mainWindowPrefData
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
self.userDefaults.setValue(self.prefsDict(self.data), forKey: PrefStore.compatibleVersion)
|
||||
self.publish(event: self.data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,6 @@ import Cocoa
|
||||
import RxSwift
|
||||
import PureLayout
|
||||
|
||||
struct PrefData {
|
||||
var general: GeneralPrefData
|
||||
var appearance: AppearancePrefData
|
||||
var advanced: AdvancedPrefData
|
||||
}
|
||||
|
||||
class PrefWindowComponent: WindowComponent, NSWindowDelegate, NSTableViewDataSource, NSTableViewDelegate {
|
||||
|
||||
fileprivate var data: PrefData
|
||||
|
@ -13,19 +13,28 @@ enum WorkspaceBarLocation {
|
||||
case left
|
||||
}
|
||||
|
||||
class Workspace: NSView {
|
||||
protocol WorkspaceDelegate: class {
|
||||
|
||||
func resizeWillStart(workspace: Workspace)
|
||||
func resizeDidEnd(workspace: Workspace)
|
||||
}
|
||||
|
||||
class Workspace: NSView, WorkspaceBarDelegate {
|
||||
|
||||
struct Config {
|
||||
let mainViewMinimumSize: CGSize
|
||||
}
|
||||
|
||||
fileprivate(set) var isBarVisible = true {
|
||||
fileprivate(set) var isAllToolsVisible = true {
|
||||
didSet {
|
||||
self.relayout()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let bars: [WorkspaceBarLocation: WorkspaceBar]
|
||||
fileprivate(set) var isToolButtonsVisible = true {
|
||||
didSet {
|
||||
self.bars.values.forEach { $0.isButtonVisible = !$0.isButtonVisible }
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
@ -33,8 +42,11 @@ class Workspace: NSView {
|
||||
|
||||
// MARK: - API
|
||||
let mainView: NSView
|
||||
let bars: [WorkspaceBarLocation: WorkspaceBar]
|
||||
let config: Config
|
||||
|
||||
weak var delegate: WorkspaceDelegate?
|
||||
|
||||
init(mainView: NSView, config: Config = Config(mainViewMinimumSize: CGSize(width: 100, height: 100))) {
|
||||
self.config = config
|
||||
self.mainView = mainView
|
||||
@ -49,6 +61,8 @@ class Workspace: NSView {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
self.bars.values.forEach { [unowned self] in $0.delegate = self }
|
||||
|
||||
self.relayout()
|
||||
}
|
||||
|
||||
@ -57,11 +71,23 @@ class Workspace: NSView {
|
||||
}
|
||||
|
||||
func toggleAllTools() {
|
||||
self.isBarVisible = !self.isBarVisible
|
||||
self.isAllToolsVisible = !self.isAllToolsVisible
|
||||
}
|
||||
|
||||
func toggleToolButtons() {
|
||||
self.bars.values.forEach { $0.isButtonVisible = !$0.isButtonVisible }
|
||||
self.isToolButtonsVisible = !self.isToolButtonsVisible
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WorkspaceBarDelegate
|
||||
extension Workspace {
|
||||
|
||||
func resizeWillStart(workspaceBar: WorkspaceBar) {
|
||||
self.delegate?.resizeWillStart(workspace: self)
|
||||
}
|
||||
|
||||
func resizeDidEnd(workspaceBar: WorkspaceBar) {
|
||||
self.delegate?.resizeDidEnd(workspace: self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +106,7 @@ extension Workspace {
|
||||
mainView.autoSetDimension(.width, toSize: self.config.mainViewMinimumSize.width, relation: .greaterThanOrEqual)
|
||||
mainView.autoSetDimension(.height, toSize: self.config.mainViewMinimumSize.height, relation: .greaterThanOrEqual)
|
||||
|
||||
guard self.isBarVisible else {
|
||||
guard self.isAllToolsVisible else {
|
||||
mainView.autoPinEdgesToSuperviewEdges()
|
||||
return
|
||||
}
|
||||
|
@ -6,6 +6,12 @@
|
||||
import Cocoa
|
||||
import PureLayout
|
||||
|
||||
protocol WorkspaceBarDelegate: class {
|
||||
|
||||
func resizeWillStart(workspaceBar: WorkspaceBar)
|
||||
func resizeDidEnd(workspaceBar: WorkspaceBar)
|
||||
}
|
||||
|
||||
class WorkspaceBar: NSView, WorkspaceToolDelegate {
|
||||
|
||||
static fileprivate let separatorColor = NSColor.controlShadowColor
|
||||
@ -24,14 +30,21 @@ class WorkspaceBar: NSView, WorkspaceToolDelegate {
|
||||
}
|
||||
|
||||
// MARK: - API
|
||||
static let minimumDimension = CGFloat(50)
|
||||
|
||||
let location: WorkspaceBarLocation
|
||||
var isButtonVisible = true {
|
||||
didSet {
|
||||
self.relayout()
|
||||
}
|
||||
}
|
||||
var isOpen: Bool {
|
||||
return self.selectedTool != nil
|
||||
}
|
||||
var dimensionConstraint = NSLayoutConstraint()
|
||||
|
||||
weak var delegate: WorkspaceBarDelegate?
|
||||
|
||||
init(location: WorkspaceBarLocation) {
|
||||
self.location = location
|
||||
|
||||
@ -47,38 +60,36 @@ class WorkspaceBar: NSView, WorkspaceToolDelegate {
|
||||
self.removeAllSubviews()
|
||||
|
||||
if self.isEmpty() {
|
||||
self.set(0)
|
||||
self.set(dimension: 0)
|
||||
return
|
||||
}
|
||||
|
||||
if self.isButtonVisible {
|
||||
self.layoutButtons()
|
||||
|
||||
if self.isOpen() {
|
||||
if self.isOpen {
|
||||
let curTool = self.selectedTool!
|
||||
|
||||
self.layout(curTool)
|
||||
|
||||
let newDimension = self.barDimension(withToolDimension: curTool.dimension)
|
||||
self.set(newDimension)
|
||||
self.set(dimension: newDimension)
|
||||
} else {
|
||||
self.set(self.barDimensionWithButtonsWithoutTool())
|
||||
self.set(dimension: self.barDimensionWithButtonsWithoutTool())
|
||||
}
|
||||
|
||||
} else {
|
||||
if self.isOpen() {
|
||||
if self.isOpen {
|
||||
let curTool = self.selectedTool!
|
||||
|
||||
self.layoutWithoutButtons(curTool)
|
||||
|
||||
let newDimension = self.barDimensionWithoutButtons(withToolDimension: curTool.dimension)
|
||||
self.set(newDimension)
|
||||
self.set(dimension: newDimension)
|
||||
} else {
|
||||
self.set(0)
|
||||
self.set(dimension: 0)
|
||||
}
|
||||
}
|
||||
|
||||
self.needsDisplay = true
|
||||
}
|
||||
|
||||
func append(tool: WorkspaceTool) {
|
||||
@ -86,7 +97,7 @@ class WorkspaceBar: NSView, WorkspaceToolDelegate {
|
||||
tool.location = self.location
|
||||
tools.append(tool)
|
||||
|
||||
if self.isOpen() {
|
||||
if self.isOpen {
|
||||
self.selectedTool?.isSelected = false
|
||||
self.selectedTool = tool
|
||||
}
|
||||
@ -105,13 +116,21 @@ extension WorkspaceBar {
|
||||
self.drawInnerSeparator(dirtyRect)
|
||||
}
|
||||
|
||||
if self.isOpen() {
|
||||
if self.isOpen {
|
||||
self.drawOuterSeparator(dirtyRect)
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: NSPoint) -> NSView? {
|
||||
if self.resizeRect().contains(point) {
|
||||
return self
|
||||
}
|
||||
|
||||
return super.hitTest(point)
|
||||
}
|
||||
|
||||
override func mouseDown(with event: NSEvent) {
|
||||
guard self.isOpen() else {
|
||||
guard self.isOpen else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -128,6 +147,7 @@ extension WorkspaceBar {
|
||||
}
|
||||
|
||||
self.isMouseDownOngoing = true
|
||||
self.delegate?.resizeWillStart(workspaceBar: self)
|
||||
self.dimensionConstraint.priority = NSLayoutPriorityDragThatCannotResizeWindow - 1
|
||||
|
||||
var dragged = false
|
||||
@ -138,10 +158,10 @@ extension WorkspaceBar {
|
||||
NSEventMask.leftMouseUp
|
||||
]
|
||||
while curEvent.type != .leftMouseUp {
|
||||
let nextEvent = NSApp.nextEvent(matching: NSEventMask(rawValue: UInt64(Int(nextEventMask.rawValue))),
|
||||
until: Date.distantFuture,
|
||||
inMode: RunLoopMode.eventTrackingRunLoopMode,
|
||||
dequeue: true)
|
||||
let nextEvent = NSApp.nextEvent(matching: nextEventMask,
|
||||
until: Date.distantFuture,
|
||||
inMode: RunLoopMode.eventTrackingRunLoopMode,
|
||||
dequeue: true)
|
||||
guard nextEvent != nil else {
|
||||
break
|
||||
}
|
||||
@ -162,19 +182,18 @@ extension WorkspaceBar {
|
||||
let locInSuperview = self.superview!.convert(curEvent.locationInWindow, from: nil)
|
||||
let newDimension = self.newDimension(forLocationInSuperview: locInSuperview)
|
||||
|
||||
self.set(newDimension)
|
||||
|
||||
self.window?.invalidateCursorRects(for: self)
|
||||
self.set(dimension: newDimension)
|
||||
|
||||
dragged = true
|
||||
}
|
||||
|
||||
self.dimensionConstraint.priority = NSLayoutPriorityDragThatCannotResizeWindow
|
||||
self.isMouseDownOngoing = false
|
||||
self.delegate?.resizeDidEnd(workspaceBar: self)
|
||||
}
|
||||
|
||||
override func resetCursorRects() {
|
||||
guard self.isOpen() else {
|
||||
guard self.isOpen else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -284,13 +303,33 @@ extension WorkspaceBar {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func set(_ dimension: CGFloat) {
|
||||
self.dimensionConstraint.constant = dimension
|
||||
fileprivate func set(dimension: CGFloat) {
|
||||
let saneDimension = self.saneDimension(from: dimension)
|
||||
|
||||
let toolDimension = self.toolDimension(fromBarDimension: dimension)
|
||||
if self.isOpen() {
|
||||
self.dimensionConstraint.constant = saneDimension
|
||||
|
||||
let toolDimension = self.toolDimension(fromBarDimension: saneDimension)
|
||||
if self.isOpen {
|
||||
self.selectedTool?.dimension = toolDimension
|
||||
}
|
||||
|
||||
// In 10.12 we need the following, otherwise resizing the tools does not work correctly.
|
||||
self.layoutSubtreeIfNeeded()
|
||||
|
||||
self.window?.invalidateCursorRects(for: self)
|
||||
self.needsDisplay = true
|
||||
}
|
||||
|
||||
fileprivate func saneDimension(from dimension: CGFloat) -> CGFloat {
|
||||
if dimension == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if self.isOpen {
|
||||
return max(dimension, self.selectedTool!.minimumDimension, WorkspaceBar.minimumDimension)
|
||||
}
|
||||
|
||||
return max(dimension, self.barDimensionWithButtonsWithoutTool())
|
||||
}
|
||||
}
|
||||
|
||||
@ -305,10 +344,6 @@ extension WorkspaceBar {
|
||||
return !self.isEmpty()
|
||||
}
|
||||
|
||||
fileprivate func isOpen() -> Bool {
|
||||
return self.selectedTool != nil
|
||||
}
|
||||
|
||||
fileprivate func layoutWithoutButtons(_ tool: WorkspaceTool) {
|
||||
let view = tool.view
|
||||
let thickness = WorkspaceBar.separatorThickness
|
||||
@ -493,7 +528,9 @@ extension WorkspaceBar {
|
||||
extension WorkspaceBar {
|
||||
|
||||
func toggle(_ tool: WorkspaceTool) {
|
||||
if self.isOpen() {
|
||||
self.delegate?.resizeWillStart(workspaceBar: self)
|
||||
|
||||
if self.isOpen {
|
||||
let curTool = self.selectedTool!
|
||||
if curTool === tool {
|
||||
// In this case, curTool.isSelected is already set to false in WorkspaceTool.toggle()
|
||||
@ -508,5 +545,7 @@ extension WorkspaceBar {
|
||||
}
|
||||
|
||||
self.relayout()
|
||||
|
||||
self.delegate?.resizeDidEnd(workspaceBar: self)
|
||||
}
|
||||
}
|
||||
|
@ -34,12 +34,14 @@ class WorkspaceTool {
|
||||
|
||||
weak var delegate: WorkspaceToolDelegate?
|
||||
|
||||
let minimumDimension = CGFloat(30)
|
||||
var dimension = CGFloat(50)
|
||||
let minimumDimension: CGFloat
|
||||
var dimension: CGFloat
|
||||
|
||||
init(title: String, view: NSView) {
|
||||
init(title: String, view: NSView, minimumDimension: CGFloat = 50) {
|
||||
self.title = title
|
||||
self.view = view
|
||||
self.minimumDimension = minimumDimension
|
||||
self.dimension = minimumDimension
|
||||
self.button = WorkspaceToolButton(title: title)
|
||||
|
||||
self.button.tool = self
|
||||
|
Loading…
Reference in New Issue
Block a user