mirror of
https://github.com/qvacua/vimr.git
synced 2024-12-26 07:13:24 +03:00
Merge remote-tracking branch 'origin/develop' into update-neovim
This commit is contained in:
commit
70e508816b
@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.25.0</string>
|
||||
<string>SNAPSHOT-298</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>297</string>
|
||||
<string>298</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
|
@ -783,7 +783,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 297;
|
||||
CURRENT_PROJECT_VERSION = 298;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@ -845,7 +845,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 297;
|
||||
CURRENT_PROJECT_VERSION = 298;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@ -874,7 +874,7 @@
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 297;
|
||||
DYLIB_CURRENT_VERSION = 298;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac";
|
||||
FRAMEWORK_VERSION = A;
|
||||
@ -896,7 +896,7 @@
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 297;
|
||||
DYLIB_CURRENT_VERSION = 298;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac";
|
||||
FRAMEWORK_VERSION = A;
|
||||
|
@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.25.0</string>
|
||||
<string>SNAPSHOT-298</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>297</string>
|
||||
<string>298</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2017 Tae Won Ha. All rights reserved.</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
@ -15,8 +15,8 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.25.0</string>
|
||||
<string>SNAPSHOT-298</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>297</string>
|
||||
<string>298</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -1272,7 +1272,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 297;
|
||||
CURRENT_PROJECT_VERSION = 298;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@ -1330,7 +1330,7 @@
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 297;
|
||||
CURRENT_PROJECT_VERSION = 298;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
|
@ -219,7 +219,7 @@ extension NSOutlineView {
|
||||
return self.item(atRow: self.clickedRow)
|
||||
}
|
||||
|
||||
func toggle(item: Any) {
|
||||
func toggle(item: Any?) {
|
||||
if self.isItemExpanded(item) {
|
||||
self.collapseItem(item)
|
||||
} else {
|
||||
|
@ -21,9 +21,14 @@ class BuffersList: NSView,
|
||||
case open(NvimView.Buffer)
|
||||
}
|
||||
|
||||
private(set) var lastThemeMark = Token()
|
||||
private(set) var theme = Theme.default
|
||||
|
||||
required init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType) {
|
||||
required init(
|
||||
source: Observable<StateType>,
|
||||
emitter: ActionEmitter,
|
||||
state: StateType
|
||||
) {
|
||||
self.emit = emitter.typedEmit()
|
||||
self.uuid = state.uuid
|
||||
|
||||
@ -52,14 +57,15 @@ class BuffersList: NSView,
|
||||
|
||||
self.usesTheme = state.appearance.usesTheme
|
||||
|
||||
if self.buffers == state.buffers && !themeChanged && self.showsFileIcon == state.appearance.showsFileIcon {
|
||||
if self.buffers == state.buffers
|
||||
&& !themeChanged
|
||||
&& self.showsFileIcon == state.appearance.showsFileIcon {
|
||||
return
|
||||
}
|
||||
|
||||
self.showsFileIcon = state.appearance.showsFileIcon
|
||||
self.buffers = state.buffers
|
||||
self.bufferList.reloadData()
|
||||
self.adjustFileViewWidth()
|
||||
})
|
||||
.disposed(by: self.disposeBag)
|
||||
}
|
||||
@ -69,7 +75,6 @@ class BuffersList: NSView,
|
||||
|
||||
private let uuid: String
|
||||
private var usesTheme: Bool
|
||||
private var lastThemeMark = Token()
|
||||
private var showsFileIcon: Bool
|
||||
|
||||
private let bufferList = NSTableView.standardTableView()
|
||||
@ -95,16 +100,6 @@ class BuffersList: NSView,
|
||||
self.addSubview(scrollView)
|
||||
scrollView.autoPinEdgesToSuperviewEdges()
|
||||
}
|
||||
|
||||
private func adjustFileViewWidth() {
|
||||
let maxWidth = self.buffers.reduce(CGFloat(100)) { (curMaxWidth, buffer) in
|
||||
return max(self.text(for: buffer).size().width, curMaxWidth)
|
||||
}
|
||||
|
||||
let column = self.bufferList.tableColumns[0]
|
||||
// If we set the minWidth and maxWidth here, the column does not get resized... Dunno why.
|
||||
column.width = maxWidth + ThemedTableCell.widthWithoutText
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
@ -132,14 +127,26 @@ extension BuffersList {
|
||||
// MARK: - NSTableViewDelegate
|
||||
extension BuffersList {
|
||||
|
||||
public func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
|
||||
return tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("buffer-row-view"), owner: self)
|
||||
as? ThemedTableRow ?? ThemedTableRow(withIdentifier: "buffer-row-view", themedView: self)
|
||||
public func tableView(
|
||||
_ tableView: NSTableView,
|
||||
rowViewForRow row: Int
|
||||
) -> NSTableRowView? {
|
||||
return tableView.makeView(
|
||||
withIdentifier: NSUserInterfaceItemIdentifier("buffer-row-view"),
|
||||
owner: self
|
||||
) as? ThemedTableRow ?? ThemedTableRow(withIdentifier: "buffer-row-view",
|
||||
themedView: self)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||
let cachedCell = (tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("buffer-cell-view"), owner: self)
|
||||
as? ThemedTableCell)?.reset()
|
||||
func tableView(
|
||||
_ tableView: NSTableView,
|
||||
viewFor tableColumn: NSTableColumn?,
|
||||
row: Int
|
||||
) -> NSView? {
|
||||
let cachedCell = (tableView.makeView(
|
||||
withIdentifier: NSUserInterfaceItemIdentifier("buffer-cell-view"),
|
||||
owner: self
|
||||
) as? ThemedTableCell)?.reset()
|
||||
|
||||
let cell = cachedCell ?? ThemedTableCell(withIdentifier: "buffer-cell-view")
|
||||
|
||||
@ -155,6 +162,22 @@ extension BuffersList {
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(
|
||||
_ tableView: NSTableView,
|
||||
didAdd rowView: NSTableRowView,
|
||||
forRow row: Int
|
||||
) {
|
||||
guard let cellWidth = (rowView.view(atColumn: 0) as? NSTableCellView)?
|
||||
.fittingSize.width
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
self.bufferList.tableColumns[0].width = max(
|
||||
self.bufferList.tableColumns[0].width, cellWidth + CGFloat(10)
|
||||
)
|
||||
}
|
||||
|
||||
private func text(for buffer: NvimView.Buffer) -> NSAttributedString {
|
||||
guard let name = buffer.name else {
|
||||
return NSAttributedString(string: "No Name")
|
||||
@ -164,16 +187,22 @@ extension BuffersList {
|
||||
return NSAttributedString(string: name)
|
||||
}
|
||||
|
||||
let pathInfo = url.pathComponents.dropFirst().dropLast().reversed().joined(separator: " / ") + " /"
|
||||
let pathInfo = url.pathComponents
|
||||
.dropFirst()
|
||||
.dropLast()
|
||||
.reversed()
|
||||
.joined(separator: " / ") + " /"
|
||||
let rowText = NSMutableAttributedString(string: "\(name) — \(pathInfo)")
|
||||
|
||||
rowText.addAttribute(NSAttributedString.Key.foregroundColor,
|
||||
value: self.theme.foreground,
|
||||
range: NSRange(location: 0, length: name.count))
|
||||
|
||||
rowText.addAttribute(NSAttributedString.Key.foregroundColor,
|
||||
value: self.theme.foreground.brightening(by: 1.15),
|
||||
range: NSRange(location: name.count, length: pathInfo.count + 3))
|
||||
rowText.addAttribute(
|
||||
NSAttributedString.Key.foregroundColor,
|
||||
value: self.theme.foreground.brightening(by: 1.15),
|
||||
range: NSRange(location: name.count, length: pathInfo.count + 3)
|
||||
)
|
||||
|
||||
return rowText
|
||||
}
|
||||
|
@ -181,7 +181,9 @@ extension FileBrowser {
|
||||
return
|
||||
}
|
||||
|
||||
#if NOPE
|
||||
self.fileView.select(url)
|
||||
#endif
|
||||
}
|
||||
|
||||
@objc func refreshAction(_ sender: Any?) {
|
||||
|
@ -7,67 +7,52 @@ import Cocoa
|
||||
import NvimView
|
||||
import PureLayout
|
||||
import RxSwift
|
||||
import CocoaFontAwesome
|
||||
|
||||
class FileOutlineView: NSOutlineView,
|
||||
UiComponent,
|
||||
NSOutlineViewDataSource,
|
||||
NSOutlineViewDelegate,
|
||||
ThemedView {
|
||||
|
||||
typealias StateType = MainWindow.State
|
||||
|
||||
@objc dynamic var content = [Node]()
|
||||
|
||||
private(set) var lastThemeMark = Token()
|
||||
private(set) var theme = Theme.default
|
||||
|
||||
required init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType) {
|
||||
required init(
|
||||
source: Observable<StateType>,
|
||||
emitter: ActionEmitter,
|
||||
state: StateType
|
||||
) {
|
||||
self.emit = emitter.typedEmit()
|
||||
self.uuid = state.uuid
|
||||
|
||||
self.root = FileBrowserItem(state.cwd)
|
||||
self.isShowHidden = state.fileBrowserShowHidden
|
||||
|
||||
self.root = Node(url: state.cwd)
|
||||
self.usesTheme = state.appearance.usesTheme
|
||||
self.showsFileIcon = state.appearance.showsFileIcon
|
||||
self.isShowHidden = state.fileBrowserShowHidden
|
||||
self.triangleClosed = NSImage.fontAwesomeIcon(
|
||||
name: .caretRight,
|
||||
textColor: self.theme.directoryForeground,
|
||||
dimension: triangleImageSize
|
||||
)
|
||||
self.triangleOpen = NSImage.fontAwesomeIcon(
|
||||
name: .caretDown,
|
||||
textColor: self.theme.directoryForeground,
|
||||
dimension: triangleImageSize
|
||||
)
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
NSOutlineView.configure(toStandard: self)
|
||||
|
||||
self.dataSource = self
|
||||
self.delegate = self
|
||||
self.allowsEmptySelection = true
|
||||
|
||||
guard Bundle.main.loadNibNamed(NSNib.Name("FileBrowserMenu"), owner: self, topLevelObjects: nil) else {
|
||||
NSLog("WARN: FileBrowserMenu.xib could not be loaded")
|
||||
return
|
||||
}
|
||||
|
||||
// If the target of the menu items is set to the first responder, the actions are not invoked
|
||||
// at all when the file monitor fires in the background...
|
||||
// Dunno why it worked before the redesign... -_-
|
||||
self.menu?.items.forEach { $0.target = self }
|
||||
|
||||
self.doubleAction = #selector(FileOutlineView.doubleClickAction)
|
||||
|
||||
source
|
||||
.filter { !self.shouldReloadData(for: $0) }
|
||||
.filter { $0.lastFileSystemUpdate.mark != self.lastFileSystemUpdateMark }
|
||||
.throttle(2 * FileMonitor.fileSystemEventsLatency + 1,
|
||||
latest: true,
|
||||
scheduler: SerialDispatchQueueScheduler(qos: .background))
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribe(onNext: { state in
|
||||
self.lastFileSystemUpdateMark = state.lastFileSystemUpdate.mark
|
||||
guard let fileBrowserItem = self.fileBrowserItem(with: state.lastFileSystemUpdate.payload) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.update(fileBrowserItem)
|
||||
})
|
||||
.disposed(by: self.disposeBag)
|
||||
|
||||
source
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribe(onNext: { state in
|
||||
if state.viewToBeFocused != nil, case .fileBrowser = state.viewToBeFocused! {
|
||||
if state.viewToBeFocused != nil,
|
||||
case .fileBrowser = state.viewToBeFocused! {
|
||||
self.beFirstResponder()
|
||||
}
|
||||
|
||||
@ -80,76 +65,235 @@ class FileOutlineView: NSOutlineView,
|
||||
|
||||
self.usesTheme = state.appearance.usesTheme
|
||||
|
||||
guard self.shouldReloadData(for: state, themeChanged: themeChanged) else {
|
||||
guard self.shouldReloadData(
|
||||
for: state, themeChanged: themeChanged
|
||||
) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.showsFileIcon = state.appearance.showsFileIcon
|
||||
self.isShowHidden = state.fileBrowserShowHidden
|
||||
self.lastFileSystemUpdateMark = state.lastFileSystemUpdate.mark
|
||||
self.root = FileBrowserItem(state.cwd)
|
||||
self.reloadData()
|
||||
self.root = Node(url: state.cwd)
|
||||
self.reloadRoot()
|
||||
})
|
||||
.disposed(by: self.disposeBag)
|
||||
}
|
||||
|
||||
override func reloadData() {
|
||||
self.cells.removeAll()
|
||||
self.widths.removeAll()
|
||||
super.reloadData()
|
||||
}
|
||||
|
||||
func select(_ url: URL) {
|
||||
var stack = [self.root]
|
||||
|
||||
while let item = stack.popLast() {
|
||||
self.expandItem(item)
|
||||
|
||||
if item.url.isDirectParent(of: url) {
|
||||
if let targetItem = item.children.first(where: { $0.url == url }) {
|
||||
let targetRow = self.row(forItem: targetItem)
|
||||
self.selectRowIndexes(IndexSet(integer: targetRow), byExtendingSelection: false)
|
||||
self.scrollRowToVisible(targetRow)
|
||||
source
|
||||
.filter { !self.shouldReloadData(for: $0) }
|
||||
.filter { $0.lastFileSystemUpdate.mark != self.lastFileSystemUpdateMark }
|
||||
.map { $0.lastFileSystemUpdate.payload }
|
||||
.throttle(2 * FileMonitor.fileSystemEventsLatency + 1,
|
||||
latest: true,
|
||||
scheduler: SerialDispatchQueueScheduler(qos: .background))
|
||||
.map { ($0, Set(self.childUrls(for: $0))) }
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribe(onNext: { (url, newChildUrls) in
|
||||
guard let changeTreeNode = self.changeRootTreeNode(for: url) else {
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
self.handleRemoval(changeTreeNode: changeTreeNode,
|
||||
newChildUrls: newChildUrls)
|
||||
self.handleAddition(changeTreeNode: changeTreeNode,
|
||||
newChildUrls: newChildUrls)
|
||||
})
|
||||
.disposed(by: self.disposeBag)
|
||||
|
||||
stack.append(contentsOf: item.children.filter { $0.url.isParent(of: url) })
|
||||
self.initContextMenu()
|
||||
self.initBindings()
|
||||
self.reloadRoot()
|
||||
}
|
||||
|
||||
// We cannot use outlineView(_:willDisplayOutlineCell:for:item:) delegate
|
||||
// method to customize the disclosure triangle in a view-based
|
||||
// NSOutlineView.
|
||||
// See https://stackoverflow.com/a/20454413/9850227
|
||||
override func makeView(
|
||||
withIdentifier identifier: NSUserInterfaceItemIdentifier, owner: Any?
|
||||
) -> NSView? {
|
||||
let result = super.makeView(withIdentifier: identifier, owner: owner)
|
||||
|
||||
if identifier == NSOutlineView.disclosureButtonIdentifier {
|
||||
let triangleButton = result as? NSButton
|
||||
triangleButton?.image = self.triangleClosed
|
||||
triangleButton?.alternateImage = self.triangleOpen
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private let emit: (UuidAction<FileBrowser.Action>) -> Void
|
||||
private let disposeBag = DisposeBag()
|
||||
|
||||
private let uuid: String
|
||||
private var lastFileSystemUpdateMark = Token()
|
||||
private var usesTheme: Bool
|
||||
private var lastThemeMark = Token()
|
||||
private var showsFileIcon: Bool
|
||||
|
||||
private var root: Node
|
||||
private var cwd: URL {
|
||||
return self.root.url
|
||||
}
|
||||
private let treeController = NSTreeController()
|
||||
|
||||
private var cachedColumnWidth = CGFloat(20)
|
||||
private var usesTheme: Bool
|
||||
private var lastFileSystemUpdateMark = Token()
|
||||
private var showsFileIcon: Bool
|
||||
private var isShowHidden: Bool
|
||||
|
||||
private var root: FileBrowserItem
|
||||
private var triangleClosed: NSImage
|
||||
private var triangleOpen: NSImage
|
||||
|
||||
private var widths = [String: CGFloat]()
|
||||
private var cells = [String: ThemedTableCell]()
|
||||
private func initContextMenu() {
|
||||
// Loading the nib file will set self.menu.
|
||||
guard Bundle.main.loadNibNamed(
|
||||
NSNib.Name("FileBrowserMenu"),
|
||||
owner: self,
|
||||
topLevelObjects: nil
|
||||
) else {
|
||||
fileLog.error("FileBrowserMenu.xib could not be loaded")
|
||||
return
|
||||
}
|
||||
self.menu?.items.forEach { $0.target = self }
|
||||
self.doubleAction = #selector(FileOutlineView.doubleClickAction)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
private func initBindings() {
|
||||
self.treeController.childrenKeyPath = "children"
|
||||
self.treeController.leafKeyPath = "isLeaf"
|
||||
self.treeController.countKeyPath = "childrenCount"
|
||||
self.treeController.objectClass = Node.self
|
||||
self.treeController.avoidsEmptySelection = false
|
||||
self.treeController.preservesSelection = true
|
||||
self.treeController.sortDescriptors = [
|
||||
NSSortDescriptor(key: "isLeaf", ascending: true), // Folders first,
|
||||
NSSortDescriptor(key: "displayName", ascending: true) // then, name
|
||||
]
|
||||
self.treeController.bind(.contentArray, to: self, withKeyPath: "content")
|
||||
self.bind(.content, to: self.treeController, withKeyPath: "arrangedObjects")
|
||||
self.bind(.selectionIndexPaths,
|
||||
to: self.treeController,
|
||||
withKeyPath: "selectionIndexPaths")
|
||||
}
|
||||
|
||||
private func changeRootTreeNode(`for` url: URL) -> NSTreeNode? {
|
||||
if url == self.cwd {
|
||||
return self.treeController.arrangedObjects
|
||||
}
|
||||
|
||||
let cwdCompsCount = self.cwd.pathComponents.count
|
||||
guard cwdCompsCount <= url.pathComponents.count else { return nil }
|
||||
let comps = url.pathComponents.suffix(cwdCompsCount)
|
||||
|
||||
let rootTreeNode = self.treeController.arrangedObjects
|
||||
let changeTreeNode = comps.reduce(rootTreeNode) { (prev, comp) in
|
||||
return prev.children?.first { child in
|
||||
return child.node?.displayName == comp
|
||||
} ?? prev
|
||||
}
|
||||
|
||||
guard let changeNode = changeTreeNode.node else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard changeNode.url == url && changeNode.children != nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return changeTreeNode
|
||||
}
|
||||
|
||||
private func handleAddition(
|
||||
changeTreeNode: NSTreeNode, newChildUrls: Set<URL>
|
||||
) {
|
||||
let existingUrls = changeTreeNode.children?
|
||||
.compactMap { $0.node?.url } ?? []
|
||||
let newNodes = newChildUrls
|
||||
.subtracting(existingUrls)
|
||||
.map { Node(url: $0) }
|
||||
let newIndexPaths = (0..<newNodes.count)
|
||||
.map { i in changeTreeNode.indexPath.appending(i) }
|
||||
|
||||
self.treeController.insert(newNodes,
|
||||
atArrangedObjectIndexPaths: newIndexPaths)
|
||||
}
|
||||
|
||||
private func handleRemoval(
|
||||
changeTreeNode: NSTreeNode, newChildUrls: Set<URL>
|
||||
) {
|
||||
let indexPathsToRemove =
|
||||
changeTreeNode.children?
|
||||
.filter { child in
|
||||
guard let url = child.node?.url else { return true }
|
||||
return newChildUrls.contains(url) == false
|
||||
}
|
||||
.map { $0.indexPath } ?? []
|
||||
|
||||
self.treeController.removeObjects(
|
||||
atArrangedObjectIndexPaths: indexPathsToRemove
|
||||
)
|
||||
}
|
||||
|
||||
private func childUrls(for url: URL) -> [URL] {
|
||||
let urls = FileUtils.directDescendants(of: url).sorted { lhs, rhs in
|
||||
return lhs.lastPathComponent < rhs.lastPathComponent
|
||||
}
|
||||
|
||||
if self.isShowHidden {
|
||||
return urls
|
||||
}
|
||||
|
||||
return urls.filter { !$0.isHidden }
|
||||
}
|
||||
|
||||
private func childNodes(for node: Node) -> [Node] {
|
||||
if node.isChildrenScanned {
|
||||
return node.children ?? []
|
||||
}
|
||||
|
||||
let nodes = FileUtils
|
||||
.directDescendants(of: node.url)
|
||||
.map { Node(url: $0) }
|
||||
|
||||
if self.isShowHidden {
|
||||
return nodes
|
||||
}
|
||||
|
||||
return nodes.filter { !$0.isHidden }
|
||||
}
|
||||
|
||||
private func reloadRoot() {
|
||||
let children = self.childNodes(for: self.root)
|
||||
|
||||
self.root.children = children
|
||||
self.content.removeAll()
|
||||
self.content.append(contentsOf: children)
|
||||
}
|
||||
|
||||
private func updateTheme(_ theme: Marked<Theme>) {
|
||||
self.theme = theme.payload
|
||||
self.enclosingScrollView?.backgroundColor = self.theme.background
|
||||
self.backgroundColor = self.theme.background
|
||||
self.triangleClosed = NSImage.fontAwesomeIcon(
|
||||
name: .caretRight,
|
||||
textColor: self.theme.directoryForeground,
|
||||
dimension: triangleImageSize
|
||||
)
|
||||
self.triangleOpen = NSImage.fontAwesomeIcon(
|
||||
name: .caretDown,
|
||||
textColor: self.theme.directoryForeground,
|
||||
dimension: triangleImageSize
|
||||
)
|
||||
|
||||
self.lastThemeMark = theme.mark
|
||||
}
|
||||
|
||||
private func shouldReloadData(for state: StateType, themeChanged: Bool = false) -> Bool {
|
||||
private func shouldReloadData(
|
||||
for state: StateType, themeChanged: Bool = false
|
||||
) -> Bool {
|
||||
if self.isShowHidden != state.fileBrowserShowHidden {
|
||||
return true
|
||||
}
|
||||
@ -169,233 +313,8 @@ class FileOutlineView: NSOutlineView,
|
||||
return false
|
||||
}
|
||||
|
||||
private func handleRemovals(for fileBrowserItem: FileBrowserItem,
|
||||
new newChildren: [FileBrowserItem]) {
|
||||
let curChildren = fileBrowserItem.children
|
||||
|
||||
let curPreparedChildren = self.prepare(curChildren)
|
||||
let newPreparedChildren = self.prepare(newChildren)
|
||||
|
||||
let indicesToRemove = curPreparedChildren
|
||||
.enumerated()
|
||||
.filter { (_, fileBrowserItem) in newPreparedChildren.contains(fileBrowserItem) == false }
|
||||
.map { (idx, _) in idx }
|
||||
|
||||
indicesToRemove.forEach { index in
|
||||
let path = curPreparedChildren[index].url.path
|
||||
|
||||
self.cells.removeValue(forKey: path)
|
||||
self.widths.removeValue(forKey: path)
|
||||
}
|
||||
|
||||
fileBrowserItem.children = curChildren.filter { newChildren.contains($0) }
|
||||
|
||||
let parent = fileBrowserItem == self.root ? nil : fileBrowserItem
|
||||
self.removeItems(at: IndexSet(indicesToRemove), inParent: parent)
|
||||
}
|
||||
|
||||
private func handleAdditions(for fileBrowserItem: FileBrowserItem,
|
||||
new newChildren: [FileBrowserItem]) {
|
||||
let curChildren = fileBrowserItem.children
|
||||
|
||||
let curPreparedChildren = self.prepare(curChildren)
|
||||
let newPreparedChildren = self.prepare(newChildren)
|
||||
|
||||
let indicesToInsert = newPreparedChildren
|
||||
.enumerated()
|
||||
.filter { (_, fileBrowserItem) in curPreparedChildren.contains(fileBrowserItem) == false }
|
||||
.map { (idx, _) in idx }
|
||||
|
||||
// We don't just take newChildren because NSOutlineView look at the pointer equality for
|
||||
// preserving the expanded states...
|
||||
fileBrowserItem.children = newChildren.substituting(elements: curChildren)
|
||||
|
||||
let parent = fileBrowserItem == self.root ? nil : fileBrowserItem
|
||||
self.insertItems(at: IndexSet(indicesToInsert), inParent: parent)
|
||||
}
|
||||
|
||||
private func sortedChildren(of url: URL) -> [FileBrowserItem] {
|
||||
return FileUtils.directDescendants(of: url).map(FileBrowserItem.init).sorted{
|
||||
if ($0.isDir == $1.isDir) {
|
||||
return $0.url.absoluteString < $1.url.absoluteString
|
||||
}
|
||||
|
||||
return $0.isDir
|
||||
}
|
||||
}
|
||||
|
||||
private func update(_ fileBrowserItem: FileBrowserItem) {
|
||||
let url = fileBrowserItem.url
|
||||
|
||||
// Sort the array to keep the order.
|
||||
let newChildren = self.sortedChildren(of: url)
|
||||
|
||||
self.beginUpdates()
|
||||
self.handleRemovals(for: fileBrowserItem, new: newChildren)
|
||||
self.endUpdates()
|
||||
|
||||
self.beginUpdates()
|
||||
self.handleAdditions(for: fileBrowserItem, new: newChildren)
|
||||
self.endUpdates()
|
||||
|
||||
fileBrowserItem.isChildrenScanned = true
|
||||
|
||||
fileBrowserItem.children.filter { self.isItemExpanded($0) }.forEach(self.update)
|
||||
}
|
||||
|
||||
private func fileBrowserItem(with url: URL) -> FileBrowserItem? {
|
||||
if self.cwd == url {
|
||||
return self.root
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
return childPart.reduce(self.root) { (resultItem, childName) -> FileBrowserItem? in
|
||||
guard let parent = resultItem else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return parent.child(with: parent.url.appendingPathComponent(childName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSOutlineViewDataSource
|
||||
extension FileOutlineView {
|
||||
|
||||
private func scanChildrenIfNecessary(_ fileBrowserItem: FileBrowserItem) {
|
||||
guard fileBrowserItem.isChildrenScanned == false else {
|
||||
return
|
||||
}
|
||||
|
||||
fileBrowserItem.children = self.sortedChildren(of: fileBrowserItem.url)
|
||||
fileBrowserItem.isChildrenScanned = true
|
||||
}
|
||||
|
||||
private func prepare(_ children: [FileBrowserItem]) -> [FileBrowserItem] {
|
||||
return self.isShowHidden ? children : children.filter { !$0.isHidden }
|
||||
}
|
||||
|
||||
func outlineView(_: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||
if item == nil {
|
||||
self.scanChildrenIfNecessary(self.root)
|
||||
|
||||
return self.prepare(self.root.children).count
|
||||
}
|
||||
|
||||
guard let fileBrowserItem = item as? FileBrowserItem else {
|
||||
return 0
|
||||
}
|
||||
|
||||
if fileBrowserItem.url.isDir {
|
||||
self.scanChildrenIfNecessary(fileBrowserItem)
|
||||
return self.prepare(fileBrowserItem.children).count
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func outlineView(_: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||
let level = self.level(forItem: item) + 2
|
||||
defer { self.adjustColumnWidths() }
|
||||
|
||||
if item == nil {
|
||||
let child = self.prepare(self.root.children)[index]
|
||||
|
||||
let cell = self.cell(forItem: child)
|
||||
self.cells[child.url.path] = cell
|
||||
self.widths[child.url.path] = self.cellWidth(for: cell, level: level)
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
guard let fileBrowserItem = item as? FileBrowserItem else {
|
||||
preconditionFailure("Should not happen")
|
||||
}
|
||||
|
||||
let child = self.prepare(fileBrowserItem.children)[index]
|
||||
|
||||
let cell = self.cell(forItem: child)
|
||||
self.cells[child.url.path] = cell
|
||||
self.widths[child.url.path] = self.cellWidth(for: cell, level: level)
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
private func cell(forItem item: FileBrowserItem) -> ThemedTableCell {
|
||||
if let existingCell = self.cells[item.url.path] {
|
||||
return existingCell
|
||||
}
|
||||
|
||||
let cell = ThemedTableCell(withIdentifier: "file-cell-view")
|
||||
|
||||
cell.isDir = item.isDir
|
||||
cell.text = item.url.lastPathComponent
|
||||
|
||||
if self.showsFileIcon {
|
||||
let icon = FileUtils.icon(forUrl: item.url)
|
||||
cell.image = cell.isHidden ? icon?.tinting(with: NSColor.white.withAlphaComponent(0.4)) : icon
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func outlineView(_: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||
guard let fileBrowserItem = item as? FileBrowserItem else {
|
||||
return false
|
||||
}
|
||||
return fileBrowserItem.url.isDir
|
||||
}
|
||||
|
||||
@objc(outlineView: objectValueForTableColumn:byItem:)
|
||||
func outlineView(_: NSOutlineView, objectValueFor: NSTableColumn?, byItem item: Any?) -> Any? {
|
||||
guard let fileBrowserItem = item as? FileBrowserItem else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fileBrowserItem
|
||||
}
|
||||
|
||||
private func cellWidth(for cell: NSView?, level: Int) -> CGFloat {
|
||||
let cellWidth = cell?.intrinsicContentSize.width ?? 0
|
||||
let indentation = CGFloat(level + 1) * self.indentationPerLevel + 4
|
||||
return cellWidth + indentation
|
||||
}
|
||||
|
||||
private func adjustColumnWidths() {
|
||||
guard let column = self.outlineTableColumn else {
|
||||
return
|
||||
}
|
||||
|
||||
column.minWidth = self.widths.values.max() ?? 100
|
||||
column.maxWidth = self.widths.values.max() ?? 100
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSOutlineViewDelegate
|
||||
extension FileOutlineView {
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? {
|
||||
return self.makeView(withIdentifier: NSUserInterfaceItemIdentifier("file-row-view"), owner: self) as? ThemedTableRow
|
||||
?? ThemedTableRow(withIdentifier: "file-row-view", themedView: self)
|
||||
}
|
||||
|
||||
func outlineView(_: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
||||
guard let fileBrowserItem = item as? FileBrowserItem else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.cells[fileBrowserItem.url.path]
|
||||
}
|
||||
|
||||
func outlineView(_: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
|
||||
return 20
|
||||
private func node(from item: Any?) -> Node? {
|
||||
return (item as? NSTreeNode)?.node
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,84 +322,184 @@ extension FileOutlineView {
|
||||
extension FileOutlineView {
|
||||
|
||||
@IBAction func doubleClickAction(_: Any?) {
|
||||
guard let item = self.clickedItem as? FileBrowserItem else {
|
||||
let clickedTreeNode = self.clickedItem
|
||||
guard let node = self.node(from: clickedTreeNode) else {
|
||||
return
|
||||
}
|
||||
|
||||
if item.url.isDir {
|
||||
self.toggle(item: item)
|
||||
if node.isDir {
|
||||
self.toggle(item: clickedTreeNode)
|
||||
} else {
|
||||
self.emit(
|
||||
UuidAction(uuid: self.uuid, action: .open(url: item.url, mode: .default))
|
||||
UuidAction(uuid: self.uuid,
|
||||
action: .open(url: node.url, mode: .default))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func openInNewTab(_: Any?) {
|
||||
guard let item = self.clickedItem as? FileBrowserItem else {
|
||||
guard let node = self.node(from: self.clickedItem) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.emit(
|
||||
UuidAction(uuid: self.uuid, action: .open(url: item.url, mode: .newTab))
|
||||
UuidAction(uuid: self.uuid, action: .open(url: node.url, mode: .newTab))
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction func openInCurrentTab(_: Any?) {
|
||||
guard let item = self.clickedItem as? FileBrowserItem else {
|
||||
guard let node = self.node(from: self.clickedItem) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.emit(
|
||||
UuidAction(uuid: self.uuid, action: .open(url: item.url, mode: .currentTab))
|
||||
UuidAction(uuid: self.uuid,
|
||||
action: .open(url: node.url, mode: .currentTab))
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction func openInHorizontalSplit(_: Any?) {
|
||||
guard let item = self.clickedItem as? FileBrowserItem else {
|
||||
guard let node = self.node(from: self.clickedItem) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.emit(
|
||||
UuidAction(uuid: self.uuid, action: .open(url: item.url, mode: .horizontalSplit))
|
||||
UuidAction(uuid: self.uuid,
|
||||
action: .open(url: node.url, mode: .horizontalSplit))
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction func openInVerticalSplit(_: Any?) {
|
||||
guard let item = self.clickedItem as? FileBrowserItem else {
|
||||
guard let node = self.node(from: self.clickedItem) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.emit(
|
||||
UuidAction(uuid: self.uuid, action: .open(url: item.url, mode: .verticalSplit))
|
||||
UuidAction(uuid: self.uuid,
|
||||
action: .open(url: node.url, mode: .verticalSplit))
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction func setAsWorkingDirectory(_: Any?) {
|
||||
guard let item = self.clickedItem as? FileBrowserItem else {
|
||||
guard let node = self.node(from: self.clickedItem) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard item.url.isDir else {
|
||||
guard node.url.isDir else {
|
||||
return
|
||||
}
|
||||
|
||||
self.emit(
|
||||
UuidAction(uuid: self.uuid, action: .setAsWorkingDirectory(item.url))
|
||||
UuidAction(uuid: self.uuid, action: .setAsWorkingDirectory(node.url))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSOutlineViewDelegate
|
||||
extension FileOutlineView {
|
||||
|
||||
func outlineView(
|
||||
_ outlineView: NSOutlineView,
|
||||
rowViewForItem item: Any
|
||||
) -> NSTableRowView? {
|
||||
let view = self.makeView(
|
||||
withIdentifier: NSUserInterfaceItemIdentifier("file-row-view"),
|
||||
owner: self
|
||||
) as? ThemedTableRow ?? ThemedTableRow(withIdentifier: "file-row-view",
|
||||
themedView: self)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
func outlineView(
|
||||
_: NSOutlineView,
|
||||
viewFor tableColumn: NSTableColumn?,
|
||||
item: Any
|
||||
) -> NSView? {
|
||||
guard let node = self.node(from: item) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let cellView = self.makeView(
|
||||
withIdentifier: NSUserInterfaceItemIdentifier("file-cell-view"),
|
||||
owner: self
|
||||
) as? ThemedTableCell ?? ThemedTableCell(withIdentifier: "file-cell-view")
|
||||
|
||||
cellView.isDir = node.isDir
|
||||
cellView.text = node.displayName
|
||||
|
||||
let icon = FileUtils.icon(forUrl: node.url)
|
||||
cellView.image = node.isHidden
|
||||
? icon?.tinting(with: NSColor.white.withAlphaComponent(0.4))
|
||||
: icon
|
||||
|
||||
return cellView
|
||||
}
|
||||
|
||||
func outlineView(_: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
|
||||
return 20
|
||||
}
|
||||
|
||||
func outlineView(
|
||||
_ outlineView: NSOutlineView,
|
||||
shouldExpandItem item: Any
|
||||
) -> Bool {
|
||||
guard let node = self.node(from: item) else {
|
||||
return false
|
||||
}
|
||||
|
||||
if node.isChildrenScanned {
|
||||
return true
|
||||
}
|
||||
|
||||
node.children = FileUtils.directDescendants(of: node.url).map { url in
|
||||
return Node(url: url)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func outlineView(
|
||||
_ outlineView: NSOutlineView,
|
||||
didAdd rowView: NSTableRowView,
|
||||
forRow row: Int
|
||||
) {
|
||||
guard let cellWidth = (rowView.view(atColumn: 0) as? NSTableCellView)?
|
||||
.fittingSize.width
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let level = CGFloat(self.level(forRow: row))
|
||||
let width = level * self.indentationPerLevel + cellWidth
|
||||
+ columnWidthRightPadding
|
||||
self.cachedColumnWidth = max(self.cachedColumnWidth, width)
|
||||
self.tableColumns[0].width = cachedColumnWidth
|
||||
|
||||
let rv = rowView as? ThemedTableRow
|
||||
guard rv?.themeToken != self.lastThemeMark else {
|
||||
return
|
||||
}
|
||||
|
||||
let triangleView = rv?.triangleView
|
||||
triangleView?.image = self.triangleClosed
|
||||
triangleView?.alternateImage = self.triangleOpen
|
||||
rv?.themeToken = self.lastThemeMark
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSUserInterfaceValidations
|
||||
extension FileOutlineView {
|
||||
|
||||
override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||
guard let clickedItem = self.clickedItem as? FileBrowserItem else {
|
||||
override func validateUserInterfaceItem(
|
||||
_ item: NSValidatedUserInterfaceItem
|
||||
) -> Bool {
|
||||
guard let clickedNode = self.node(from: self.clickedItem) else {
|
||||
return true
|
||||
}
|
||||
|
||||
if item.action == #selector(setAsWorkingDirectory(_:)) {
|
||||
return clickedItem.url.isDir
|
||||
return clickedNode.url.isDir
|
||||
}
|
||||
|
||||
return true
|
||||
@ -496,18 +515,19 @@ extension FileOutlineView {
|
||||
return
|
||||
}
|
||||
|
||||
guard let item = self.selectedItem as? FileBrowserItem else {
|
||||
guard let node = self.node(from: self.selectedItem) else {
|
||||
super.keyDown(with: event)
|
||||
return
|
||||
}
|
||||
|
||||
switch char {
|
||||
case " ", "\r": // Why "\r" and not "\n"?
|
||||
if item.url.isDir || item.url.isPackage {
|
||||
self.toggle(item: item)
|
||||
if node.url.isDir || node.url.isPackage {
|
||||
self.toggle(item: node)
|
||||
} else {
|
||||
self.emit(
|
||||
UuidAction(uuid: self.uuid, action: .open(url: item.url, mode: .newTab))
|
||||
UuidAction(uuid: self.uuid,
|
||||
action: .open(url: node.url, mode: .newTab))
|
||||
)
|
||||
}
|
||||
|
||||
@ -517,40 +537,50 @@ extension FileOutlineView {
|
||||
}
|
||||
}
|
||||
|
||||
private class FileBrowserItem: Hashable, Comparable, CustomStringConvertible {
|
||||
class Node: NSObject, Comparable {
|
||||
|
||||
static func ==(left: FileBrowserItem, right: FileBrowserItem) -> Bool {
|
||||
return left.url == right.url
|
||||
static func <(lhs: Node, rhs: Node) -> Bool {
|
||||
return lhs.displayName < rhs.displayName
|
||||
}
|
||||
|
||||
static func <(left: FileBrowserItem, right: FileBrowserItem) -> Bool {
|
||||
return left.url.lastPathComponent < right.url.lastPathComponent
|
||||
@objc dynamic var url: URL
|
||||
@objc dynamic var isLeaf: Bool
|
||||
@objc dynamic var isHidden: Bool
|
||||
@objc dynamic var children: [Node]?
|
||||
|
||||
@objc dynamic var childrenCount: Int {
|
||||
return self.children?.count ?? -1
|
||||
}
|
||||
@objc dynamic var displayName: String {
|
||||
return self.url.lastPathComponent
|
||||
}
|
||||
|
||||
var hashValue: Int {
|
||||
var isDir: Bool {
|
||||
return !self.isLeaf
|
||||
}
|
||||
var isChildrenScanned = false
|
||||
|
||||
override var description: String {
|
||||
return "<Node: \(self.url): \(self.childrenCount) children>"
|
||||
}
|
||||
|
||||
override var hash: Int {
|
||||
return self.url.hashValue
|
||||
}
|
||||
|
||||
var description: String {
|
||||
return self.url.path
|
||||
}
|
||||
|
||||
let url: URL
|
||||
let isDir: Bool
|
||||
let isHidden: Bool
|
||||
var children: [FileBrowserItem] = []
|
||||
var isChildrenScanned = false
|
||||
|
||||
init(_ url: URL) {
|
||||
init(url: URL) {
|
||||
self.url = url
|
||||
|
||||
// We cache the value here since we often get the value when the file is not there, eg when
|
||||
// updating because the file gets deleted (in self.prepare() function)
|
||||
self.isLeaf = !url.isDir
|
||||
self.isHidden = url.isHidden
|
||||
self.isDir = url.isDir
|
||||
}
|
||||
|
||||
func child(with url: URL) -> FileBrowserItem? {
|
||||
return self.children.first { $0.url == url }
|
||||
}
|
||||
}
|
||||
|
||||
private extension NSTreeNode {
|
||||
|
||||
var node: Node? {
|
||||
return self.representedObject as? Node
|
||||
}
|
||||
}
|
||||
|
||||
private let columnWidthRightPadding = CGFloat(40)
|
||||
private let triangleImageSize = CGFloat(18)
|
||||
|
@ -1224,7 +1224,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.25.0</string>
|
||||
<string>SNAPSHOT-298</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@ -1241,7 +1241,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>297</string>
|
||||
<string>298</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.productivity</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
@ -10,18 +10,30 @@ import PureLayout
|
||||
protocol ThemedView: class {
|
||||
|
||||
var theme: Theme { get }
|
||||
var lastThemeMark: Token { get }
|
||||
}
|
||||
|
||||
class ThemedTableRow: NSTableRowView {
|
||||
|
||||
weak var triangleView: NSButton?
|
||||
var themeToken: Token
|
||||
|
||||
init(withIdentifier identifier: String, themedView: ThemedView) {
|
||||
self.themedView = themedView
|
||||
self.themeToken = themedView.lastThemeMark
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.identifier = NSUserInterfaceItemIdentifier(identifier)
|
||||
}
|
||||
|
||||
override func didAddSubview(_ subview: NSView) {
|
||||
super.didAddSubview(subview)
|
||||
if subview.identifier == NSOutlineView.disclosureButtonIdentifier {
|
||||
self.triangleView = subview as? NSButton
|
||||
}
|
||||
}
|
||||
|
||||
open override func drawBackground(in dirtyRect: NSRect) {
|
||||
if let cell = self.view(atColumn: 0) as? ThemedTableCell {
|
||||
if cell.isDir {
|
||||
@ -57,16 +69,16 @@ class ThemedTableCell: NSTableCellView {
|
||||
static let font = NSFont.systemFont(ofSize: 12)
|
||||
static let widthWithoutText = CGFloat(2 + 16 + 4 + 2)
|
||||
|
||||
static func width(with text: String) -> CGFloat {
|
||||
let attrStr = NSAttributedString(string: text, attributes: [NSAttributedString.Key.font: ThemedTableCell.font])
|
||||
|
||||
return self.widthWithoutText + attrStr.size().width
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
return CGSize(width: ThemedTableCell.widthWithoutText + self._textField.intrinsicContentSize.width,
|
||||
height: max(self._textField.intrinsicContentSize.height, 16))
|
||||
}
|
||||
// static func width(with text: String) -> CGFloat {
|
||||
// let attrStr = NSAttributedString(string: text, attributes: [NSAttributedString.Key.font: ThemedTableCell.font])
|
||||
//
|
||||
// return self.widthWithoutText + attrStr.size().width
|
||||
// }
|
||||
//
|
||||
// override var intrinsicContentSize: CGSize {
|
||||
// return CGSize(width: ThemedTableCell.widthWithoutText + self._textField.intrinsicContentSize.width,
|
||||
// height: max(self._textField.intrinsicContentSize.height, 16))
|
||||
// }
|
||||
|
||||
var isDir = false
|
||||
|
||||
|
@ -15,10 +15,10 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.25.0</string>
|
||||
<string>SNAPSHOT-298</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>297</string>
|
||||
<string>298</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -7,36 +7,22 @@
|
||||
<description>Most recent changes with links to updates for VimR.</description>
|
||||
<language>en</language>
|
||||
<item>
|
||||
<title>v0.25.0-297</title>
|
||||
<title>SNAPSHOT-298</title>
|
||||
<description><![CDATA[
|
||||
<ul>
|
||||
<li>Neovim 0.3.4</li>
|
||||
<li>GH-625: <code>vimr --cur-env</code> will pass the current environment variables to the new neovim process. This will result in <code>virtualenv</code> support.</li>
|
||||
<li>GH-443: <code>vimr --line ${LINE_NUMBER} ${SOME_FILE}</code> will open the file and go to the given line. If the file is already open in a UI window, then that window will be selected and the cursor will be moved to the given line. This can be used for example to reverse-search LaTeX.</li>
|
||||
<li>GH-603: Bugfix: <code>Cmd-V</code> pastes at the wrong location in the insert mode.</li>
|
||||
<li>GH-659: Bugfix (introduced in a snapshot): Turning off ligatures does not really turn off ligatures.</li>
|
||||
<li>GH-664: Bugfix: VimR crashes for some shell configurations.</li>
|
||||
<li>GH-666: Adapt to the new UI-API of Neovim</li>
|
||||
<li>Dependencies updates:<ul>
|
||||
<li>ReactiveX/RxSwift@4.4.1</li>
|
||||
<li>httpswift/swifter@1.4.5</li>
|
||||
<li>PureLayout/PureLayout@3.1.4</li>
|
||||
<li>sindresorhus/github-markdown-css@3.0.1</li>
|
||||
<li>sparkle-project/Sparkle@1.21.3</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Improved handling of changes of <code>cwd</code> in the file browser.</li>
|
||||
</ul>
|
||||
]]></description>
|
||||
<releaseNotesLink>
|
||||
https://github.com/qvacua/vimr/releases/tag/v0.25.0-297
|
||||
https://github.com/qvacua/vimr/releases/tag/snapshot/298
|
||||
</releaseNotesLink>
|
||||
<pubDate>2019-02-23T17:55:14.811734</pubDate>
|
||||
<pubDate>2019-02-26T07:24:18.032526</pubDate>
|
||||
<minimumSystemVersion>10.10.0</minimumSystemVersion>
|
||||
<enclosure url="https://github.com/qvacua/vimr/releases/download/v0.25.0-297/VimR-v0.25.0-297.tar.bz2"
|
||||
sparkle:version="297"
|
||||
sparkle:shortVersionString="0.25.0"
|
||||
sparkle:dsaSignature="MC4CFQC7d60NkbkgZndkDtFrmfJ2Um1yBAIVAKwxaxYy+9FpNNJXOFhAhVUTdxfK"
|
||||
length="14935040"
|
||||
<enclosure url="https://github.com/qvacua/vimr/releases/download/snapshot/298/VimR-SNAPSHOT-298.tar.bz2"
|
||||
sparkle:version="298"
|
||||
sparkle:shortVersionString="SNAPSHOT-298"
|
||||
sparkle:dsaSignature="MC0CFQCvFlIJAyiqFN5B1OqsorK6OV1IJwIUJPVNVeWDXCpCSj76EWCYPnNxD4A="
|
||||
length="14969397"
|
||||
type="application/octet-stream"/>
|
||||
</item>
|
||||
</channel>
|
||||
|
@ -1,4 +1,9 @@
|
||||
# 0.25.0-???
|
||||
# 0.26.0-???
|
||||
|
||||
* Draw the disclosure triangle in appropriate color of the current color scheme (and improve handling of changes of `cwd` in the file browser).
|
||||
* ...
|
||||
|
||||
# 0.25.0-297
|
||||
|
||||
* Neovim 0.3.4
|
||||
* GH-625: `vimr --cur-env` will pass the current environment variables to the new neovim process. This will result in `virtualenv` support.
|
||||
|
Loading…
Reference in New Issue
Block a user