2016-10-03 16:03:18 +03:00
|
|
|
/**
|
|
|
|
* Tae Won Ha - http://taewon.de - @hataewon
|
|
|
|
* See LICENSE
|
|
|
|
*/
|
|
|
|
|
|
|
|
import Cocoa
|
2016-11-25 17:25:55 +03:00
|
|
|
import PureLayout
|
2016-10-03 16:03:18 +03:00
|
|
|
import RxSwift
|
|
|
|
|
|
|
|
enum FileOutlineViewAction {
|
|
|
|
|
2016-11-12 18:42:10 +03:00
|
|
|
case open(fileItem: FileItem)
|
|
|
|
case openFileInNewTab(fileItem: FileItem)
|
|
|
|
case openFileInCurrentTab(fileItem: FileItem)
|
|
|
|
case openFileInHorizontalSplit(fileItem: FileItem)
|
|
|
|
case openFileInVerticalSplit(fileItem: FileItem)
|
|
|
|
case setAsWorkingDirectory(fileItem: FileItem)
|
|
|
|
case setParentAsWorkingDirectory(fileItem: FileItem)
|
2016-10-03 16:03:18 +03:00
|
|
|
}
|
|
|
|
|
2016-11-26 12:34:52 +03:00
|
|
|
fileprivate class FileBrowserItem: Hashable, Comparable, CustomStringConvertible {
|
2016-11-25 14:02:55 +03:00
|
|
|
|
|
|
|
static func ==(left: FileBrowserItem, right: FileBrowserItem) -> Bool {
|
|
|
|
return left.fileItem == right.fileItem
|
|
|
|
}
|
|
|
|
|
2016-11-26 12:34:52 +03:00
|
|
|
static func <(left: FileBrowserItem, right: FileBrowserItem) -> Bool {
|
|
|
|
return left.fileItem.url.lastPathComponent < right.fileItem.url.lastPathComponent
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
var hashValue: Int {
|
|
|
|
return self.fileItem.hashValue
|
|
|
|
}
|
|
|
|
|
2016-11-26 12:34:52 +03:00
|
|
|
var description: String {
|
|
|
|
return self.fileItem.url.path
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
let fileItem: FileItem
|
|
|
|
var children: [FileBrowserItem] = []
|
|
|
|
var isExpanded = false
|
|
|
|
|
|
|
|
/**
|
2016-11-25 15:19:41 +03:00
|
|
|
`fileItem` is copied. Children are _not_ populated.
|
2016-11-25 14:02:55 +03:00
|
|
|
*/
|
|
|
|
init(fileItem: FileItem) {
|
|
|
|
self.fileItem = fileItem.copy()
|
|
|
|
}
|
2016-11-25 23:41:25 +03:00
|
|
|
|
|
|
|
func child(with url: URL) -> FileBrowserItem? {
|
|
|
|
return self.children.filter { $0.fileItem.url == url }.first
|
|
|
|
}
|
2016-11-25 14:02:55 +03:00
|
|
|
}
|
|
|
|
|
2016-10-05 22:45:52 +03:00
|
|
|
class FileOutlineView: NSOutlineView, Flow, NSOutlineViewDataSource, NSOutlineViewDelegate {
|
2016-10-03 16:03:18 +03:00
|
|
|
|
|
|
|
fileprivate let flow: EmbeddableComponent
|
2016-10-05 22:45:52 +03:00
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
fileprivate var root: FileBrowserItem
|
2016-10-07 22:14:43 +03:00
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
fileprivate let fileItemService: FileItemService
|
2016-10-03 16:03:18 +03:00
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
|
2016-10-05 22:45:52 +03:00
|
|
|
// MARK: - API
|
2016-10-03 16:03:18 +03:00
|
|
|
var sink: Observable<Any> {
|
|
|
|
return self.flow.sink
|
|
|
|
}
|
|
|
|
|
2016-10-05 22:45:52 +03:00
|
|
|
var cwd: URL = FileUtils.userHomeUrl
|
|
|
|
|
|
|
|
init(source: Observable<Any>, fileItemService: FileItemService) {
|
2016-10-03 16:03:18 +03:00
|
|
|
self.flow = EmbeddableComponent(source: source)
|
2016-10-05 22:45:52 +03:00
|
|
|
self.fileItemService = fileItemService
|
2016-10-03 16:03:18 +03:00
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
let rootFileItem = fileItemService.fileItemWithChildren(for: self.cwd)
|
|
|
|
?? fileItemService.fileItemWithChildren(for: FileUtils.userHomeUrl)!
|
|
|
|
self.root = FileBrowserItem(fileItem: rootFileItem)
|
|
|
|
|
2016-10-03 16:03:18 +03:00
|
|
|
super.init(frame: CGRect.zero)
|
2016-11-25 19:03:31 +03:00
|
|
|
NSOutlineView.configure(toStandard: self)
|
2016-10-05 22:45:52 +03:00
|
|
|
|
|
|
|
self.dataSource = self
|
|
|
|
self.delegate = self
|
2016-11-12 18:42:10 +03:00
|
|
|
|
|
|
|
guard Bundle.main.loadNibNamed("FileBrowserMenu", owner: self, topLevelObjects: nil) else {
|
|
|
|
NSLog("WARN: FileBrowserMenu.xib could not be loaded")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
self.doubleAction = #selector(FileOutlineView.doubleClickAction)
|
2016-10-05 22:45:52 +03:00
|
|
|
}
|
2016-11-25 17:25:55 +03:00
|
|
|
|
2016-11-25 23:17:24 +03:00
|
|
|
func update(_ fileItem: FileItem) {
|
2016-11-26 12:34:52 +03:00
|
|
|
let url = fileItem.url
|
2016-11-26 17:13:21 +03:00
|
|
|
|
2016-11-26 12:34:52 +03:00
|
|
|
guard let fileBrowserItem = self.fileBrowserItem(with: url) else {
|
2016-11-25 23:41:25 +03:00
|
|
|
return
|
|
|
|
}
|
2016-11-25 23:17:24 +03:00
|
|
|
|
2016-11-26 20:05:35 +03:00
|
|
|
self.beginUpdates()
|
2016-11-26 17:13:21 +03:00
|
|
|
self.update(fileBrowserItem)
|
2016-11-26 20:05:35 +03:00
|
|
|
self.endUpdates()
|
2016-11-26 12:34:52 +03:00
|
|
|
}
|
|
|
|
|
2016-11-26 19:59:55 +03:00
|
|
|
fileprivate func handleRemovals(for fileBrowserItem: FileBrowserItem, new newChildren: [FileBrowserItem]) {
|
2016-11-26 17:13:21 +03:00
|
|
|
let curChildren = fileBrowserItem.children.sorted()
|
|
|
|
|
|
|
|
let curPreparedChildren = self.prepare(curChildren)
|
|
|
|
let newPreparedChildren = self.prepare(newChildren)
|
|
|
|
|
|
|
|
let childrenToRemoveIndices = curPreparedChildren
|
|
|
|
.enumerated()
|
|
|
|
.filter { newPreparedChildren.contains($0.1) == false }
|
|
|
|
.map { $0.0 }
|
2016-11-26 19:59:55 +03:00
|
|
|
|
2016-11-26 17:13:21 +03:00
|
|
|
fileBrowserItem.children = curChildren.filter { newChildren.contains($0) }
|
|
|
|
|
2016-11-26 19:59:55 +03:00
|
|
|
let parent = fileBrowserItem == self.root ? nil : fileBrowserItem
|
|
|
|
self.removeItems(at: IndexSet(childrenToRemoveIndices), inParent: parent)
|
|
|
|
}
|
|
|
|
|
|
|
|
fileprivate func handleAdditions(for fileBrowserItem: FileBrowserItem, new newChildren: [FileBrowserItem]) {
|
|
|
|
let curChildren = fileBrowserItem.children.sorted()
|
|
|
|
|
2016-11-26 20:05:35 +03:00
|
|
|
// We don't just take newChildren because NSOutlineView look at the pointer equality for preserving the expanded
|
|
|
|
// states...
|
|
|
|
fileBrowserItem.children = newChildren.substituting(elements: curChildren)
|
|
|
|
|
2016-11-26 19:59:55 +03:00
|
|
|
let curPreparedChildren = self.prepare(curChildren)
|
|
|
|
let newPreparedChildren = self.prepare(newChildren)
|
|
|
|
|
2016-11-26 20:05:35 +03:00
|
|
|
let indicesToInsert = newPreparedChildren
|
|
|
|
.enumerated()
|
|
|
|
.filter { curPreparedChildren.contains($0.1) == false }
|
|
|
|
.map { $0.0 }
|
2016-11-26 19:59:55 +03:00
|
|
|
|
2016-11-26 20:05:35 +03:00
|
|
|
let parent = fileBrowserItem == self.root ? nil : fileBrowserItem
|
|
|
|
self.insertItems(at: IndexSet(indicesToInsert), inParent: parent)
|
2016-11-26 19:59:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fileprivate func handleChildren(for fileBrowserItem: FileBrowserItem, new newChildren: [FileBrowserItem]) {
|
|
|
|
let curChildren = fileBrowserItem.children.sorted()
|
|
|
|
|
|
|
|
let curPreparedChildren = self.prepare(curChildren)
|
|
|
|
let newPreparedChildren = self.prepare(newChildren)
|
|
|
|
|
|
|
|
let keptChildren = curPreparedChildren.filter { newPreparedChildren.contains($0) }
|
2016-11-26 20:05:35 +03:00
|
|
|
let childrenToRecurse = keptChildren.filter { self.isItemExpanded($0) }
|
2016-11-26 17:13:21 +03:00
|
|
|
|
2016-11-26 20:05:35 +03:00
|
|
|
childrenToRecurse.forEach(self.update)
|
2016-11-26 19:59:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fileprivate func update(_ fileBrowserItem: FileBrowserItem) {
|
|
|
|
let url = fileBrowserItem.fileItem.url
|
|
|
|
|
|
|
|
// Sort the array to keep the order.
|
|
|
|
let newChildren = (self.fileItemService.fileItemWithChildren(for: url)?.children ?? [])
|
|
|
|
.map(FileBrowserItem.init)
|
|
|
|
.sorted()
|
|
|
|
|
|
|
|
self.handleRemovals(for: fileBrowserItem, new: newChildren)
|
|
|
|
self.handleAdditions(for: fileBrowserItem, new: newChildren)
|
|
|
|
self.handleChildren(for: fileBrowserItem, new: newChildren)
|
2016-11-25 23:41:25 +03:00
|
|
|
}
|
2016-11-25 17:25:55 +03:00
|
|
|
|
2016-11-25 23:41:25 +03:00
|
|
|
fileprivate func fileBrowserItem(with url: URL) -> FileBrowserItem? {
|
2016-11-26 12:34:52 +03:00
|
|
|
if self.cwd == url {
|
|
|
|
return self.root
|
|
|
|
}
|
|
|
|
|
2016-11-25 23:41:25 +03:00
|
|
|
guard self.cwd.isParent(of: url) else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
let rootPathComps = self.cwd.pathComponents
|
|
|
|
let pathComps = url.pathComponents
|
|
|
|
let childPart = pathComps[rootPathComps.count ..< pathComps.count]
|
2016-11-25 23:17:24 +03:00
|
|
|
|
2016-11-25 23:41:25 +03:00
|
|
|
return childPart.reduce(self.root) { (resultItem, childName) -> FileBrowserItem? in
|
|
|
|
guard let parent = resultItem else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return parent.child(with: parent.fileItem.url.appendingPathComponent(childName))
|
|
|
|
}
|
2016-11-25 23:17:24 +03:00
|
|
|
}
|
2016-10-05 22:45:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - NSOutlineViewDataSource
|
|
|
|
extension FileOutlineView {
|
|
|
|
|
2016-11-26 12:34:52 +03:00
|
|
|
fileprivate func prepare(_ children: [FileBrowserItem]) -> [FileBrowserItem] {
|
|
|
|
return children.filter { !$0.fileItem.isHidden }.sorted()
|
|
|
|
}
|
|
|
|
|
2016-10-05 22:45:52 +03:00
|
|
|
func outlineView(_: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
|
|
|
if item == nil {
|
2016-11-25 14:02:55 +03:00
|
|
|
let rootFileItem = fileItemService.fileItemWithChildren(for: self.cwd)
|
|
|
|
?? fileItemService.fileItemWithChildren(for: FileUtils.userHomeUrl)!
|
|
|
|
self.root = FileBrowserItem(fileItem: rootFileItem)
|
|
|
|
self.root.children = rootFileItem.children.map(FileBrowserItem.init)
|
|
|
|
|
2016-11-26 12:34:52 +03:00
|
|
|
return self.prepare(self.root.children).count
|
2016-10-05 22:45:52 +03:00
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let fileBrowserItem = item as? FileBrowserItem else {
|
2016-10-05 22:45:52 +03:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
let fileItem = fileBrowserItem.fileItem
|
2016-11-12 18:42:10 +03:00
|
|
|
if fileItem.isDir {
|
2016-11-25 14:02:55 +03:00
|
|
|
let fileItemChildren = self.fileItemService.fileItemWithChildren(for: fileItem.url)?.children ?? []
|
2016-11-26 12:34:52 +03:00
|
|
|
fileBrowserItem.fileItem.children = fileItemChildren
|
2016-11-25 14:02:55 +03:00
|
|
|
fileBrowserItem.children = fileItemChildren.map(FileBrowserItem.init)
|
2016-11-26 12:34:52 +03:00
|
|
|
return self.prepare(fileBrowserItem.children).count
|
2016-10-05 22:45:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func outlineView(_: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
2016-11-25 19:03:31 +03:00
|
|
|
let level = self.level(forItem: item)
|
|
|
|
|
2016-10-05 22:45:52 +03:00
|
|
|
if item == nil {
|
2016-11-25 19:03:31 +03:00
|
|
|
self.adjustColumnWidth(for: self.root.children, outlineViewLevel: level)
|
2016-11-26 12:34:52 +03:00
|
|
|
return self.prepare(self.root.children)[index]
|
2016-10-05 22:45:52 +03:00
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let fileBrowserItem = item as? FileBrowserItem else {
|
2016-10-05 22:45:52 +03:00
|
|
|
preconditionFailure("Should not happen")
|
|
|
|
}
|
|
|
|
|
2016-11-25 19:03:31 +03:00
|
|
|
self.adjustColumnWidth(for: fileBrowserItem.children, outlineViewLevel: level)
|
2016-11-26 12:34:52 +03:00
|
|
|
return self.prepare(fileBrowserItem.children)[index]
|
2016-10-05 22:45:52 +03:00
|
|
|
}
|
|
|
|
|
2016-11-26 12:34:52 +03:00
|
|
|
func outlineView(_: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let fileBrowserItem = item as? FileBrowserItem else {
|
2016-10-05 22:45:52 +03:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
return fileBrowserItem.fileItem.isDir
|
2016-10-05 22:45:52 +03:00
|
|
|
}
|
|
|
|
|
2016-11-26 12:34:52 +03:00
|
|
|
@objc(outlineView: objectValueForTableColumn:byItem:)
|
2016-10-05 22:45:52 +03:00
|
|
|
func outlineView(_: NSOutlineView, objectValueFor: NSTableColumn?, byItem item: Any?) -> Any? {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let fileBrowserItem = item as? FileBrowserItem else {
|
2016-10-05 22:45:52 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
return fileBrowserItem
|
2016-10-03 16:03:18 +03:00
|
|
|
}
|
2016-10-05 22:45:52 +03:00
|
|
|
|
2016-11-25 19:03:31 +03:00
|
|
|
fileprivate func adjustColumnWidth(for items: [FileBrowserItem], outlineViewLevel level: Int) {
|
|
|
|
let cellWidth = items.reduce(CGFloat(0)) { (curMaxWidth, item) in
|
|
|
|
let itemWidth = ImageAndTextTableCell.width(with: item.fileItem.url.lastPathComponent)
|
|
|
|
if itemWidth > curMaxWidth {
|
|
|
|
return itemWidth
|
2016-11-25 17:25:55 +03:00
|
|
|
}
|
|
|
|
|
2016-11-25 19:03:31 +03:00
|
|
|
return curMaxWidth
|
2016-11-25 17:25:55 +03:00
|
|
|
}
|
|
|
|
|
2016-11-25 19:03:31 +03:00
|
|
|
let width = cellWidth + (CGFloat(level + 2) * (self.indentationPerLevel + 2)) // + 2 just to have a buffer... -_-
|
2016-11-25 17:25:55 +03:00
|
|
|
let column = self.outlineTableColumn!
|
2016-11-25 19:03:31 +03:00
|
|
|
guard column.minWidth < width else {
|
2016-11-25 17:25:55 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 19:03:31 +03:00
|
|
|
column.minWidth = width
|
|
|
|
column.maxWidth = width
|
2016-11-25 17:25:55 +03:00
|
|
|
}
|
2016-11-25 19:03:31 +03:00
|
|
|
}
|
2016-11-25 17:25:55 +03:00
|
|
|
|
2016-11-25 19:03:31 +03:00
|
|
|
// MARK: - NSOutlineViewDelegate
|
|
|
|
extension FileOutlineView {
|
2016-11-25 17:25:55 +03:00
|
|
|
|
2016-11-26 12:34:52 +03:00
|
|
|
@objc(outlineView: viewForTableColumn:item:)
|
2016-10-05 22:45:52 +03:00
|
|
|
func outlineView(_: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let fileBrowserItem = item as? FileBrowserItem else {
|
2016-10-05 22:45:52 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
let cachedCell = self.make(withIdentifier: "file-view-row", owner: self)
|
|
|
|
let cell = cachedCell as? ImageAndTextTableCell ?? ImageAndTextTableCell(withIdentifier: "file-view-row")
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
cell.text = fileBrowserItem.fileItem.url.lastPathComponent
|
|
|
|
cell.image = self.fileItemService.icon(forUrl: fileBrowserItem.fileItem.url)
|
2016-10-05 22:45:52 +03:00
|
|
|
|
|
|
|
return cell
|
|
|
|
}
|
|
|
|
|
|
|
|
func outlineView(_: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
|
|
|
|
return 20
|
|
|
|
}
|
2016-11-25 15:19:41 +03:00
|
|
|
|
|
|
|
func outlineViewItemDidExpand(_ notification: Notification) {
|
|
|
|
if let item = notification.userInfo?["NSObject"] as? FileBrowserItem {
|
|
|
|
item.isExpanded = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func outlineViewItemDidCollapse(_ notification: Notification) {
|
|
|
|
if let item = notification.userInfo?["NSObject"] as? FileBrowserItem {
|
|
|
|
item.isExpanded = false
|
|
|
|
}
|
2016-10-05 22:45:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-12 18:42:10 +03:00
|
|
|
// MARK: - Actions
|
|
|
|
extension FileOutlineView {
|
|
|
|
|
|
|
|
@IBAction func doubleClickAction(_: Any?) {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let item = self.clickedItem as? FileBrowserItem else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
if item.fileItem.isDir {
|
2016-11-12 18:42:10 +03:00
|
|
|
self.toggle(item: item)
|
|
|
|
} else {
|
2016-11-25 14:02:55 +03:00
|
|
|
self.flow.publish(event: FileOutlineViewAction.open(fileItem: item.fileItem))
|
2016-11-12 18:42:10 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func openInNewTab(_: Any?) {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let item = self.clickedItem as? FileBrowserItem else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
self.flow.publish(event: FileOutlineViewAction.openFileInNewTab(fileItem: item.fileItem))
|
2016-11-12 18:42:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func openInCurrentTab(_: Any?) {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let item = self.clickedItem as? FileBrowserItem else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
self.flow.publish(event: FileOutlineViewAction.openFileInCurrentTab(fileItem: item.fileItem))
|
2016-11-12 18:42:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func openInHorizontalSplit(_: Any?) {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let item = self.clickedItem as? FileBrowserItem else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
self.flow.publish(event: FileOutlineViewAction.openFileInHorizontalSplit(fileItem: item.fileItem))
|
2016-11-12 18:42:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func openInVerticalSplit(_: Any?) {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let item = self.clickedItem as? FileBrowserItem else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
self.flow.publish(event: FileOutlineViewAction.openFileInVerticalSplit(fileItem: item.fileItem))
|
2016-11-12 18:42:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func setAsWorkingDirectory(_: Any?) {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let item = self.clickedItem as? FileBrowserItem else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
guard item.fileItem.isDir else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
self.flow.publish(event: FileOutlineViewAction.setAsWorkingDirectory(fileItem: item.fileItem))
|
2016-11-12 18:42:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func setParentAsWorkingDirectory(_: Any?) {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let item = self.clickedItem as? FileBrowserItem else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-14 00:53:57 +03:00
|
|
|
guard self.level(forItem: clickedItem) > 0 else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
guard item.fileItem.url.path != "/" else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
self.flow.publish(event: FileOutlineViewAction.setParentAsWorkingDirectory(fileItem: item.fileItem))
|
2016-11-12 18:42:10 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - NSUserInterfaceValidations
|
|
|
|
extension FileOutlineView {
|
|
|
|
|
|
|
|
override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let clickedItem = self.clickedItem as? FileBrowserItem else {
|
2016-11-12 18:42:10 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-11-14 00:53:57 +03:00
|
|
|
if item.action == #selector(setAsWorkingDirectory(_:)) {
|
2016-11-25 14:02:55 +03:00
|
|
|
return clickedItem.fileItem.isDir
|
2016-11-12 18:42:10 +03:00
|
|
|
}
|
|
|
|
|
2016-11-14 00:53:57 +03:00
|
|
|
if item.action == #selector(setParentAsWorkingDirectory(_:)) {
|
|
|
|
return self.level(forItem: clickedItem) > 0
|
|
|
|
}
|
|
|
|
|
2016-11-12 18:42:10 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-05 22:45:52 +03:00
|
|
|
// MARK: - NSView
|
|
|
|
extension FileOutlineView {
|
2016-10-03 16:03:18 +03:00
|
|
|
|
|
|
|
override func keyDown(with event: NSEvent) {
|
|
|
|
guard let char = event.charactersIgnoringModifiers?.characters.first else {
|
|
|
|
super.keyDown(with: event)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-25 14:02:55 +03:00
|
|
|
guard let item = self.selectedItem as? FileBrowserItem else {
|
2016-10-03 16:03:18 +03:00
|
|
|
super.keyDown(with: event)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch char {
|
|
|
|
case " ", "\r": // Why "\r" and not "\n"?
|
2016-11-25 14:02:55 +03:00
|
|
|
if item.fileItem.isDir || item.fileItem.isPackage {
|
2016-10-03 16:03:18 +03:00
|
|
|
self.toggle(item: item)
|
|
|
|
} else {
|
2016-11-25 14:02:55 +03:00
|
|
|
self.flow.publish(event: FileOutlineViewAction.openFileInNewTab(fileItem: item.fileItem))
|
2016-10-03 16:03:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
super.keyDown(with: event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|